MCP Web Tools Server
by surya-madhav
- docs
# Connecting to MCP Servers
This document explains the different methods for connecting to Model Context Protocol (MCP) servers. Whether you're using Claude Desktop, a custom client, or programmatic access, this guide will help you establish and manage connections to MCP servers.
## Overview of MCP Clients
Before diving into implementation details, it's important to understand what an MCP client does:
1. **Discovers** MCP servers (through configuration or discovery mechanisms)
2. **Establishes** connections to servers using appropriate transport methods
3. **Negotiates** capabilities through protocol initialization
4. **Lists** available tools, resources, and prompts
5. **Facilitates** tool execution, resource retrieval, and prompt application
6. **Handles** errors, timeouts, and reconnection
```mermaid
flowchart LR
Client[MCP Client]
Server1[MCP Server 1]
Server2[MCP Server 2]
LLM[LLM]
User[User]
User <--> Client
Client <--> Server1
Client <--> Server2
Client <--> LLM
```
## Client Types
There are several ways to connect to MCP servers:
1. **Integrated Clients**: Built into applications like Claude Desktop
2. **Standalone Clients**: Dedicated applications for MCP interaction (like our Streamlit UI)
3. **SDK Clients**: Using MCP SDKs for programmatic access
4. **Development Tools**: Tools like MCP Inspector for testing and development
## Using Claude Desktop
[Claude Desktop](https://claude.ai/download) is an integrated client that can connect to MCP servers through configuration.
### Configuration Setup
To configure Claude Desktop to use MCP servers:
1. Locate the configuration file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
2. Create or edit the file to include your MCP servers:
```json
{
"mcpServers": {
"web-tools": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
},
"database-tools": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "postgres://user:pass@localhost/db"]
}
}
}
```
Each server configuration includes:
- A unique name (e.g., "web-tools")
- The command to run the server
- Arguments to pass to the command
- Optional environment variables
### Starting Servers
After configuring Claude Desktop:
1. Restart the application
2. Claude will automatically start configured servers
3. You'll see the MCP tools icon in the interface
4. You can now use the servers in conversations
### Using MCP Features in Claude
With MCP servers configured, you can:
1. **Use tools**: Ask Claude to perform actions using server tools
2. **Access resources**: Request information from resources
3. **Apply prompts**: Use the prompts menu for standardized interactions
## Using the Streamlit UI
The Streamlit UI included in this repository provides a graphical interface for interacting with MCP servers.
### Running the UI
```bash
streamlit run streamlit_app.py
```
This will open a web browser with the UI.
### Connecting to Servers
1. Enter the path to your Claude Desktop config file
2. Click "Load Servers" to see all configured servers
3. Select a server tab and click "Connect"
4. The UI will display tools, resources, and prompts
### Using Tools
1. Select a tool tab
2. Fill in the required parameters
3. Click "Execute" to run the tool
4. View the results in the UI
## Programmatic Access with Python
For programmatic access, you can use the MCP Python SDK.
### Basic Client Example
```python
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def connect_to_server():
# Set up server parameters
server_params = StdioServerParameters(
command="python",
args=["server.py"]
)
# Connect to the server
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List tools
tools_result = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools_result.tools]}")
# Call a tool
result = await session.call_tool("web_scrape", {"url": "example.com"})
print(f"Result: {result.content[0].text if result.content else 'No content'}")
# Run the async function
if __name__ == "__main__":
asyncio.run(connect_to_server())
```
### Tool Execution
To call a tool programmatically:
```python
# Call a tool with parameters
result = await session.call_tool("tool_name", {
"param1": "value1",
"param2": 42
})
# Process the result
if hasattr(result, 'content') and result.content:
for item in result.content:
if hasattr(item, 'text'):
print(item.text)
```
### Resource Access
To access resources programmatically:
```python
# List available resources
resources_result = await session.list_resources()
for resource in resources_result.resources:
print(f"Resource: {resource.name} ({resource.uri})")
# Read a resource
result = await session.read_resource("resource://uri")
content, mime_type = result.contents[0].text, result.contents[0].mimeType
print(f"Content ({mime_type}): {content[:100]}...")
```
### Prompt Usage
To use prompts programmatically:
```python
# List available prompts
prompts_result = await session.list_prompts()
for prompt in prompts_result.prompts:
print(f"Prompt: {prompt.name}")
# Get a prompt
result = await session.get_prompt("prompt_name", {"arg1": "value1"})
for message in result.messages:
print(f"{message.role}: {message.content.text}")
```
## Transport Methods
MCP supports different transport methods for client-server communication.
### STDIO Transport
Standard Input/Output (STDIO) transport is the simplest method:
```python
# STDIO server parameters
server_params = StdioServerParameters(
command="python", # Command to run the server
args=["server.py"], # Arguments
env={"ENV_VAR": "value"} # Optional environment variables
)
# Connect using STDIO
async with stdio_client(server_params) as (read, write):
# Use the connection...
```
STDIO transport:
- Is simple to set up
- Works well for local processes
- Doesn't require network configuration
- Automatically terminates when the process ends
### SSE Transport
Server-Sent Events (SSE) transport is used for web-based connections:
```python
from mcp.client.sse import sse_client
# Connect to an SSE server
async with sse_client("http://localhost:5000") as (read, write):
async with ClientSession(read, write) as session:
# Use the session...
```
SSE transport:
- Supports remote connections
- Works over standard HTTP
- Can be used with web servers
- Supports multiple clients per server
## Connection Lifecycle
Understanding the connection lifecycle is important for robust implementations:
```mermaid
sequenceDiagram
participant Client
participant Server
Client->>Server: initialize request
Server->>Client: initialize response (capabilities)
Client->>Server: initialized notification
Note over Client,Server: Connection Ready
loop Normal Operation
Client->>Server: Requests (list_tools, call_tool, etc.)
Server->>Client: Responses
end
Note over Client,Server: Termination
Client->>Server: exit notification
Client->>Server: Close connection
```
### Initialization
When a connection is established:
1. Client sends `initialize` request with supported capabilities
2. Server responds with its capabilities
3. Client sends `initialized` notification
4. Normal operation begins
### Normal Operation
During normal operation:
1. Client sends requests (e.g., `list_tools`, `call_tool`)
2. Server processes requests and sends responses
3. Server may send notifications (e.g., `resources/list_changed`)
### Termination
When ending a connection:
1. Client sends `exit` notification
2. Client closes the connection
3. Server cleans up resources
## Error Handling
Robust error handling is essential for MCP clients:
```python
try:
result = await session.call_tool("tool_name", params)
except Exception as e:
print(f"Error calling tool: {str(e)}")
# Check for specific error types
if isinstance(e, mcp.McpProtocolError):
print(f"Protocol error: {e.code}")
elif isinstance(e, mcp.McpTimeoutError):
print("Request timed out")
elif isinstance(e, mcp.McpConnectionError):
print("Connection lost")
```
Common error scenarios:
1. **Connection Failures**: Server not found or refused connection
2. **Initialization Errors**: Protocol incompatibility or capability mismatch
3. **Request Errors**: Invalid parameters or tool not found
4. **Execution Errors**: Tool execution failed or timed out
5. **Connection Loss**: Server terminated unexpectedly
## Building Your Own Client
To build a custom MCP client, follow these steps:
### 1. Set Up Transport
Choose a transport method and establish a connection:
```python
import asyncio
from mcp.client.stdio import stdio_client
from mcp import ClientSession
# Set up server parameters
server_params = StdioServerParameters(
command="python",
args=["server.py"]
)
# Establish connection
async with stdio_client(server_params) as (read, write):
# Create session and use it...
```
### 2. Create a Session
The `ClientSession` manages the protocol interaction:
```python
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# Now you can use the session
```
### 3. Implement Feature Discovery
List available features from the server:
```python
# List tools
tools_result = await session.list_tools()
tools = tools_result.tools if hasattr(tools_result, 'tools') else []
# List resources
resources_result = await session.list_resources()
resources = resources_result.resources if hasattr(resources_result, 'resources') else []
# List prompts
prompts_result = await session.list_prompts()
prompts = prompts_result.prompts if hasattr(prompts_result, 'prompts') else []
```
### 4. Implement Tool Execution
Create a function to call tools:
```python
async def call_tool(session, tool_name, tool_args):
try:
result = await session.call_tool(tool_name, arguments=tool_args)
# Format the result
if hasattr(result, 'content') and result.content:
content_text = []
for item in result.content:
if hasattr(item, 'text'):
content_text.append(item.text)
return "\n".join(content_text)
return "Tool executed, but no text content was returned."
except Exception as e:
return f"Error calling tool: {str(e)}"
```
### 5. Implement Resource Access
Create a function to read resources:
```python
async def read_resource(session, resource_uri):
try:
result = await session.read_resource(resource_uri)
# Format the result
content_items = []
for content in result.contents:
if hasattr(content, 'text'):
content_items.append(content.text)
elif hasattr(content, 'blob'):
content_items.append(f"[Binary data: {len(content.blob)} bytes]")
return "\n".join(content_items)
except Exception as e:
return f"Error reading resource: {str(e)}"
```
### 6. Implement User Interface
Create a user interface appropriate for your application:
- Command-line interface
- Web UI (like our Streamlit example)
- GUI application
- Integration with existing tools
## Example: Command-Line Client
Here's a simple command-line client example:
```python
import asyncio
import argparse
import json
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main(args):
server_params = StdioServerParameters(
command=args.command,
args=args.args
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
if args.action == "list-tools":
tools_result = await session.list_tools()
tools = tools_result.tools if hasattr(tools_result, 'tools') else []
print(json.dumps([{
"name": tool.name,
"description": tool.description
} for tool in tools], indent=2))
elif args.action == "call-tool":
tool_args = json.loads(args.params)
result = await session.call_tool(args.tool, arguments=tool_args)
if hasattr(result, 'content') and result.content:
for item in result.content:
if hasattr(item, 'text'):
print(item.text)
elif args.action == "list-resources":
resources_result = await session.list_resources()
resources = resources_result.resources if hasattr(resources_result, 'resources') else []
print(json.dumps([{
"name": resource.name,
"uri": resource.uri
} for resource in resources], indent=2))
elif args.action == "read-resource":
result = await session.read_resource(args.uri)
for content in result.contents:
if hasattr(content, 'text'):
print(content.text)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="MCP Command Line Client")
parser.add_argument("--command", required=True, help="Server command")
parser.add_argument("--args", nargs="*", default=[], help="Server arguments")
subparsers = parser.add_subparsers(dest="action", required=True)
list_tools_parser = subparsers.add_parser("list-tools")
call_tool_parser = subparsers.add_parser("call-tool")
call_tool_parser.add_argument("--tool", required=True, help="Tool name")
call_tool_parser.add_argument("--params", required=True, help="Tool parameters (JSON)")
list_resources_parser = subparsers.add_parser("list-resources")
read_resource_parser = subparsers.add_parser("read-resource")
read_resource_parser.add_argument("--uri", required=True, help="Resource URI")
args = parser.parse_args()
asyncio.run(main(args))
```
## Integration with LLMs
To integrate MCP clients with LLMs like Claude:
1. **Tool Registration**: Register MCP tools with the LLM system
2. **Resource Loading**: Provide a way to load resources into LLM context
3. **Permission Handling**: Implement approval flows for tool execution
4. **Result Processing**: Process and present tool results to the LLM
Example integration with Anthropic Claude:
```python
import anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def process_claude_query(client, query):
# Connect to MCP server
server_params = StdioServerParameters(
command="python",
args=["server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize
await session.initialize()
# Get available tools
tools_result = await session.list_tools()
tools = []
for tool in tools_result.tools:
tools.append({
"name": tool.name,
"description": tool.description,
"input_schema": tool.inputSchema
})
# Initial Claude query
messages = [{"role": "user", "content": query}]
response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
messages=messages,
tools=tools
)
# Process tool calls
for content in response.content:
if content.type == "tool_use":
# Execute the tool
tool_name = content.name
tool_args = content.input
# Call MCP tool
result = await session.call_tool(tool_name, arguments=tool_args)
# Format result
result_text = ""
if hasattr(result, 'content') and result.content:
for item in result.content:
if hasattr(item, 'text'):
result_text += item.text
# Send result back to Claude
messages.append({
"role": "assistant",
"content": [content]
})
messages.append({
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": content.id,
"content": result_text
}
]
})
# Get final response
final_response = client.messages.create(
model="claude-3-opus-20240229",
max_tokens=1000,
messages=messages
)
return final_response.content[0].text
# If no tool calls, return initial response
return response.content[0].text
```
## Troubleshooting Connection Issues
### Common Problems and Solutions
1. **Server Not Found**:
- Check that the command path is correct
- Verify the server file exists
- Check that Python or Node.js is properly installed
2. **Connection Refused**:
- For SSE, verify the port is available
- Check for firewall or network issues
- Ensure the server is running
3. **Protocol Errors**:
- Verify MCP versions are compatible
- Check for syntax errors in tool schemas
- Ensure tools are properly registered
4. **Tool Execution Failures**:
- Validate input parameters
- Check for runtime errors in tool implementation
- Verify external dependencies are available
5. **Node.js Environment Issues**:
- Ensure Node.js is properly installed
- Check for proper paths to node, npm, and npx
- Verify global packages are accessible
### Debugging Techniques
1. **Logging**:
- Enable debug logging in your client
- Check server logs for errors
- Use the MCP Inspector for detailed message logs
2. **Environment Variables**:
- Set `MCP_DEBUG=1` for verbose logging
- Use appropriate environment variables for servers
3. **Manual Testing**:
- Test servers directly with the MCP Inspector
- Try simple tools first to isolate issues
- Verify transport works with echo tools
## Conclusion
Connecting to MCP servers opens up powerful capabilities for extending LLMs with custom tools and data sources. Whether using existing clients like Claude Desktop, building custom integrations, or developing your own applications, the MCP protocol provides a standardized way to enhance LLM interactions.
In the next document, we'll explore the communication protocols used by MCP in more detail.