Skip to content

Instantly share code, notes, and snippets.

@abhiaiyer91
Created November 29, 2025 14:44
Show Gist options
  • Select an option

  • Save abhiaiyer91/4457159c0a04b0e8064146060783cfd3 to your computer and use it in GitHub Desktop.

Select an option

Save abhiaiyer91/4457159c0a04b0e8064146060783cfd3 to your computer and use it in GitHub Desktop.
/**
* 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