import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { ElicitResultSchema } from "@modelcontextprotocol/sdk/types.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; // Tool configuration const name = "trigger-elicitation-request"; const config = { title: "Trigger Elicitation Request Tool", description: "Trigger a Request from the Server for User Elicitation", inputSchema: {}, }; /** * Registers the 'trigger-elicitation-request' tool within the provided McpServer instance. * * The registered tool performs the following operations: * * This tool sends a detailed request for the user to provide information based * on a pre-defined schema of fields including text inputs, booleans, numbers, * email, dates, etc. It uses validation and handles multiple possible outcomes * from the user's response, such as acceptance with content, decline, or * cancellation of the dialog. The process also ensures parsing and validating * the elicitation input arguments at runtime. * * The tool resolves the elicitation dialog response into a structured result, * which contains both user-submitted input data (if provided) and debugging * information, including raw results. * * @param {McpServer} server - The MCP server instance to which the tool will be registered. */ export const registerTriggerElicitationRequestTool = (server: McpServer) => { server.registerTool( name, config, async (args, extra): Promise => { const elicitationResult = await extra.sendRequest( { method: "elicitation/create", params: { message: "Please provide inputs for the following fields:", requestedSchema: { type: "object", properties: { name: { title: "String", type: "string", description: "Your full, legal name", }, check: { title: "Boolean", type: "boolean", description: "Agree to the terms and conditions", }, firstLine: { title: "String with default", type: "string", description: "Favorite first line of a story", default: "It was a dark and stormy night.", }, email: { title: "String with email format", type: "string", format: "email", description: "Your email address (will be verified, and never shared with anyone else)", }, homepage: { type: "string", format: "uri", title: "String with uri format", description: "Portfolio / personal website", }, birthdate: { title: "String with date format", type: "string", format: "date", description: "Your date of birth", }, integer: { title: "Integer", type: "integer", description: "Your favorite integer (do not give us your phone number, pin, or other sensitive info)", minimum: 1, maximum: 100, default: 42, }, number: { title: "Number in range 1-1000", type: "number", description: "Favorite number (there are no wrong answers)", minimum: 0, maximum: 1000, default: 3.14, }, untitledSingleSelectEnum: { type: "string", title: "Untitled Single Select Enum", description: "Choose your favorite friend", enum: [ "Monica", "Rachel", "Joey", "Chandler", "Ross", "Phoebe", ], default: "Monica", }, untitledMultipleSelectEnum: { type: "array", title: "Untitled Multiple Select Enum", description: "Choose your favorite instruments", minItems: 1, maxItems: 3, items: { type: "string", enum: ["Guitar", "Piano", "Violin", "Drums", "Bass"], }, default: ["Guitar"], }, titledSingleSelectEnum: { type: "string", title: "Titled Single Select Enum", description: "Choose your favorite hero", oneOf: [ { const: "hero-1", title: "Superman" }, { const: "hero-2", title: "Green Lantern" }, { const: "hero-3", title: "Wonder Woman" }, ], default: "hero-1", }, titledMultipleSelectEnum: { type: "array", title: "Titled Multiple Select Enum", description: "Choose your favorite types of fish", minItems: 1, maxItems: 3, items: { anyOf: [ { const: "fish-1", title: "Tuna" }, { const: "fish-2", title: "Salmon" }, { const: "fish-3", title: "Trout" }, ], }, default: ["fish-1"], }, legacyTitledEnum: { type: "string", title: "Legacy Titled Single Select Enum", description: "Choose your favorite type of pet", enum: ["pet-1", "pet-2", "pet-3", "pet-4", "pet-5"], enumNames: ["Cats", "Dogs", "Birds", "Fish", "Reptiles"], default: "pet-1", }, }, required: ["name"], }, }, }, ElicitResultSchema, { timeout: 10 * 60 * 1000 /* 10 minutes */ } ); // Handle different response actions const content: CallToolResult["content"] = []; if (elicitationResult.action === "accept" && elicitationResult.content) { content.push({ type: "text", text: `✅ User provided the requested information!`, }); // Only access elicitationResult.content when action is accept const userData = elicitationResult.content; const lines = []; if (userData.name) lines.push(`- Name: ${userData.name}`); if (userData.check !== undefined) lines.push(`- Agreed to terms: ${userData.check}`); if (userData.color) lines.push(`- Favorite Color: ${userData.color}`); if (userData.email) lines.push(`- Email: ${userData.email}`); if (userData.homepage) lines.push(`- Homepage: ${userData.homepage}`); if (userData.birthdate) lines.push(`- Birthdate: ${userData.birthdate}`); if (userData.integer !== undefined) lines.push(`- Favorite Integer: ${userData.integer}`); if (userData.number !== undefined) lines.push(`- Favorite Number: ${userData.number}`); if (userData.petType) lines.push(`- Pet Type: ${userData.petType}`); content.push({ type: "text", text: `User inputs:\n${lines.join("\n")}`, }); } else if (elicitationResult.action === "decline") { content.push({ type: "text", text: `❌ User declined to provide the requested information.`, }); } else if (elicitationResult.action === "cancel") { content.push({ type: "text", text: `⚠️ User cancelled the elicitation dialog.`, }); } // Include raw result for debugging content.push({ type: "text", text: `\nRaw result: ${JSON.stringify(elicitationResult, null, 2)}`, }); return { content }; } ); };