Skip to content

Commit 4ebabe5

Browse files
committed
Add registerPrompts() bulk method
- Accepts array of prompt configurations - Registers all prompts with single notification - Follows existing registration patterns - Comprehensive test coverage with notification validation
1 parent 2dfda30 commit 4ebabe5

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

src/server/mcp.test.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4624,4 +4624,120 @@ describe("elicitInput()", () => {
46244624
text: "Bulk resource 1 content"
46254625
});
46264626
});
4627+
4628+
test("registerPrompts() should register multiple prompts with single notification", async () => {
4629+
const mcpServer = new McpServer({
4630+
name: "test server",
4631+
version: "1.0",
4632+
});
4633+
const notifications: Notification[] = []
4634+
const client = new Client({
4635+
name: "test client",
4636+
version: "1.0",
4637+
});
4638+
client.fallbackNotificationHandler = async (notification) => {
4639+
notifications.push(notification)
4640+
}
4641+
4642+
// Register a single prompt first to test notification behavior
4643+
mcpServer.registerPrompt(
4644+
"initial",
4645+
{
4646+
title: "Initial Prompt",
4647+
description: "An initial prompt",
4648+
argsSchema: { input: z.string() }
4649+
},
4650+
({ input }) => ({
4651+
messages: [{
4652+
role: "user" as const,
4653+
content: { type: "text" as const, text: `Initial: ${input}` }
4654+
}]
4655+
})
4656+
);
4657+
4658+
const [clientTransport, serverTransport] =
4659+
InMemoryTransport.createLinkedPair();
4660+
4661+
await Promise.all([
4662+
client.connect(clientTransport),
4663+
mcpServer.connect(serverTransport),
4664+
]);
4665+
4666+
// Clear any initial notifications
4667+
notifications.length = 0;
4668+
4669+
// Register multiple prompts in bulk
4670+
const registeredPrompts = mcpServer.registerPrompts([
4671+
{
4672+
name: "bulk1",
4673+
config: {
4674+
title: "Bulk Prompt One",
4675+
description: "First bulk prompt",
4676+
argsSchema: { input: z.string() }
4677+
},
4678+
callback: (args, _extra) => ({
4679+
messages: [{
4680+
role: "user" as const,
4681+
content: { type: "text" as const, text: `Bulk 1: ${args.input}` }
4682+
}]
4683+
})
4684+
},
4685+
{
4686+
name: "bulk2",
4687+
config: {
4688+
title: "Bulk Prompt Two",
4689+
description: "Second bulk prompt",
4690+
argsSchema: { value: z.string() }
4691+
},
4692+
callback: (args, _extra) => ({
4693+
messages: [{
4694+
role: "assistant" as const,
4695+
content: { type: "text" as const, text: `Bulk 2: ${args.value}` }
4696+
}]
4697+
})
4698+
}
4699+
]);
4700+
4701+
// Yield event loop to let notifications process
4702+
await new Promise(process.nextTick);
4703+
4704+
// Should return array of registered prompts
4705+
expect(registeredPrompts).toHaveLength(2);
4706+
expect(registeredPrompts[0].title).toBe("Bulk Prompt One");
4707+
expect(registeredPrompts[1].title).toBe("Bulk Prompt Two");
4708+
4709+
// Should have sent exactly ONE notification for all prompts
4710+
expect(notifications).toHaveLength(1);
4711+
expect(notifications[0]).toMatchObject({
4712+
method: "notifications/prompts/list_changed",
4713+
});
4714+
4715+
// Test list prompts shows all prompts
4716+
const promptsResult = await client.request(
4717+
{
4718+
method: "prompts/list",
4719+
params: {}
4720+
},
4721+
ListPromptsResultSchema,
4722+
);
4723+
4724+
const promptNames = promptsResult.prompts.map(p => p.name).sort();
4725+
expect(promptNames).toEqual(["bulk1", "bulk2", "initial"]);
4726+
4727+
// Test that a prompt actually works
4728+
const getResult = await client.request(
4729+
{
4730+
method: "prompts/get",
4731+
params: {
4732+
name: "bulk1",
4733+
arguments: { input: "test" }
4734+
}
4735+
},
4736+
GetPromptResultSchema,
4737+
);
4738+
expect(getResult.messages[0]).toMatchObject({
4739+
role: "user",
4740+
content: { type: "text", text: "Bulk 1: test" }
4741+
});
4742+
});
46274743
});

src/server/mcp.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,49 @@ export class McpServer {
722722
return results;
723723
}
724724

725+
/**
726+
* Register multiple prompts in a single operation with a single notification.
727+
* This is more efficient than calling registerPrompt() multiple times when registering many prompts,
728+
* especially when registering many prompts, as it sends only one list_changed notification.
729+
*/
730+
registerPrompts<T extends Array<{
731+
name: string;
732+
config: {
733+
title?: string;
734+
description?: string;
735+
argsSchema?: PromptArgsRawShape;
736+
};
737+
callback: PromptCallback<PromptArgsRawShape | undefined>;
738+
}>>(prompts: T): RegisteredPrompt[] {
739+
const results: RegisteredPrompt[] = [];
740+
741+
// First, validate that none of the prompts are already registered
742+
for (const { name } of prompts) {
743+
if (this._registeredPrompts[name]) {
744+
throw new Error(`Prompt ${name} is already registered`);
745+
}
746+
}
747+
748+
// Register all prompts without sending notifications
749+
for (const { name, config, callback } of prompts) {
750+
const { title, description, argsSchema } = config;
751+
const result = this._createRegisteredPrompt(
752+
name,
753+
title,
754+
description,
755+
argsSchema,
756+
callback
757+
);
758+
results.push(result);
759+
}
760+
761+
// Set up handlers and send single notification at the end
762+
this.setPromptRequestHandlers();
763+
this.sendPromptListChanged();
764+
765+
return results;
766+
}
767+
725768
private _createRegisteredResource(
726769
name: string,
727770
title: string | undefined,

0 commit comments

Comments
 (0)