Skip to content

Commit 886b03a

Browse files
committed
fix(server): preserve prompt icons in list responses
1 parent ab552c3 commit 886b03a

3 files changed

Lines changed: 55 additions & 1 deletion

File tree

.changeset/prompt-icons-list.md

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+
Preserve prompt icons registered through `registerPrompt()` in `prompts/list`.

packages/server/src/server/mcp.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
CompleteRequestResourceTemplate,
66
CompleteResult,
77
GetPromptResult,
8+
Icon,
89
Implementation,
910
ListPromptsResult,
1011
ListResourcesResult,
@@ -438,6 +439,7 @@ export class McpServer {
438439
name,
439440
title: prompt.title,
440441
description: prompt.description,
442+
icons: prompt.icons,
441443
arguments: prompt.argsSchema ? promptArgumentsFromStandardSchema(prompt.argsSchema) : undefined,
442444
_meta: prompt._meta
443445
};
@@ -607,6 +609,7 @@ export class McpServer {
607609
name: string,
608610
title: string | undefined,
609611
description: string | undefined,
612+
icons: Icon[] | undefined,
610613
argsSchema: StandardSchemaWithJSON | undefined,
611614
callback: PromptCallback<StandardSchemaWithJSON | undefined>,
612615
_meta: Record<string, unknown> | undefined
@@ -618,6 +621,7 @@ export class McpServer {
618621
const registeredPrompt: RegisteredPrompt = {
619622
title,
620623
description,
624+
icons,
621625
argsSchema,
622626
_meta,
623627
handler: createPromptHandler(name, argsSchema, callback),
@@ -632,6 +636,7 @@ export class McpServer {
632636
}
633637
if (updates.title !== undefined) registeredPrompt.title = updates.title;
634638
if (updates.description !== undefined) registeredPrompt.description = updates.description;
639+
if (updates.icons !== undefined) registeredPrompt.icons = updates.icons;
635640
if (updates._meta !== undefined) registeredPrompt._meta = updates._meta;
636641

637642
// Track if we need to regenerate the handler
@@ -857,6 +862,7 @@ export class McpServer {
857862
config: {
858863
title?: string;
859864
description?: string;
865+
icons?: Icon[];
860866
argsSchema?: Args;
861867
_meta?: Record<string, unknown>;
862868
},
@@ -868,6 +874,7 @@ export class McpServer {
868874
config: {
869875
title?: string;
870876
description?: string;
877+
icons?: Icon[];
871878
argsSchema?: Args;
872879
_meta?: Record<string, unknown>;
873880
},
@@ -878,6 +885,7 @@ export class McpServer {
878885
config: {
879886
title?: string;
880887
description?: string;
888+
icons?: Icon[];
881889
argsSchema?: StandardSchemaWithJSON | ZodRawShape;
882890
_meta?: Record<string, unknown>;
883891
},
@@ -887,12 +895,13 @@ export class McpServer {
887895
throw new Error(`Prompt ${name} is already registered`);
888896
}
889897

890-
const { title, description, argsSchema, _meta } = config;
898+
const { title, description, icons, argsSchema, _meta } = config;
891899

892900
const registeredPrompt = this._createRegisteredPrompt(
893901
name,
894902
title,
895903
description,
904+
icons,
896905
normalizeRawShapeSchema(argsSchema),
897906
cb as PromptCallback<StandardSchemaWithJSON | undefined>,
898907
_meta
@@ -1192,6 +1201,7 @@ type ToolCallbackInternal = (args: unknown, ctx: ServerContext) => CallToolResul
11921201
export type RegisteredPrompt = {
11931202
title?: string;
11941203
description?: string;
1204+
icons?: Icon[];
11951205
argsSchema?: StandardSchemaWithJSON;
11961206
_meta?: Record<string, unknown>;
11971207
/** @hidden */
@@ -1203,6 +1213,7 @@ export type RegisteredPrompt = {
12031213
name?: string | null;
12041214
title?: string;
12051215
description?: string;
1216+
icons?: Icon[];
12061217
argsSchema?: Args;
12071218
_meta?: Record<string, unknown>;
12081219
callback?: PromptCallback<Args>;

packages/server/test/server/mcp.compat.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,44 @@ describe('registerTool/registerPrompt accept raw Zod shape (auto-wrapped)', () =
7777
expect(isStandardSchema(prompts['p']?.argsSchema)).toBe(true);
7878
});
7979

80+
it('registerPrompt includes icons in prompts/list', async () => {
81+
const server = new McpServer({ name: 't', version: '1.0.0' });
82+
83+
const icons = [{ src: 'data:image/svg+xml;base64,PHN2Zy8+', mimeType: 'image/svg+xml', sizes: ['24x24'] }];
84+
server.registerPrompt('with-icon', { description: 'Prompt with an icon', icons }, async () => ({
85+
messages: [{ role: 'user' as const, content: { type: 'text' as const, text: 'hello' } }]
86+
}));
87+
88+
const [client, srv] = InMemoryTransport.createLinkedPair();
89+
await server.connect(srv);
90+
await client.start();
91+
92+
const responses: JSONRPCMessage[] = [];
93+
client.onmessage = m => responses.push(m);
94+
95+
await client.send({
96+
jsonrpc: '2.0',
97+
id: 1,
98+
method: 'initialize',
99+
params: {
100+
protocolVersion: LATEST_PROTOCOL_VERSION,
101+
capabilities: {},
102+
clientInfo: { name: 'c', version: '1.0.0' }
103+
}
104+
} as JSONRPCMessage);
105+
await client.send({ jsonrpc: '2.0', method: 'notifications/initialized' } as JSONRPCMessage);
106+
await client.send({ jsonrpc: '2.0', id: 2, method: 'prompts/list', params: {} } as JSONRPCMessage);
107+
108+
await vi.waitFor(() => expect(responses.some(r => 'id' in r && r.id === 2)).toBe(true));
109+
110+
const response = responses.find(r => 'id' in r && r.id === 2) as {
111+
result?: { prompts: Array<{ name: string; icons?: typeof icons }> };
112+
};
113+
expect(response.result?.prompts).toContainEqual(expect.objectContaining({ name: 'with-icon', icons }));
114+
115+
await server.close();
116+
});
117+
80118
it('callback receives validated, typed args end-to-end via tools/call', async () => {
81119
const server = new McpServer({ name: 't', version: '1.0.0' });
82120

0 commit comments

Comments
 (0)