chore: update mcp-envelope-integration task status to completed
This commit is contained in:
92
tasks/adapters/001-mcp-envelope-integration.md
Normal file
92
tasks/adapters/001-mcp-envelope-integration.md
Normal file
@@ -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<OperationSpec & { handler: OperationHandler }>`
|
||||||
|
- [ ] 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
|
||||||
Reference in New Issue
Block a user