simpleStreamableHttp.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const index_js_1 = require("../../client/index.js");
  4. const streamableHttp_js_1 = require("../../client/streamableHttp.js");
  5. const node_readline_1 = require("node:readline");
  6. const types_js_1 = require("../../types.js");
  7. const in_memory_js_1 = require("../../experimental/tasks/stores/in-memory.js");
  8. const metadataUtils_js_1 = require("../../shared/metadataUtils.js");
  9. const ajv_1 = require("ajv");
  10. // Create readline interface for user input
  11. const readline = (0, node_readline_1.createInterface)({
  12. input: process.stdin,
  13. output: process.stdout
  14. });
  15. // Track received notifications for debugging resumability
  16. let notificationCount = 0;
  17. // Global client and transport for interactive commands
  18. let client = null;
  19. let transport = null;
  20. let serverUrl = 'http://localhost:3000/mcp';
  21. let notificationsToolLastEventId = undefined;
  22. let sessionId = undefined;
  23. async function main() {
  24. console.log('MCP Interactive Client');
  25. console.log('=====================');
  26. // Connect to server immediately with default settings
  27. await connect();
  28. // Print help and start the command loop
  29. printHelp();
  30. commandLoop();
  31. }
  32. function printHelp() {
  33. console.log('\nAvailable commands:');
  34. console.log(' connect [url] - Connect to MCP server (default: http://localhost:3000/mcp)');
  35. console.log(' disconnect - Disconnect from server');
  36. console.log(' terminate-session - Terminate the current session');
  37. console.log(' reconnect - Reconnect to the server');
  38. console.log(' list-tools - List available tools');
  39. console.log(' call-tool <name> [args] - Call a tool with optional JSON arguments');
  40. console.log(' call-tool-task <name> [args] - Call a tool with task-based execution (example: call-tool-task delay {"duration":3000})');
  41. console.log(' greet [name] - Call the greet tool');
  42. console.log(' multi-greet [name] - Call the multi-greet tool with notifications');
  43. console.log(' collect-info [type] - Test form elicitation with collect-user-info tool (contact/preferences/feedback)');
  44. console.log(' collect-info-task [type] - Test bidirectional task support (server+client tasks) with elicitation');
  45. console.log(' start-notifications [interval] [count] - Start periodic notifications');
  46. console.log(' run-notifications-tool-with-resumability [interval] [count] - Run notification tool with resumability');
  47. console.log(' list-prompts - List available prompts');
  48. console.log(' get-prompt [name] [args] - Get a prompt with optional JSON arguments');
  49. console.log(' list-resources - List available resources');
  50. console.log(' read-resource <uri> - Read a specific resource by URI');
  51. console.log(' help - Show this help');
  52. console.log(' quit - Exit the program');
  53. }
  54. function commandLoop() {
  55. readline.question('\n> ', async (input) => {
  56. const args = input.trim().split(/\s+/);
  57. const command = args[0]?.toLowerCase();
  58. try {
  59. switch (command) {
  60. case 'connect':
  61. await connect(args[1]);
  62. break;
  63. case 'disconnect':
  64. await disconnect();
  65. break;
  66. case 'terminate-session':
  67. await terminateSession();
  68. break;
  69. case 'reconnect':
  70. await reconnect();
  71. break;
  72. case 'list-tools':
  73. await listTools();
  74. break;
  75. case 'call-tool':
  76. if (args.length < 2) {
  77. console.log('Usage: call-tool <name> [args]');
  78. }
  79. else {
  80. const toolName = args[1];
  81. let toolArgs = {};
  82. if (args.length > 2) {
  83. try {
  84. toolArgs = JSON.parse(args.slice(2).join(' '));
  85. }
  86. catch {
  87. console.log('Invalid JSON arguments. Using empty args.');
  88. }
  89. }
  90. await callTool(toolName, toolArgs);
  91. }
  92. break;
  93. case 'greet':
  94. await callGreetTool(args[1] || 'MCP User');
  95. break;
  96. case 'multi-greet':
  97. await callMultiGreetTool(args[1] || 'MCP User');
  98. break;
  99. case 'collect-info':
  100. await callCollectInfoTool(args[1] || 'contact');
  101. break;
  102. case 'collect-info-task': {
  103. await callCollectInfoWithTask(args[1] || 'contact');
  104. break;
  105. }
  106. case 'start-notifications': {
  107. const interval = args[1] ? parseInt(args[1], 10) : 2000;
  108. const count = args[2] ? parseInt(args[2], 10) : 10;
  109. await startNotifications(interval, count);
  110. break;
  111. }
  112. case 'run-notifications-tool-with-resumability': {
  113. const interval = args[1] ? parseInt(args[1], 10) : 2000;
  114. const count = args[2] ? parseInt(args[2], 10) : 10;
  115. await runNotificationsToolWithResumability(interval, count);
  116. break;
  117. }
  118. case 'call-tool-task':
  119. if (args.length < 2) {
  120. console.log('Usage: call-tool-task <name> [args]');
  121. }
  122. else {
  123. const toolName = args[1];
  124. let toolArgs = {};
  125. if (args.length > 2) {
  126. try {
  127. toolArgs = JSON.parse(args.slice(2).join(' '));
  128. }
  129. catch {
  130. console.log('Invalid JSON arguments. Using empty args.');
  131. }
  132. }
  133. await callToolTask(toolName, toolArgs);
  134. }
  135. break;
  136. case 'list-prompts':
  137. await listPrompts();
  138. break;
  139. case 'get-prompt':
  140. if (args.length < 2) {
  141. console.log('Usage: get-prompt <name> [args]');
  142. }
  143. else {
  144. const promptName = args[1];
  145. let promptArgs = {};
  146. if (args.length > 2) {
  147. try {
  148. promptArgs = JSON.parse(args.slice(2).join(' '));
  149. }
  150. catch {
  151. console.log('Invalid JSON arguments. Using empty args.');
  152. }
  153. }
  154. await getPrompt(promptName, promptArgs);
  155. }
  156. break;
  157. case 'list-resources':
  158. await listResources();
  159. break;
  160. case 'read-resource':
  161. if (args.length < 2) {
  162. console.log('Usage: read-resource <uri>');
  163. }
  164. else {
  165. await readResource(args[1]);
  166. }
  167. break;
  168. case 'help':
  169. printHelp();
  170. break;
  171. case 'quit':
  172. case 'exit':
  173. await cleanup();
  174. return;
  175. default:
  176. if (command) {
  177. console.log(`Unknown command: ${command}`);
  178. }
  179. break;
  180. }
  181. }
  182. catch (error) {
  183. console.error(`Error executing command: ${error}`);
  184. }
  185. // Continue the command loop
  186. commandLoop();
  187. });
  188. }
  189. async function connect(url) {
  190. if (client) {
  191. console.log('Already connected. Disconnect first.');
  192. return;
  193. }
  194. if (url) {
  195. serverUrl = url;
  196. }
  197. console.log(`Connecting to ${serverUrl}...`);
  198. try {
  199. // Create task store for client-side task support
  200. const clientTaskStore = new in_memory_js_1.InMemoryTaskStore();
  201. // Create a new client with form elicitation capability and task support
  202. client = new index_js_1.Client({
  203. name: 'example-client',
  204. version: '1.0.0'
  205. }, {
  206. capabilities: {
  207. elicitation: {
  208. form: {}
  209. },
  210. tasks: {
  211. requests: {
  212. elicitation: {
  213. create: {}
  214. }
  215. }
  216. }
  217. },
  218. taskStore: clientTaskStore
  219. });
  220. client.onerror = error => {
  221. console.error('\x1b[31mClient error:', error, '\x1b[0m');
  222. };
  223. // Set up elicitation request handler with proper validation and task support
  224. client.setRequestHandler(types_js_1.ElicitRequestSchema, async (request, extra) => {
  225. if (request.params.mode !== 'form') {
  226. throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
  227. }
  228. console.log('\n🔔 Elicitation (form) Request Received:');
  229. console.log(`Message: ${request.params.message}`);
  230. console.log(`Related Task: ${request.params._meta?.[types_js_1.RELATED_TASK_META_KEY]?.taskId}`);
  231. console.log(`Task Creation Requested: ${request.params.task ? 'yes' : 'no'}`);
  232. console.log('Requested Schema:');
  233. console.log(JSON.stringify(request.params.requestedSchema, null, 2));
  234. // Helper to return result, optionally creating a task if requested
  235. const returnResult = async (result) => {
  236. if (request.params.task && extra.taskStore) {
  237. // Create a task and store the result
  238. const task = await extra.taskStore.createTask({ ttl: extra.taskRequestedTtl });
  239. await extra.taskStore.storeTaskResult(task.taskId, 'completed', result);
  240. console.log(`📋 Created client-side task: ${task.taskId}`);
  241. return { task };
  242. }
  243. return result;
  244. };
  245. const schema = request.params.requestedSchema;
  246. const properties = schema.properties;
  247. const required = schema.required || [];
  248. // Set up AJV validator for the requested schema
  249. const ajv = new ajv_1.Ajv();
  250. const validate = ajv.compile(schema);
  251. let attempts = 0;
  252. const maxAttempts = 3;
  253. while (attempts < maxAttempts) {
  254. attempts++;
  255. console.log(`\nPlease provide the following information (attempt ${attempts}/${maxAttempts}):`);
  256. const content = {};
  257. let inputCancelled = false;
  258. // Collect input for each field
  259. for (const [fieldName, fieldSchema] of Object.entries(properties)) {
  260. const field = fieldSchema;
  261. const isRequired = required.includes(fieldName);
  262. let prompt = `${field.title || fieldName}`;
  263. // Add helpful information to the prompt
  264. if (field.description) {
  265. prompt += ` (${field.description})`;
  266. }
  267. if (field.enum) {
  268. prompt += ` [options: ${field.enum.join(', ')}]`;
  269. }
  270. if (field.type === 'number' || field.type === 'integer') {
  271. if (field.minimum !== undefined && field.maximum !== undefined) {
  272. prompt += ` [${field.minimum}-${field.maximum}]`;
  273. }
  274. else if (field.minimum !== undefined) {
  275. prompt += ` [min: ${field.minimum}]`;
  276. }
  277. else if (field.maximum !== undefined) {
  278. prompt += ` [max: ${field.maximum}]`;
  279. }
  280. }
  281. if (field.type === 'string' && field.format) {
  282. prompt += ` [format: ${field.format}]`;
  283. }
  284. if (isRequired) {
  285. prompt += ' *required*';
  286. }
  287. if (field.default !== undefined) {
  288. prompt += ` [default: ${field.default}]`;
  289. }
  290. prompt += ': ';
  291. const answer = await new Promise(resolve => {
  292. readline.question(prompt, input => {
  293. resolve(input.trim());
  294. });
  295. });
  296. // Check for cancellation
  297. if (answer.toLowerCase() === 'cancel' || answer.toLowerCase() === 'c') {
  298. inputCancelled = true;
  299. break;
  300. }
  301. // Parse and validate the input
  302. try {
  303. if (answer === '' && field.default !== undefined) {
  304. content[fieldName] = field.default;
  305. }
  306. else if (answer === '' && !isRequired) {
  307. // Skip optional empty fields
  308. continue;
  309. }
  310. else if (answer === '') {
  311. throw new Error(`${fieldName} is required`);
  312. }
  313. else {
  314. // Parse the value based on type
  315. let parsedValue;
  316. if (field.type === 'boolean') {
  317. parsedValue = answer.toLowerCase() === 'true' || answer.toLowerCase() === 'yes' || answer === '1';
  318. }
  319. else if (field.type === 'number') {
  320. parsedValue = parseFloat(answer);
  321. if (isNaN(parsedValue)) {
  322. throw new Error(`${fieldName} must be a valid number`);
  323. }
  324. }
  325. else if (field.type === 'integer') {
  326. parsedValue = parseInt(answer, 10);
  327. if (isNaN(parsedValue)) {
  328. throw new Error(`${fieldName} must be a valid integer`);
  329. }
  330. }
  331. else if (field.enum) {
  332. if (!field.enum.includes(answer)) {
  333. throw new Error(`${fieldName} must be one of: ${field.enum.join(', ')}`);
  334. }
  335. parsedValue = answer;
  336. }
  337. else {
  338. parsedValue = answer;
  339. }
  340. content[fieldName] = parsedValue;
  341. }
  342. }
  343. catch (error) {
  344. console.log(`❌ Error: ${error}`);
  345. // Continue to next attempt
  346. break;
  347. }
  348. }
  349. if (inputCancelled) {
  350. return returnResult({ action: 'cancel' });
  351. }
  352. // If we didn't complete all fields due to an error, try again
  353. if (Object.keys(content).length !==
  354. Object.keys(properties).filter(name => required.includes(name) || content[name] !== undefined).length) {
  355. if (attempts < maxAttempts) {
  356. console.log('Please try again...');
  357. continue;
  358. }
  359. else {
  360. console.log('Maximum attempts reached. Declining request.');
  361. return returnResult({ action: 'decline' });
  362. }
  363. }
  364. // Validate the complete object against the schema
  365. const isValid = validate(content);
  366. if (!isValid) {
  367. console.log('❌ Validation errors:');
  368. validate.errors?.forEach(error => {
  369. console.log(` - ${error.instancePath || 'root'}: ${error.message}`);
  370. });
  371. if (attempts < maxAttempts) {
  372. console.log('Please correct the errors and try again...');
  373. continue;
  374. }
  375. else {
  376. console.log('Maximum attempts reached. Declining request.');
  377. return returnResult({ action: 'decline' });
  378. }
  379. }
  380. // Show the collected data and ask for confirmation
  381. console.log('\n✅ Collected data:');
  382. console.log(JSON.stringify(content, null, 2));
  383. const confirmAnswer = await new Promise(resolve => {
  384. readline.question('\nSubmit this information? (yes/no/cancel): ', input => {
  385. resolve(input.trim().toLowerCase());
  386. });
  387. });
  388. switch (confirmAnswer) {
  389. case 'yes':
  390. case 'y': {
  391. return returnResult({
  392. action: 'accept',
  393. content: content
  394. });
  395. }
  396. case 'cancel':
  397. case 'c': {
  398. return returnResult({ action: 'cancel' });
  399. }
  400. case 'no':
  401. case 'n': {
  402. if (attempts < maxAttempts) {
  403. console.log('Please re-enter the information...');
  404. continue;
  405. }
  406. else {
  407. return returnResult({ action: 'decline' });
  408. }
  409. break;
  410. }
  411. }
  412. }
  413. console.log('Maximum attempts reached. Declining request.');
  414. return returnResult({ action: 'decline' });
  415. });
  416. transport = new streamableHttp_js_1.StreamableHTTPClientTransport(new URL(serverUrl), {
  417. sessionId: sessionId
  418. });
  419. // Set up notification handlers
  420. client.setNotificationHandler(types_js_1.LoggingMessageNotificationSchema, notification => {
  421. notificationCount++;
  422. console.log(`\nNotification #${notificationCount}: ${notification.params.level} - ${notification.params.data}`);
  423. // Re-display the prompt
  424. process.stdout.write('> ');
  425. });
  426. client.setNotificationHandler(types_js_1.ResourceListChangedNotificationSchema, async (_) => {
  427. console.log(`\nResource list changed notification received!`);
  428. try {
  429. if (!client) {
  430. console.log('Client disconnected, cannot fetch resources');
  431. return;
  432. }
  433. const resourcesResult = await client.request({
  434. method: 'resources/list',
  435. params: {}
  436. }, types_js_1.ListResourcesResultSchema);
  437. console.log('Available resources count:', resourcesResult.resources.length);
  438. }
  439. catch {
  440. console.log('Failed to list resources after change notification');
  441. }
  442. // Re-display the prompt
  443. process.stdout.write('> ');
  444. });
  445. // Connect the client
  446. await client.connect(transport);
  447. sessionId = transport.sessionId;
  448. console.log('Transport created with session ID:', sessionId);
  449. console.log('Connected to MCP server');
  450. }
  451. catch (error) {
  452. console.error('Failed to connect:', error);
  453. client = null;
  454. transport = null;
  455. }
  456. }
  457. async function disconnect() {
  458. if (!client || !transport) {
  459. console.log('Not connected.');
  460. return;
  461. }
  462. try {
  463. await transport.close();
  464. console.log('Disconnected from MCP server');
  465. client = null;
  466. transport = null;
  467. }
  468. catch (error) {
  469. console.error('Error disconnecting:', error);
  470. }
  471. }
  472. async function terminateSession() {
  473. if (!client || !transport) {
  474. console.log('Not connected.');
  475. return;
  476. }
  477. try {
  478. console.log('Terminating session with ID:', transport.sessionId);
  479. await transport.terminateSession();
  480. console.log('Session terminated successfully');
  481. // Check if sessionId was cleared after termination
  482. if (!transport.sessionId) {
  483. console.log('Session ID has been cleared');
  484. sessionId = undefined;
  485. // Also close the transport and clear client objects
  486. await transport.close();
  487. console.log('Transport closed after session termination');
  488. client = null;
  489. transport = null;
  490. }
  491. else {
  492. console.log('Server responded with 405 Method Not Allowed (session termination not supported)');
  493. console.log('Session ID is still active:', transport.sessionId);
  494. }
  495. }
  496. catch (error) {
  497. console.error('Error terminating session:', error);
  498. }
  499. }
  500. async function reconnect() {
  501. if (client) {
  502. await disconnect();
  503. }
  504. await connect();
  505. }
  506. async function listTools() {
  507. if (!client) {
  508. console.log('Not connected to server.');
  509. return;
  510. }
  511. try {
  512. const toolsRequest = {
  513. method: 'tools/list',
  514. params: {}
  515. };
  516. const toolsResult = await client.request(toolsRequest, types_js_1.ListToolsResultSchema);
  517. console.log('Available tools:');
  518. if (toolsResult.tools.length === 0) {
  519. console.log(' No tools available');
  520. }
  521. else {
  522. for (const tool of toolsResult.tools) {
  523. console.log(` - id: ${tool.name}, name: ${(0, metadataUtils_js_1.getDisplayName)(tool)}, description: ${tool.description}`);
  524. }
  525. }
  526. }
  527. catch (error) {
  528. console.log(`Tools not supported by this server (${error})`);
  529. }
  530. }
  531. async function callTool(name, args) {
  532. if (!client) {
  533. console.log('Not connected to server.');
  534. return;
  535. }
  536. try {
  537. const request = {
  538. method: 'tools/call',
  539. params: {
  540. name,
  541. arguments: args
  542. }
  543. };
  544. console.log(`Calling tool '${name}' with args:`, args);
  545. const result = await client.request(request, types_js_1.CallToolResultSchema);
  546. console.log('Tool result:');
  547. const resourceLinks = [];
  548. result.content.forEach(item => {
  549. if (item.type === 'text') {
  550. console.log(` ${item.text}`);
  551. }
  552. else if (item.type === 'resource_link') {
  553. const resourceLink = item;
  554. resourceLinks.push(resourceLink);
  555. console.log(` 📁 Resource Link: ${resourceLink.name}`);
  556. console.log(` URI: ${resourceLink.uri}`);
  557. if (resourceLink.mimeType) {
  558. console.log(` Type: ${resourceLink.mimeType}`);
  559. }
  560. if (resourceLink.description) {
  561. console.log(` Description: ${resourceLink.description}`);
  562. }
  563. }
  564. else if (item.type === 'resource') {
  565. console.log(` [Embedded Resource: ${item.resource.uri}]`);
  566. }
  567. else if (item.type === 'image') {
  568. console.log(` [Image: ${item.mimeType}]`);
  569. }
  570. else if (item.type === 'audio') {
  571. console.log(` [Audio: ${item.mimeType}]`);
  572. }
  573. else {
  574. console.log(` [Unknown content type]:`, item);
  575. }
  576. });
  577. // Offer to read resource links
  578. if (resourceLinks.length > 0) {
  579. console.log(`\nFound ${resourceLinks.length} resource link(s). Use 'read-resource <uri>' to read their content.`);
  580. }
  581. }
  582. catch (error) {
  583. console.log(`Error calling tool ${name}: ${error}`);
  584. }
  585. }
  586. async function callGreetTool(name) {
  587. await callTool('greet', { name });
  588. }
  589. async function callMultiGreetTool(name) {
  590. console.log('Calling multi-greet tool with notifications...');
  591. await callTool('multi-greet', { name });
  592. }
  593. async function callCollectInfoTool(infoType) {
  594. console.log(`Testing form elicitation with collect-user-info tool (${infoType})...`);
  595. await callTool('collect-user-info', { infoType });
  596. }
  597. async function callCollectInfoWithTask(infoType) {
  598. console.log(`\n🔄 Testing bidirectional task support with collect-user-info-task tool (${infoType})...`);
  599. console.log('This will create a task on the server, which will elicit input and create a task on the client.\n');
  600. await callToolTask('collect-user-info-task', { infoType });
  601. }
  602. async function startNotifications(interval, count) {
  603. console.log(`Starting notification stream: interval=${interval}ms, count=${count || 'unlimited'}`);
  604. await callTool('start-notification-stream', { interval, count });
  605. }
  606. async function runNotificationsToolWithResumability(interval, count) {
  607. if (!client) {
  608. console.log('Not connected to server.');
  609. return;
  610. }
  611. try {
  612. console.log(`Starting notification stream with resumability: interval=${interval}ms, count=${count || 'unlimited'}`);
  613. console.log(`Using resumption token: ${notificationsToolLastEventId || 'none'}`);
  614. const request = {
  615. method: 'tools/call',
  616. params: {
  617. name: 'start-notification-stream',
  618. arguments: { interval, count }
  619. }
  620. };
  621. const onLastEventIdUpdate = (event) => {
  622. notificationsToolLastEventId = event;
  623. console.log(`Updated resumption token: ${event}`);
  624. };
  625. const result = await client.request(request, types_js_1.CallToolResultSchema, {
  626. resumptionToken: notificationsToolLastEventId,
  627. onresumptiontoken: onLastEventIdUpdate
  628. });
  629. console.log('Tool result:');
  630. result.content.forEach(item => {
  631. if (item.type === 'text') {
  632. console.log(` ${item.text}`);
  633. }
  634. else {
  635. console.log(` ${item.type} content:`, item);
  636. }
  637. });
  638. }
  639. catch (error) {
  640. console.log(`Error starting notification stream: ${error}`);
  641. }
  642. }
  643. async function listPrompts() {
  644. if (!client) {
  645. console.log('Not connected to server.');
  646. return;
  647. }
  648. try {
  649. const promptsRequest = {
  650. method: 'prompts/list',
  651. params: {}
  652. };
  653. const promptsResult = await client.request(promptsRequest, types_js_1.ListPromptsResultSchema);
  654. console.log('Available prompts:');
  655. if (promptsResult.prompts.length === 0) {
  656. console.log(' No prompts available');
  657. }
  658. else {
  659. for (const prompt of promptsResult.prompts) {
  660. console.log(` - id: ${prompt.name}, name: ${(0, metadataUtils_js_1.getDisplayName)(prompt)}, description: ${prompt.description}`);
  661. }
  662. }
  663. }
  664. catch (error) {
  665. console.log(`Prompts not supported by this server (${error})`);
  666. }
  667. }
  668. async function getPrompt(name, args) {
  669. if (!client) {
  670. console.log('Not connected to server.');
  671. return;
  672. }
  673. try {
  674. const promptRequest = {
  675. method: 'prompts/get',
  676. params: {
  677. name,
  678. arguments: args
  679. }
  680. };
  681. const promptResult = await client.request(promptRequest, types_js_1.GetPromptResultSchema);
  682. console.log('Prompt template:');
  683. promptResult.messages.forEach((msg, index) => {
  684. console.log(` [${index + 1}] ${msg.role}: ${msg.content.type === 'text' ? msg.content.text : JSON.stringify(msg.content)}`);
  685. });
  686. }
  687. catch (error) {
  688. console.log(`Error getting prompt ${name}: ${error}`);
  689. }
  690. }
  691. async function listResources() {
  692. if (!client) {
  693. console.log('Not connected to server.');
  694. return;
  695. }
  696. try {
  697. const resourcesRequest = {
  698. method: 'resources/list',
  699. params: {}
  700. };
  701. const resourcesResult = await client.request(resourcesRequest, types_js_1.ListResourcesResultSchema);
  702. console.log('Available resources:');
  703. if (resourcesResult.resources.length === 0) {
  704. console.log(' No resources available');
  705. }
  706. else {
  707. for (const resource of resourcesResult.resources) {
  708. console.log(` - id: ${resource.name}, name: ${(0, metadataUtils_js_1.getDisplayName)(resource)}, description: ${resource.uri}`);
  709. }
  710. }
  711. }
  712. catch (error) {
  713. console.log(`Resources not supported by this server (${error})`);
  714. }
  715. }
  716. async function readResource(uri) {
  717. if (!client) {
  718. console.log('Not connected to server.');
  719. return;
  720. }
  721. try {
  722. const request = {
  723. method: 'resources/read',
  724. params: { uri }
  725. };
  726. console.log(`Reading resource: ${uri}`);
  727. const result = await client.request(request, types_js_1.ReadResourceResultSchema);
  728. console.log('Resource contents:');
  729. for (const content of result.contents) {
  730. console.log(` URI: ${content.uri}`);
  731. if (content.mimeType) {
  732. console.log(` Type: ${content.mimeType}`);
  733. }
  734. if ('text' in content && typeof content.text === 'string') {
  735. console.log(' Content:');
  736. console.log(' ---');
  737. console.log(content.text
  738. .split('\n')
  739. .map((line) => ' ' + line)
  740. .join('\n'));
  741. console.log(' ---');
  742. }
  743. else if ('blob' in content && typeof content.blob === 'string') {
  744. console.log(` [Binary data: ${content.blob.length} bytes]`);
  745. }
  746. }
  747. }
  748. catch (error) {
  749. console.log(`Error reading resource ${uri}: ${error}`);
  750. }
  751. }
  752. async function callToolTask(name, args) {
  753. if (!client) {
  754. console.log('Not connected to server.');
  755. return;
  756. }
  757. console.log(`Calling tool '${name}' with task-based execution...`);
  758. console.log('Arguments:', args);
  759. // Use task-based execution - call now, fetch later
  760. // Using the experimental tasks API - WARNING: may change without notice
  761. console.log('This will return immediately while processing continues in the background...');
  762. try {
  763. // Call the tool with task metadata using streaming API
  764. const stream = client.experimental.tasks.callToolStream({
  765. name,
  766. arguments: args
  767. }, types_js_1.CallToolResultSchema, {
  768. task: {
  769. ttl: 60000 // Keep results for 60 seconds
  770. }
  771. });
  772. console.log('Waiting for task completion...');
  773. let lastStatus = '';
  774. for await (const message of stream) {
  775. switch (message.type) {
  776. case 'taskCreated':
  777. console.log('Task created successfully with ID:', message.task.taskId);
  778. break;
  779. case 'taskStatus':
  780. if (lastStatus !== message.task.status) {
  781. console.log(` ${message.task.status}${message.task.statusMessage ? ` - ${message.task.statusMessage}` : ''}`);
  782. }
  783. lastStatus = message.task.status;
  784. break;
  785. case 'result':
  786. console.log('Task completed!');
  787. console.log('Tool result:');
  788. message.result.content.forEach(item => {
  789. if (item.type === 'text') {
  790. console.log(` ${item.text}`);
  791. }
  792. });
  793. break;
  794. case 'error':
  795. throw message.error;
  796. }
  797. }
  798. }
  799. catch (error) {
  800. console.log(`Error with task-based execution: ${error}`);
  801. }
  802. }
  803. async function cleanup() {
  804. if (client && transport) {
  805. try {
  806. // First try to terminate the session gracefully
  807. if (transport.sessionId) {
  808. try {
  809. console.log('Terminating session before exit...');
  810. await transport.terminateSession();
  811. console.log('Session terminated successfully');
  812. }
  813. catch (error) {
  814. console.error('Error terminating session:', error);
  815. }
  816. }
  817. // Then close the transport
  818. await transport.close();
  819. }
  820. catch (error) {
  821. console.error('Error closing transport:', error);
  822. }
  823. }
  824. process.stdin.setRawMode(false);
  825. readline.close();
  826. console.log('\nGoodbye!');
  827. process.exit(0);
  828. }
  829. // Set up raw mode for keyboard input to capture Escape key
  830. process.stdin.setRawMode(true);
  831. process.stdin.on('data', async (data) => {
  832. // Check for Escape key (27)
  833. if (data.length === 1 && data[0] === 27) {
  834. console.log('\nESC key pressed. Disconnecting from server...');
  835. // Abort current operation and disconnect from server
  836. if (client && transport) {
  837. await disconnect();
  838. console.log('Disconnected. Press Enter to continue.');
  839. }
  840. else {
  841. console.log('Not connected to server.');
  842. }
  843. // Re-display the prompt
  844. process.stdout.write('> ');
  845. }
  846. });
  847. // Handle Ctrl+C
  848. process.on('SIGINT', async () => {
  849. console.log('\nReceived SIGINT. Cleaning up...');
  850. await cleanup();
  851. });
  852. // Start the interactive client
  853. main().catch((error) => {
  854. console.error('Error running MCP client:', error);
  855. process.exit(1);
  856. });
  857. //# sourceMappingURL=simpleStreamableHttp.js.map