From f833fe3d5334df7e06e1945fb1c996606796f00b Mon Sep 17 00:00:00 2001 From: "glm-5.1" Date: Mon, 11 May 2026 01:59:52 +0000 Subject: [PATCH] chore: update mcp-envelope-integration task status to completed --- .../adapters/001-mcp-envelope-integration.md | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 tasks/adapters/001-mcp-envelope-integration.md diff --git a/tasks/adapters/001-mcp-envelope-integration.md b/tasks/adapters/001-mcp-envelope-integration.md new file mode 100644 index 0000000..a0762d4 --- /dev/null +++ b/tasks/adapters/001-mcp-envelope-integration.md @@ -0,0 +1,92 @@ +--- +id: mcp-envelope-integration +name: Update from_mcp adapter to use mcpEnvelope, structuredContent, and outputSchema extraction +status: completed +depends_on: [response-envelope-types] +scope: moderate +risk: medium +impact: component +level: implementation +--- + +## Description + +Update `src/from_mcp.ts` to align with the response envelope specification documented in `response-envelopes.md` § MCP Adapter, `adapters.md` § from_mcp, and the drift tables in `call-protocol.md` and `api-surface.md`. + +Changes needed (current source → target): + +| What | Current source | Target | +|------|---------------|--------| +| `outputSchema` at discovery time | `Type.Unknown()` for all tools | `tool.outputSchema ? FromSchema(tool.outputSchema) : Type.Unknown()` | +| Handler return value | Returns `result.content` (line 79) | Returns `mcpEnvelope(data, meta)` | +| `structuredContent` | Not used | Prefer as `data` when present; fall back to `mapMCPContentBlocks(result.content)` | +| `isError` handling | Throws `Error` (line 76) | Wraps in envelope with `meta.isError: true`, does NOT throw | +| `Value.Cast()` | Not used | If `structuredContent && outputSchema !== Unknown`, cast `Value.Cast(outputSchema, structuredContent)` | + +Implementation details: + +1. **`outputSchema` extraction**: When `tool.outputSchema` is present (MCP spec 2025-06-18+), convert it to TypeBox via `FromSchema(tool.outputSchema)`. Fall back to `Type.Unknown()`. + +2. **Handler behavior**: The handler should use `mcpEnvelope()` to wrap results: + ```ts + handler: async (input, context) => { + const result = await client.callTool({ name: tool.name, arguments: input }) + const data = result.structuredContent + ? (spec.outputSchema !== Type.Unknown() + ? Value.Cast(spec.outputSchema, result.structuredContent) + : result.structuredContent) + : mapMCPContentBlocks(result.content) + return mcpEnvelope(data, { + isError: result.isError ?? false, + content: mapMCPContentBlocks(result.content), + structuredContent: result.structuredContent, + _meta: result._meta, + }) + } + ``` + +3. **`mapMCPContentBlocks()`**: A helper function that maps SDK `ContentBlock[]` to our `MCPContentBlock[]` types. Unknown block types are mapped to `{ type: "text", text: JSON.stringify(block) }` as fallback. + +4. **`isError` no longer throws**: MCP errors are represented as data, not thrown exceptions. Only transport-level errors (connection failure, tool not found) throw. + +5. **The tool iteration** needs to capture `outputSchema` per tool since the handler closure needs it for `Value.Cast`. + +## Acceptance Criteria + +- [ ] `tool.outputSchema` is converted via `FromSchema()` when present; falls back to `Type.Unknown()` +- [ ] Handler returns `mcpEnvelope()` — never raw content and never throws on `isError` +- [ ] `structuredContent` is preferred as data when present +- [ ] `mapMCPContentBlocks()` helper maps SDK content blocks to our `MCPContentBlock[]` types +- [ ] Unknown MCP content block types fall back to `{ type: "text", text: JSON.stringify(block) }` +- [ ] `Value.Cast()` normalization applied when `outputSchema !== Type.Unknown()` and `structuredContent` is present +- [ ] `isError: true` results are wrapped in envelope, not thrown +- [ ] Transport-level errors (connection, etc.) still throw `CallError` +- [ ] `MCPClientWrapper.tools` type still returns `Array` +- [ ] Existing tests updated; new tests for envelope wrapping, structuredContent, isError handling +- [ ] `npm run build` passes, `npm run lint` passes, `npm test` passes + +## References + +- docs/architecture/adapters.md § from_mcp (Implementation changes needed table) +- docs/architecture/response-envelopes.md § MCP Adapter, § Handler behavior change +- docs/architecture/api-surface.md § Source vs. Spec Drift (from_mcp row) +- src/from_mcp.ts + +## Notes + +Merged `feat/response-envelope-types` branch first to get `ResponseEnvelope` types. The `Kind` symbol from `@alkdev/typebox` is used to detect `Type.Unknown()` schema at runtime, with a fallback to empty-object detection. + +## Summary + +Implemented MCP adapter envelope integration in `src/from_mcp.ts`: +- Created: `test/from_mcp.test.ts` (20 tests) +- Modified: `src/from_mcp.ts` + - `outputSchema` extraction from `tool.outputSchema` via `FromSchema()`, falls back to `Type.Unknown()` + - Handler returns `mcpEnvelope()` with structured/legacy data path + - `structuredContent` preferred as `data` when present, `Value.Cast()` applied when `outputSchema` is not Unknown + - `isError: true` wrapped in envelope meta, NOT thrown + - Transport-level config errors throw `CallError` + - Added `mapMCPContentBlocks()` exported helper mapping SDK `ContentBlock[]` to our `MCPContentBlock[]` + - Unknown block types fall back to `{ type: "text", text: JSON.stringify(block) }` + - Removed unused `OperationContext` import +- Build, lint, and all 95 tests pass \ No newline at end of file