simpleTaskInteractiveClient.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * Simple interactive task client demonstrating elicitation and sampling responses.
  3. *
  4. * This client connects to simpleTaskInteractive.ts server and demonstrates:
  5. * - Handling elicitation requests (y/n confirmation)
  6. * - Handling sampling requests (returns a hardcoded haiku)
  7. * - Using task-based tool execution with streaming
  8. */
  9. import { Client } from '../../client/index.js';
  10. import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js';
  11. import { createInterface } from 'node:readline';
  12. import { CallToolResultSchema, ElicitRequestSchema, CreateMessageRequestSchema, ErrorCode, McpError } from '../../types.js';
  13. // Create readline interface for user input
  14. const readline = createInterface({
  15. input: process.stdin,
  16. output: process.stdout
  17. });
  18. function question(prompt) {
  19. return new Promise(resolve => {
  20. readline.question(prompt, answer => {
  21. resolve(answer.trim());
  22. });
  23. });
  24. }
  25. function getTextContent(result) {
  26. const textContent = result.content.find((c) => c.type === 'text');
  27. return textContent?.text ?? '(no text)';
  28. }
  29. async function elicitationCallback(params) {
  30. console.log(`\n[Elicitation] Server asks: ${params.message}`);
  31. // Simple terminal prompt for y/n
  32. const response = await question('Your response (y/n): ');
  33. const confirmed = ['y', 'yes', 'true', '1'].includes(response.toLowerCase());
  34. console.log(`[Elicitation] Responding with: confirm=${confirmed}`);
  35. return { action: 'accept', content: { confirm: confirmed } };
  36. }
  37. async function samplingCallback(params) {
  38. // Get the prompt from the first message
  39. let prompt = 'unknown';
  40. if (params.messages && params.messages.length > 0) {
  41. const firstMessage = params.messages[0];
  42. const content = firstMessage.content;
  43. if (typeof content === 'object' && !Array.isArray(content) && content.type === 'text' && 'text' in content) {
  44. prompt = content.text;
  45. }
  46. else if (Array.isArray(content)) {
  47. const textPart = content.find(c => c.type === 'text' && 'text' in c);
  48. if (textPart && 'text' in textPart) {
  49. prompt = textPart.text;
  50. }
  51. }
  52. }
  53. console.log(`\n[Sampling] Server requests LLM completion for: ${prompt}`);
  54. // Return a hardcoded haiku (in real use, call your LLM here)
  55. const haiku = `Cherry blossoms fall
  56. Softly on the quiet pond
  57. Spring whispers goodbye`;
  58. console.log('[Sampling] Responding with haiku');
  59. return {
  60. model: 'mock-haiku-model',
  61. role: 'assistant',
  62. content: { type: 'text', text: haiku }
  63. };
  64. }
  65. async function run(url) {
  66. console.log('Simple Task Interactive Client');
  67. console.log('==============================');
  68. console.log(`Connecting to ${url}...`);
  69. // Create client with elicitation and sampling capabilities
  70. const client = new Client({ name: 'simple-task-interactive-client', version: '1.0.0' }, {
  71. capabilities: {
  72. elicitation: { form: {} },
  73. sampling: {}
  74. }
  75. });
  76. // Set up elicitation request handler
  77. client.setRequestHandler(ElicitRequestSchema, async (request) => {
  78. if (request.params.mode && request.params.mode !== 'form') {
  79. throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
  80. }
  81. return elicitationCallback(request.params);
  82. });
  83. // Set up sampling request handler
  84. client.setRequestHandler(CreateMessageRequestSchema, async (request) => {
  85. return samplingCallback(request.params);
  86. });
  87. // Connect to server
  88. const transport = new StreamableHTTPClientTransport(new URL(url));
  89. await client.connect(transport);
  90. console.log('Connected!\n');
  91. // List tools
  92. const toolsResult = await client.listTools();
  93. console.log(`Available tools: ${toolsResult.tools.map(t => t.name).join(', ')}`);
  94. // Demo 1: Elicitation (confirm_delete)
  95. console.log('\n--- Demo 1: Elicitation ---');
  96. console.log('Calling confirm_delete tool...');
  97. const confirmStream = client.experimental.tasks.callToolStream({ name: 'confirm_delete', arguments: { filename: 'important.txt' } }, CallToolResultSchema, { task: { ttl: 60000 } });
  98. for await (const message of confirmStream) {
  99. switch (message.type) {
  100. case 'taskCreated':
  101. console.log(`Task created: ${message.task.taskId}`);
  102. break;
  103. case 'taskStatus':
  104. console.log(`Task status: ${message.task.status}`);
  105. break;
  106. case 'result':
  107. console.log(`Result: ${getTextContent(message.result)}`);
  108. break;
  109. case 'error':
  110. console.error(`Error: ${message.error}`);
  111. break;
  112. }
  113. }
  114. // Demo 2: Sampling (write_haiku)
  115. console.log('\n--- Demo 2: Sampling ---');
  116. console.log('Calling write_haiku tool...');
  117. const haikuStream = client.experimental.tasks.callToolStream({ name: 'write_haiku', arguments: { topic: 'autumn leaves' } }, CallToolResultSchema, {
  118. task: { ttl: 60000 }
  119. });
  120. for await (const message of haikuStream) {
  121. switch (message.type) {
  122. case 'taskCreated':
  123. console.log(`Task created: ${message.task.taskId}`);
  124. break;
  125. case 'taskStatus':
  126. console.log(`Task status: ${message.task.status}`);
  127. break;
  128. case 'result':
  129. console.log(`Result:\n${getTextContent(message.result)}`);
  130. break;
  131. case 'error':
  132. console.error(`Error: ${message.error}`);
  133. break;
  134. }
  135. }
  136. // Cleanup
  137. console.log('\nDemo complete. Closing connection...');
  138. await transport.close();
  139. readline.close();
  140. }
  141. // Parse command line arguments
  142. const args = process.argv.slice(2);
  143. let url = 'http://localhost:8000/mcp';
  144. for (let i = 0; i < args.length; i++) {
  145. if (args[i] === '--url' && args[i + 1]) {
  146. url = args[i + 1];
  147. i++;
  148. }
  149. }
  150. // Run the client
  151. run(url).catch(error => {
  152. console.error('Error running client:', error);
  153. process.exit(1);
  154. });
  155. //# sourceMappingURL=simpleTaskInteractiveClient.js.map