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 }; } ); };