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