streamableHttpWithSseFallbackClient.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { Client } from '../../client/index.js';
  2. import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js';
  3. import { SSEClientTransport } from '../../client/sse.js';
  4. import { ListToolsResultSchema, CallToolResultSchema, LoggingMessageNotificationSchema } from '../../types.js';
  5. /**
  6. * Simplified Backwards Compatible MCP Client
  7. *
  8. * This client demonstrates backward compatibility with both:
  9. * 1. Modern servers using Streamable HTTP transport (protocol version 2025-03-26)
  10. * 2. Older servers using HTTP+SSE transport (protocol version 2024-11-05)
  11. *
  12. * Following the MCP specification for backwards compatibility:
  13. * - Attempts to POST an initialize request to the server URL first (modern transport)
  14. * - If that fails with 4xx status, falls back to GET request for SSE stream (older transport)
  15. */
  16. // Command line args processing
  17. const args = process.argv.slice(2);
  18. const serverUrl = args[0] || 'http://localhost:3000/mcp';
  19. async function main() {
  20. console.log('MCP Backwards Compatible Client');
  21. console.log('===============================');
  22. console.log(`Connecting to server at: ${serverUrl}`);
  23. let client;
  24. let transport;
  25. try {
  26. // Try connecting with automatic transport detection
  27. const connection = await connectWithBackwardsCompatibility(serverUrl);
  28. client = connection.client;
  29. transport = connection.transport;
  30. // Set up notification handler
  31. client.setNotificationHandler(LoggingMessageNotificationSchema, notification => {
  32. console.log(`Notification: ${notification.params.level} - ${notification.params.data}`);
  33. });
  34. // DEMO WORKFLOW:
  35. // 1. List available tools
  36. console.log('\n=== Listing Available Tools ===');
  37. await listTools(client);
  38. // 2. Call the notification tool
  39. console.log('\n=== Starting Notification Stream ===');
  40. await startNotificationTool(client);
  41. // 3. Wait for all notifications (5 seconds)
  42. console.log('\n=== Waiting for all notifications ===');
  43. await new Promise(resolve => setTimeout(resolve, 5000));
  44. // 4. Disconnect
  45. console.log('\n=== Disconnecting ===');
  46. await transport.close();
  47. console.log('Disconnected from MCP server');
  48. }
  49. catch (error) {
  50. console.error('Error running client:', error);
  51. process.exit(1);
  52. }
  53. }
  54. /**
  55. * Connect to an MCP server with backwards compatibility
  56. * Following the spec for client backward compatibility
  57. */
  58. async function connectWithBackwardsCompatibility(url) {
  59. console.log('1. Trying Streamable HTTP transport first...');
  60. // Step 1: Try Streamable HTTP transport first
  61. const client = new Client({
  62. name: 'backwards-compatible-client',
  63. version: '1.0.0'
  64. });
  65. client.onerror = error => {
  66. console.error('Client error:', error);
  67. };
  68. const baseUrl = new URL(url);
  69. try {
  70. // Create modern transport
  71. const streamableTransport = new StreamableHTTPClientTransport(baseUrl);
  72. await client.connect(streamableTransport);
  73. console.log('Successfully connected using modern Streamable HTTP transport.');
  74. return {
  75. client,
  76. transport: streamableTransport,
  77. transportType: 'streamable-http'
  78. };
  79. }
  80. catch (error) {
  81. // Step 2: If transport fails, try the older SSE transport
  82. console.log(`StreamableHttp transport connection failed: ${error}`);
  83. console.log('2. Falling back to deprecated HTTP+SSE transport...');
  84. try {
  85. // Create SSE transport pointing to /sse endpoint
  86. const sseTransport = new SSEClientTransport(baseUrl);
  87. const sseClient = new Client({
  88. name: 'backwards-compatible-client',
  89. version: '1.0.0'
  90. });
  91. await sseClient.connect(sseTransport);
  92. console.log('Successfully connected using deprecated HTTP+SSE transport.');
  93. return {
  94. client: sseClient,
  95. transport: sseTransport,
  96. transportType: 'sse'
  97. };
  98. }
  99. catch (sseError) {
  100. console.error(`Failed to connect with either transport method:\n1. Streamable HTTP error: ${error}\n2. SSE error: ${sseError}`);
  101. throw new Error('Could not connect to server with any available transport');
  102. }
  103. }
  104. }
  105. /**
  106. * List available tools on the server
  107. */
  108. async function listTools(client) {
  109. try {
  110. const toolsRequest = {
  111. method: 'tools/list',
  112. params: {}
  113. };
  114. const toolsResult = await client.request(toolsRequest, ListToolsResultSchema);
  115. console.log('Available tools:');
  116. if (toolsResult.tools.length === 0) {
  117. console.log(' No tools available');
  118. }
  119. else {
  120. for (const tool of toolsResult.tools) {
  121. console.log(` - ${tool.name}: ${tool.description}`);
  122. }
  123. }
  124. }
  125. catch (error) {
  126. console.log(`Tools not supported by this server: ${error}`);
  127. }
  128. }
  129. /**
  130. * Start a notification stream by calling the notification tool
  131. */
  132. async function startNotificationTool(client) {
  133. try {
  134. // Call the notification tool using reasonable defaults
  135. const request = {
  136. method: 'tools/call',
  137. params: {
  138. name: 'start-notification-stream',
  139. arguments: {
  140. interval: 1000, // 1 second between notifications
  141. count: 5 // Send 5 notifications
  142. }
  143. }
  144. };
  145. console.log('Calling notification tool...');
  146. const result = await client.request(request, CallToolResultSchema);
  147. console.log('Tool result:');
  148. result.content.forEach(item => {
  149. if (item.type === 'text') {
  150. console.log(` ${item.text}`);
  151. }
  152. else {
  153. console.log(` ${item.type} content:`, item);
  154. }
  155. });
  156. }
  157. catch (error) {
  158. console.log(`Error calling notification tool: ${error}`);
  159. }
  160. }
  161. // Start the client
  162. main().catch((error) => {
  163. console.error('Error running MCP client:', error);
  164. process.exit(1);
  165. });
  166. //# sourceMappingURL=streamableHttpWithSseFallbackClient.js.map