Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions docs/servers/input-required.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,20 +174,23 @@ Sampling and roots are deprecated as of protocol revision 2026-07-28 (SEP-2577)
To run rounds in sequence, return an opaque `requestState` string alongside the requests. The client echoes it back byte-for-byte on the retry, and `ctx.mcpReq.requestState<State>()` reads its decoded payload on re-entry. Mint it with the codec from the next section.

```ts source="../../examples/guides/servers/input-required.examples.ts#requestState_mint"
const wipeCacheConfirmationSchema = z.object({ confirm: z.boolean() });
const wipeCacheScopeSchema = z.object({ scope: z.string() });

server.registerTool(
'wipe-cache',
{ description: 'Confirm, then pick a scope, then wipe', inputSchema: z.object({}) },
async (_args, ctx): Promise<CallToolResult | InputRequiredResult> => {
const state = ctx.mcpReq.requestState<{ step: string }>();

if (state?.step !== 'confirmed') {
const confirmed = acceptedContent<{ confirm: boolean }>(ctx.mcpReq.inputResponses, 'confirm');
const confirmed = acceptedContent(ctx.mcpReq.inputResponses, 'confirm', wipeCacheConfirmationSchema);
if (confirmed?.confirm !== true) {
return inputRequired({
inputRequests: {
confirm: inputRequired.elicit({
message: 'Really wipe the cache?',
requestedSchema: { type: 'object', properties: { confirm: { type: 'boolean' } }, required: ['confirm'] }
requestedSchema: wipeCacheConfirmationSchema
})
}
});
Expand All @@ -197,15 +200,26 @@ server.registerTool(
inputRequests: {
scope: inputRequired.elicit({
message: 'Which scope?',
requestedSchema: { type: 'object', properties: { scope: { type: 'string' } }, required: ['scope'] }
requestedSchema: wipeCacheScopeSchema
})
},
requestState: await stateCodec.mint({ step: 'confirmed' })
});
}

const scope = acceptedContent<{ scope: string }>(ctx.mcpReq.inputResponses, 'scope');
return { content: [{ type: 'text', text: `Wiped ${scope?.scope ?? 'all'}` }] };
const scope = acceptedContent(ctx.mcpReq.inputResponses, 'scope', wipeCacheScopeSchema);
if (scope === undefined) {
return inputRequired({
inputRequests: {
scope: inputRequired.elicit({
message: 'Which scope?',
requestedSchema: wipeCacheScopeSchema
})
},
requestState: await stateCodec.mint({ step: 'confirmed' })
});
}
return { content: [{ type: 'text', text: `Wiped ${scope.scope}` }] };
}
);
```
Expand Down
24 changes: 19 additions & 5 deletions examples/guides/servers/input-required.examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,20 +125,23 @@ server.registerTool(

// "Carry state across rounds with `requestState`" — two sequential rounds.
//#region requestState_mint
const wipeCacheConfirmationSchema = z.object({ confirm: z.boolean() });
const wipeCacheScopeSchema = z.object({ scope: z.string() });

server.registerTool(
'wipe-cache',
{ description: 'Confirm, then pick a scope, then wipe', inputSchema: z.object({}) },
async (_args, ctx): Promise<CallToolResult | InputRequiredResult> => {
const state = ctx.mcpReq.requestState<{ step: string }>();

if (state?.step !== 'confirmed') {
const confirmed = acceptedContent<{ confirm: boolean }>(ctx.mcpReq.inputResponses, 'confirm');
const confirmed = acceptedContent(ctx.mcpReq.inputResponses, 'confirm', wipeCacheConfirmationSchema);
if (confirmed?.confirm !== true) {
return inputRequired({
inputRequests: {
confirm: inputRequired.elicit({
message: 'Really wipe the cache?',
requestedSchema: { type: 'object', properties: { confirm: { type: 'boolean' } }, required: ['confirm'] }
requestedSchema: wipeCacheConfirmationSchema
})
}
});
Expand All @@ -148,15 +151,26 @@ server.registerTool(
inputRequests: {
scope: inputRequired.elicit({
message: 'Which scope?',
requestedSchema: { type: 'object', properties: { scope: { type: 'string' } }, required: ['scope'] }
requestedSchema: wipeCacheScopeSchema
})
},
requestState: await stateCodec.mint({ step: 'confirmed' })
});
}

const scope = acceptedContent<{ scope: string }>(ctx.mcpReq.inputResponses, 'scope');
return { content: [{ type: 'text', text: `Wiped ${scope?.scope ?? 'all'}` }] };
const scope = acceptedContent(ctx.mcpReq.inputResponses, 'scope', wipeCacheScopeSchema);
if (scope === undefined) {
return inputRequired({
inputRequests: {
scope: inputRequired.elicit({
message: 'Which scope?',
requestedSchema: wipeCacheScopeSchema
})
},
requestState: await stateCodec.mint({ step: 'confirmed' })
});
}
return { content: [{ type: 'text', text: `Wiped ${scope.scope}` }] };
}
);
//#endregion requestState_mint
Expand Down
Loading