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