Repository navigation
feat(mcp): type mcp block replies from declared output schemas - #9466
waleedlatif1 wants to merge 4 commits into
Conversation
MCP servers may declare an outputSchema on a tool and return
structuredContent, but discovery copied only name, description and
inputSchema, so no surface showed a reply shape and the MCP block declared
only content. Agents building workflows guessed field names, and a
reference such as <mcp.structuredContent.x> failed validation even when the
tool returned it.
- Discovery keeps a declared outputSchema on McpTool; the tool cache carries
it, and managed connections persist it in the existing mcp_tools jsonb
snapshot (type only, no migration).
- Surfaced on GET /api/v2/mcp-servers/{id}/tools (and so the CLI's
mcp-servers tools list), mcp_list_operations, the editor tool list, and the
integration catalog (synced contract, optional field). The v2 tool shape is
strict, so keeping the field without declaring it would have turned
discovery into a 500.
- The MCP block gains a hidden, server-managed toolOutputSchema sub-block: a
structure-only snapshot of the selected operation's schema, bound to the
server and operation it was taken for. The editor writes it when an
operation is picked; applyWorkflowOperations writes it for agent edits,
through the same authorized discovery use cases, best effort and only when
the snapshot is missing or stale.
- With a matching snapshot, the block declares a typed structuredContent
output beside content, so the run's reference check, lint (which now walks
paths below structuredContent and names the declared fields) and the tag
paths all read the declared shape, and workflows deps fills mock leaves
from it. Without one, outputs are exactly as before.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
[Medium impact] The PR appears safe to merge; no new actionable issues remain. SummaryKeeps MCP tools’ declared
|
There was a problem hiding this comment.
All reported issues were addressed across 42 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
- Read the MCP target with the block's saved canonical modes in lint, workflows deps and effective outputs, so a dormant basic/advanced member never selects or drops the snapshot. - Expand repeated bracket indices before the nested lint check, as the run resolves them. - Stop expanding a schema once its node budget is spent and leave that object open, instead of saving an untyped node per remaining field. - Keep every declared field name, constructor and __proto__ included. - Snapshot only blocks the edit changed, and discover best effort, so an unreachable server neither slows unrelated edits nor gets marked unhealthy.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
All reported issues were addressed across 42 files
Reply with feedback, questions, or to request a fix.
Turn on auto-fix | Re-trigger cubic
…__ fields safely workflows deps now adds an operation's declared fields wherever a consumer reads a structuredContent value whole, beside the paths it reads by name. Trigger mock payloads define sampled fields with Object.fromEntries, so a declared __proto__ field stays a field instead of replacing the sample's prototype.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
A whole read now adds every declared field below it, through nested objects and array items, even where the consumer also reads a nested path by name.
|
@cubic-dev-ai review this PR |
@waleedlatif1 I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
No issues found across 44 files
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
You've manually re-run cubic several times on this PR. Each manual re-review checks the full PR again and counts toward your usage quota. To preserve your usage limits, we recommend letting cubic automatically review new commits.
Turn on auto-fix | Re-trigger cubic
Summary
MCP servers may declare an
outputSchemaon a tool and returnstructuredContent(spec 2025-06-18), but Sim dropped the schema at discovery:McpClient.listToolscopied onlyname,descriptionandinputSchema, and the MCP block declared onlycontent. So nothing showed a tool's reply shape, and a reference such as<mcp.structuredContent.status>failed validation even when the tool returned it. Anyone (or any agent) parsing an MCP reply had to guess field names.This keeps the declared schema end to end and types the block's reply from it:
outputSchemaonMcpTool. The discovery cache carries it, and managed connections persist it in their existingmcp_toolsjsonb snapshot (a$typechange only, no migration).GET /api/v2/mcp-servers/{id}/tools(and so the CLI'smcp-servers tools list),mcp_list_operations, the editor's tool list, and the integration catalog (an optional field on the syncedIntegrationCatalogResponse). The v2 tool shape is.strict(), so keeping the field without declaring it would have turned discovery into a 500; a route test pins that.toolOutputSchemasub-block, a structure-only snapshot (types, properties, items; no description strings, since block inputs are scanned for<…>references) bound to the server and operation it was taken for. The editor writes it when an operation is picked;applyWorkflowOperationswrites it for API/Copilot edits through the same authorized discovery use cases, best effort and only when the snapshot is missing or stale. With a matching snapshot the block declaresstructuredContentbesidecontent, so:structuredContentand names the declared fields (unknown-field … has no output field "structuredContent.onCall" … Declared fields: …),workflows depsfills mock leaves understructuredContentwith the declared skeleton.contentis untouched for every workflow.Companion: a mothership change adds the same optional
outputSchemato the source ofintegration-catalog.tsand regenerates the CLI grammar (mcp-servers tools listshape). It will be linked once opened.Type of Change
Testing
Tests were written first and confirmed red against the current code, then green:
serializer/mcp-output-schema.test.ts: serializer → runtime schema → reference resolution (declared, omitted-declared, undeclared, and every "no typed output" case).lib/workflows/editing/dangling-refs.test.ts: lint accepts declared paths, flags undeclared nested ones with the declared field list, unchanged without a schema.lib/workflows/application/mcp-output-schemas.test.ts: edit-time snapshots (declared/undeclared, one discovery per server, stale snapshot replaced, references/list/empty skipped, active canonical member only, discovery failure keeps the edit, managed connections).lib/mcp/client.test.ts,lib/internal/mcp/list-operations.test.ts,app/api/v2/mcp-servers/[mcpServerId]/tools/route.test.ts,lib/mothership/integrations/application/catalog.test.ts,lib/credentials/application/discover-managed-mcp-tools.test.ts,lib/mothership/agent-cli/engines.test.ts(deps).Local:
bun run lint:check,bun run type-check,bun run check:audits,check-block-registry.ts origin/staging,check:test-types, and the related Vitest suites (~11.5k tests acrosslib/mcp,lib/workflows,serializer,blocks,executor,lib/mothership,tools, MCP routes) all pass.Reviewers: the snapshot binding in
getMcpStructuredContentOutputs(only a snapshot for the block's current server and operation applies), and the edit-time discovery inapply-workflow-operations.ts(bounded at 10s, cache-aside, never fails the edit).Checklist