agentscope-sift / node_modules /@modelcontextprotocol /sdk /dist /cjs /examples /client /elicitationUrlExample.js
| ; | |
| // Run with: npx tsx src/examples/client/elicitationUrlExample.ts | |
| // | |
| // This example demonstrates how to use URL elicitation to securely | |
| // collect user input in a remote (HTTP) server. | |
| // URL elicitation allows servers to prompt the end-user to open a URL in their browser | |
| // to collect sensitive information. | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| const index_js_1 = require("../../client/index.js"); | |
| const streamableHttp_js_1 = require("../../client/streamableHttp.js"); | |
| const node_readline_1 = require("node:readline"); | |
| const types_js_1 = require("../../types.js"); | |
| const metadataUtils_js_1 = require("../../shared/metadataUtils.js"); | |
| const simpleOAuthClientProvider_js_1 = require("./simpleOAuthClientProvider.js"); | |
| const auth_js_1 = require("../../client/auth.js"); | |
| const node_http_1 = require("node:http"); | |
| // Set up OAuth (required for this example) | |
| const OAUTH_CALLBACK_PORT = 8090; // Use different port than auth server (3001) | |
| const OAUTH_CALLBACK_URL = `http://localhost:${OAUTH_CALLBACK_PORT}/callback`; | |
| let oauthProvider = undefined; | |
| console.log('Getting OAuth token...'); | |
| const clientMetadata = { | |
| client_name: 'Elicitation MCP Client', | |
| redirect_uris: [OAUTH_CALLBACK_URL], | |
| grant_types: ['authorization_code', 'refresh_token'], | |
| response_types: ['code'], | |
| token_endpoint_auth_method: 'client_secret_post', | |
| scope: 'mcp:tools' | |
| }; | |
| oauthProvider = new simpleOAuthClientProvider_js_1.InMemoryOAuthClientProvider(OAUTH_CALLBACK_URL, clientMetadata, (redirectUrl) => { | |
| console.log(`\n🔗 Please open this URL in your browser to authorize:\n ${redirectUrl.toString()}`); | |
| }); | |
| // Create readline interface for user input | |
| const readline = (0, node_readline_1.createInterface)({ | |
| input: process.stdin, | |
| output: process.stdout | |
| }); | |
| let abortCommand = new AbortController(); | |
| // Global client and transport for interactive commands | |
| let client = null; | |
| let transport = null; | |
| let serverUrl = 'http://localhost:3000/mcp'; | |
| let sessionId = undefined; | |
| let isProcessingCommand = false; | |
| let isProcessingElicitations = false; | |
| const elicitationQueue = []; | |
| let elicitationQueueSignal = null; | |
| let elicitationsCompleteSignal = null; | |
| // Map to track pending URL elicitations waiting for completion notifications | |
| const pendingURLElicitations = new Map(); | |
| async function main() { | |
| console.log('MCP Interactive Client'); | |
| console.log('====================='); | |
| // Connect to server immediately with default settings | |
| await connect(); | |
| // Start the elicitation loop in the background | |
| elicitationLoop().catch(error => { | |
| console.error('Unexpected error in elicitation loop:', error); | |
| process.exit(1); | |
| }); | |
| // Short delay allowing the server to send any SSE elicitations on connection | |
| await new Promise(resolve => setTimeout(resolve, 200)); | |
| // Wait until we are done processing any initial elicitations | |
| await waitForElicitationsToComplete(); | |
| // Print help and start the command loop | |
| printHelp(); | |
| await commandLoop(); | |
| } | |
| async function waitForElicitationsToComplete() { | |
| // Wait until the queue is empty and nothing is being processed | |
| while (elicitationQueue.length > 0 || isProcessingElicitations) { | |
| await new Promise(resolve => setTimeout(resolve, 100)); | |
| } | |
| } | |
| function printHelp() { | |
| console.log('\nAvailable commands:'); | |
| console.log(' connect [url] - Connect to MCP server (default: http://localhost:3000/mcp)'); | |
| console.log(' disconnect - Disconnect from server'); | |
| console.log(' terminate-session - Terminate the current session'); | |
| console.log(' reconnect - Reconnect to the server'); | |
| console.log(' list-tools - List available tools'); | |
| console.log(' call-tool <name> [args] - Call a tool with optional JSON arguments'); | |
| console.log(' payment-confirm - Test URL elicitation via error response with payment-confirm tool'); | |
| console.log(' third-party-auth - Test tool that requires third-party OAuth credentials'); | |
| console.log(' help - Show this help'); | |
| console.log(' quit - Exit the program'); | |
| } | |
| async function commandLoop() { | |
| await new Promise(resolve => { | |
| if (!isProcessingElicitations) { | |
| resolve(); | |
| } | |
| else { | |
| elicitationsCompleteSignal = resolve; | |
| } | |
| }); | |
| readline.question('\n> ', { signal: abortCommand.signal }, async (input) => { | |
| isProcessingCommand = true; | |
| const args = input.trim().split(/\s+/); | |
| const command = args[0]?.toLowerCase(); | |
| try { | |
| switch (command) { | |
| case 'connect': | |
| await connect(args[1]); | |
| break; | |
| case 'disconnect': | |
| await disconnect(); | |
| break; | |
| case 'terminate-session': | |
| await terminateSession(); | |
| break; | |
| case 'reconnect': | |
| await reconnect(); | |
| break; | |
| case 'list-tools': | |
| await listTools(); | |
| break; | |
| case 'call-tool': | |
| if (args.length < 2) { | |
| console.log('Usage: call-tool <name> [args]'); | |
| } | |
| else { | |
| const toolName = args[1]; | |
| let toolArgs = {}; | |
| if (args.length > 2) { | |
| try { | |
| toolArgs = JSON.parse(args.slice(2).join(' ')); | |
| } | |
| catch { | |
| console.log('Invalid JSON arguments. Using empty args.'); | |
| } | |
| } | |
| await callTool(toolName, toolArgs); | |
| } | |
| break; | |
| case 'payment-confirm': | |
| await callPaymentConfirmTool(); | |
| break; | |
| case 'third-party-auth': | |
| await callThirdPartyAuthTool(); | |
| break; | |
| case 'help': | |
| printHelp(); | |
| break; | |
| case 'quit': | |
| case 'exit': | |
| await cleanup(); | |
| return; | |
| default: | |
| if (command) { | |
| console.log(`Unknown command: ${command}`); | |
| } | |
| break; | |
| } | |
| } | |
| catch (error) { | |
| console.error(`Error executing command: ${error}`); | |
| } | |
| finally { | |
| isProcessingCommand = false; | |
| } | |
| // Process another command after we've processed the this one | |
| await commandLoop(); | |
| }); | |
| } | |
| async function elicitationLoop() { | |
| while (true) { | |
| // Wait until we have elicitations to process | |
| await new Promise(resolve => { | |
| if (elicitationQueue.length > 0) { | |
| resolve(); | |
| } | |
| else { | |
| elicitationQueueSignal = resolve; | |
| } | |
| }); | |
| isProcessingElicitations = true; | |
| abortCommand.abort(); // Abort the command loop if it's running | |
| // Process all queued elicitations | |
| while (elicitationQueue.length > 0) { | |
| const queued = elicitationQueue.shift(); | |
| console.log(`📤 Processing queued elicitation (${elicitationQueue.length} remaining)`); | |
| try { | |
| const result = await handleElicitationRequest(queued.request); | |
| queued.resolve(result); | |
| } | |
| catch (error) { | |
| queued.reject(error instanceof Error ? error : new Error(String(error))); | |
| } | |
| } | |
| console.log('✅ All queued elicitations processed. Resuming command loop...\n'); | |
| isProcessingElicitations = false; | |
| // Reset the abort controller for the next command loop | |
| abortCommand = new AbortController(); | |
| // Resume the command loop | |
| if (elicitationsCompleteSignal) { | |
| elicitationsCompleteSignal(); | |
| elicitationsCompleteSignal = null; | |
| } | |
| } | |
| } | |
| /** | |
| * Enqueues an elicitation request and returns the result. | |
| * | |
| * This function is used so that our CLI (which can only handle one input request at a time) | |
| * can handle elicitation requests and the command loop. | |
| * | |
| * @param request - The elicitation request to be handled | |
| * @returns The elicitation result | |
| */ | |
| async function elicitationRequestHandler(request) { | |
| // If we are processing a command, handle this elicitation immediately | |
| if (isProcessingCommand) { | |
| console.log('📋 Processing elicitation immediately (during command execution)'); | |
| return await handleElicitationRequest(request); | |
| } | |
| // Otherwise, queue the request to be handled by the elicitation loop | |
| console.log(`📥 Queueing elicitation request (queue size will be: ${elicitationQueue.length + 1})`); | |
| return new Promise((resolve, reject) => { | |
| elicitationQueue.push({ | |
| request, | |
| resolve, | |
| reject | |
| }); | |
| // Signal the elicitation loop that there's work to do | |
| if (elicitationQueueSignal) { | |
| elicitationQueueSignal(); | |
| elicitationQueueSignal = null; | |
| } | |
| }); | |
| } | |
| /** | |
| * Handles an elicitation request. | |
| * | |
| * This function is used to handle the elicitation request and return the result. | |
| * | |
| * @param request - The elicitation request to be handled | |
| * @returns The elicitation result | |
| */ | |
| async function handleElicitationRequest(request) { | |
| const mode = request.params.mode; | |
| console.log('\n🔔 Elicitation Request Received:'); | |
| console.log(`Mode: ${mode}`); | |
| if (mode === 'url') { | |
| return { | |
| action: await handleURLElicitation(request.params) | |
| }; | |
| } | |
| else { | |
| // Should not happen because the client declares its capabilities to the server, | |
| // but being defensive is a good practice: | |
| throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Unsupported elicitation mode: ${mode}`); | |
| } | |
| } | |
| /** | |
| * Handles a URL elicitation by opening the URL in the browser. | |
| * | |
| * Note: This is a shared code for both request handlers and error handlers. | |
| * As a result of sharing schema, there is no big forking of logic for the client. | |
| * | |
| * @param params - The URL elicitation request parameters | |
| * @returns The action to take (accept, cancel, or decline) | |
| */ | |
| async function handleURLElicitation(params) { | |
| const url = params.url; | |
| const elicitationId = params.elicitationId; | |
| const message = params.message; | |
| console.log(`🆔 Elicitation ID: ${elicitationId}`); // Print for illustration | |
| // Parse URL to show domain for security | |
| let domain = 'unknown domain'; | |
| try { | |
| const parsedUrl = new URL(url); | |
| domain = parsedUrl.hostname; | |
| } | |
| catch { | |
| console.error('Invalid URL provided by server'); | |
| return 'decline'; | |
| } | |
| // Example security warning to help prevent phishing attacks | |
| console.log('\n⚠️ \x1b[33mSECURITY WARNING\x1b[0m ⚠️'); | |
| console.log('\x1b[33mThe server is requesting you to open an external URL.\x1b[0m'); | |
| console.log('\x1b[33mOnly proceed if you trust this server and understand why it needs this.\x1b[0m\n'); | |
| console.log(`🌐 Target domain: \x1b[36m${domain}\x1b[0m`); | |
| console.log(`🔗 Full URL: \x1b[36m${url}\x1b[0m`); | |
| console.log(`\nℹ️ Server's reason:\n\n\x1b[36m${message}\x1b[0m\n`); | |
| // 1. Ask for user consent to open the URL | |
| const consent = await new Promise(resolve => { | |
| readline.question('\nDo you want to open this URL in your browser? (y/n): ', input => { | |
| resolve(input.trim().toLowerCase()); | |
| }); | |
| }); | |
| // 2. If user did not consent, return appropriate result | |
| if (consent === 'no' || consent === 'n') { | |
| console.log('❌ URL navigation declined.'); | |
| return 'decline'; | |
| } | |
| else if (consent !== 'yes' && consent !== 'y') { | |
| console.log('🚫 Invalid response. Cancelling elicitation.'); | |
| return 'cancel'; | |
| } | |
| // 3. Wait for completion notification in the background | |
| const completionPromise = new Promise((resolve, reject) => { | |
| const timeout = setTimeout(() => { | |
| pendingURLElicitations.delete(elicitationId); | |
| console.log(`\x1b[31m❌ Elicitation ${elicitationId} timed out waiting for completion.\x1b[0m`); | |
| reject(new Error('Elicitation completion timeout')); | |
| }, 5 * 60 * 1000); // 5 minute timeout | |
| pendingURLElicitations.set(elicitationId, { | |
| resolve: () => { | |
| clearTimeout(timeout); | |
| resolve(); | |
| }, | |
| reject, | |
| timeout | |
| }); | |
| }); | |
| completionPromise.catch(error => { | |
| console.error('Background completion wait failed:', error); | |
| }); | |
| // 4. Direct user to open the URL in their browser | |
| console.log(`\n🔗 Please open this URL in your browser:\n ${url}`); | |
| console.log('\n⏳ Waiting for you to complete the interaction in your browser...'); | |
| console.log(' The server will send a notification once you complete the action.'); | |
| // 5. Acknowledge the user accepted the elicitation | |
| return 'accept'; | |
| } | |
| /** | |
| * Example OAuth callback handler - in production, use a more robust approach | |
| * for handling callbacks and storing tokens | |
| */ | |
| /** | |
| * Starts a temporary HTTP server to receive the OAuth callback | |
| */ | |
| async function waitForOAuthCallback() { | |
| return new Promise((resolve, reject) => { | |
| const server = (0, node_http_1.createServer)((req, res) => { | |
| // Ignore favicon requests | |
| if (req.url === '/favicon.ico') { | |
| res.writeHead(404); | |
| res.end(); | |
| return; | |
| } | |
| console.log(`📥 Received callback: ${req.url}`); | |
| const parsedUrl = new URL(req.url || '', 'http://localhost'); | |
| const code = parsedUrl.searchParams.get('code'); | |
| const error = parsedUrl.searchParams.get('error'); | |
| if (code) { | |
| console.log(`✅ Authorization code received: ${code?.substring(0, 10)}...`); | |
| res.writeHead(200, { 'Content-Type': 'text/html' }); | |
| res.end(` | |
| <html> | |
| <body> | |
| <h1>Authorization Successful!</h1> | |
| <p>This simulates successful authorization of the MCP client, which now has an access token for the MCP server.</p> | |
| <p>This window will close automatically in 10 seconds.</p> | |
| <script>setTimeout(() => window.close(), 10000);</script> | |
| </body> | |
| </html> | |
| `); | |
| resolve(code); | |
| setTimeout(() => server.close(), 15000); | |
| } | |
| else if (error) { | |
| console.log(`❌ Authorization error: ${error}`); | |
| res.writeHead(400, { 'Content-Type': 'text/html' }); | |
| res.end(` | |
| <html> | |
| <body> | |
| <h1>Authorization Failed</h1> | |
| <p>Error: ${error}</p> | |
| </body> | |
| </html> | |
| `); | |
| reject(new Error(`OAuth authorization failed: ${error}`)); | |
| } | |
| else { | |
| console.log(`❌ No authorization code or error in callback`); | |
| res.writeHead(400); | |
| res.end('Bad request'); | |
| reject(new Error('No authorization code provided')); | |
| } | |
| }); | |
| server.listen(OAUTH_CALLBACK_PORT, () => { | |
| console.log(`OAuth callback server started on http://localhost:${OAUTH_CALLBACK_PORT}`); | |
| }); | |
| }); | |
| } | |
| /** | |
| * Attempts to connect to the MCP server with OAuth authentication. | |
| * Handles OAuth flow recursively if authorization is required. | |
| */ | |
| async function attemptConnection(oauthProvider) { | |
| console.log('🚢 Creating transport with OAuth provider...'); | |
| const baseUrl = new URL(serverUrl); | |
| transport = new streamableHttp_js_1.StreamableHTTPClientTransport(baseUrl, { | |
| sessionId: sessionId, | |
| authProvider: oauthProvider | |
| }); | |
| console.log('🚢 Transport created'); | |
| try { | |
| console.log('🔌 Attempting connection (this will trigger OAuth redirect if needed)...'); | |
| await client.connect(transport); | |
| sessionId = transport.sessionId; | |
| console.log('Transport created with session ID:', sessionId); | |
| console.log('✅ Connected successfully'); | |
| } | |
| catch (error) { | |
| if (error instanceof auth_js_1.UnauthorizedError) { | |
| console.log('🔐 OAuth required - waiting for authorization...'); | |
| const callbackPromise = waitForOAuthCallback(); | |
| const authCode = await callbackPromise; | |
| await transport.finishAuth(authCode); | |
| console.log('🔐 Authorization code received:', authCode); | |
| console.log('🔌 Reconnecting with authenticated transport...'); | |
| // Recursively retry connection after OAuth completion | |
| await attemptConnection(oauthProvider); | |
| } | |
| else { | |
| console.error('❌ Connection failed with non-auth error:', error); | |
| throw error; | |
| } | |
| } | |
| } | |
| async function connect(url) { | |
| if (client) { | |
| console.log('Already connected. Disconnect first.'); | |
| return; | |
| } | |
| if (url) { | |
| serverUrl = url; | |
| } | |
| console.log(`🔗 Attempting to connect to ${serverUrl}...`); | |
| // Create a new client with elicitation capability | |
| console.log('👤 Creating MCP client...'); | |
| client = new index_js_1.Client({ | |
| name: 'example-client', | |
| version: '1.0.0' | |
| }, { | |
| capabilities: { | |
| elicitation: { | |
| // Only URL elicitation is supported in this demo | |
| // (see server/elicitationExample.ts for a demo of form mode elicitation) | |
| url: {} | |
| } | |
| } | |
| }); | |
| console.log('👤 Client created'); | |
| // Set up elicitation request handler with proper validation | |
| client.setRequestHandler(types_js_1.ElicitRequestSchema, elicitationRequestHandler); | |
| // Set up notification handler for elicitation completion | |
| client.setNotificationHandler(types_js_1.ElicitationCompleteNotificationSchema, notification => { | |
| const { elicitationId } = notification.params; | |
| const pending = pendingURLElicitations.get(elicitationId); | |
| if (pending) { | |
| clearTimeout(pending.timeout); | |
| pendingURLElicitations.delete(elicitationId); | |
| console.log(`\x1b[32m✅ Elicitation ${elicitationId} completed!\x1b[0m`); | |
| pending.resolve(); | |
| } | |
| else { | |
| // Shouldn't happen - discard it! | |
| console.warn(`Received completion notification for unknown elicitation: ${elicitationId}`); | |
| } | |
| }); | |
| try { | |
| console.log('🔐 Starting OAuth flow...'); | |
| await attemptConnection(oauthProvider); | |
| console.log('Connected to MCP server'); | |
| // Set up error handler after connection is established so we don't double log errors | |
| client.onerror = error => { | |
| console.error('\x1b[31mClient error:', error, '\x1b[0m'); | |
| }; | |
| } | |
| catch (error) { | |
| console.error('Failed to connect:', error); | |
| client = null; | |
| transport = null; | |
| return; | |
| } | |
| } | |
| async function disconnect() { | |
| if (!client || !transport) { | |
| console.log('Not connected.'); | |
| return; | |
| } | |
| try { | |
| await transport.close(); | |
| console.log('Disconnected from MCP server'); | |
| client = null; | |
| transport = null; | |
| } | |
| catch (error) { | |
| console.error('Error disconnecting:', error); | |
| } | |
| } | |
| async function terminateSession() { | |
| if (!client || !transport) { | |
| console.log('Not connected.'); | |
| return; | |
| } | |
| try { | |
| console.log('Terminating session with ID:', transport.sessionId); | |
| await transport.terminateSession(); | |
| console.log('Session terminated successfully'); | |
| // Check if sessionId was cleared after termination | |
| if (!transport.sessionId) { | |
| console.log('Session ID has been cleared'); | |
| sessionId = undefined; | |
| // Also close the transport and clear client objects | |
| await transport.close(); | |
| console.log('Transport closed after session termination'); | |
| client = null; | |
| transport = null; | |
| } | |
| else { | |
| console.log('Server responded with 405 Method Not Allowed (session termination not supported)'); | |
| console.log('Session ID is still active:', transport.sessionId); | |
| } | |
| } | |
| catch (error) { | |
| console.error('Error terminating session:', error); | |
| } | |
| } | |
| async function reconnect() { | |
| if (client) { | |
| await disconnect(); | |
| } | |
| await connect(); | |
| } | |
| async function listTools() { | |
| if (!client) { | |
| console.log('Not connected to server.'); | |
| return; | |
| } | |
| try { | |
| const toolsRequest = { | |
| method: 'tools/list', | |
| params: {} | |
| }; | |
| const toolsResult = await client.request(toolsRequest, types_js_1.ListToolsResultSchema); | |
| console.log('Available tools:'); | |
| if (toolsResult.tools.length === 0) { | |
| console.log(' No tools available'); | |
| } | |
| else { | |
| for (const tool of toolsResult.tools) { | |
| console.log(` - id: ${tool.name}, name: ${(0, metadataUtils_js_1.getDisplayName)(tool)}, description: ${tool.description}`); | |
| } | |
| } | |
| } | |
| catch (error) { | |
| console.log(`Tools not supported by this server (${error})`); | |
| } | |
| } | |
| async function callTool(name, args) { | |
| if (!client) { | |
| console.log('Not connected to server.'); | |
| return; | |
| } | |
| try { | |
| const request = { | |
| method: 'tools/call', | |
| params: { | |
| name, | |
| arguments: args | |
| } | |
| }; | |
| console.log(`Calling tool '${name}' with args:`, args); | |
| const result = await client.request(request, types_js_1.CallToolResultSchema); | |
| console.log('Tool result:'); | |
| const resourceLinks = []; | |
| result.content.forEach(item => { | |
| if (item.type === 'text') { | |
| console.log(` ${item.text}`); | |
| } | |
| else if (item.type === 'resource_link') { | |
| const resourceLink = item; | |
| resourceLinks.push(resourceLink); | |
| console.log(` 📁 Resource Link: ${resourceLink.name}`); | |
| console.log(` URI: ${resourceLink.uri}`); | |
| if (resourceLink.mimeType) { | |
| console.log(` Type: ${resourceLink.mimeType}`); | |
| } | |
| if (resourceLink.description) { | |
| console.log(` Description: ${resourceLink.description}`); | |
| } | |
| } | |
| else if (item.type === 'resource') { | |
| console.log(` [Embedded Resource: ${item.resource.uri}]`); | |
| } | |
| else if (item.type === 'image') { | |
| console.log(` [Image: ${item.mimeType}]`); | |
| } | |
| else if (item.type === 'audio') { | |
| console.log(` [Audio: ${item.mimeType}]`); | |
| } | |
| else { | |
| console.log(` [Unknown content type]:`, item); | |
| } | |
| }); | |
| // Offer to read resource links | |
| if (resourceLinks.length > 0) { | |
| console.log(`\nFound ${resourceLinks.length} resource link(s). Use 'read-resource <uri>' to read their content.`); | |
| } | |
| } | |
| catch (error) { | |
| if (error instanceof types_js_1.UrlElicitationRequiredError) { | |
| console.log('\n🔔 Elicitation Required Error Received:'); | |
| console.log(`Message: ${error.message}`); | |
| for (const e of error.elicitations) { | |
| await handleURLElicitation(e); // For the error handler, we discard the action result because we don't respond to an error response | |
| } | |
| return; | |
| } | |
| console.log(`Error calling tool ${name}: ${error}`); | |
| } | |
| } | |
| async function cleanup() { | |
| if (client && transport) { | |
| try { | |
| // First try to terminate the session gracefully | |
| if (transport.sessionId) { | |
| try { | |
| console.log('Terminating session before exit...'); | |
| await transport.terminateSession(); | |
| console.log('Session terminated successfully'); | |
| } | |
| catch (error) { | |
| console.error('Error terminating session:', error); | |
| } | |
| } | |
| // Then close the transport | |
| await transport.close(); | |
| } | |
| catch (error) { | |
| console.error('Error closing transport:', error); | |
| } | |
| } | |
| process.stdin.setRawMode(false); | |
| readline.close(); | |
| console.log('\nGoodbye!'); | |
| process.exit(0); | |
| } | |
| async function callPaymentConfirmTool() { | |
| console.log('Calling payment-confirm tool...'); | |
| await callTool('payment-confirm', { cartId: 'cart_123' }); | |
| } | |
| async function callThirdPartyAuthTool() { | |
| console.log('Calling third-party-auth tool...'); | |
| await callTool('third-party-auth', { param1: 'test' }); | |
| } | |
| // Set up raw mode for keyboard input to capture Escape key | |
| process.stdin.setRawMode(true); | |
| process.stdin.on('data', async (data) => { | |
| // Check for Escape key (27) | |
| if (data.length === 1 && data[0] === 27) { | |
| console.log('\nESC key pressed. Disconnecting from server...'); | |
| // Abort current operation and disconnect from server | |
| if (client && transport) { | |
| await disconnect(); | |
| console.log('Disconnected. Press Enter to continue.'); | |
| } | |
| else { | |
| console.log('Not connected to server.'); | |
| } | |
| // Re-display the prompt | |
| process.stdout.write('> '); | |
| } | |
| }); | |
| // Handle Ctrl+C | |
| process.on('SIGINT', async () => { | |
| console.log('\nReceived SIGINT. Cleaning up...'); | |
| await cleanup(); | |
| }); | |
| // Start the interactive client | |
| main().catch((error) => { | |
| console.error('Error running MCP client:', error); | |
| process.exit(1); | |
| }); | |
| //# sourceMappingURL=elicitationUrlExample.js.map |