pse-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GEMINI.md +72 -0
- package/License.md +3 -0
- package/MCP Documents/README.md +1 -0
- package/MCP Documents/mcp-client-guide.txt +736 -0
- package/MCP Documents/mcp-complete-guide.txt +522 -0
- package/MCP Documents/mcp-enhanced-instructions.md +297 -0
- package/MCP Documents/mcp-server-guide.md +415 -0
- package/MCP Documents/mcp-windows.txt +161 -0
- package/QWEN.md +207 -0
- package/README.md +220 -0
- package/dist/content-fetcher.js +36 -0
- package/dist/google-search.js +421 -0
- package/dist/services/content-extractor.service.js +195 -0
- package/dist/services/google-search.service.js +244 -0
- package/dist/types.js +1 -0
- package/dist-package/README.md +210 -0
- package/dist-package/dist/content-fetcher.js +36 -0
- package/dist-package/dist/google-search.js +420 -0
- package/dist-package/dist/services/content-extractor.service.js +195 -0
- package/dist-package/dist/services/google-search.service.js +244 -0
- package/dist-package/dist/types.js +1 -0
- package/dist-package/package-lock.json +3104 -0
- package/dist-package/package.json +23 -0
- package/license +4 -0
- package/package.json +40 -0
- package/src/google-search.ts +477 -0
- package/src/mcp.d.ts +36 -0
- package/src/services/content-extractor.service.ts +232 -0
- package/src/services/google-search.service.ts +305 -0
- package/src/types.ts +64 -0
- package/tasks.md +141 -0
- package/tsconfig.json +16 -0
@@ -0,0 +1,736 @@
|
|
1
|
+
# Model Context Protocol (MCP) Client Implementation Guide
|
2
|
+
|
3
|
+
## Introduction
|
4
|
+
|
5
|
+
The Model Context Protocol (MCP) is an open standard that enables seamless integration between LLM applications and external data sources and tools. Similar to how the Language Server Protocol (LSP) standardized the connection between code editors and language servers, MCP standardizes how AI models interact with external resources.
|
6
|
+
|
7
|
+
This guide focuses specifically on implementing the **client side** of MCP, which is responsible for establishing and maintaining connections with MCP servers to leverage their capabilities.
|
8
|
+
|
9
|
+
## What is an MCP Client?
|
10
|
+
|
11
|
+
In the MCP architecture, a client is a component that:
|
12
|
+
|
13
|
+
- Maintains 1:1 connections with MCP servers
|
14
|
+
- Requests capabilities like tools, resources, and prompts from servers
|
15
|
+
- Passes these capabilities to an LLM for use
|
16
|
+
- Handles the execution of tool calls when requested by the LLM
|
17
|
+
- Manages communication protocols (stdio, HTTP/SSE, etc.)
|
18
|
+
|
19
|
+
## Prerequisites
|
20
|
+
|
21
|
+
Before implementing an MCP client, ensure you have:
|
22
|
+
|
23
|
+
1. A development environment with your preferred language (Python, TypeScript, etc.)
|
24
|
+
2. Basic understanding of async programming (as most MCP operations are asynchronous)
|
25
|
+
3. Access to MCP servers you want to connect to
|
26
|
+
4. Familiarity with the LLM platform you're integrating with
|
27
|
+
|
28
|
+
## MCP Client Implementation Steps
|
29
|
+
|
30
|
+
### 1. Choose Your SDK
|
31
|
+
|
32
|
+
MCP offers official SDKs for various languages:
|
33
|
+
|
34
|
+
- **Python SDK**: For Python applications
|
35
|
+
- **TypeScript/JavaScript SDK**: For web/Node.js applications
|
36
|
+
- **Swift SDK**: For iOS/macOS applications
|
37
|
+
|
38
|
+
For this guide, we'll provide examples in both Python and TypeScript.
|
39
|
+
|
40
|
+
### 2. Install Dependencies
|
41
|
+
|
42
|
+
#### Python
|
43
|
+
|
44
|
+
```python
|
45
|
+
# Using uv (recommended)
|
46
|
+
uv init mcp-client
|
47
|
+
cd mcp-client
|
48
|
+
uv venv
|
49
|
+
# Activate virtual environment
|
50
|
+
# On Windows: .venv\Scripts\activate
|
51
|
+
# On Unix or MacOS: source .venv/bin/activate
|
52
|
+
uv add mcp
|
53
|
+
```
|
54
|
+
|
55
|
+
#### TypeScript/JavaScript
|
56
|
+
|
57
|
+
```bash
|
58
|
+
# Using npm
|
59
|
+
mkdir mcp-client
|
60
|
+
cd mcp-client
|
61
|
+
npm init -y
|
62
|
+
npm install @modelcontextprotocol/client
|
63
|
+
```
|
64
|
+
|
65
|
+
### 3. Establish Connection with MCP Server
|
66
|
+
|
67
|
+
MCP clients can connect to servers using different transport methods:
|
68
|
+
|
69
|
+
#### Stdio Transport (Local Server)
|
70
|
+
|
71
|
+
This method runs the server as a subprocess and communicates via standard input/output.
|
72
|
+
|
73
|
+
##### Python Example
|
74
|
+
|
75
|
+
```python
|
76
|
+
import asyncio
|
77
|
+
from mcp import ClientSession
|
78
|
+
from mcp.client.stdio import stdio_client
|
79
|
+
from mcp import StdioServerParameters
|
80
|
+
|
81
|
+
async def main():
|
82
|
+
# Define server parameters (command to start server)
|
83
|
+
server_params = StdioServerParameters(
|
84
|
+
command="npx", # Command to run
|
85
|
+
args=["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"] # Arguments
|
86
|
+
)
|
87
|
+
|
88
|
+
# Connect to the server
|
89
|
+
async with stdio_client(server_params) as (read, write):
|
90
|
+
# Create a client session
|
91
|
+
async with ClientSession(read, write) as session:
|
92
|
+
# Initialize the session
|
93
|
+
await session.initialize()
|
94
|
+
|
95
|
+
# Now you can interact with the server
|
96
|
+
# ...
|
97
|
+
|
98
|
+
if __name__ == "__main__":
|
99
|
+
asyncio.run(main())
|
100
|
+
```
|
101
|
+
|
102
|
+
##### TypeScript Example
|
103
|
+
|
104
|
+
```typescript
|
105
|
+
import { createStdioClient, StdioServerParameters } from '@modelcontextprotocol/client';
|
106
|
+
|
107
|
+
async function main() {
|
108
|
+
// Define server parameters
|
109
|
+
const serverParams: StdioServerParameters = {
|
110
|
+
command: 'npx',
|
111
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/files']
|
112
|
+
};
|
113
|
+
|
114
|
+
// Create client and session
|
115
|
+
const client = await createStdioClient(serverParams);
|
116
|
+
const session = await client.createSession();
|
117
|
+
|
118
|
+
// Initialize session
|
119
|
+
await session.initialize();
|
120
|
+
|
121
|
+
// Now you can interact with the server
|
122
|
+
// ...
|
123
|
+
}
|
124
|
+
|
125
|
+
main().catch(console.error);
|
126
|
+
```
|
127
|
+
|
128
|
+
#### HTTP/SSE Transport (Remote Server)
|
129
|
+
|
130
|
+
For servers available over HTTP with Server-Sent Events (SSE).
|
131
|
+
|
132
|
+
##### Python Example
|
133
|
+
|
134
|
+
```python
|
135
|
+
import asyncio
|
136
|
+
from mcp import ClientSession
|
137
|
+
from mcp.client.sse import sse_client
|
138
|
+
from mcp.client.types import SseServerParameters
|
139
|
+
|
140
|
+
async def main():
|
141
|
+
# Define server parameters
|
142
|
+
server_params = SseServerParameters(
|
143
|
+
url="https://your-mcp-server.com", # Server URL
|
144
|
+
headers={"Authorization": "Bearer your-token"} # Optional headers
|
145
|
+
)
|
146
|
+
|
147
|
+
# Connect to the server
|
148
|
+
async with sse_client(server_params) as (read, write):
|
149
|
+
# Create a client session
|
150
|
+
async with ClientSession(read, write) as session:
|
151
|
+
# Initialize the session
|
152
|
+
await session.initialize()
|
153
|
+
|
154
|
+
# Now you can interact with the server
|
155
|
+
# ...
|
156
|
+
|
157
|
+
if __name__ == "__main__":
|
158
|
+
asyncio.run(main())
|
159
|
+
```
|
160
|
+
|
161
|
+
##### TypeScript Example
|
162
|
+
|
163
|
+
```typescript
|
164
|
+
import { createSseClient, SseServerParameters } from '@modelcontextprotocol/client';
|
165
|
+
|
166
|
+
async function main() {
|
167
|
+
// Define server parameters
|
168
|
+
const serverParams: SseServerParameters = {
|
169
|
+
url: 'https://your-mcp-server.com',
|
170
|
+
headers: { 'Authorization': 'Bearer your-token' }
|
171
|
+
};
|
172
|
+
|
173
|
+
// Create client and session
|
174
|
+
const client = await createSseClient(serverParams);
|
175
|
+
const session = await client.createSession();
|
176
|
+
|
177
|
+
// Initialize session
|
178
|
+
await session.initialize();
|
179
|
+
|
180
|
+
// Now you can interact with the server
|
181
|
+
// ...
|
182
|
+
}
|
183
|
+
|
184
|
+
main().catch(console.error);
|
185
|
+
```
|
186
|
+
|
187
|
+
### 4. Discover Server Capabilities
|
188
|
+
|
189
|
+
After establishing a connection, you need to discover what capabilities the server offers:
|
190
|
+
|
191
|
+
#### Python Example
|
192
|
+
|
193
|
+
```python
|
194
|
+
async def discover_capabilities(session):
|
195
|
+
# List available tools
|
196
|
+
tools_response = await session.list_tools()
|
197
|
+
tools = tools_response.tools
|
198
|
+
print(f"Available tools: {[tool.name for tool in tools]}")
|
199
|
+
|
200
|
+
# List available resources
|
201
|
+
try:
|
202
|
+
resources_response = await session.list_resources()
|
203
|
+
resources = resources_response.resources
|
204
|
+
print(f"Available resources: {[resource.name for resource in resources]}")
|
205
|
+
except Exception as e:
|
206
|
+
# Not all servers implement resources
|
207
|
+
print(f"No resources available: {e}")
|
208
|
+
|
209
|
+
# List available prompts
|
210
|
+
try:
|
211
|
+
prompts_response = await session.list_prompts()
|
212
|
+
prompts = prompts_response.prompts
|
213
|
+
print(f"Available prompts: {[prompt.name for prompt in prompts]}")
|
214
|
+
except Exception as e:
|
215
|
+
# Not all servers implement prompts
|
216
|
+
print(f"No prompts available: {e}")
|
217
|
+
|
218
|
+
return tools, resources, prompts
|
219
|
+
```
|
220
|
+
|
221
|
+
#### TypeScript Example
|
222
|
+
|
223
|
+
```typescript
|
224
|
+
async function discoverCapabilities(session) {
|
225
|
+
// List available tools
|
226
|
+
const toolsResponse = await session.listTools();
|
227
|
+
const tools = toolsResponse.tools;
|
228
|
+
console.log(`Available tools: ${tools.map(tool => tool.name)}`);
|
229
|
+
|
230
|
+
// List available resources
|
231
|
+
try {
|
232
|
+
const resourcesResponse = await session.listResources();
|
233
|
+
const resources = resourcesResponse.resources;
|
234
|
+
console.log(`Available resources: ${resources.map(resource => resource.name)}`);
|
235
|
+
} catch (e) {
|
236
|
+
// Not all servers implement resources
|
237
|
+
console.log(`No resources available: ${e}`);
|
238
|
+
}
|
239
|
+
|
240
|
+
// List available prompts
|
241
|
+
try {
|
242
|
+
const promptsResponse = await session.listPrompts();
|
243
|
+
const prompts = promptsResponse.prompts;
|
244
|
+
console.log(`Available prompts: ${prompts.map(prompt => prompt.name)}`);
|
245
|
+
} catch (e) {
|
246
|
+
// Not all servers implement prompts
|
247
|
+
console.log(`No prompts available: ${e}`);
|
248
|
+
}
|
249
|
+
|
250
|
+
return { tools, resources, prompts };
|
251
|
+
}
|
252
|
+
```
|
253
|
+
|
254
|
+
### 5. Use Server Resources
|
255
|
+
|
256
|
+
Resources provide context data to the LLM:
|
257
|
+
|
258
|
+
#### Python Example
|
259
|
+
|
260
|
+
```python
|
261
|
+
async def get_resource(session, resource_name, params=None):
|
262
|
+
try:
|
263
|
+
resource_response = await session.get_resource(resource_name, params or {})
|
264
|
+
return resource_response.value
|
265
|
+
except Exception as e:
|
266
|
+
print(f"Error getting resource {resource_name}: {e}")
|
267
|
+
return None
|
268
|
+
```
|
269
|
+
|
270
|
+
#### TypeScript Example
|
271
|
+
|
272
|
+
```typescript
|
273
|
+
async function getResource(session, resourceName, params = {}) {
|
274
|
+
try {
|
275
|
+
const resourceResponse = await session.getResource(resourceName, params);
|
276
|
+
return resourceResponse.value;
|
277
|
+
} catch (e) {
|
278
|
+
console.log(`Error getting resource ${resourceName}: ${e}`);
|
279
|
+
return null;
|
280
|
+
}
|
281
|
+
}
|
282
|
+
```
|
283
|
+
|
284
|
+
### 6. Invoke Server Tools
|
285
|
+
|
286
|
+
Tools allow the LLM to perform actions:
|
287
|
+
|
288
|
+
#### Python Example
|
289
|
+
|
290
|
+
```python
|
291
|
+
async def call_tool(session, tool_name, params=None):
|
292
|
+
try:
|
293
|
+
tool_response = await session.call_tool(tool_name, params or {})
|
294
|
+
return tool_response.result
|
295
|
+
except Exception as e:
|
296
|
+
print(f"Error calling tool {tool_name}: {e}")
|
297
|
+
return None
|
298
|
+
```
|
299
|
+
|
300
|
+
#### TypeScript Example
|
301
|
+
|
302
|
+
```typescript
|
303
|
+
async function callTool(session, toolName, params = {}) {
|
304
|
+
try {
|
305
|
+
const toolResponse = await session.callTool(toolName, params);
|
306
|
+
return toolResponse.result;
|
307
|
+
} catch (e) {
|
308
|
+
console.log(`Error calling tool ${toolName}: ${e}`);
|
309
|
+
return null;
|
310
|
+
}
|
311
|
+
}
|
312
|
+
```
|
313
|
+
|
314
|
+
### 7. Use Server Prompts
|
315
|
+
|
316
|
+
Prompts provide templated interactions for the LLM:
|
317
|
+
|
318
|
+
#### Python Example
|
319
|
+
|
320
|
+
```python
|
321
|
+
async def get_prompt(session, prompt_name, params=None):
|
322
|
+
try:
|
323
|
+
prompt_response = await session.get_prompt(prompt_name, params or {})
|
324
|
+
return prompt_response.messages
|
325
|
+
except Exception as e:
|
326
|
+
print(f"Error getting prompt {prompt_name}: {e}")
|
327
|
+
return None
|
328
|
+
```
|
329
|
+
|
330
|
+
#### TypeScript Example
|
331
|
+
|
332
|
+
```typescript
|
333
|
+
async function getPrompt(session, promptName, params = {}) {
|
334
|
+
try {
|
335
|
+
const promptResponse = await session.getPrompt(promptName, params);
|
336
|
+
return promptResponse.messages;
|
337
|
+
} catch (e) {
|
338
|
+
console.log(`Error getting prompt ${promptName}: ${e}`);
|
339
|
+
return null;
|
340
|
+
}
|
341
|
+
}
|
342
|
+
```
|
343
|
+
|
344
|
+
### 8. Integrate with LLM
|
345
|
+
|
346
|
+
Now you need to connect the MCP capabilities with your LLM:
|
347
|
+
|
348
|
+
#### Python Example with Anthropic's Claude
|
349
|
+
|
350
|
+
```python
|
351
|
+
import os
|
352
|
+
from anthropic import Anthropic
|
353
|
+
|
354
|
+
async def chat_with_claude(client_session, user_message, tools=None):
|
355
|
+
# Discover available tools
|
356
|
+
if tools is None:
|
357
|
+
tools_response = await client_session.list_tools()
|
358
|
+
tools = tools_response.tools
|
359
|
+
|
360
|
+
# Convert MCP tools to Claude's tool format
|
361
|
+
claude_tools = []
|
362
|
+
for tool in tools:
|
363
|
+
claude_tools.append({
|
364
|
+
"name": tool.name,
|
365
|
+
"description": tool.description,
|
366
|
+
"input_schema": tool.parameters
|
367
|
+
})
|
368
|
+
|
369
|
+
# Initialize Anthropic client
|
370
|
+
anthropic = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
|
371
|
+
|
372
|
+
# Create a message with tools
|
373
|
+
response = anthropic.messages.create(
|
374
|
+
model="claude-3-5-sonnet-20241022",
|
375
|
+
max_tokens=1000,
|
376
|
+
messages=[{"role": "user", "content": user_message}],
|
377
|
+
tools=claude_tools
|
378
|
+
)
|
379
|
+
|
380
|
+
# Check if Claude wants to use any tools
|
381
|
+
for content_block in response.content:
|
382
|
+
if content_block.type == "tool_use":
|
383
|
+
tool_name = content_block.name
|
384
|
+
tool_params = content_block.input
|
385
|
+
|
386
|
+
# Call the tool via MCP
|
387
|
+
tool_result = await call_tool(client_session, tool_name, tool_params)
|
388
|
+
|
389
|
+
# Send the tool result back to Claude
|
390
|
+
follow_up = anthropic.messages.create(
|
391
|
+
model="claude-3-5-sonnet-20241022",
|
392
|
+
max_tokens=1000,
|
393
|
+
messages=[
|
394
|
+
{"role": "user", "content": user_message},
|
395
|
+
{"role": "assistant", "content": response.content},
|
396
|
+
{"role": "tool", "name": tool_name, "content": str(tool_result)}
|
397
|
+
]
|
398
|
+
)
|
399
|
+
return follow_up
|
400
|
+
|
401
|
+
return response
|
402
|
+
```
|
403
|
+
|
404
|
+
### 9. Complete Client Example
|
405
|
+
|
406
|
+
Here's a complete example of a simple MCP client implementation in Python:
|
407
|
+
|
408
|
+
```python
|
409
|
+
import asyncio
|
410
|
+
import os
|
411
|
+
import json
|
412
|
+
from typing import Optional, List, Dict, Any
|
413
|
+
from contextlib import AsyncExitStack
|
414
|
+
|
415
|
+
from mcp import ClientSession, StdioServerParameters
|
416
|
+
from mcp.client.stdio import stdio_client
|
417
|
+
from anthropic import Anthropic
|
418
|
+
|
419
|
+
class MCPClientApp:
|
420
|
+
def __init__(self):
|
421
|
+
self.session: Optional[ClientSession] = None
|
422
|
+
self.exit_stack = AsyncExitStack()
|
423
|
+
self.anthropic = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
|
424
|
+
self.tools = []
|
425
|
+
|
426
|
+
async def connect_to_server(self, server_script_path):
|
427
|
+
"""Connect to an MCP server using stdio transport"""
|
428
|
+
server_params = StdioServerParameters(
|
429
|
+
command="python",
|
430
|
+
args=[server_script_path]
|
431
|
+
)
|
432
|
+
|
433
|
+
read, write = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
434
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
|
435
|
+
|
436
|
+
# Initialize session
|
437
|
+
await self.session.initialize()
|
438
|
+
|
439
|
+
# Discover tools
|
440
|
+
tools_response = await self.session.list_tools()
|
441
|
+
self.tools = tools_response.tools
|
442
|
+
|
443
|
+
print(f"Connected to MCP server with {len(self.tools)} tools available:")
|
444
|
+
for tool in self.tools:
|
445
|
+
print(f"- {tool.name}: {tool.description}")
|
446
|
+
|
447
|
+
async def call_tool(self, tool_name, params=None):
|
448
|
+
"""Call an MCP tool with parameters"""
|
449
|
+
if not self.session:
|
450
|
+
raise RuntimeError("No active MCP session")
|
451
|
+
|
452
|
+
try:
|
453
|
+
tool_response = await self.session.call_tool(tool_name, params or {})
|
454
|
+
return tool_response.result
|
455
|
+
except Exception as e:
|
456
|
+
print(f"Error calling tool {tool_name}: {e}")
|
457
|
+
return None
|
458
|
+
|
459
|
+
async def chat_loop(self):
|
460
|
+
"""Run an interactive chat loop with Claude using MCP tools"""
|
461
|
+
if not self.session:
|
462
|
+
raise RuntimeError("No active MCP session")
|
463
|
+
|
464
|
+
print("\nChat with Claude (using MCP tools). Type 'exit' to quit.")
|
465
|
+
|
466
|
+
# Convert MCP tools to Claude's tool format
|
467
|
+
claude_tools = []
|
468
|
+
for tool in self.tools:
|
469
|
+
claude_tools.append({
|
470
|
+
"name": tool.name,
|
471
|
+
"description": tool.description,
|
472
|
+
"input_schema": tool.parameters
|
473
|
+
})
|
474
|
+
|
475
|
+
# Keep track of conversation
|
476
|
+
messages = []
|
477
|
+
|
478
|
+
while True:
|
479
|
+
# Get user input
|
480
|
+
user_input = input("\nYou: ")
|
481
|
+
if user_input.lower() == 'exit':
|
482
|
+
break
|
483
|
+
|
484
|
+
# Add to conversation history
|
485
|
+
messages.append({"role": "user", "content": user_input})
|
486
|
+
|
487
|
+
# Send to Claude with tools
|
488
|
+
response = self.anthropic.messages.create(
|
489
|
+
model="claude-3-5-sonnet-20241022",
|
490
|
+
max_tokens=1000,
|
491
|
+
messages=messages,
|
492
|
+
tools=claude_tools
|
493
|
+
)
|
494
|
+
|
495
|
+
# Process response
|
496
|
+
assistant_message = {"role": "assistant", "content": []}
|
497
|
+
for content_block in response.content:
|
498
|
+
if content_block.type == "text":
|
499
|
+
print(f"\nClaude: {content_block.text}")
|
500
|
+
assistant_message["content"].append(content_block)
|
501
|
+
|
502
|
+
elif content_block.type == "tool_use":
|
503
|
+
tool_name = content_block.name
|
504
|
+
tool_params = content_block.input
|
505
|
+
|
506
|
+
print(f"\nClaude is using tool: {tool_name}")
|
507
|
+
assistant_message["content"].append(content_block)
|
508
|
+
|
509
|
+
# Call the tool via MCP
|
510
|
+
tool_result = await self.call_tool(tool_name, tool_params)
|
511
|
+
print(f"Tool result: {tool_result}")
|
512
|
+
|
513
|
+
# Add tool response to conversation
|
514
|
+
messages.append(assistant_message)
|
515
|
+
messages.append({
|
516
|
+
"role": "tool",
|
517
|
+
"name": tool_name,
|
518
|
+
"content": json.dumps(tool_result)
|
519
|
+
})
|
520
|
+
|
521
|
+
# Get Claude's follow-up response with the tool result
|
522
|
+
follow_up = self.anthropic.messages.create(
|
523
|
+
model="claude-3-5-sonnet-20241022",
|
524
|
+
max_tokens=1000,
|
525
|
+
messages=messages,
|
526
|
+
tools=claude_tools
|
527
|
+
)
|
528
|
+
|
529
|
+
# Print Claude's response after using the tool
|
530
|
+
for fb_content in follow_up.content:
|
531
|
+
if fb_content.type == "text":
|
532
|
+
print(f"\nClaude: {fb_content.text}")
|
533
|
+
|
534
|
+
# Update messages for next turn
|
535
|
+
assistant_message = {"role": "assistant", "content": follow_up.content}
|
536
|
+
|
537
|
+
# Add assistant's response to conversation history
|
538
|
+
messages.append(assistant_message)
|
539
|
+
|
540
|
+
async def cleanup(self):
|
541
|
+
"""Clean up resources"""
|
542
|
+
await self.exit_stack.aclose()
|
543
|
+
|
544
|
+
async def main():
|
545
|
+
if len(sys.argv) < 2:
|
546
|
+
print("Usage: python client.py <path_to_server_script>")
|
547
|
+
sys.exit(1)
|
548
|
+
|
549
|
+
client = MCPClientApp()
|
550
|
+
try:
|
551
|
+
await client.connect_to_server(sys.argv[1])
|
552
|
+
await client.chat_loop()
|
553
|
+
finally:
|
554
|
+
await client.cleanup()
|
555
|
+
|
556
|
+
if __name__ == "__main__":
|
557
|
+
import sys
|
558
|
+
asyncio.run(main())
|
559
|
+
```
|
560
|
+
|
561
|
+
## Best Practices for MCP Clients
|
562
|
+
|
563
|
+
1. **Error Handling**: Implement robust error handling for all MCP operations
|
564
|
+
2. **Authentication**: Support secure authentication methods for remote servers
|
565
|
+
3. **Rate Limiting**: Implement rate limiting to avoid overwhelming servers
|
566
|
+
4. **Caching**: Cache server capabilities to reduce latency
|
567
|
+
5. **Timeouts**: Set appropriate timeouts for server operations
|
568
|
+
6. **Logging**: Implement comprehensive logging for debugging
|
569
|
+
7. **Reconnection Logic**: Handle disconnections gracefully with reconnection attempts
|
570
|
+
8. **Security Checks**: Validate server responses before processing
|
571
|
+
9. **Platform Compatibility**:
|
572
|
+
- Handle Windows/Unix line ending differences
|
573
|
+
- Use binary mode for stdio on Windows
|
574
|
+
- Implement proper path handling
|
575
|
+
- Consider cross-platform transport options
|
576
|
+
10. **Resource Management**:
|
577
|
+
- Clean up resources properly
|
578
|
+
- Monitor memory usage
|
579
|
+
- Handle connection pooling
|
580
|
+
- Implement proper shutdown procedures
|
581
|
+
11. **Performance Optimization**:
|
582
|
+
- Batch requests when possible
|
583
|
+
- Implement response caching
|
584
|
+
- Use connection pooling
|
585
|
+
- Monitor and optimize memory usage
|
586
|
+
|
587
|
+
## Platform-Specific Considerations
|
588
|
+
|
589
|
+
### Windows Implementation
|
590
|
+
|
591
|
+
1. **stdio Handling**:
|
592
|
+
```python
|
593
|
+
# Python
|
594
|
+
if sys.platform == 'win32':
|
595
|
+
import msvcrt
|
596
|
+
msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
|
597
|
+
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
|
598
|
+
```
|
599
|
+
|
600
|
+
```typescript
|
601
|
+
// TypeScript
|
602
|
+
if (process.platform === 'win32') {
|
603
|
+
process.stdin.setEncoding('binary');
|
604
|
+
process.stdout.setDefaultEncoding('binary');
|
605
|
+
}
|
606
|
+
```
|
607
|
+
|
608
|
+
2. **Path Management**:
|
609
|
+
```typescript
|
610
|
+
import { join } from 'path';
|
611
|
+
|
612
|
+
const configPath = join(process.cwd(), 'config.json');
|
613
|
+
```
|
614
|
+
|
615
|
+
3. **Error Handling**:
|
616
|
+
```typescript
|
617
|
+
process.on('uncaughtException', (error) => {
|
618
|
+
console.error(`Uncaught Exception: ${error.message}`);
|
619
|
+
process.exit(1);
|
620
|
+
});
|
621
|
+
```
|
622
|
+
|
623
|
+
### Unix Implementation
|
624
|
+
|
625
|
+
1. **Signal Handling**:
|
626
|
+
```typescript
|
627
|
+
process.on('SIGTERM', () => {
|
628
|
+
console.error('Received SIGTERM, cleaning up...');
|
629
|
+
// Perform cleanup
|
630
|
+
process.exit(0);
|
631
|
+
});
|
632
|
+
```
|
633
|
+
|
634
|
+
2. **File Permissions**:
|
635
|
+
```python
|
636
|
+
import os
|
637
|
+
os.chmod('client.sh', 0o755)
|
638
|
+
```
|
639
|
+
|
640
|
+
## Advanced Features
|
641
|
+
|
642
|
+
### 1. Sampling
|
643
|
+
|
644
|
+
Sampling is a powerful MCP feature where servers can request LLM completions from the client, reversing the usual flow:
|
645
|
+
|
646
|
+
```python
|
647
|
+
async def handle_sampling(session):
|
648
|
+
# Set up a sampling handler
|
649
|
+
def sampling_handler(request, extra):
|
650
|
+
# Process sampling request
|
651
|
+
prompt = request.get("prompt", "")
|
652
|
+
|
653
|
+
# Generate completion with your LLM
|
654
|
+
# This is a simplified example
|
655
|
+
completion = generate_completion(prompt)
|
656
|
+
|
657
|
+
# Return completion
|
658
|
+
return {"completion": completion}
|
659
|
+
|
660
|
+
# Register the sampling handler
|
661
|
+
await session.set_sampling_handler(sampling_handler)
|
662
|
+
```
|
663
|
+
|
664
|
+
### 2. Multi-Server Management
|
665
|
+
|
666
|
+
For applications that need to connect to multiple MCP servers:
|
667
|
+
|
668
|
+
```python
|
669
|
+
class McpManager:
|
670
|
+
def __init__(self):
|
671
|
+
self.servers = {}
|
672
|
+
|
673
|
+
async def add_server(self, server_id, params):
|
674
|
+
"""Add a new MCP server connection"""
|
675
|
+
if server_id in self.servers:
|
676
|
+
await self.remove_server(server_id)
|
677
|
+
|
678
|
+
read, write = await stdio_client(params)
|
679
|
+
session = ClientSession(read, write)
|
680
|
+
await session.initialize()
|
681
|
+
|
682
|
+
self.servers[server_id] = session
|
683
|
+
return session
|
684
|
+
|
685
|
+
async def remove_server(self, server_id):
|
686
|
+
"""Remove and cleanup an MCP server connection"""
|
687
|
+
if server_id in self.servers:
|
688
|
+
session = self.servers[server_id]
|
689
|
+
await session.shutdown()
|
690
|
+
del self.servers[server_id]
|
691
|
+
```
|
692
|
+
|
693
|
+
### 3. LLM-Powered MCP Development
|
694
|
+
|
695
|
+
You can use LLMs like Claude to help build and debug MCP implementations:
|
696
|
+
|
697
|
+
```python
|
698
|
+
async def get_mcp_help(query):
|
699
|
+
"""Use Claude to help with MCP development questions"""
|
700
|
+
client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY"))
|
701
|
+
|
702
|
+
system_prompt = """
|
703
|
+
You are an expert in Model Context Protocol (MCP) development.
|
704
|
+
Provide specific, technical advice on implementing MCP clients and servers.
|
705
|
+
Include code examples when relevant.
|
706
|
+
"""
|
707
|
+
|
708
|
+
response = client.messages.create(
|
709
|
+
model="claude-3-5-sonnet-20241022",
|
710
|
+
system=system_prompt,
|
711
|
+
messages=[{"role": "user", "content": query}]
|
712
|
+
)
|
713
|
+
|
714
|
+
return response.content[0].text
|
715
|
+
```
|
716
|
+
|
717
|
+
## Troubleshooting Common Issues
|
718
|
+
|
719
|
+
1. **Connection Failures**: Ensure server is running and accessible
|
720
|
+
2. **Protocol Errors**: Check client and server protocol versions are compatible
|
721
|
+
3. **Tool Execution Errors**: Validate parameters before sending to server
|
722
|
+
4. **Authentication Issues**: Verify credentials and token validity
|
723
|
+
5. **Timeout Errors**: Adjust timeout settings for long-running operations
|
724
|
+
|
725
|
+
## Resources
|
726
|
+
|
727
|
+
- [Official MCP Documentation](https://modelcontextprotocol.io/)
|
728
|
+
- [Python SDK Repository](https://github.com/modelcontextprotocol/python-sdk)
|
729
|
+
- [TypeScript SDK Repository](https://github.com/modelcontextprotocol/typescript-sdk)
|
730
|
+
- [MCP Client Examples](https://modelcontextprotocol.io/clients)
|
731
|
+
|
732
|
+
## Conclusion
|
733
|
+
|
734
|
+
Building an MCP client allows your application to connect seamlessly with a wide range of data sources and tools through a standardized protocol. This guide covered the basics of implementing a client, from establishing connections to integrating with LLMs.
|
735
|
+
|
736
|
+
As the MCP ecosystem continues to grow, your client implementation can leverage an expanding catalog of servers without requiring custom integrations for each new data source or tool.
|