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 @@
1+import { describe, expect, it } from "@effect/vitest";
2+import { projectCatalogForAgentSdk } from "@executor/ir/catalog";
3+import type { Source } from "@executor/source-core";
4+
5+import {
6+ createMcpCatalogSnapshot,
7+ type McpCatalogOperationInput,
8+} from "./catalog";
9+
10+const 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+
22+const createDashboardInputSchema = {
23+ type: "object",
24+ properties: {
25+ dashboardJson: {
26+ type: "string",
27+ },
28+ },
29+ required: ["dashboardJson"],
30+ additionalProperties: false,
31+} as const;
32+
33+const 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+
68+describe("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
9999 protocol: "mcp",
100100 })}`,
101101 );
102+ // MCP tools frequently omit outputSchema even when they return data.
103+ // Treat that as unknown rather than as an explicit null response.
102104 const outputShapeId =
103− input.operation.outputSchema !== undefined
104− ? input.importer.importSchema(
105− input.operation.outputSchema,
106− `#/mcp/${input.operation.providerData.toolId}/output`,
107− )
108− : undefined;
105+ input.importer.importSchema(
106+ input.operation.outputSchema === undefined
107+ ? true
108+ : input.operation.outputSchema,
109+ `#/mcp/${input.operation.providerData.toolId}/output`,
110+ );
109111 const callShapeId =
110112 input.operation.inputSchema === undefined
111113 ? input.importer.importSchema(