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