History

fix(mcp): default missing output schemas to unknown

Rhys Sullivan committed 7 months ago

Commit dbff25f1 parent 81e63052 files · +112 −6Browse files

Unified · Split

2 files
  1. plugins/mcp/sdk/catalog.test.ts +104 −0
  2. plugins/mcp/sdk/catalog.ts +8 −6
plugins/mcp/sdk/catalog.test.ts+104−0
@@ -0,0 +1,104 @@
1import { describe, expect, it } from "@effect/vitest";
2import { projectCatalogForAgentSdk } from "@executor/ir/catalog";
3import type { Source } from "@executor/source-core";
4
5import {
6 createMcpCatalogSnapshot,
7 type McpCatalogOperationInput,
8} from "./catalog";
9
10const createSource = (): Source => ({
11 id: "axiom-mcp",
12 scopeId: "scope_local",
13 name: "Axiom MCP",
14 kind: "mcp",
15 status: "connected",
16 enabled: true,
17 namespace: "axiom.mcp",
18 createdAt: 0,
19 updatedAt: 0,
20});
21
22const createDashboardInputSchema = {
23 type: "object",
24 properties: {
25 dashboardJson: {
26 type: "string",
27 },
28 },
29 required: ["dashboardJson"],
30 additionalProperties: false,
31} as const;
32
33const createOperation = (
34 input: { outputSchema?: unknown } = {},
35): McpCatalogOperationInput => ({
36 toolId: "createdashboard",
37 title: "createDashboard",
38 description: "Create a new dashboard from a JSON document.",
39 effect: "write",
40 inputSchema: createDashboardInputSchema,
41 ...(input.outputSchema !== undefined
42 ? { outputSchema: input.outputSchema }
43 : {}),
44 providerData: {
45 toolId: "createdashboard",
46 toolName: "createDashboard",
47 displayTitle: "createDashboard",
48 title: null,
49 description: "Create a new dashboard from a JSON document.",
50 annotations: null,
51 execution: {
52 taskSupport: "forbidden",
53 },
54 icons: null,
55 meta: null,
56 rawTool: {
57 name: "createDashboard",
58 description: "Create a new dashboard from a JSON document.",
59 inputSchema: createDashboardInputSchema,
60 ...(input.outputSchema !== undefined
61 ? { outputSchema: input.outputSchema }
62 : {}),
63 },
64 server: null,
65 },
66});
67
68describe("MCP catalog", () => {
69 it("projects missing MCP output schemas as unknown result data", () => {
70 const snapshot = createMcpCatalogSnapshot({
71 source: createSource(),
72 documents: [{
73 documentKind: "mcp_manifest",
74 documentKey: "https://mcp.axiom.co/mcp",
75 contentText: JSON.stringify({ version: 2, tools: [] }),
76 }],
77 operations: [createOperation()],
78 });
79 const projected = projectCatalogForAgentSdk({
80 catalog: snapshot.catalog,
81 });
82 const descriptor = Object.values(projected.toolDescriptors).find((entry) =>
83 entry.toolPath.join(".") === "axiom.mcp.createdashboard");
84
85 if (!descriptor?.resultShapeId) {
86 throw new Error("Expected createdashboard to have a projected result shape");
87 }
88
89 const resultShape = projected.catalog.symbols[descriptor.resultShapeId];
90 if (resultShape?.kind !== "shape" || resultShape.node.type !== "object") {
91 throw new Error("Expected MCP result envelope");
92 }
93
94 const dataShape = projected.catalog.symbols[resultShape.node.fields.data.shapeId];
95 if (dataShape?.kind !== "shape" || dataShape.node.type !== "anyOf") {
96 throw new Error("Expected nullable MCP result data");
97 }
98
99 const dataVariants = dataShape.node.items.map((shapeId) => projected.catalog.symbols[shapeId]);
100 expect(
101 dataVariants.some((shape) => shape?.kind === "shape" && shape.node.type === "unknown"),
102 ).toBe(true);
103 });
104});
plugins/mcp/sdk/catalog.ts+8−6
@@ -99,13 +99,15 @@ const createMcpCapability = (input: { · 98 unmodified lines
99 protocol: "mcp",99 protocol: "mcp",
100 })}`,100 })}`,
101 );101 );
102 // MCP tools frequently omit outputSchema even when they return data.
103 // Treat that as unknown rather than as an explicit null response.
102 const outputShapeId =104 const outputShapeId =
103 input.operation.outputSchema !== undefined105 input.importer.importSchema(
104 ? input.importer.importSchema(106 input.operation.outputSchema === undefined
105 input.operation.outputSchema,107 ? true
106 `#/mcp/${input.operation.providerData.toolId}/output`,108 : input.operation.outputSchema,
107 )109 `#/mcp/${input.operation.providerData.toolId}/output`,
108 : undefined;110 );
109 const callShapeId =111 const callShapeId =
110 input.operation.inputSchema === undefined112 input.operation.inputSchema === undefined
111 ? input.importer.importSchema(113 ? input.importer.importSchema(