Skip to content

Commit 2d0cb9c

Browse files
author
Sainath Reddy Bobbala
committed
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
1 parent 1eb80c4 commit 2d0cb9c

4 files changed

Lines changed: 176 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@modelcontextprotocol/server': patch
3+
---
4+
5+
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()`.

packages/server/src/experimental/tasks/mcpServer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* @experimental
66
*/
77

8-
import type { StandardSchemaWithJSON, TaskToolExecution, ToolAnnotations, ToolExecution } from '@modelcontextprotocol/core';
8+
import type { Icon, StandardSchemaWithJSON, TaskToolExecution, ToolAnnotations, ToolExecution } from '@modelcontextprotocol/core';
99

1010
import type { AnyToolHandler, McpServer, RegisteredTool } from '../../server/mcp.js';
1111
import type { ToolTaskHandler } from './interfaces.js';
@@ -21,6 +21,7 @@ interface McpServerInternal {
2121
description: string | undefined,
2222
inputSchema: StandardSchemaWithJSON | undefined,
2323
outputSchema: StandardSchemaWithJSON | undefined,
24+
icons: Icon[] | undefined,
2425
annotations: ToolAnnotations | undefined,
2526
execution: ToolExecution | undefined,
2627
_meta: Record<string, unknown> | undefined,
@@ -83,6 +84,7 @@ export class ExperimentalMcpServerTasks {
8384
description?: string;
8485
outputSchema?: OutputArgs;
8586
annotations?: ToolAnnotations;
87+
icons?: Icon[];
8688
execution?: TaskToolExecution;
8789
_meta?: Record<string, unknown>;
8890
},
@@ -97,6 +99,7 @@ export class ExperimentalMcpServerTasks {
9799
inputSchema: InputArgs;
98100
outputSchema?: OutputArgs;
99101
annotations?: ToolAnnotations;
102+
icons?: Icon[];
100103
execution?: TaskToolExecution;
101104
_meta?: Record<string, unknown>;
102105
},
@@ -111,6 +114,7 @@ export class ExperimentalMcpServerTasks {
111114
inputSchema?: InputArgs;
112115
outputSchema?: OutputArgs;
113116
annotations?: ToolAnnotations;
117+
icons?: Icon[];
114118
execution?: TaskToolExecution;
115119
_meta?: Record<string, unknown>;
116120
},
@@ -130,6 +134,7 @@ export class ExperimentalMcpServerTasks {
130134
config.description,
131135
config.inputSchema,
132136
config.outputSchema,
137+
config.icons,
133138
config.annotations,
134139
execution,
135140
config._meta,

packages/server/src/server/mcp.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
CreateTaskResult,
99
CreateTaskServerContext,
1010
GetPromptResult,
11+
Icon,
1112
Implementation,
1213
ListPromptsResult,
1314
ListResourcesResult,
@@ -145,6 +146,7 @@ export class McpServer {
145146
? (standardSchemaToJsonSchema(tool.inputSchema, 'input') as Tool['inputSchema'])
146147
: EMPTY_OBJECT_JSON_SCHEMA,
147148
annotations: tool.annotations,
149+
icons: tool.icons,
148150
execution: tool.execution,
149151
_meta: tool._meta
150152
};
@@ -770,6 +772,7 @@ export class McpServer {
770772
description: string | undefined,
771773
inputSchema: StandardSchemaWithJSON | undefined,
772774
outputSchema: StandardSchemaWithJSON | undefined,
775+
icons: Icon[] | undefined,
773776
annotations: ToolAnnotations | undefined,
774777
execution: ToolExecution | undefined,
775778
_meta: Record<string, unknown> | undefined,
@@ -787,6 +790,7 @@ export class McpServer {
787790
inputSchema,
788791
outputSchema,
789792
annotations,
793+
icons,
790794
execution,
791795
_meta,
792796
handler: handler,
@@ -823,6 +827,7 @@ export class McpServer {
823827

824828
if (updates.outputSchema !== undefined) registeredTool.outputSchema = updates.outputSchema;
825829
if (updates.annotations !== undefined) registeredTool.annotations = updates.annotations;
830+
if (updates.icons !== undefined) registeredTool.icons = updates.icons;
826831
if (updates._meta !== undefined) registeredTool._meta = updates._meta;
827832
if (updates.enabled !== undefined) registeredTool.enabled = updates.enabled;
828833
this.sendToolListChanged();
@@ -870,6 +875,7 @@ export class McpServer {
870875
inputSchema?: InputArgs;
871876
outputSchema?: OutputArgs;
872877
annotations?: ToolAnnotations;
878+
icons?: Icon[];
873879
_meta?: Record<string, unknown>;
874880
},
875881
cb: ToolCallback<InputArgs>
@@ -878,14 +884,15 @@ export class McpServer {
878884
throw new Error(`Tool ${name} is already registered`);
879885
}
880886

881-
const { title, description, inputSchema, outputSchema, annotations, _meta } = config;
887+
const { title, description, inputSchema, outputSchema, annotations, icons, _meta } = config;
882888

883889
return this._createRegisteredTool(
884890
name,
885891
title,
886892
description,
887893
inputSchema,
888894
outputSchema,
895+
icons,
889896
annotations,
890897
{ taskSupport: 'forbidden' },
891898
_meta,
@@ -1095,6 +1102,7 @@ export type RegisteredTool = {
10951102
inputSchema?: StandardSchemaWithJSON;
10961103
outputSchema?: StandardSchemaWithJSON;
10971104
annotations?: ToolAnnotations;
1105+
icons?: Icon[];
10981106
execution?: ToolExecution;
10991107
_meta?: Record<string, unknown>;
11001108
handler: AnyToolHandler<StandardSchemaWithJSON | undefined>;
@@ -1110,6 +1118,7 @@ export type RegisteredTool = {
11101118
paramsSchema?: StandardSchemaWithJSON;
11111119
outputSchema?: StandardSchemaWithJSON;
11121120
annotations?: ToolAnnotations;
1121+
icons?: Icon[];
11131122
_meta?: Record<string, unknown>;
11141123
callback?: ToolCallback<StandardSchemaWithJSON>;
11151124
enabled?: boolean;

test/integration/test/server/mcp.test.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,6 +1177,161 @@ describe('Zod v4', () => {
11771177
});
11781178
});
11791179

1180+
/***
1181+
* Test: Tool Registration with Icons
1182+
*/
1183+
test('should register tool with icons', async () => {
1184+
const mcpServer = new McpServer({
1185+
name: 'test server',
1186+
version: '1.0'
1187+
});
1188+
const client = new Client({
1189+
name: 'test client',
1190+
version: '1.0'
1191+
});
1192+
1193+
mcpServer.registerTool(
1194+
'test',
1195+
{
1196+
description: 'A tool with icons',
1197+
icons: [
1198+
{ src: 'https://example.com/icon.png', mimeType: 'image/png' },
1199+
{ src: 'https://example.com/icon.svg', mimeType: 'image/svg+xml' }
1200+
]
1201+
},
1202+
async () => ({
1203+
content: [{ type: 'text', text: 'Test response' }]
1204+
})
1205+
);
1206+
1207+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1208+
1209+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
1210+
1211+
const result = await client.request({ method: 'tools/list' });
1212+
1213+
expect(result.tools).toHaveLength(1);
1214+
expect(result.tools[0]!.name).toBe('test');
1215+
expect(result.tools[0]!.icons).toEqual([
1216+
{ src: 'https://example.com/icon.png', mimeType: 'image/png' },
1217+
{ src: 'https://example.com/icon.svg', mimeType: 'image/svg+xml' }
1218+
]);
1219+
});
1220+
1221+
/***
1222+
* Test: Tool Registration with Icons and Annotations
1223+
*/
1224+
test('should register tool with icons and annotations', async () => {
1225+
const mcpServer = new McpServer({
1226+
name: 'test server',
1227+
version: '1.0'
1228+
});
1229+
const client = new Client({
1230+
name: 'test client',
1231+
version: '1.0'
1232+
});
1233+
1234+
mcpServer.registerTool(
1235+
'test',
1236+
{
1237+
description: 'A tool with icons and annotations',
1238+
inputSchema: z.object({ name: z.string() }),
1239+
icons: [{ src: 'https://example.com/icon.png', mimeType: 'image/png' }],
1240+
annotations: { title: 'Test Tool', readOnlyHint: true }
1241+
},
1242+
async ({ name }) => ({
1243+
content: [{ type: 'text', text: `Hello, ${name}!` }]
1244+
})
1245+
);
1246+
1247+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1248+
1249+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
1250+
1251+
const result = await client.request({ method: 'tools/list' });
1252+
1253+
expect(result.tools).toHaveLength(1);
1254+
expect(result.tools[0]!.name).toBe('test');
1255+
expect(result.tools[0]!.icons).toEqual([
1256+
{ src: 'https://example.com/icon.png', mimeType: 'image/png' }
1257+
]);
1258+
expect(result.tools[0]!.annotations).toEqual({
1259+
title: 'Test Tool',
1260+
readOnlyHint: true
1261+
});
1262+
});
1263+
1264+
/***
1265+
* Test: Updating Tool Icons
1266+
*/
1267+
test('should update tool icons', async () => {
1268+
const mcpServer = new McpServer({
1269+
name: 'test server',
1270+
version: '1.0'
1271+
});
1272+
const client = new Client({
1273+
name: 'test client',
1274+
version: '1.0'
1275+
});
1276+
1277+
const tool = mcpServer.registerTool(
1278+
'test',
1279+
{
1280+
icons: [{ src: 'https://example.com/old-icon.png', mimeType: 'image/png' }]
1281+
},
1282+
async () => ({
1283+
content: [{ type: 'text', text: 'Test response' }]
1284+
})
1285+
);
1286+
1287+
// Update icons
1288+
tool.update({
1289+
icons: [{ src: 'https://example.com/new-icon.svg', mimeType: 'image/svg+xml' }]
1290+
});
1291+
1292+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1293+
1294+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
1295+
1296+
const result = await client.request({ method: 'tools/list' });
1297+
1298+
expect(result.tools).toHaveLength(1);
1299+
expect(result.tools[0]!.icons).toEqual([
1300+
{ src: 'https://example.com/new-icon.svg', mimeType: 'image/svg+xml' }
1301+
]);
1302+
});
1303+
1304+
/***
1305+
* Test: Tool without Icons should not include icons in listing
1306+
*/
1307+
test('should not include icons in listing when not provided', async () => {
1308+
const mcpServer = new McpServer({
1309+
name: 'test server',
1310+
version: '1.0'
1311+
});
1312+
const client = new Client({
1313+
name: 'test client',
1314+
version: '1.0'
1315+
});
1316+
1317+
mcpServer.registerTool(
1318+
'test',
1319+
{ description: 'A tool without icons' },
1320+
async () => ({
1321+
content: [{ type: 'text', text: 'Test response' }]
1322+
})
1323+
);
1324+
1325+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
1326+
1327+
await Promise.all([client.connect(clientTransport), mcpServer.server.connect(serverTransport)]);
1328+
1329+
const result = await client.request({ method: 'tools/list' });
1330+
1331+
expect(result.tools).toHaveLength(1);
1332+
expect(result.tools[0]!.icons).toBeUndefined();
1333+
});
1334+
11801335
/***
11811336
* Test: Tool Argument Validation
11821337
*/

0 commit comments

Comments
 (0)