Showing the changes to plugins (2 of 2 files). Show all
Unified · Split
| @@ -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 | }); | ||
| @@ -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 !== undefined | 105 | 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 === undefined | 112 | input.operation.inputSchema === undefined |
| 111 | ? input.importer.importSchema( | 113 | ? input.importer.importSchema( |