Skip to content

Commit b92243b

Browse files
authored
Merge branch 'main' into main
2 parents 5b5e192 + 9b99ffb commit b92243b

18 files changed

+1770
-73
lines changed

.git-blame-ignore-revs

Whitespace-only changes.

README.md

Lines changed: 197 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [Resources](#resources)
1212
- [Tools](#tools)
1313
- [Prompts](#prompts)
14+
- [Completions](#completions)
1415
- [Running Your Server](#running-your-server)
1516
- [stdio](#stdio)
1617
- [Streamable HTTP](#streamable-http)
@@ -150,6 +151,33 @@ server.registerResource(
150151
}]
151152
})
152153
);
154+
155+
// Resource with context-aware completion
156+
server.registerResource(
157+
"repository",
158+
new ResourceTemplate("github://repos/{owner}/{repo}", {
159+
list: undefined,
160+
complete: {
161+
// Provide intelligent completions based on previously resolved parameters
162+
repo: (value, context) => {
163+
if (context?.arguments?.["owner"] === "org1") {
164+
return ["project1", "project2", "project3"].filter(r => r.startsWith(value));
165+
}
166+
return ["default-repo"].filter(r => r.startsWith(value));
167+
}
168+
}
169+
}),
170+
{
171+
title: "GitHub Repository",
172+
description: "Repository information"
173+
},
174+
async (uri, { owner, repo }) => ({
175+
contents: [{
176+
uri: uri.href,
177+
text: `Repository: ${owner}/${repo}`
178+
}]
179+
})
180+
);
153181
```
154182

155183
### Tools
@@ -233,12 +261,14 @@ Tools can return `ResourceLink` objects to reference resources without embedding
233261
Prompts are reusable templates that help LLMs interact with your server effectively:
234262

235263
```typescript
264+
import { completable } from "@modelcontextprotocol/sdk/server/completable.js";
265+
236266
server.registerPrompt(
237267
"review-code",
238268
{
239269
title: "Code Review",
240270
description: "Review code for best practices and potential issues",
241-
arguments: { code: z.string() }
271+
argsSchema: { code: z.string() }
242272
},
243273
({ code }) => ({
244274
messages: [{
@@ -250,6 +280,68 @@ server.registerPrompt(
250280
}]
251281
})
252282
);
283+
284+
// Prompt with context-aware completion
285+
server.registerPrompt(
286+
"team-greeting",
287+
{
288+
title: "Team Greeting",
289+
description: "Generate a greeting for team members",
290+
argsSchema: {
291+
department: completable(z.string(), (value) => {
292+
// Department suggestions
293+
return ["engineering", "sales", "marketing", "support"].filter(d => d.startsWith(value));
294+
}),
295+
name: completable(z.string(), (value, context) => {
296+
// Name suggestions based on selected department
297+
const department = context?.arguments?.["department"];
298+
if (department === "engineering") {
299+
return ["Alice", "Bob", "Charlie"].filter(n => n.startsWith(value));
300+
} else if (department === "sales") {
301+
return ["David", "Eve", "Frank"].filter(n => n.startsWith(value));
302+
} else if (department === "marketing") {
303+
return ["Grace", "Henry", "Iris"].filter(n => n.startsWith(value));
304+
}
305+
return ["Guest"].filter(n => n.startsWith(value));
306+
})
307+
}
308+
},
309+
({ department, name }) => ({
310+
messages: [{
311+
role: "assistant",
312+
content: {
313+
type: "text",
314+
text: `Hello ${name}, welcome to the ${department} team!`
315+
}
316+
}]
317+
})
318+
);
319+
```
320+
321+
### Completions
322+
323+
MCP supports argument completions to help users fill in prompt arguments and resource template parameters. See the examples above for [resource completions](#resources) and [prompt completions](#prompts).
324+
325+
#### Client Usage
326+
327+
```typescript
328+
// Request completions for any argument
329+
const result = await client.complete({
330+
ref: {
331+
type: "ref/prompt", // or "ref/resource"
332+
name: "example" // or uri: "template://..."
333+
},
334+
argument: {
335+
name: "argumentName",
336+
value: "partial" // What the user has typed so far
337+
},
338+
context: { // Optional: Include previously resolved arguments
339+
arguments: {
340+
previousArg: "value"
341+
}
342+
}
343+
});
344+
253345
```
254346

255347
### Display Names and Metadata
@@ -772,6 +864,109 @@ const transport = new StdioServerTransport();
772864
await server.connect(transport);
773865
```
774866

867+
### Eliciting User Input
868+
869+
MCP servers can request additional information from users through the elicitation feature. This is useful for interactive workflows where the server needs user input or confirmation:
870+
871+
```typescript
872+
// Server-side: Restaurant booking tool that asks for alternatives
873+
server.tool(
874+
"book-restaurant",
875+
{
876+
restaurant: z.string(),
877+
date: z.string(),
878+
partySize: z.number()
879+
},
880+
async ({ restaurant, date, partySize }) => {
881+
// Check availability
882+
const available = await checkAvailability(restaurant, date, partySize);
883+
884+
if (!available) {
885+
// Ask user if they want to try alternative dates
886+
const result = await server.server.elicitInput({
887+
message: `No tables available at ${restaurant} on ${date}. Would you like to check alternative dates?`,
888+
requestedSchema: {
889+
type: "object",
890+
properties: {
891+
checkAlternatives: {
892+
type: "boolean",
893+
title: "Check alternative dates",
894+
description: "Would you like me to check other dates?"
895+
},
896+
flexibleDates: {
897+
type: "string",
898+
title: "Date flexibility",
899+
description: "How flexible are your dates?",
900+
enum: ["next_day", "same_week", "next_week"],
901+
enumNames: ["Next day", "Same week", "Next week"]
902+
}
903+
},
904+
required: ["checkAlternatives"]
905+
}
906+
});
907+
908+
if (result.action === "accept" && result.content?.checkAlternatives) {
909+
const alternatives = await findAlternatives(
910+
restaurant,
911+
date,
912+
partySize,
913+
result.content.flexibleDates as string
914+
);
915+
return {
916+
content: [{
917+
type: "text",
918+
text: `Found these alternatives: ${alternatives.join(", ")}`
919+
}]
920+
};
921+
}
922+
923+
return {
924+
content: [{
925+
type: "text",
926+
text: "No booking made. Original date not available."
927+
}]
928+
};
929+
}
930+
931+
// Book the table
932+
await makeBooking(restaurant, date, partySize);
933+
return {
934+
content: [{
935+
type: "text",
936+
text: `Booked table for ${partySize} at ${restaurant} on ${date}`
937+
}]
938+
};
939+
}
940+
);
941+
```
942+
943+
Client-side: Handle elicitation requests
944+
945+
```typescript
946+
// This is a placeholder - implement based on your UI framework
947+
async function getInputFromUser(message: string, schema: any): Promise<{
948+
action: "accept" | "decline" | "cancel";
949+
data?: Record<string, any>;
950+
}> {
951+
// This should be implemented depending on the app
952+
throw new Error("getInputFromUser must be implemented for your platform");
953+
}
954+
955+
client.setRequestHandler(ElicitRequestSchema, async (request) => {
956+
const userResponse = await getInputFromUser(
957+
request.params.message,
958+
request.params.requestedSchema
959+
);
960+
961+
return {
962+
action: userResponse.action,
963+
content: userResponse.action === "accept" ? userResponse.data : undefined
964+
};
965+
});
966+
```
967+
968+
**Note**: Elicitation requires client support. Clients must declare the `elicitation` capability during initialization.
969+
775970
### Writing MCP Clients
776971

777972
The SDK provides a high-level client interface:
@@ -820,6 +1015,7 @@ const result = await client.callTool({
8201015
arg1: "value"
8211016
}
8221017
});
1018+
8231019
```
8241020

8251021
### Proxy Authorization Requests Upstream

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client/auth.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { LATEST_PROTOCOL_VERSION } from '../types.js';
12
import {
23
discoverOAuthMetadata,
34
startAuthorization,
@@ -202,7 +203,7 @@ describe("OAuth Authorization", () => {
202203
const [url, options] = calls[0];
203204
expect(url.toString()).toBe("https://auth.example.com/.well-known/oauth-authorization-server");
204205
expect(options.headers).toEqual({
205-
"MCP-Protocol-Version": "2025-03-26"
206+
"MCP-Protocol-Version": LATEST_PROTOCOL_VERSION
206207
});
207208
});
208209

src/client/index.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
ListToolsRequestSchema,
1515
CallToolRequestSchema,
1616
CreateMessageRequestSchema,
17+
ElicitRequestSchema,
1718
ListRootsRequestSchema,
1819
ErrorCode,
1920
} from "../types.js";
@@ -597,6 +598,43 @@ test("should only allow setRequestHandler for declared capabilities", () => {
597598
}).toThrow("Client does not support roots capability");
598599
});
599600

601+
test("should allow setRequestHandler for declared elicitation capability", () => {
602+
const client = new Client(
603+
{
604+
name: "test-client",
605+
version: "1.0.0",
606+
},
607+
{
608+
capabilities: {
609+
elicitation: {},
610+
},
611+
},
612+
);
613+
614+
// This should work because elicitation is a declared capability
615+
expect(() => {
616+
client.setRequestHandler(ElicitRequestSchema, () => ({
617+
action: "accept",
618+
content: {
619+
username: "test-user",
620+
confirmed: true,
621+
},
622+
}));
623+
}).not.toThrow();
624+
625+
// This should throw because sampling is not a declared capability
626+
expect(() => {
627+
client.setRequestHandler(CreateMessageRequestSchema, () => ({
628+
model: "test-model",
629+
role: "assistant",
630+
content: {
631+
type: "text",
632+
text: "Test response",
633+
},
634+
}));
635+
}).toThrow("Client does not support sampling capability");
636+
});
637+
600638
/***
601639
* Test: Type Checking
602640
* Test that custom request/notification/result schemas can be used with the Client class.

src/client/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,14 @@ export class Client<
307307
}
308308
break;
309309

310+
case "elicitation/create":
311+
if (!this._capabilities.elicitation) {
312+
throw new Error(
313+
`Client does not support elicitation capability (required for ${method})`,
314+
);
315+
}
316+
break;
317+
310318
case "roots/list":
311319
if (!this._capabilities.roots) {
312320
throw new Error(

0 commit comments

Comments
 (0)