Created
November 29, 2025 14:44
-
-
Save abhiaiyer91/4457159c0a04b0e8064146060783cfd3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Reproduction script for issue #10668 | |
| * | |
| * This script reproduces the exact user pattern: | |
| * 1. Initialize MCPClient with capabilities.elicitation.form | |
| * 2. Set up handler AFTER client creation | |
| * 3. Server sends elicitation request with required field | |
| * 4. Handler returns empty content (mismatch with schema) | |
| */ | |
| import { MCPClient } from './src/client/configuration'; | |
| import { MCPServer } from './src/server/server'; | |
| import http from 'http'; | |
| import { z } from 'zod'; | |
| const PORT = 9700 + Math.floor(Math.random() * 1000); | |
| const SERVER_URL = `http://localhost:${PORT}/http`; | |
| // Create the MCP server with a tool that uses elicitation | |
| const server = new MCPServer({ | |
| name: 'TestElicitationServer', | |
| version: '1.0.0', | |
| tools: { | |
| confirmAction: { | |
| description: 'A tool that requires user confirmation via elicitation', | |
| parameters: z.object({ | |
| action: z.string().describe('The action to confirm'), | |
| }), | |
| execute: async (inputData, context) => { | |
| try { | |
| const userApproval = await context?.mcp?.elicitation?.sendRequest({ | |
| mode: 'form', | |
| message: 'Are you sure?', | |
| requestedSchema: { | |
| type: 'object', | |
| properties: { | |
| userConfirmation: { | |
| type: 'boolean', | |
| title: 'User Confirmation', | |
| description: 'Are you sure?', | |
| }, | |
| }, | |
| required: ['userConfirmation'], | |
| }, | |
| }); | |
| if (userApproval.action !== 'accept') { | |
| console.log('User did not accept!'); | |
| return { | |
| content: [ | |
| { | |
| type: 'text' as const, | |
| text: 'User did not accept!', | |
| }, | |
| ], | |
| }; | |
| } | |
| return { | |
| content: [ | |
| { | |
| type: 'text' as const, | |
| text: 'User has given confirmation!', | |
| }, | |
| ], | |
| }; | |
| } catch (error) { | |
| console.error('Error in tool execution:', error); | |
| return { | |
| content: [ | |
| { | |
| type: 'text' as const, | |
| text: `Error: ${error instanceof Error ? error.message : String(error)}`, | |
| }, | |
| ], | |
| isError: true, | |
| }; | |
| } | |
| }, | |
| }, | |
| }, | |
| }); | |
| // Start HTTP server | |
| const httpServer = http.createServer(async (req, res) => { | |
| const url = new URL(req.url || '', `http://localhost:${PORT}`); | |
| await server.startHTTP({ | |
| url, | |
| httpPath: '/http', | |
| req, | |
| res, | |
| }); | |
| }); | |
| await new Promise<void>(resolve => { | |
| httpServer.listen(PORT, () => { | |
| console.log(`β Server started on port ${PORT}`); | |
| resolve(); | |
| }); | |
| }); | |
| // Simulate user's exact initialization pattern | |
| console.log('\nπ Step 1: Creating server configs in a loop...'); | |
| // Simulate config.mcps structure | |
| const config = { | |
| mcps: [ | |
| { | |
| type: 'http' as const, | |
| name: 'BASIC_MCP', | |
| url: SERVER_URL, | |
| timeout: 15000, | |
| headers: undefined as Record<string, string> | undefined, | |
| }, | |
| ], | |
| }; | |
| const servers: Record<string, any> = {}; | |
| for (const mcp of config.mcps) { | |
| if (mcp.type === 'http') { | |
| const serverConfig: any = { | |
| url: new URL(mcp.url), | |
| timeout: mcp.timeout || 15000, | |
| capabilities: { | |
| elicitation: { | |
| form: {}, | |
| }, | |
| }, | |
| }; | |
| if (mcp.headers) { | |
| serverConfig.requestInit = { | |
| headers: mcp.headers, | |
| }; | |
| } | |
| servers[mcp.name] = serverConfig; | |
| } | |
| } | |
| console.log('β Server configs created:', Object.keys(servers)); | |
| // Create MCPClient (user's exact pattern) | |
| console.log('\nπ Step 2: Creating MCPClient...'); | |
| const mcpClient = new MCPClient({ servers: servers, timeout: 15000 }); | |
| console.log('β MCPClient created'); | |
| // User's pattern: Set up handler AFTER client creation | |
| console.log('\nπ Step 3: Setting up elicitation handler (AFTER client creation)...'); | |
| if (config.mcps && mcpClient) { | |
| for (const mcp of config.mcps) { | |
| switch (mcp.name) { | |
| case 'BASIC_MCP': | |
| console.log('Setting up elicitation handler for BASIC_MCP'); | |
| await mcpClient.elicitation.onRequest('BASIC_MCP', async request => { | |
| console.log('π Elicitation request received for BASIC_MCP:'); | |
| console.log(JSON.stringify(request, null, 2)); | |
| const schema: any = request.requestedSchema; | |
| const properties = schema.properties || {}; | |
| const required = (schema.required as string[]) || []; | |
| console.log('π Schema properties:', Object.keys(properties)); | |
| console.log('π Required fields:', required); | |
| // User's handler returns empty content - THIS IS THE ISSUE! | |
| // Schema requires 'userConfirmation' but handler returns {} | |
| console.log('β οΈ ISSUE: Returning empty content (schema requires userConfirmation)'); | |
| console.log('β οΈ This will cause a validation error!'); | |
| // FIX: Return content that matches the schema | |
| // return { action: 'accept', content: { userConfirmation: true } }; | |
| // Current (broken) behavior: | |
| return { action: 'accept', content: {} }; | |
| }); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| console.log('β Handler set up'); | |
| // Get tools and execute | |
| console.log('\nπ Step 4: Getting tools...'); | |
| const tools = await mcpClient.listTools(); | |
| console.log('β Available tools:', Object.keys(tools)); | |
| const tool = tools['BASIC_MCP_confirmAction']; | |
| if (!tool) { | |
| console.error('β Tool not found! Available tools:', Object.keys(tools)); | |
| process.exit(1); | |
| } | |
| console.log('β Tool found:', 'BASIC_MCP_confirmAction'); | |
| // Execute the tool | |
| console.log('\nπ Step 5: Executing tool (this should trigger elicitation)...'); | |
| try { | |
| const result = await tool.execute({ | |
| action: 'test action', | |
| }); | |
| console.log('\nπ Result:'); | |
| console.log(JSON.stringify(result, null, 2)); | |
| if (result.isError) { | |
| console.log('\nβ Tool execution resulted in error'); | |
| } else { | |
| console.log('\nβ Tool execution completed'); | |
| // Check if the result contains an error message | |
| const resultText = result.content[0]?.text; | |
| if (resultText && resultText.includes('Error')) { | |
| console.log('\nπ ANALYSIS:'); | |
| console.log(' The handler WAS called (we saw the log message)'); | |
| console.log(' But the response was rejected because it doesn\'t match the schema'); | |
| console.log(' The handler returned: { action: "accept", content: {} }'); | |
| console.log(' But schema requires: { userConfirmation: boolean }'); | |
| console.log('\nπ‘ FIX:'); | |
| console.log(' Change the handler to return:'); | |
| console.log(' { action: "accept", content: { userConfirmation: true } }'); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('\nβ Error executing tool:', error); | |
| if (error instanceof Error) { | |
| console.error('Stack:', error.stack); | |
| } | |
| } | |
| // Cleanup | |
| console.log('\nπ Cleaning up...'); | |
| await mcpClient.disconnect(); | |
| await server.close(); | |
| httpServer.closeAllConnections?.(); | |
| await new Promise<void>(resolve => { | |
| httpServer.close(() => resolve()); | |
| }); | |
| console.log('\nβ Done'); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment