Skip to content

Add opt-in structured results for MCP tools - #10235

Merged
isaacbmiller merged 5 commits into
mainfrom
isaacbmiller/mcp-structured-output
Aug 20, 2026
Merged

isaacbmiller merged 5 commits into
mainfrom
isaacbmiller/mcp-structured-output

Conversation

@isaacbmiller

@isaacbmiller isaacbmiller commented Aug 18, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Add an explicit, backward-compatible way to consume MCP's machine-readable structuredContent through dspy.Tool.

structured_tool = dspy.Tool.from_mcp_tool(
    client,
    mcp_tool,
    result_mode="structured",
)
  • Keep result_mode="text" as the default, preserving the result behavior established before and during Support MCP SDK v2 without changing tool result semantics #10188.
  • In structured mode, return structuredContent / structured_content exactly when the field is present.
  • Preserve every JSON root type and falsy/empty value, including objects, arrays, strings, numbers, booleans, null, {}, [], "", and 0.
  • Distinguish an omitted structured field from an explicitly present JSON null using the SDK model's field-presence metadata.
  • Fall back to the existing text/non-text conversion when a server does not provide structured content.
  • Keep tool errors ahead of normal result conversion.
  • Document when model-facing content is preferable (for example ReAct observations) versus when structured data is preferable (programmatic validation, indexing, and explicit tool chaining).
  • Include a concise user example against the maintained Everything MCP reference server.

Result contract

DSPy returns structured content exactly as the server sent it. It does not parse text, infer provenance, or unwrap a single result field.

MCP result Default text mode structured mode
content="3", structuredContent={"result":3} "3" {"result":3}
structured object existing content conversion exact object
structured array/scalar/boolean/null existing content conversion exact JSON value
no structured field existing content conversion existing content conversion
isError=true raises RuntimeError raises RuntimeError

Keeping the envelope is deliberate: in an application-defined object, result may be real domain data rather than an SDK wrapper.

Deliberate scope

This PR builds on the transport compatibility merged in #10188. It does not:

  • change the default result mode;
  • automatically unwrap Python SDK {"result": ...} envelopes;
  • parse JSON strings from content;
  • change MCP connection or multi-round-trip lifecycle handling;
  • add a DSPy output-schema abstraction.

Separate DSPy issue / follow-up: #10236. Mixed MCP content blocks need their own contract. The existing converter favors text when a result also contains images, audio, embedded resources, or resource links. Preserving those mixed blocks is useful, but it is independent of structured JSON selection and should be addressed in a separate issue and PR.

Verification

Focused MCP suite:

  • mcp==1.5.0: 29 passed, 2 skipped
  • mcp==1.10.0: 30 passed, 1 skipped
  • mcp==2.0.0: 31 passed
  • Real @modelcontextprotocol/server-everything@2026.7.4 stdio call: text and native weather object observed
  • Real @modelcontextprotocol/server-filesystem@2026.7.10 stdio call: text and exact structured file-content object observed
  • tests/adapters/test_tool.py: 38 passed
  • Ruff check and format checks on touched Python files: passed
  • git diff --check: passed

@isaacbmiller
isaacbmiller marked this pull request as ready for review August 18, 2026 15:30
@greptile-apps

greptile-apps Bot commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This change adds an opt-in structured-result mode for MCP-backed tools while preserving the existing content conversion by default. The MCP bridge returns structured JSON values exactly when supplied, distinguishes an explicit null from an omitted structured field, falls back to content when structured data is absent, and retains MCP error handling.

Confidence Score: 5/5

No blocking failure remains.

The asynchronous MCP tool bridge was exercised with MCP v1 and v2 result conventions, including structured values, explicit nulls, omitted-field fallback, error handling, and invalid mode rejection. The observed behavior matches the intended result contract.

T-Rex T-Rex Logs

What T-Rex did

  • Ran the focused MCP 1.29.0 bridge validation script to establish the baseline before the structured-result PR.
  • Inspected the pre-PR Explicit null behavior log to confirm baseline handling of explicit null values.
  • Ran MCP 1.29.0 async bridge validation after the structured-result PR and verified the updated behavior passes.
  • Executed the isolated MCP 2.0.0 convention and bridge validation script to confirm compatibility with the new version.
  • Validated MCP 2.0.0 snake_case and async bridge behavior and ran the existing MCP test invocation to ensure end-to-end coverage.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (4): Last reviewed commit: "docs: streamline MCP structured result g..." | Re-trigger Greptile

@isaacbmiller
isaacbmiller merged commit ca0a088 into main Aug 20, 2026
23 checks passed
@isaacbmiller
isaacbmiller deleted the isaacbmiller/mcp-structured-output branch August 20, 2026 17:13
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.

2 participants