Skip to content

Add MCP payload/response reduction (read + write) and verification fixes - #280

Open
yakky wants to merge 58 commits into
feature/issue-267-add-mcpfrom
feature/267-mcp-write-payload-reduction
Open

yakky wants to merge 58 commits into
feature/issue-267-add-mcpfrom
feature/267-mcp-write-payload-reduction

Conversation

@yakky

@yakky yakky commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

Stacks on feature/issue-267-add-mcp (#268). Adds opt-in payload-reduction for the MCP server's read and write tools, plus bugfixes found during live verification against taiga.nephila.it.

Read-side (payload/fields/strip_media/expand/resolve_assigned_users/strict_filters on the full-resource list_*/get_* tools (not history or custom-attribute values)) and write-side (return_representation on every create_*/update_* tool, plus a new update_work_items batch tool) - both opt-in, byte-identical by default.

Bugfixes: malformed-batch-item isolation, stale-version merge-order leak, resolve_assigned_users field-name bug, several MINIMAL_FIELDS gaps, generic error messages now surface the real message, invited_by now collapses under compact/minimal, expand now also strips media.

Also documents three issues found with no code fix available: a client-side MCP validation quirk, a Taiga-server-side list-filter collapse, and a genuine Taiga API gap in embedded user_stories.

Replaces #277 (head branch renamed so the towncrier branch-name check passes). The epic-link / version-fill fixes are in #279.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.18%. Comparing base (7ebbe49) to head (16acdb7).

Additional details and impacted files
@@                      Coverage Diff                      @@
##           feature/issue-267-add-mcp     #280      +/-   ##
=============================================================
+ Coverage                      97.68%   98.18%   +0.49%     
=============================================================
  Files                             12       12              
  Lines                           1340     1540     +200     
  Branches                          92      146      +54     
=============================================================
+ Hits                            1309     1512     +203     
+ Misses                            20       19       -1     
+ Partials                          11        9       -2     
Flag Coverage Δ
unittests 98.18% <100.00%> (+0.49%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coveralls

coveralls commented Oct 4, 2026 •

Copy link
Copy Markdown

Coverage Status

coverage: 98.766% (+0.3%) from 98.507% — feature/267-mcp-write-payload-reduction into feature/issue-267-add-mcp

@yakky
yakky force-pushed the feature/267-mcp-write-payload-reduction branch from 6a28cff to 158ec68 Compare October 4, 2026 13:54
@yakky
yakky added this pull request to stack #281 October 4, 2026 13:55
@yakky yakky changed the title Add MCP payload/response reduction (read + write) and b5/b6 verification fixes Add MCP payload/response reduction (read + write) and verification fixes Oct 4, 2026
@yakky
yakky requested review from protoroto and a balanced review from Copilot October 4, 2026 14:06
@yakky yakky self-assigned this Oct 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Payload semantics, membership pagination, batch efficiency, and release-version configuration contain unresolved defects.

Review effort: Balanced
Findings: 4 Medium severity

Open (4)
What changed in this PR

Adds opt-in MCP response reduction for reads and writes while preserving default response behavior.

Changes:

  • Adds payload projection, media stripping, field selection, and assignee resolution.
  • Adds reduced write representations and batch work-item updates.
  • Expands documentation, tests, changelog fragments, and prerelease metadata.
File Description
taiga/​mcp_server/​server.py Adds read/write reduction and batch updates.
taiga/​mcp_server/​serialize.py Implements response projections and collapsing.
tests/​test_mcp_server.py Tests MCP tool behavior.
tests/​test_mcp_server_serialize.py Tests serialization helpers.
docs/​mcp.rst Documents new parameters and limitations.
taiga/​__init__.py Updates package version.
pyproject.toml Updates bumpversion metadata.
changes/​+mcp-write-payload-reduction.feature Records write reduction feature.
changes/​+mcp-surface-real-error-messages.bugfix Records error-message fix.
changes/​+mcp-resolve-assigned-users-field-name-fix.bugfix Records assignee-name fix.
changes/​+mcp-payload-reduction.feature Records read reduction feature.
changes/​+mcp-minimal-field-set-fixes.bugfix Records minimal-field fixes.
changes/​+mcp-invited-by-collapse.bugfix Records membership collapsing fix.
changes/​+mcp-expand-strips-media.bugfix Records expanded-media fix.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pyproject.toml
Comment thread taiga/mcp_server/serialize.py
Comment thread taiga/mcp_server/server.py
Comment thread taiga/mcp_server/server.py
@yakky
yakky requested a balanced review from Copilot October 4, 2026 14:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Batch updates can report an error after the write succeeded when response re-fetching fails.

Review effort: Balanced
Findings: 4 Medium severity · 1 Low severity

Open (5)
Resolved since last review (1)

Comment thread taiga/mcp_server/server.py
Comment thread changes/+mcp-payload-reduction.feature
@yakky
yakky requested a balanced review from Copilot October 4, 2026 14:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Explicit field projections can still lose requested media fields because payload-based stripping remains active.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
Resolved since last review (5)

Comment thread taiga/mcp_server/serialize.py Outdated
yakky and others added 13 commits October 4, 2026 16:31
Neither Milestones.list() nor WikiPages.list() require project at the
client/API level (confirmed live against taiga.nephila.it), unlike
project-scoped tools such as search or the by_ref lookups. Bring
list_milestones and list_wiki_pages in line with the sibling
list_user_stories/list_tasks/list_issues/list_epics pattern, which
already treat project as optional.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…estone

Milestone.user_stories is always fully expanded by python-taiga's parser,
making the embedded field potentially large. Add include_user_stories
(default True, preserving current output) to both tools so callers can
opt into a trimmed response with that key stripped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tests

Minor code-review follow-ups: replace the Any -> Any signature with the
actual dict[str, Any] | list[dict[str, Any]] shape it's always called
with, and add unit tests exercising the helper directly (dict, list,
and no-op-when-absent cases) rather than only through list_milestones/
get_milestone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…es toggle

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
yakky and others added 27 commits October 4, 2026 16:31
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
A task review found that entity_type/ref/fields were extracted from each
item before the try/except block, so a malformed item (missing a
required key) raised KeyError outside the per-item handler - aborting
the whole batch and discarding results already built for earlier
successful items, contradicting the tool's own "not atomic, one row per
item" contract. Moves the extraction inside the try block so a
malformed item now produces its own error row instead of crashing the
batch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
…prove batch error messages

Addresses final-review findings: `_represent`'s "minimal" branch merged
`{**base, **written}`, letting a caller-supplied stale `version` inside
`fields` (used for the write's own optimistic locking) silently
overwrite the correct post-write version - fixed by reversing the merge
order so `base` always wins. `docs/mcp.rst` claimed `version` is "not a
separate parameter anywhere in this server", which is false -
`set_custom_attribute_value` has one - scoped the claim to
`create_*`/`update_*` tools. `update_work_items`'s error rows used bare
`str(exc)`, producing opaque messages like `"'wiki'"` for structural
failures (unsupported entity_type, missing keys) - now includes the
exception type name.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
…display

resolve_assigned_users always returned null names — confirmed against a
live Taiga instance that membership records expose the display name as
full_name, not full_name_display (the field name every other Taiga user
block in this codebase uses, which this code wrongly assumed memberships
shared too). The output key stays full_name_display for consistency with
those other blocks; only the source field read from the membership
record changes. Also corrects an unrelated test's example field name
(list_memberships' generic fields-projection test) to use a real
membership field instead of the same wrong assumption.
…issue

- milestone: add project_extra_info.name/.slug so a cross-project report
  can display/link each board without a second call (M4)
- userstory: add assigned_users so minimal never under-reports secondary
  assignees (M5)
- userstory/issue: drop the redundant per-project status id in favour of
  status_extra_info.name, which is comparable across boards (M8b)
- docs: document verified filter keys and the is_closed/
  status_extra_info.is_closed contradiction caveat (M11)

Ref: artifacts/specs/2026-09-17-taiga-mcp-fix-recommendations.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
…set fixes

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
strict_filters only catches this server's own parameter names nested
inside filters by mistake. It gives no protection against a misspelled
or unsupported Taiga filter key (e.g. milestone__in, silently ignored
by Taiga's REST backend) - that class of mistake returns a normal-
looking but wrong result set with no error either way. Document this
prominently so filter-based narrowing is re-asserted client-side
rather than trusted purely because the call didn't raise.

Recommendation 2 (echoing the filters Taiga actually applied) was
considered and dropped: Taiga's list API gives no such signal back to
this client, so the only thing this server could honestly echo is the
query it sent, not what Taiga did with it - materially weaker than
what the finding asked for, so it's left undone rather than shipped
with an implied guarantee it can't deliver.

Ref: artifacts/specs/2026-09-17-taiga-mcp-fix-recommendations.md (M10)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
Anticipated failures - a strict_filters violation naming the offending
keys, a stale/missing version on a single-item update - were reaching
callers as a bare "Error executing tool <name>" with no detail. The mcp
SDK replaces any exception that isn't its own ToolError with a fixed
generic message, treating it as an unanticipated crash; only ToolError
(or a ValueError raised inside a pydantic validator) preserves the real
text.

Add a _patch() helper wrapping every single-item update_*/update_*_by_id
tool's resource.patch() call, re-raising as ToolError with the original
message, and change _check_strict_filters to raise ToolError directly.
update_work_items already avoided this by catching exceptions itself and
returning them as structured rows; single-item tools now surface the
same detail.

Ref: artifacts/specs/verify-writeside-b5.md,
artifacts/specs/taiga-mcp-2.0.0b5-verification.md (N1/N3)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
collapse_extra_info() only matched keys ending in _extra_info, so
invited_by (seen on membership records) - a full nested user block -
escaped the same shrinking every other user/project block gets, even
though it carries the same id + full_name_display shape.

Ref: artifacts/specs/verify-readside-b5.md,
artifacts/specs/taiga-mcp-2.0.0b5-verification.md (M7)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
apply_payload() previously merged expand's raw content after the strip
pass, guaranteeing an expanded block was byte-identical to the raw
resource - including avatar/logo fields, which strip_media otherwise
removes from everywhere else in the response. Measured as a 2.81x size
penalty in practice, with over half of it one duplicated signed logo
URL.

Reorders the merge before the strip pass instead: expand still adds
back every field of the named block that MINIMAL_FIELDS/fields would
otherwise drop, but the block now goes through the same strip_media
semantics as the rest of the response. This reverses a Day 1 ruling
that took the opposite position; usage measurement since then shows
the media leak costs more than the completeness guarantee is worth.

Ref: artifacts/specs/verify-readside-b5.md,
artifacts/specs/taiga-mcp-2.0.0b5-verification.md (M6)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
… issue

Two independent live verification sessions found that some MCP clients
reject a tool call outright when a parameter carrying a schema-level
default is omitted, even though it is absent from the schema's
required array - affecting every such parameter, including ones that
predate this server's payload-reduction work (e.g. list_milestones'
include_user_stories). Confirmed this server's own schema and its own
protocol-level validation are correct (live tools/list shows no
required entries for these parameters, and a raw stdio JSON-RPC call
omitting them succeeds); the rejection traces to the calling client's
own schema-to-validator bridge, not to this server, and there is no
hook in this server's dependencies to change what gets emitted to work
around it.

Ref: artifacts/specs/verify-readside-b5.md,
artifacts/specs/taiga-mcp-2.0.0b5-verification.md (B1/N1),
artifacts/specs/2026-09-17-b5-verification-spike-report.md

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
Confirmed via a raw, unfiltered probe: a milestone-embedded user_story
has no assigned_users key at all - Taiga's embedded serializer omits
it entirely, unlike the top-level list_user_stories/get_user_story
shape. Not something fields or resolve_assigned_users can produce,
since there's no bare id list in the embedded payload to project or
resolve from. The one-call "everything in a sprint" route is real and
valuable, but yields primary assignees only.

Ref: artifacts/specs/taiga-mcp-2.0.0b5-verification.md (N4)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
…the last id (N2)

filters={"milestone": [1444, 1446]} returns a small, clean, plausible-
looking result set - but only for the last id, with every other id's
items silently dropped. Traced to taiga/requestmaker.py: query is
passed straight to requests.get(..., params=query), and requests
already serializes a list value as repeated same-name query params
correctly - this client is not the one collapsing it. The collapse
happens in Taiga's own REST backend (standard Django QueryDict.get()
semantics on repeated params), outside this repo's control - the only
available mitigation is documenting it, alongside the existing
milestone__in caveat.

Ref: artifacts/specs/taiga-mcp-2.0.0b5-verification.md (N2)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
Fragments for the three b6 bugfix commits (060e3a4, 4901a62, 40b8f94)
that were missed at the time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QBEycLqZR68HpZF5jCdq7Y
…s resolution

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@yakky
yakky force-pushed the feature/267-mcp-write-payload-reduction branch from 158ec68 to 903cce4 Compare October 4, 2026 14:32
…ds is set

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants