Google Search MCP Server

# Model Context Protocol (MCP) Client Implementation Guide ## Introduction 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. 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. ## What is an MCP Client? In the MCP architecture, a client is a component that: - Maintains 1:1 connections with MCP servers - Requests capabilities like tools, resources, and prompts from servers - Passes these capabilities to an LLM for use - Handles the execution of tool calls when requested by the LLM - Manages communication protocols (stdio, HTTP/SSE, etc.) ## Prerequisites Before implementing an MCP client, ensure you have: 1. A development environment with your preferred language (Python, TypeScript, etc.) 2. Basic understanding of async programming (as most MCP operations are asynchronous) 3. Access to MCP servers you want to connect to 4. Familiarity with the LLM platform you're integrating with ## MCP Client Implementation Steps ### 1. Choose Your SDK MCP offers official SDKs for various languages: - **Python SDK**: For Python applications - **TypeScript/JavaScript SDK**: For web/Node.js applications - **Swift SDK**: For iOS/macOS applications For this guide, we'll provide examples in both Python and TypeScript. ### 2. Install Dependencies #### Python ```python # Using uv (recommended) uv init mcp-client cd mcp-client uv venv # Activate virtual environment # On Windows: .venv\Scripts\activate # On Unix or MacOS: source .venv/bin/activate uv add mcp ``` #### TypeScript/JavaScript ```bash # Using npm mkdir mcp-client cd mcp-client npm init -y npm install @modelcontextprotocol/client ``` ### 3. Establish Connection with MCP Server MCP clients can connect to servers using different transport methods: #### Stdio Transport (Local Server) This method runs the server as a subprocess and communicates via standard input/output. ##### Python Example ```python import asyncio from mcp import ClientSession from mcp.client.stdio import stdio_client from mcp import StdioServerParameters async def main(): # Define server parameters (command to start server) server_params = StdioServerParameters( command="npx", # Command to run args=["-y", "@modelcontextprotocol/server-filesystem", "/path/to/files"] # Arguments ) # Connect to the server async with stdio_client(server_params) as (read, write): # Create a client session async with ClientSession(read, write) as session: # Initialize the session await session.initialize() # Now you can interact with the server # ... if __name__ == "__main__": asyncio.run(main()) ``` ##### TypeScript Example ```typescript import { createStdioClient, StdioServerParameters } from '@modelcontextprotocol/client'; async function main() { // Define server parameters const serverParams: StdioServerParameters = { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/files'] }; // Create client and session const client = await createStdioClient(serverParams); const session = await client.createSession(); // Initialize session await session.initialize(); // Now you can interact with the server // ... } main().catch(console.error); ``` #### HTTP/SSE Transport (Remote Server) For servers available over HTTP with Server-Sent Events (SSE). ##### Python Example ```python import asyncio from mcp import ClientSession from mcp.client.sse import sse_client from mcp.client.types import SseServerParameters async def main(): # Define server parameters server_params = SseServerParameters( url="https://your-mcp-server.com", # Server URL headers={"Authorization": "Bearer your-token"} # Optional headers ) # Connect to the server async with sse_client(server_params) as (read, write): # Create a client session async with ClientSession(read, write) as session: # Initialize the session await session.initialize() # Now you can interact with the server # ... if __name__ == "__main__": asyncio.run(main()) ``` ##### TypeScript Example ```typescript import { createSseClient, SseServerParameters } from '@modelcontextprotocol/client'; async function main() { // Define server parameters const serverParams: SseServerParameters = { url: 'https://your-mcp-server.com', headers: { 'Authorization': 'Bearer your-token' } }; // Create client and session const client = await createSseClient(serverParams); const session = await client.createSession(); // Initialize session await session.initialize(); // Now you can interact with the server // ... } main().catch(console.error); ``` ### 4. Discover Server Capabilities After establishing a connection, you need to discover what capabilities the server offers: #### Python Example ```python async def discover_capabilities(session): # List available tools tools_response = await session.list_tools() tools = tools_response.tools print(f"Available tools: {[tool.name for tool in tools]}") # List available resources try: resources_response = await session.list_resources() resources = resources_response.resources print(f"Available resources: {[resource.name for resource in resources]}") except Exception as e: # Not all servers implement resources print(f"No resources available: {e}") # List available prompts try: prompts_response = await session.list_prompts() prompts = prompts_response.prompts print(f"Available prompts: {[prompt.name for prompt in prompts]}") except Exception as e: # Not all servers implement prompts print(f"No prompts available: {e}") return tools, resources, prompts ``` #### TypeScript Example ```typescript async function discoverCapabilities(session) { // List available tools const toolsResponse = await session.listTools(); const tools = toolsResponse.tools; console.log(`Available tools: ${tools.map(tool => tool.name)}`); // List available resources try { const resourcesResponse = await session.listResources(); const resources = resourcesResponse.resources; console.log(`Available resources: ${resources.map(resource => resource.name)}`); } catch (e) { // Not all servers implement resources console.log(`No resources available: ${e}`); } // List available prompts try { const promptsResponse = await session.listPrompts(); const prompts = promptsResponse.prompts; console.log(`Available prompts: ${prompts.map(prompt => prompt.name)}`); } catch (e) { // Not all servers implement prompts console.log(`No prompts available: ${e}`); } return { tools, resources, prompts }; } ``` ### 5. Use Server Resources Resources provide context data to the LLM: #### Python Example ```python async def get_resource(session, resource_name, params=None): try: resource_response = await session.get_resource(resource_name, params or {}) return resource_response.value except Exception as e: print(f"Error getting resource {resource_name}: {e}") return None ``` #### TypeScript Example ```typescript async function getResource(session, resourceName, params = {}) { try { const resourceResponse = await session.getResource(resourceName, params); return resourceResponse.value; } catch (e) { console.log(`Error getting resource ${resourceName}: ${e}`); return null; } } ``` ### 6. Invoke Server Tools Tools allow the LLM to perform actions: #### Python Example ```python async def call_tool(session, tool_name, params=None): try: tool_response = await session.call_tool(tool_name, params or {}) return tool_response.result except Exception as e: print(f"Error calling tool {tool_name}: {e}") return None ``` #### TypeScript Example ```typescript async function callTool(session, toolName, params = {}) { try { const toolResponse = await session.callTool(toolName, params); return toolResponse.result; } catch (e) { console.log(`Error calling tool ${toolName}: ${e}`); return null; } } ``` ### 7. Use Server Prompts Prompts provide templated interactions for the LLM: #### Python Example ```python async def get_prompt(session, prompt_name, params=None): try: prompt_response = await session.get_prompt(prompt_name, params or {}) return prompt_response.messages except Exception as e: print(f"Error getting prompt {prompt_name}: {e}") return None ``` #### TypeScript Example ```typescript async function getPrompt(session, promptName, params = {}) { try { const promptResponse = await session.getPrompt(promptName, params); return promptResponse.messages; } catch (e) { console.log(`Error getting prompt ${promptName}: ${e}`); return null; } } ``` ### 8. Integrate with LLM Now you need to connect the MCP capabilities with your LLM: #### Python Example with Anthropic's Claude ```python import os from anthropic import Anthropic async def chat_with_claude(client_session, user_message, tools=None): # Discover available tools if tools is None: tools_response = await client_session.list_tools() tools = tools_response.tools # Convert MCP tools to Claude's tool format claude_tools = [] for tool in tools: claude_tools.append({ "name": tool.name, "description": tool.description, "input_schema": tool.parameters }) # Initialize Anthropic client anthropic = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) # Create a message with tools response = anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=[{"role": "user", "content": user_message}], tools=claude_tools ) # Check if Claude wants to use any tools for content_block in response.content: if content_block.type == "tool_use": tool_name = content_block.name tool_params = content_block.input # Call the tool via MCP tool_result = await call_tool(client_session, tool_name, tool_params) # Send the tool result back to Claude follow_up = anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=[ {"role": "user", "content": user_message}, {"role": "assistant", "content": response.content}, {"role": "tool", "name": tool_name, "content": str(tool_result)} ] ) return follow_up return response ``` ### 9. Complete Client Example Here's a complete example of a simple MCP client implementation in Python: ```python import asyncio import os import json from typing import Optional, List, Dict, Any from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from anthropic import Anthropic class MCPClientApp: def __init__(self): self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() self.anthropic = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) self.tools = [] async def connect_to_server(self, server_script_path): """Connect to an MCP server using stdio transport""" server_params = StdioServerParameters( command="python", args=[server_script_path] ) read, write = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.session = await self.exit_stack.enter_async_context(ClientSession(read, write)) # Initialize session await self.session.initialize() # Discover tools tools_response = await self.session.list_tools() self.tools = tools_response.tools print(f"Connected to MCP server with {len(self.tools)} tools available:") for tool in self.tools: print(f"- {tool.name}: {tool.description}") async def call_tool(self, tool_name, params=None): """Call an MCP tool with parameters""" if not self.session: raise RuntimeError("No active MCP session") try: tool_response = await self.session.call_tool(tool_name, params or {}) return tool_response.result except Exception as e: print(f"Error calling tool {tool_name}: {e}") return None async def chat_loop(self): """Run an interactive chat loop with Claude using MCP tools""" if not self.session: raise RuntimeError("No active MCP session") print("\nChat with Claude (using MCP tools). Type 'exit' to quit.") # Convert MCP tools to Claude's tool format claude_tools = [] for tool in self.tools: claude_tools.append({ "name": tool.name, "description": tool.description, "input_schema": tool.parameters }) # Keep track of conversation messages = [] while True: # Get user input user_input = input("\nYou: ") if user_input.lower() == 'exit': break # Add to conversation history messages.append({"role": "user", "content": user_input}) # Send to Claude with tools response = self.anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=messages, tools=claude_tools ) # Process response assistant_message = {"role": "assistant", "content": []} for content_block in response.content: if content_block.type == "text": print(f"\nClaude: {content_block.text}") assistant_message["content"].append(content_block) elif content_block.type == "tool_use": tool_name = content_block.name tool_params = content_block.input print(f"\nClaude is using tool: {tool_name}") assistant_message["content"].append(content_block) # Call the tool via MCP tool_result = await self.call_tool(tool_name, tool_params) print(f"Tool result: {tool_result}") # Add tool response to conversation messages.append(assistant_message) messages.append({ "role": "tool", "name": tool_name, "content": json.dumps(tool_result) }) # Get Claude's follow-up response with the tool result follow_up = self.anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=1000, messages=messages, tools=claude_tools ) # Print Claude's response after using the tool for fb_content in follow_up.content: if fb_content.type == "text": print(f"\nClaude: {fb_content.text}") # Update messages for next turn assistant_message = {"role": "assistant", "content": follow_up.content} # Add assistant's response to conversation history messages.append(assistant_message) async def cleanup(self): """Clean up resources""" await self.exit_stack.aclose() async def main(): if len(sys.argv) < 2: print("Usage: python client.py <path_to_server_script>") sys.exit(1) client = MCPClientApp() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup() if __name__ == "__main__": import sys asyncio.run(main()) ``` ## Best Practices for MCP Clients 1. **Error Handling**: Implement robust error handling for all MCP operations 2. **Authentication**: Support secure authentication methods for remote servers 3. **Rate Limiting**: Implement rate limiting to avoid overwhelming servers 4. **Caching**: Cache server capabilities to reduce latency 5. **Timeouts**: Set appropriate timeouts for server operations 6. **Logging**: Implement comprehensive logging for debugging 7. **Reconnection Logic**: Handle disconnections gracefully with reconnection attempts 8. **Security Checks**: Validate server responses before processing 9. **Platform Compatibility**: - Handle Windows/Unix line ending differences - Use binary mode for stdio on Windows - Implement proper path handling - Consider cross-platform transport options 10. **Resource Management**: - Clean up resources properly - Monitor memory usage - Handle connection pooling - Implement proper shutdown procedures 11. **Performance Optimization**: - Batch requests when possible - Implement response caching - Use connection pooling - Monitor and optimize memory usage ## Platform-Specific Considerations ### Windows Implementation 1. **stdio Handling**: ```python # Python if sys.platform == 'win32': import msvcrt msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) ``` ```typescript // TypeScript if (process.platform === 'win32') { process.stdin.setEncoding('binary'); process.stdout.setDefaultEncoding('binary'); } ``` 2. **Path Management**: ```typescript import { join } from 'path'; const configPath = join(process.cwd(), 'config.json'); ``` 3. **Error Handling**: ```typescript process.on('uncaughtException', (error) => { console.error(`Uncaught Exception: ${error.message}`); process.exit(1); }); ``` ### Unix Implementation 1. **Signal Handling**: ```typescript process.on('SIGTERM', () => { console.error('Received SIGTERM, cleaning up...'); // Perform cleanup process.exit(0); }); ``` 2. **File Permissions**: ```python import os os.chmod('client.sh', 0o755) ``` ## Advanced Features ### 1. Sampling Sampling is a powerful MCP feature where servers can request LLM completions from the client, reversing the usual flow: ```python async def handle_sampling(session): # Set up a sampling handler def sampling_handler(request, extra): # Process sampling request prompt = request.get("prompt", "") # Generate completion with your LLM # This is a simplified example completion = generate_completion(prompt) # Return completion return {"completion": completion} # Register the sampling handler await session.set_sampling_handler(sampling_handler) ``` ### 2. Multi-Server Management For applications that need to connect to multiple MCP servers: ```python class McpManager: def __init__(self): self.servers = {} async def add_server(self, server_id, params): """Add a new MCP server connection""" if server_id in self.servers: await self.remove_server(server_id) read, write = await stdio_client(params) session = ClientSession(read, write) await session.initialize() self.servers[server_id] = session return session async def remove_server(self, server_id): """Remove and cleanup an MCP server connection""" if server_id in self.servers: session = self.servers[server_id] await session.shutdown() del self.servers[server_id] ``` ### 3. LLM-Powered MCP Development You can use LLMs like Claude to help build and debug MCP implementations: ```python async def get_mcp_help(query): """Use Claude to help with MCP development questions""" client = Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) system_prompt = """ You are an expert in Model Context Protocol (MCP) development. Provide specific, technical advice on implementing MCP clients and servers. Include code examples when relevant. """ response = client.messages.create( model="claude-3-5-sonnet-20241022", system=system_prompt, messages=[{"role": "user", "content": query}] ) return response.content[0].text ``` ## Troubleshooting Common Issues 1. **Connection Failures**: Ensure server is running and accessible 2. **Protocol Errors**: Check client and server protocol versions are compatible 3. **Tool Execution Errors**: Validate parameters before sending to server 4. **Authentication Issues**: Verify credentials and token validity 5. **Timeout Errors**: Adjust timeout settings for long-running operations ## Resources - [Official MCP Documentation](https://modelcontextprotocol.io/) - [Python SDK Repository](https://github.com/modelcontextprotocol/python-sdk) - [TypeScript SDK Repository](https://github.com/modelcontextprotocol/typescript-sdk) - [MCP Client Examples](https://modelcontextprotocol.io/clients) ## Conclusion 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. 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.
ID: 8m8reyzrcd