diff options
Diffstat (limited to 'packages/mcp/src/registry.test.ts')
| -rw-r--r-- | packages/mcp/src/registry.test.ts | 416 |
1 files changed, 208 insertions, 208 deletions
diff --git a/packages/mcp/src/registry.test.ts b/packages/mcp/src/registry.test.ts index 4c88828..37cfea5 100644 --- a/packages/mcp/src/registry.test.ts +++ b/packages/mcp/src/registry.test.ts @@ -4,21 +4,21 @@ import { adaptTool, flattenContent, namespace } from "./registry.js"; import type { McpCallResult, McpContentItem, McpToolCaller, McpToolInfo } from "./types.js"; const mockSpan = { - id: "span-1", - log: {} as Logger, - setAttributes: () => {}, - addLink: () => {}, - child: () => mockSpan, - end: () => {}, + id: "span-1", + log: {} as Logger, + setAttributes: () => {}, + addLink: () => {}, + child: () => mockSpan, + end: () => {}, }; const mockLogger: Logger = { - info: () => {}, - warn: () => {}, - error: () => {}, - debug: () => {}, - child: () => mockLogger, - span: () => mockSpan, + info: () => {}, + warn: () => {}, + error: () => {}, + debug: () => {}, + child: () => mockLogger, + span: () => mockSpan, }; /** @@ -26,212 +26,212 @@ const mockLogger: Logger = { * It records calls and returns a configurable result. */ function makeCaller( - result: McpCallResult, + result: McpCallResult, ): McpToolCaller & { calls: Array<{ name: string; args: unknown }> } { - const calls: Array<{ name: string; args: unknown }> = []; - return { - calls, - callTool: async (name: string, args: unknown) => { - calls.push({ name, args }); - return result; - }, - }; + const calls: Array<{ name: string; args: unknown }> = []; + return { + calls, + callTool: async (name: string, args: unknown) => { + calls.push({ name, args }); + return result; + }, + }; } describe("namespace", () => { - it("produces <serverId>__<toolName>", () => { - expect(namespace("freecad", "create_object")).toBe("freecad__create_object"); - }); + it("produces <serverId>__<toolName>", () => { + expect(namespace("freecad", "create_object")).toBe("freecad__create_object"); + }); - it("handles serverId with special chars", () => { - expect(namespace("chrome-devtools", "navigate")).toBe("chrome-devtools__navigate"); - }); + it("handles serverId with special chars", () => { + expect(namespace("chrome-devtools", "navigate")).toBe("chrome-devtools__navigate"); + }); - it("handles empty toolName", () => { - expect(namespace("server", "")).toBe("server__"); - }); + it("handles empty toolName", () => { + expect(namespace("server", "")).toBe("server__"); + }); }); describe("flattenContent", () => { - it("flattens text content", () => { - const content: McpContentItem[] = [{ type: "text", text: "hello world" }]; - expect(flattenContent(content)).toBe("hello world"); - }); - - it("flattens image content", () => { - const content: McpContentItem[] = [ - { type: "image", data: "base64data", mimeType: "image/png" }, - ]; - expect(flattenContent(content)).toBe("[image: image/png]"); - }); - - it("flattens resource content with text", () => { - const content: McpContentItem[] = [ - { type: "resource", resource: { uri: "file:///test", text: "resource text" } }, - ]; - expect(flattenContent(content)).toBe("resource text"); - }); - - it("flattens resource content without text", () => { - const content: McpContentItem[] = [{ type: "resource", resource: { uri: "file:///test" } }]; - expect(flattenContent(content)).toBe("[resource: file:///test]"); - }); - - it("joins multiple items with newline", () => { - const content: McpContentItem[] = [ - { type: "text", text: "first" }, - { type: "text", text: "second" }, - ]; - expect(flattenContent(content)).toBe("first\nsecond"); - }); - - it("returns empty string for empty content", () => { - expect(flattenContent([])).toBe(""); - }); - - it("handles mixed content types", () => { - const content: McpContentItem[] = [ - { type: "text", text: "here is an image:" }, - { type: "image", data: "data", mimeType: "image/jpeg" }, - { type: "resource", resource: { uri: "file:///x", text: "some data" } }, - ]; - expect(flattenContent(content)).toBe("here is an image:\n[image: image/jpeg]\nsome data"); - }); + it("flattens text content", () => { + const content: McpContentItem[] = [{ type: "text", text: "hello world" }]; + expect(flattenContent(content)).toBe("hello world"); + }); + + it("flattens image content", () => { + const content: McpContentItem[] = [ + { type: "image", data: "base64data", mimeType: "image/png" }, + ]; + expect(flattenContent(content)).toBe("[image: image/png]"); + }); + + it("flattens resource content with text", () => { + const content: McpContentItem[] = [ + { type: "resource", resource: { uri: "file:///test", text: "resource text" } }, + ]; + expect(flattenContent(content)).toBe("resource text"); + }); + + it("flattens resource content without text", () => { + const content: McpContentItem[] = [{ type: "resource", resource: { uri: "file:///test" } }]; + expect(flattenContent(content)).toBe("[resource: file:///test]"); + }); + + it("joins multiple items with newline", () => { + const content: McpContentItem[] = [ + { type: "text", text: "first" }, + { type: "text", text: "second" }, + ]; + expect(flattenContent(content)).toBe("first\nsecond"); + }); + + it("returns empty string for empty content", () => { + expect(flattenContent([])).toBe(""); + }); + + it("handles mixed content types", () => { + const content: McpContentItem[] = [ + { type: "text", text: "here is an image:" }, + { type: "image", data: "data", mimeType: "image/jpeg" }, + { type: "resource", resource: { uri: "file:///x", text: "some data" } }, + ]; + expect(flattenContent(content)).toBe("here is an image:\n[image: image/jpeg]\nsome data"); + }); }); describe("adaptTool", () => { - it("maps inputSchema to ToolParameterSchema", () => { - const mcpTool: McpToolInfo = { - name: "create_obj", - description: "Create an object", - inputSchema: { - type: "object", - properties: { name: { type: "string", description: "Object name" } }, - required: ["name"], - }, - }; - const caller = makeCaller({ content: [] }); - const adapted = adaptTool("freecad", mcpTool, caller); - - expect(adapted.name).toBe("freecad__create_obj"); - expect(adapted.description).toBe("[freecad] Create an object"); - expect(adapted.parameters.type).toBe("object"); - expect(adapted.parameters.properties).toEqual({ - name: { type: "string", description: "Object name" }, - }); - expect(adapted.parameters.required).toEqual(["name"]); - expect(adapted.concurrencySafe).toBe(false); - }); - - it("execute proxies to callTool", async () => { - const mcpTool: McpToolInfo = { - name: "test_tool", - description: "Test", - inputSchema: { type: "object" }, - }; - const caller = makeCaller({ - content: [{ type: "text", text: "called" }], - isError: false, - }); - const adapted = adaptTool("server", mcpTool, caller); - - const result = await adapted.execute( - { input: "value" }, - { - toolCallId: "call-1", - onOutput: () => {}, - signal: new AbortController().signal, - log: mockLogger, - }, - ); - - expect(result.content).toBe("called"); - expect(result.isError).toBe(false); - expect(caller.calls).toEqual([{ name: "test_tool", args: { input: "value" } }]); - }); - - it("execute propagates isError", async () => { - const caller = makeCaller({ - content: [{ type: "text", text: "error occurred" }], - isError: true, - }); - - const mcpTool: McpToolInfo = { - name: "fail_tool", - description: "Fails", - inputSchema: { type: "object" }, - }; - const adapted = adaptTool("server", mcpTool, caller); - - const result = await adapted.execute( - {}, - { - toolCallId: "call-2", - onOutput: () => {}, - signal: new AbortController().signal, - log: mockLogger, - }, - ); - - expect(result.isError).toBe(true); - expect(result.content).toBe("error occurred"); - }); - - it("handles inputSchema without optional fields", () => { - const mcpTool: McpToolInfo = { - name: "simple", - description: "Simple tool", - inputSchema: { type: "object" }, - }; - const caller = makeCaller({ content: [] }); - const adapted = adaptTool("server", mcpTool, caller); - - expect(adapted.parameters.type).toBe("object"); - expect(adapted.parameters.properties).toBeUndefined(); - expect(adapted.parameters.required).toBeUndefined(); - expect(adapted.parameters.additionalProperties).toBeUndefined(); - }); - - it("preserves additionalProperties", () => { - const mcpTool: McpToolInfo = { - name: "flex", - description: "Flexible", - inputSchema: { - type: "object", - additionalProperties: true, - }, - }; - const caller = makeCaller({ content: [] }); - const adapted = adaptTool("server", mcpTool, caller); - - expect(adapted.parameters.additionalProperties).toBe(true); - }); - - it("execute flattens multi-item content", async () => { - const caller = makeCaller({ - content: [ - { type: "text", text: "summary" }, - { type: "image", data: "d", mimeType: "image/png" }, - ], - isError: false, - }); - const mcpTool: McpToolInfo = { - name: "multi", - description: "Multi", - inputSchema: { type: "object" }, - }; - const adapted = adaptTool("srv", mcpTool, caller); - - const result = await adapted.execute( - {}, - { - toolCallId: "c", - onOutput: () => {}, - signal: new AbortController().signal, - log: mockLogger, - }, - ); - - expect(result.content).toBe("summary\n[image: image/png]"); - }); + it("maps inputSchema to ToolParameterSchema", () => { + const mcpTool: McpToolInfo = { + name: "create_obj", + description: "Create an object", + inputSchema: { + type: "object", + properties: { name: { type: "string", description: "Object name" } }, + required: ["name"], + }, + }; + const caller = makeCaller({ content: [] }); + const adapted = adaptTool("freecad", mcpTool, caller); + + expect(adapted.name).toBe("freecad__create_obj"); + expect(adapted.description).toBe("[freecad] Create an object"); + expect(adapted.parameters.type).toBe("object"); + expect(adapted.parameters.properties).toEqual({ + name: { type: "string", description: "Object name" }, + }); + expect(adapted.parameters.required).toEqual(["name"]); + expect(adapted.concurrencySafe).toBe(false); + }); + + it("execute proxies to callTool", async () => { + const mcpTool: McpToolInfo = { + name: "test_tool", + description: "Test", + inputSchema: { type: "object" }, + }; + const caller = makeCaller({ + content: [{ type: "text", text: "called" }], + isError: false, + }); + const adapted = adaptTool("server", mcpTool, caller); + + const result = await adapted.execute( + { input: "value" }, + { + toolCallId: "call-1", + onOutput: () => {}, + signal: new AbortController().signal, + log: mockLogger, + }, + ); + + expect(result.content).toBe("called"); + expect(result.isError).toBe(false); + expect(caller.calls).toEqual([{ name: "test_tool", args: { input: "value" } }]); + }); + + it("execute propagates isError", async () => { + const caller = makeCaller({ + content: [{ type: "text", text: "error occurred" }], + isError: true, + }); + + const mcpTool: McpToolInfo = { + name: "fail_tool", + description: "Fails", + inputSchema: { type: "object" }, + }; + const adapted = adaptTool("server", mcpTool, caller); + + const result = await adapted.execute( + {}, + { + toolCallId: "call-2", + onOutput: () => {}, + signal: new AbortController().signal, + log: mockLogger, + }, + ); + + expect(result.isError).toBe(true); + expect(result.content).toBe("error occurred"); + }); + + it("handles inputSchema without optional fields", () => { + const mcpTool: McpToolInfo = { + name: "simple", + description: "Simple tool", + inputSchema: { type: "object" }, + }; + const caller = makeCaller({ content: [] }); + const adapted = adaptTool("server", mcpTool, caller); + + expect(adapted.parameters.type).toBe("object"); + expect(adapted.parameters.properties).toBeUndefined(); + expect(adapted.parameters.required).toBeUndefined(); + expect(adapted.parameters.additionalProperties).toBeUndefined(); + }); + + it("preserves additionalProperties", () => { + const mcpTool: McpToolInfo = { + name: "flex", + description: "Flexible", + inputSchema: { + type: "object", + additionalProperties: true, + }, + }; + const caller = makeCaller({ content: [] }); + const adapted = adaptTool("server", mcpTool, caller); + + expect(adapted.parameters.additionalProperties).toBe(true); + }); + + it("execute flattens multi-item content", async () => { + const caller = makeCaller({ + content: [ + { type: "text", text: "summary" }, + { type: "image", data: "d", mimeType: "image/png" }, + ], + isError: false, + }); + const mcpTool: McpToolInfo = { + name: "multi", + description: "Multi", + inputSchema: { type: "object" }, + }; + const adapted = adaptTool("srv", mcpTool, caller); + + const result = await adapted.execute( + {}, + { + toolCallId: "c", + onOutput: () => {}, + signal: new AbortController().signal, + log: mockLogger, + }, + ); + + expect(result.content).toBe("summary\n[image: image/png]"); + }); }); |
