Skip to content

Commit bbc128c

Browse files
authored
Merge pull request #520 from modelcontextprotocol/ihrpr/elicitation
Introduce Elicitation capability
2 parents 61ef3c9 + 4af2d4f commit bbc128c

File tree

10 files changed

+1220
-10
lines changed

10 files changed

+1220
-10
lines changed

README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,109 @@ const transport = new StdioServerTransport();
849849
await server.connect(transport);
850850
```
851851

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

854957
The SDK provides a high-level client interface:

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)