Conversation
4baca4a to
74e2d8e
Compare
There was a problem hiding this comment.
Pull request overview
Updates the server and desktop UI API types to match the MCP wire spec by emitting camelCase fields for CallToolResponse and ensuring tool content blocks are OpenAPI-discriminated unions (with a literal type field), so generated TypeScript aligns with the SDK’s CallToolResult.
Changes:
- Backend: serialize
CallToolResponsein camelCase and introduce schema-onlyContentBlockvariants withtypediscriminators for OpenAPI/TS generation. - Frontend: remove the redundant
McpAppToolResultand passCallToolResultthrough toAppRendererdirectly. - Regenerate OpenAPI + TypeScript client types to reflect the corrected response shape.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/desktop/src/components/ToolCallWithResponse.tsx | Switches tool result handling to ContentBlock and adjusts embedded resource rendering. |
| ui/desktop/src/components/McpApps/types.ts | Removes the redundant McpAppToolResult type. |
| ui/desktop/src/components/McpApps/McpAppRenderer.tsx | Accepts CallToolResult directly and updates callTool response field names to camelCase. |
| ui/desktop/src/api/types.gen.ts | Regenerated types: CallToolResponse camelCase + new ContentBlock discriminated union. |
| ui/desktop/src/api/index.ts | Re-exports newly generated API types (including ContentBlock variants). |
| ui/desktop/openapi.json | OpenAPI schema updated for camelCase fields and discriminated ContentBlock schemas. |
| crates/goose-server/src/routes/agent.rs | Implements camelCase CallToolResponse + schema-only content block schemas and serializes content via Value. |
| crates/goose-server/src/openapi.rs | Registers the new schema-only content block schemas for OpenAPI generation. |
74e2d8e to
00dfded
Compare
I am not sure about this. It looks complicated and since it constructs the types independently of the actual Rust code, only give us the illusion of type safety |
The goal is to get a discriminated union for TS, the current way RMCP approaches this lacks the |
|
@aharvard I pulled your branch and tried adding support to the existing schemars-to-utoipa macros we have in openapi.rs, and I think the result looks like what we want: #7792 (that's a PR targeting this branch) Feel free to merge it into this if it accomplishes what you're doing. The types are defined slightly differently, but you still get a discriminated union at the end |
|
thanks @jamadeo, just merged in your PR (will work on cleaning up this PR next) |
…rs to ContentBlock - Add #[serde(rename_all = "camelCase")] to CallToolResponse so fields serialize as isError, structuredContent (matching MCP spec) - Define schema-only ContentBlock types with proper 'type' discriminator fields (text, image, resource, audio, resource_link) for OpenAPI - Serialize content via serde_json::Value to bypass utoipa's inability to reflect rmcp's #[serde(tag = "type")] on external types - Replace old Content type with ContentBlock in ToolCallWithResponse - Remove redundant McpAppToolResult type; use CallToolResult directly in McpAppRenderer Co-authored-by: Goose <[email protected]> Ai-assisted: true
Co-authored-by: Goose <[email protected]> Ai-assisted: true
Co-authored-by: Goose <[email protected]> Ai-assisted: true
b96683e to
fe0dfd3
Compare
Co-authored-by: Goose <[email protected]> Ai-assisted: true
Co-authored-by: Goose <[email protected]> Ai-assisted: true
Co-authored-by: Goose <[email protected]> Ai-assisted: true
* main: (45 commits)
fix: resolve {{ recipe_dir }} in nested sub-recipe paths during secret discovery (#7797)
Add @DOsinga as CODEOWNER for documentation (#7799)
feat: Add summarize tool for deterministic reads (#7054)
fix(api): use camelCase in CallToolResponse and add type discriminators to ContentBlock (#7487)
feat: ACP providers for claude code and codex (#6605)
chore(deps): bump express-rate-limit from 8.2.1 to 8.3.0 in /evals/open-model-gym/mcp-harness (#7703)
feat(openai): capture reasoning summaries from responses API (#7375)
Fix some dependencies (#7794)
fix: improve keyring availability error detection (#7766)
feat: add MiniMax provider with Anthropic-compatible API (#7640)
feat: add Tensorix as a declarative provider (#7712)
fix(security): remove insecure default secret from GOOSE_EXTERNAL_BACKEND (#7783)
refactor: Convert Tanzu provider to declarative JSON config (#7124)
replaces https://github.com/block/goose/pull/7340/changes (#7786)
feat(summon): make skill supporting files individually loadable via load() (#7583)
Keep toast open on failed extension (#7771)
fix(ui-desktop): unify path resolution around GOOSE_PATH_ROOT (#7335)
fix: pass OAuth scopes to DCR and extract granted_scopes from token response (#7571)
fix: write to real file if config.yaml is symlink (#7669)
fix: preserve pairings when stopping gateway (#7733)
...
* main: (69 commits)
fix: resolve {{ recipe_dir }} in nested sub-recipe paths during secret discovery (#7797)
Add @DOsinga as CODEOWNER for documentation (#7799)
feat: Add summarize tool for deterministic reads (#7054)
fix(api): use camelCase in CallToolResponse and add type discriminators to ContentBlock (#7487)
feat: ACP providers for claude code and codex (#6605)
chore(deps): bump express-rate-limit from 8.2.1 to 8.3.0 in /evals/open-model-gym/mcp-harness (#7703)
feat(openai): capture reasoning summaries from responses API (#7375)
Fix some dependencies (#7794)
fix: improve keyring availability error detection (#7766)
feat: add MiniMax provider with Anthropic-compatible API (#7640)
feat: add Tensorix as a declarative provider (#7712)
fix(security): remove insecure default secret from GOOSE_EXTERNAL_BACKEND (#7783)
refactor: Convert Tanzu provider to declarative JSON config (#7124)
replaces https://github.com/block/goose/pull/7340/changes (#7786)
feat(summon): make skill supporting files individually loadable via load() (#7583)
Keep toast open on failed extension (#7771)
fix(ui-desktop): unify path resolution around GOOSE_PATH_ROOT (#7335)
fix: pass OAuth scopes to DCR and extract granted_scopes from token response (#7571)
fix: write to real file if config.yaml is symlink (#7669)
fix: preserve pairings when stopping gateway (#7733)
...
Summary
Fixes the
CallToolResponseAPI to use camelCase field names matching the MCP spec, and adds propertypediscriminator fields to content blocks so generated TypeScript types align with the SDK'sCallToolResult.Problem
CallToolResponseserialized fields as snake_case (is_error,structured_content) but the MCP spec and SDK expect camelCase (isError,structuredContent)Contenttype from rmcp includes atypediscriminator on the wire (via#[serde(tag = "type")]) but utoipa can't reflect this for external types, so the generated OpenAPI schema and TypeScript types were missing itMcpAppToolResultwas a redundant intermediate type that duplicatedCallToolResultChanges
Rust Backend (
agent.rs):#[serde(rename_all = "camelCase")]toCallToolResponsecontenttoVec<Value>with schema-onlyContentBlocktypes that includetypediscriminator literals ("text","image","resource","audio","resource_link")ToSchemaimpls to work around utoipa's inability to reflect rmcp's#[serde(tag = "type")]Frontend:
McpAppToolResulttypeMcpAppRenderernow acceptsCallToolResultdirectly — no conversion useMemoToolCallWithResponseusesContentBlock(with propertypefield) instead of oldContentWhy schema-only types?
rmcp's schemars schema is correct (it reflects the
typediscriminator), but goose uses utoipa for OpenAPI generation. utoipa can't derive the right schema for rmcp's externally-defined tagged enum. The schema-only types exist solely to produce a correct OpenAPI spec — actual serialization goes throughserde_json::Value.Follows up on #7476