From 25ef4305fa1ddabf66cc037b66f3144cf3631da6 Mon Sep 17 00:00:00 2001 From: Sainath Reddy Bobbala Date: Sun, 12 Apr 2026 02:06:44 +0000 Subject: [PATCH] fix(server): add icons field to registerTool and registerToolTask Add icons support to the registerTool() and registerToolTask() APIs, matching the ToolSchema spec definition which includes IconsSchema.shape. - Add icons?: Icon[] to registerTool() config parameter type - Add icons to _createRegisteredTool() signature and object construction - Add icons to tools/list handler toolDefinition object - Add icons to RegisteredTool type and its update() method - Add icons to all registerToolTask() overload signatures - Add integration tests for icons registration, update, and listing Ref #1864 --- .changeset/add-tool-icons-support.md | 5 + .../src/experimental/tasks/mcpServer.ts | 7 +- packages/server/src/server/mcp.ts | 11 +- test/integration/test/server/mcp.test.ts | 147 ++++++++++++++++++ 4 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 .changeset/add-tool-icons-support.md diff --git a/.changeset/add-tool-icons-support.md b/.changeset/add-tool-icons-support.md new file mode 100644 index 000000000..c998360d3 --- /dev/null +++ b/.changeset/add-tool-icons-support.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/server': patch +--- + +Added `icons` field support to `registerTool()` and `registerToolTask()` APIs, matching the `ToolSchema` spec definition. Icons are now included in `tools/list` responses and can be updated via `tool.update()`. diff --git a/packages/server/src/experimental/tasks/mcpServer.ts b/packages/server/src/experimental/tasks/mcpServer.ts index b7c28c40d..7b1c425e3 100644 --- a/packages/server/src/experimental/tasks/mcpServer.ts +++ b/packages/server/src/experimental/tasks/mcpServer.ts @@ -5,7 +5,7 @@ * @experimental */ -import type { StandardSchemaWithJSON, TaskToolExecution, ToolAnnotations, ToolExecution } from '@modelcontextprotocol/core'; +import type { Icon, StandardSchemaWithJSON, TaskToolExecution, ToolAnnotations, ToolExecution } from '@modelcontextprotocol/core'; import type { AnyToolHandler, McpServer, RegisteredTool } from '../../server/mcp.js'; import type { ToolTaskHandler } from './interfaces.js'; @@ -21,6 +21,7 @@ interface McpServerInternal { description: string | undefined, inputSchema: StandardSchemaWithJSON | undefined, outputSchema: StandardSchemaWithJSON | undefined, + icons: Icon[] | undefined, annotations: ToolAnnotations | undefined, execution: ToolExecution | undefined, _meta: Record | undefined, @@ -83,6 +84,7 @@ export class ExperimentalMcpServerTasks { description?: string; outputSchema?: OutputArgs; annotations?: ToolAnnotations; + icons?: Icon[]; execution?: TaskToolExecution; _meta?: Record; }, @@ -97,6 +99,7 @@ export class ExperimentalMcpServerTasks { inputSchema: InputArgs; outputSchema?: OutputArgs; annotations?: ToolAnnotations; + icons?: Icon[]; execution?: TaskToolExecution; _meta?: Record; }, @@ -111,6 +114,7 @@ export class ExperimentalMcpServerTasks { inputSchema?: InputArgs; outputSchema?: OutputArgs; annotations?: ToolAnnotations; + icons?: Icon[]; execution?: TaskToolExecution; _meta?: Record; }, @@ -130,6 +134,7 @@ export class ExperimentalMcpServerTasks { config.description, config.inputSchema, config.outputSchema, + config.icons, config.annotations, execution, config._meta, diff --git a/packages/server/src/server/mcp.ts b/packages/server/src/server/mcp.ts index 6c2699997..e3954c23d 100644 --- a/packages/server/src/server/mcp.ts +++ b/packages/server/src/server/mcp.ts @@ -8,6 +8,7 @@ import type { CreateTaskResult, CreateTaskServerContext, GetPromptResult, + Icon, Implementation, ListPromptsResult, ListResourcesResult, @@ -145,6 +146,7 @@ export class McpServer { ? (standardSchemaToJsonSchema(tool.inputSchema, 'input') as Tool['inputSchema']) : EMPTY_OBJECT_JSON_SCHEMA, annotations: tool.annotations, + icons: tool.icons, execution: tool.execution, _meta: tool._meta }; @@ -770,6 +772,7 @@ export class McpServer { description: string | undefined, inputSchema: StandardSchemaWithJSON | undefined, outputSchema: StandardSchemaWithJSON | undefined, + icons: Icon[] | undefined, annotations: ToolAnnotations | undefined, execution: ToolExecution | undefined, _meta: Record | undefined, @@ -787,6 +790,7 @@ export class McpServer { inputSchema, outputSchema, annotations, + icons, execution, _meta, handler: handler, @@ -823,6 +827,7 @@ export class McpServer { if (updates.outputSchema !== undefined) registeredTool.outputSchema = updates.outputSchema; if (updates.annotations !== undefined) registeredTool.annotations = updates.annotations; + if (updates.icons !== undefined) registeredTool.icons = updates.icons; if (updates._meta !== undefined) registeredTool._meta = updates._meta; if (updates.enabled !== undefined) registeredTool.enabled = updates.enabled; this.sendToolListChanged(); @@ -870,6 +875,7 @@ export class McpServer { inputSchema?: InputArgs; outputSchema?: OutputArgs; annotations?: ToolAnnotations; + icons?: Icon[]; _meta?: Record; }, cb: ToolCallback @@ -878,7 +884,7 @@ export class McpServer { throw new Error(`Tool ${name} is already registered`); } - const { title, description, inputSchema, outputSchema, annotations, _meta } = config; + const { title, description, inputSchema, outputSchema, annotations, icons, _meta } = config; return this._createRegisteredTool( name, @@ -886,6 +892,7 @@ export class McpServer { description, inputSchema, outputSchema, + icons, annotations, { taskSupport: 'forbidden' }, _meta, @@ -1095,6 +1102,7 @@ export type RegisteredTool = { inputSchema?: StandardSchemaWithJSON; outputSchema?: StandardSchemaWithJSON; annotations?: ToolAnnotations; + icons?: Icon[]; execution?: ToolExecution; _meta?: Record; handler: AnyToolHandler; @@ -1110,6 +1118,7 @@ export type RegisteredTool = { paramsSchema?: StandardSchemaWithJSON; outputSchema?: StandardSchemaWithJSON; annotations?: ToolAnnotations; + icons?: Icon[]; _meta?: Record; callback?: ToolCallback; enabled?: boolean; diff --git a/test/integration/test/server/mcp.test.ts b/test/integration/test/server/mcp.test.ts index 92af09744..57bf084d9 100644 --- a/test/integration/test/server/mcp.test.ts +++ b/test/integration/test/server/mcp.test.ts @@ -1177,6 +1177,153 @@ describe('Zod v4', () => { }); }); + /*** + * Test: Tool Registration with Icons + */ + test('should register tool with icons', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + + mcpServer.registerTool( + 'test', + { + description: 'A tool with icons', + icons: [ + { src: 'https://example.com/icon.png', mimeType: 'image/png' }, + { src: 'https://example.com/icon.svg', mimeType: 'image/svg+xml' } + ] + }, + async () => ({ + content: [{ type: 'text', text: 'Test response' }] + }) + ); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + const result = await client.request({ method: 'tools/list' }); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0]!.name).toBe('test'); + expect(result.tools[0]!.icons).toEqual([ + { src: 'https://example.com/icon.png', mimeType: 'image/png' }, + { src: 'https://example.com/icon.svg', mimeType: 'image/svg+xml' } + ]); + }); + + /*** + * Test: Tool Registration with Icons and Annotations + */ + test('should register tool with icons and annotations', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + + mcpServer.registerTool( + 'test', + { + description: 'A tool with icons and annotations', + inputSchema: z.object({ name: z.string() }), + icons: [{ src: 'https://example.com/icon.png', mimeType: 'image/png' }], + annotations: { title: 'Test Tool', readOnlyHint: true } + }, + async ({ name }) => ({ + content: [{ type: 'text', text: `Hello, ${name}!` }] + }) + ); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + const result = await client.request({ method: 'tools/list' }); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0]!.name).toBe('test'); + expect(result.tools[0]!.icons).toEqual([{ src: 'https://example.com/icon.png', mimeType: 'image/png' }]); + expect(result.tools[0]!.annotations).toEqual({ + title: 'Test Tool', + readOnlyHint: true + }); + }); + + /*** + * Test: Updating Tool Icons + */ + test('should update tool icons', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + + const tool = mcpServer.registerTool( + 'test', + { + icons: [{ src: 'https://example.com/old-icon.png', mimeType: 'image/png' }] + }, + async () => ({ + content: [{ type: 'text', text: 'Test response' }] + }) + ); + + // Update icons + tool.update({ + icons: [{ src: 'https://example.com/new-icon.svg', mimeType: 'image/svg+xml' }] + }); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + const result = await client.request({ method: 'tools/list' }); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0]!.icons).toEqual([{ src: 'https://example.com/new-icon.svg', mimeType: 'image/svg+xml' }]); + }); + + /*** + * Test: Tool without Icons should not include icons in listing + */ + test('should not include icons in listing when not provided', async () => { + const mcpServer = new McpServer({ + name: 'test server', + version: '1.0' + }); + const client = new Client({ + name: 'test client', + version: '1.0' + }); + + mcpServer.registerTool('test', { description: 'A tool without icons' }, async () => ({ + content: [{ type: 'text', text: 'Test response' }] + })); + + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + + await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]); + + const result = await client.request({ method: 'tools/list' }); + + expect(result.tools).toHaveLength(1); + expect(result.tools[0]!.icons).toBeUndefined(); + }); + /*** * Test: Tool Argument Validation */