Skip to content

fix(mcp): preserve structured-only federated tool results - #6369

Open
feiiiiii5 wants to merge 1 commit into
IBM:mainfrom
feiiiiii5:fix/structured-only-tool-results
Open

fix(mcp): preserve structured-only federated tool results#6369
feiiiiii5 wants to merge 1 commit into
IBM:mainfrom
feiiiiii5:fix/structured-only-tool-results

Conversation

@feiiiiii5

Copy link
Copy Markdown

🐛 Bug-fix PR

📌 Summary

A successful federated tools/call result may carry all of its data in structuredContent while content is empty. The MCP Tools specification makes unstructured content a SHOULD, not a MUST, when structured content is present. ContextForge's streamable-HTTP transport treated that valid shape as an empty result and returned before reaching the structured-content handler.

For tools declaring an outputSchema, the MCP SDK then substituted:

Output validation error: outputSchema defined but no structured output returned

The real structured payload never reached the client.

Closes #5472

🔁 Reproduction Steps

  1. Federate an upstream MCP tool that declares an outputSchema.
  2. Return { "content": [], "structuredContent": { ... } } from its tools/call result.
  3. Invoke the tool through ContextForge's /mcp streamable-HTTP endpoint.
  4. Current main returns the validation-error text instead of forwarding the structured payload.

The issue includes a standalone reproduction repository and before/after output.

🐞 Root Cause

After invoke_tool() returned successfully, the early guard was:

if not result or not result.content:
    return []

It ignored result.structured_content. Structured-only results therefore returned [] before the later handler could assemble (unstructured, structured) for SDK validation.

💡 Fix Description

Treat the result as empty only when both representations are absent:

if not result or (not result.content and not getattr(result, "structured_content", None)):

Structured-only results now fall through to the existing handler and return (unstructured, structured). Results with neither representation continue to return [].

This is intentionally separate from #6182/#6274: those track preservation of an empty dict (structuredContent: {}) in the service layer. This fix handles non-empty structured payloads whose unstructured content array is empty.

📏 Reviewability

  • This PR has one clear purpose
  • The linked issue is not labeled triage
  • Unrelated bugs or improvements are tracked in separate issues/PRs
  • Tests are included with the code they validate
  • If AI-assisted, I understand and can explain the generated changes

🧪 Verification

Pre-fix: the new structured-only test failed with result == [].

Post-fix on this branch:

python -m pytest -q tests/unit/mcpgateway/transports/test_streamablehttp_transport.py -k 'call_tool_success or with_structured_content or structured_only_result or neither_representation or preserves_is_error_for_egress'
# 5 passed

Additional checks:

uvx ruff@0.16.1 check mcpgateway/transports/streamablehttp_transport.py
# All checks passed

Full project lint/test/coverage were not duplicated locally due to the large dependency set; CI will provide that result.

Check Command Status
Changed source lint Ruff 0.16.1 Pass
Targeted regression 5 tests Pass
Pre-fix regression Current-main behavior Fail as expected
Full lint / test / coverage make lint / CI Pending CI

📐 MCP Compliance (if relevant)

  • Matches current MCP spec — unstructured content is a SHOULD when structuredContent is present
  • No breaking change to MCP clients
  • Existing genuinely-empty results still return []

✅ Checklist

  • Code formatted
  • No secrets/credentials committed

MCP allows unstructured content to be empty when structuredContent is
present. The streamable-HTTP transport treated an empty content array as
an empty result and returned before reaching the structured-content
handler, so every outputSchema tool backed by a structured-only upstream
failed validation.

Only return early when neither representation is present. Add regression
coverage for structured-only success and the genuinely-empty fallback.

Signed-off-by: fei <204683769+feiiiiii5@users.noreply.github.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.

Federated tool results with structuredContent + empty content are dropped (Output validation error for every outputSchema tool)

1 participant