Skip to content

Commit 80b0bab

Browse files
committed
backwards compatible client
1 parent 8328cfe commit 80b0bab

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
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+
});

src/examples/server/simpleSseServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ app.use(express.json());
7878
const transports: Record<string, SSEServerTransport> = {};
7979

8080
// SSE endpoint for establishing the stream
81-
app.get('/sse', async (req: Request, res: Response) => {
81+
app.get('/mcp', async (req: Request, res: Response) => {
8282
console.log('Received GET request to /sse (establishing SSE stream)');
8383

8484
try {

0 commit comments

Comments
 (0)