Merge pull request #63 from modelcontextprotocol/zack-lee/quickstart-updates
Phase 1 of documentation revamp: new Quickstart, servers, etc
|
|
@ -1,828 +0,0 @@
|
|||
---
|
||||
title: "Python"
|
||||
description: "Create a simple MCP server in Python in 15 minutes"
|
||||
---
|
||||
|
||||
Let's build your first MCP server in Python! We'll create a weather server that provides current weather data as a resource and lets Claude fetch forecasts using tools.
|
||||
|
||||
<Note>
|
||||
This guide uses the OpenWeatherMap API. You'll need a free API key from [OpenWeatherMap](https://openweathermap.org/api) to follow along.
|
||||
</Note>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<Info>
|
||||
The following steps are for macOS. Guides for other platforms are coming soon.
|
||||
</Info>
|
||||
|
||||
<Steps>
|
||||
<Step title="Install Python">
|
||||
You'll need Python 3.10 or higher:
|
||||
|
||||
```bash
|
||||
python --version # Should be 3.10 or higher
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Install uv via homebrew">
|
||||
See https://docs.astral.sh/uv/ for more information.
|
||||
|
||||
```bash
|
||||
brew install uv
|
||||
uv --version # Should be 0.4.18 or higher
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create a new project using the MCP project creator">
|
||||
```bash
|
||||
uvx create-mcp-server --path weather_service
|
||||
cd weather_service
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Install additional dependencies">
|
||||
```bash
|
||||
uv add httpx python-dotenv
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Set up environment">
|
||||
Create `.env`:
|
||||
|
||||
```bash
|
||||
OPENWEATHER_API_KEY=your-api-key-here
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Create your server
|
||||
|
||||
<Steps>
|
||||
<Step title="Add the base imports and setup">
|
||||
In `weather_service/src/weather_service/server.py`
|
||||
```python
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from collections.abc import Sequence
|
||||
from functools import lru_cache
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
import asyncio
|
||||
from dotenv import load_dotenv
|
||||
from mcp.server import Server
|
||||
from mcp.types import (
|
||||
Resource,
|
||||
Tool,
|
||||
TextContent,
|
||||
ImageContent,
|
||||
EmbeddedResource,
|
||||
LoggingLevel
|
||||
)
|
||||
from pydantic import AnyUrl
|
||||
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("weather-server")
|
||||
|
||||
# API configuration
|
||||
API_KEY = os.getenv("OPENWEATHER_API_KEY")
|
||||
if not API_KEY:
|
||||
raise ValueError("OPENWEATHER_API_KEY environment variable required")
|
||||
|
||||
API_BASE_URL = "http://api.openweathermap.org/data/2.5"
|
||||
DEFAULT_CITY = "London"
|
||||
CURRENT_WEATHER_ENDPOINT = "weather"
|
||||
FORECAST_ENDPOINT = "forecast"
|
||||
|
||||
# The rest of our server implementation will go here
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add weather fetching functionality">
|
||||
Add this functionality:
|
||||
|
||||
```python
|
||||
# Create reusable params
|
||||
http_params = {
|
||||
"appid": API_KEY,
|
||||
"units": "metric"
|
||||
}
|
||||
|
||||
async def fetch_weather(city: str) -> dict[str, Any]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"{API_BASE_URL}/weather",
|
||||
params={"q": city, **http_params}
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
return {
|
||||
"temperature": data["main"]["temp"],
|
||||
"conditions": data["weather"][0]["description"],
|
||||
"humidity": data["main"]["humidity"],
|
||||
"wind_speed": data["wind"]["speed"],
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
|
||||
app = Server("weather-server")
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Implement resource handlers">
|
||||
Add these resource-related handlers to our main function:
|
||||
|
||||
```python
|
||||
app = Server("weather-server")
|
||||
|
||||
@app.list_resources()
|
||||
async def list_resources() -> list[Resource]:
|
||||
"""List available weather resources."""
|
||||
uri = AnyUrl(f"weather://{DEFAULT_CITY}/current")
|
||||
return [
|
||||
Resource(
|
||||
uri=uri,
|
||||
name=f"Current weather in {DEFAULT_CITY}",
|
||||
mimeType="application/json",
|
||||
description="Real-time weather data"
|
||||
)
|
||||
]
|
||||
|
||||
@app.read_resource()
|
||||
async def read_resource(uri: AnyUrl) -> str:
|
||||
"""Read current weather data for a city."""
|
||||
city = DEFAULT_CITY
|
||||
if str(uri).startswith("weather://") and str(uri).endswith("/current"):
|
||||
city = str(uri).split("/")[-2]
|
||||
else:
|
||||
raise ValueError(f"Unknown resource: {uri}")
|
||||
|
||||
try:
|
||||
weather_data = await fetch_weather(city)
|
||||
return json.dumps(weather_data, indent=2)
|
||||
except httpx.HTTPError as e:
|
||||
raise RuntimeError(f"Weather API error: {str(e)}")
|
||||
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Implement tool handlers">
|
||||
Add these tool-related handlers:
|
||||
|
||||
```python
|
||||
app = Server("weather-server")
|
||||
|
||||
# Resource implementation ...
|
||||
|
||||
@app.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
"""List available weather tools."""
|
||||
return [
|
||||
Tool(
|
||||
name="get_forecast",
|
||||
description="Get weather forecast for a city",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "City name"
|
||||
},
|
||||
"days": {
|
||||
"type": "number",
|
||||
"description": "Number of days (1-5)",
|
||||
"minimum": 1,
|
||||
"maximum": 5
|
||||
}
|
||||
},
|
||||
"required": ["city"]
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
@app.call_tool()
|
||||
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
|
||||
"""Handle tool calls for weather forecasts."""
|
||||
if name != "get_forecast":
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
if not isinstance(arguments, dict) or "city" not in arguments:
|
||||
raise ValueError("Invalid forecast arguments")
|
||||
|
||||
city = arguments["city"]
|
||||
days = min(int(arguments.get("days", 3)), 5)
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"{API_BASE_URL}/{FORECAST_ENDPOINT}",
|
||||
params={
|
||||
"q": city,
|
||||
"cnt": days * 8, # API returns 3-hour intervals
|
||||
**http_params,
|
||||
}
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
forecasts = []
|
||||
for i in range(0, len(data["list"]), 8):
|
||||
day_data = data["list"][i]
|
||||
forecasts.append({
|
||||
"date": day_data["dt_txt"].split()[0],
|
||||
"temperature": day_data["main"]["temp"],
|
||||
"conditions": day_data["weather"][0]["description"]
|
||||
})
|
||||
|
||||
return [
|
||||
TextContent(
|
||||
type="text",
|
||||
text=json.dumps(forecasts, indent=2)
|
||||
)
|
||||
]
|
||||
except httpx.HTTPError as e:
|
||||
logger.error(f"Weather API error: {str(e)}")
|
||||
raise RuntimeError(f"Weather API error: {str(e)}")
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add the main function">
|
||||
Add this to the end of `weather_service/src/weather_service/server.py`:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
# Import here to avoid issues with event loops
|
||||
from mcp.server.stdio import stdio_server
|
||||
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await app.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
app.create_initialization_options()
|
||||
)
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Check your entry point in __init__.py">
|
||||
Add this to the end of `weather_service/src/weather_service/__init__.py`:
|
||||
|
||||
```python
|
||||
from . import server
|
||||
import asyncio
|
||||
|
||||
def main():
|
||||
"""Main entry point for the package."""
|
||||
asyncio.run(server.main())
|
||||
|
||||
# Optionally expose other important items at package level
|
||||
__all__ = ['main', 'server']
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
|
||||
|
||||
|
||||
## Connect to Claude Desktop
|
||||
|
||||
<Steps>
|
||||
<Step title="Update Claude config">
|
||||
Add to `claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "uv",
|
||||
"args": [
|
||||
"--directory",
|
||||
"path/to/your/project",
|
||||
"run",
|
||||
"weather-service"
|
||||
],
|
||||
"env": {
|
||||
"OPENWEATHER_API_KEY": "your-api-key"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Restart Claude">
|
||||
1. Quit Claude completely
|
||||
|
||||
2. Start Claude again
|
||||
|
||||
3. Look for your weather server in the 🔌 menu
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Try it out!
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Check Current Weather" active>
|
||||
Ask Claude:
|
||||
|
||||
```
|
||||
What's the current weather in San Francisco? Can you analyze the conditions and tell me if it's a good day for outdoor activities?
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Get a Forecast">
|
||||
Ask Claude:
|
||||
|
||||
```
|
||||
Can you get me a 5-day forecast for Tokyo and help me plan what clothes to pack for my trip?
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Compare Weather">
|
||||
Ask Claude:
|
||||
|
||||
```
|
||||
Can you analyze the forecast for both Tokyo and San Francisco and tell me which city would be better for outdoor photography this week?
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Understanding the code
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Type Hints">
|
||||
```python
|
||||
async def read_resource(self, uri: str) -> ReadResourceResult:
|
||||
# ...
|
||||
```
|
||||
|
||||
Python type hints help catch errors early and improve code maintainability.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Resources">
|
||||
```python
|
||||
@app.list_resources()
|
||||
async def list_resources(self) -> ListResourcesResult:
|
||||
return ListResourcesResult(
|
||||
resources=[
|
||||
Resource(
|
||||
uri=f"weather://{DEFAULT_CITY}/current",
|
||||
name=f"Current weather in {DEFAULT_CITY}",
|
||||
mimeType="application/json",
|
||||
description="Real-time weather data"
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Resources provide data that Claude can access as context.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Tools">
|
||||
```python
|
||||
Tool(
|
||||
name="get_forecast",
|
||||
description="Get weather forecast for a city",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": {
|
||||
"type": "string",
|
||||
"description": "City name"
|
||||
},
|
||||
"days": {
|
||||
"type": "number",
|
||||
"description": "Number of days (1-5)",
|
||||
"minimum": 1,
|
||||
"maximum": 5
|
||||
}
|
||||
},
|
||||
"required": ["city"]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Tools let Claude take actions through your server with validated inputs.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Server Structure">
|
||||
```python
|
||||
# Create server instance with name
|
||||
app = Server("weather-server")
|
||||
|
||||
# Register resource handler
|
||||
@app.list_resources()
|
||||
async def list_resources() -> list[Resource]:
|
||||
"""List available resources"""
|
||||
return [...]
|
||||
|
||||
# Register tool handler
|
||||
@app.call_tool()
|
||||
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
|
||||
"""Handle tool execution"""
|
||||
return [...]
|
||||
|
||||
# Register additional handlers
|
||||
@app.read_resource()
|
||||
...
|
||||
@app.list_tools()
|
||||
...
|
||||
```
|
||||
|
||||
The MCP server uses a simple app pattern - create a Server instance and register handlers with decorators. Each handler maps to a specific MCP protocol operation.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Best practices
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card title="Error Handling" icon="shield">
|
||||
```python
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(..., params={..., **http_params})
|
||||
response.raise_for_status()
|
||||
except httpx.HTTPError as e:
|
||||
raise McpError(
|
||||
ErrorCode.INTERNAL_ERROR,
|
||||
f"API error: {str(e)}"
|
||||
)
|
||||
```
|
||||
</Card>
|
||||
|
||||
<Card title="Type Validation" icon="check">
|
||||
```python
|
||||
if not isinstance(args, dict) or "city" not in args:
|
||||
raise McpError(
|
||||
ErrorCode.INVALID_PARAMS,
|
||||
"Invalid forecast arguments"
|
||||
)
|
||||
```
|
||||
</Card>
|
||||
|
||||
<Card title="Environment Variables" icon="gear">
|
||||
```python
|
||||
if not API_KEY:
|
||||
raise ValueError("OPENWEATHER_API_KEY is required")
|
||||
```
|
||||
</Card>
|
||||
|
||||
</CardGroup>
|
||||
|
||||
## Available transports
|
||||
|
||||
While this guide uses stdio transport, MCP supports additional transport options:
|
||||
|
||||
### SSE (Server-Sent Events)
|
||||
|
||||
```python
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
|
||||
# Create SSE transport with endpoint
|
||||
sse = SseServerTransport("/messages")
|
||||
|
||||
# Handler for SSE connections
|
||||
async def handle_sse(scope, receive, send):
|
||||
async with sse.connect_sse(scope, receive, send) as streams:
|
||||
await app.run(
|
||||
streams[0], streams[1], app.create_initialization_options()
|
||||
)
|
||||
|
||||
# Handler for client messages
|
||||
async def handle_messages(scope, receive, send):
|
||||
await sse.handle_post_message(scope, receive, send)
|
||||
|
||||
# Create Starlette app with routes
|
||||
app = Starlette(
|
||||
debug=True,
|
||||
routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/messages", endpoint=handle_messages, methods=["POST"]),
|
||||
],
|
||||
)
|
||||
|
||||
# Run with any ASGI server
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
```
|
||||
|
||||
## Advanced features
|
||||
|
||||
<Steps>
|
||||
<Step title="Understanding Request Context">
|
||||
The request context provides access to the current request's metadata and the active client session. Access it through `server.request_context`:
|
||||
|
||||
```python
|
||||
@app.call_tool()
|
||||
async def call_tool(name: str, arguments: Any) -> Sequence[TextContent]:
|
||||
# Access the current request context
|
||||
ctx = self.request_context
|
||||
|
||||
# Get request metadata like progress tokens
|
||||
if progress_token := ctx.meta.progressToken:
|
||||
# Send progress notifications via the session
|
||||
await ctx.session.send_progress_notification(
|
||||
progress_token=progress_token,
|
||||
progress=0.5,
|
||||
total=1.0
|
||||
)
|
||||
|
||||
# Sample from the LLM client
|
||||
result = await ctx.session.create_message(
|
||||
messages=[
|
||||
SamplingMessage(
|
||||
role="user",
|
||||
content=TextContent(
|
||||
type="text",
|
||||
text="Analyze this weather data: " + json.dumps(arguments)
|
||||
)
|
||||
)
|
||||
],
|
||||
max_tokens=100
|
||||
)
|
||||
|
||||
return [TextContent(type="text", text=result.content.text)]
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add caching">
|
||||
```python
|
||||
# Cache settings
|
||||
cache_timeout = timedelta(minutes=15)
|
||||
last_cache_time = None
|
||||
cached_weather = None
|
||||
|
||||
async def fetch_weather(city: str) -> dict[str, Any]:
|
||||
global cached_weather, last_cache_time
|
||||
|
||||
now = datetime.now()
|
||||
if (cached_weather is None or
|
||||
last_cache_time is None or
|
||||
now - last_cache_time > cache_timeout):
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.get(
|
||||
f"{API_BASE_URL}/{CURRENT_WEATHER_ENDPOINT}",
|
||||
params={"q": city, **http_params}
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
cached_weather = {
|
||||
"temperature": data["main"]["temp"],
|
||||
"conditions": data["weather"][0]["description"],
|
||||
"humidity": data["main"]["humidity"],
|
||||
"wind_speed": data["wind"]["speed"],
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
last_cache_time = now
|
||||
|
||||
return cached_weather
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add progress notifications">
|
||||
```python
|
||||
@self.call_tool()
|
||||
async def call_tool(self, name: str, arguments: Any) -> CallToolResult:
|
||||
if progress_token := self.request_context.meta.progressToken:
|
||||
# Send progress notifications
|
||||
await self.request_context.session.send_progress_notification(
|
||||
progress_token=progress_token,
|
||||
progress=1,
|
||||
total=2
|
||||
)
|
||||
|
||||
# Fetch data...
|
||||
|
||||
await self.request_context.session.send_progress_notification(
|
||||
progress_token=progress_token,
|
||||
progress=2,
|
||||
total=2
|
||||
)
|
||||
|
||||
# Rest of the method implementation...
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add logging support">
|
||||
```python
|
||||
# Set up logging
|
||||
logger = logging.getLogger("weather-server")
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@app.set_logging_level()
|
||||
async def set_logging_level(level: LoggingLevel) -> EmptyResult:
|
||||
logger.setLevel(level.upper())
|
||||
await app.request_context.session.send_log_message(
|
||||
level="info",
|
||||
data=f"Log level set to {level}",
|
||||
logger="weather-server"
|
||||
)
|
||||
return EmptyResult()
|
||||
|
||||
# Use logger throughout the code
|
||||
# For example:
|
||||
# logger.info("Weather data fetched successfully")
|
||||
# logger.error(f"Error fetching weather data: {str(e)}")
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add resource templates">
|
||||
```python
|
||||
@app.list_resource_templates()
|
||||
async def list_resource_templates() -> list[ResourceTemplate]:
|
||||
return [
|
||||
ResourceTemplate(
|
||||
uriTemplate="weather://{city}/current",
|
||||
name="Current weather for any city",
|
||||
mimeType="application/json"
|
||||
)
|
||||
]
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Testing
|
||||
|
||||
<Steps>
|
||||
<Step title="Create test file">
|
||||
Create `tests/weather_test.py`:
|
||||
|
||||
```python
|
||||
import pytest
|
||||
import os
|
||||
from unittest.mock import patch, Mock
|
||||
from datetime import datetime
|
||||
import json
|
||||
from pydantic import AnyUrl
|
||||
os.environ["OPENWEATHER_API_KEY"] = "TEST"
|
||||
|
||||
from weather_service.server import (
|
||||
fetch_weather,
|
||||
read_resource,
|
||||
call_tool,
|
||||
list_resources,
|
||||
list_tools,
|
||||
DEFAULT_CITY
|
||||
)
|
||||
|
||||
@pytest.fixture
|
||||
def anyio_backend():
|
||||
return "asyncio"
|
||||
|
||||
@pytest.fixture
|
||||
def mock_weather_response():
|
||||
return {
|
||||
"main": {
|
||||
"temp": 20.5,
|
||||
"humidity": 65
|
||||
},
|
||||
"weather": [
|
||||
{"description": "scattered clouds"}
|
||||
],
|
||||
"wind": {
|
||||
"speed": 3.6
|
||||
}
|
||||
}
|
||||
|
||||
@pytest.fixture
|
||||
def mock_forecast_response():
|
||||
return {
|
||||
"list": [
|
||||
{
|
||||
"dt_txt": "2024-01-01 12:00:00",
|
||||
"main": {"temp": 18.5},
|
||||
"weather": [{"description": "sunny"}]
|
||||
},
|
||||
{
|
||||
"dt_txt": "2024-01-02 12:00:00",
|
||||
"main": {"temp": 17.2},
|
||||
"weather": [{"description": "cloudy"}]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_fetch_weather(mock_weather_response):
|
||||
with patch('requests.Session.get') as mock_get:
|
||||
mock_get.return_value.json.return_value = mock_weather_response
|
||||
mock_get.return_value.raise_for_status = Mock()
|
||||
|
||||
weather = await fetch_weather("London")
|
||||
|
||||
assert weather["temperature"] == 20.5
|
||||
assert weather["conditions"] == "scattered clouds"
|
||||
assert weather["humidity"] == 65
|
||||
assert weather["wind_speed"] == 3.6
|
||||
assert "timestamp" in weather
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_read_resource():
|
||||
with patch('weather_service.server.fetch_weather') as mock_fetch:
|
||||
mock_fetch.return_value = {
|
||||
"temperature": 20.5,
|
||||
"conditions": "clear sky",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
uri = AnyUrl("weather://London/current")
|
||||
result = await read_resource(uri)
|
||||
|
||||
assert isinstance(result, str)
|
||||
assert "temperature" in result
|
||||
assert "clear sky" in result
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_call_tool(mock_forecast_response):
|
||||
class Response():
|
||||
def raise_for_status(self):
|
||||
pass
|
||||
|
||||
def json(self):
|
||||
return mock_forecast_response
|
||||
|
||||
class AsyncClient():
|
||||
async def __aenter__(self):
|
||||
return self
|
||||
|
||||
async def __aexit__(self, *exc_info):
|
||||
pass
|
||||
|
||||
async def get(self, *args, **kwargs):
|
||||
return Response()
|
||||
|
||||
with patch('httpx.AsyncClient', new=AsyncClient) as mock_client:
|
||||
result = await call_tool("get_forecast", {"city": "London", "days": 2})
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0].type == "text"
|
||||
forecast_data = json.loads(result[0].text)
|
||||
assert len(forecast_data) == 1
|
||||
assert forecast_data[0]["temperature"] == 18.5
|
||||
assert forecast_data[0]["conditions"] == "sunny"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_list_resources():
|
||||
resources = await list_resources()
|
||||
assert len(resources) == 1
|
||||
assert resources[0].name == f"Current weather in {DEFAULT_CITY}"
|
||||
assert resources[0].mimeType == "application/json"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_list_tools():
|
||||
tools = await list_tools()
|
||||
assert len(tools) == 1
|
||||
assert tools[0].name == "get_forecast"
|
||||
assert "city" in tools[0].inputSchema["properties"]
|
||||
```
|
||||
</Step>
|
||||
<Step title="Run tests">
|
||||
```bash
|
||||
uv add --dev pytest
|
||||
uv run pytest
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Installation issues
|
||||
|
||||
```bash
|
||||
# Check Python version
|
||||
python --version
|
||||
|
||||
# Reinstall dependencies
|
||||
uv sync --reinstall
|
||||
```
|
||||
|
||||
### Type checking
|
||||
|
||||
```bash
|
||||
# Install mypy
|
||||
uv add --dev pyright
|
||||
|
||||
# Run type checker
|
||||
uv run pyright src
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Architecture overview" icon="sitemap" href="/docs/concepts/architecture">
|
||||
Learn more about the MCP architecture
|
||||
</Card>
|
||||
|
||||
<Card title="Python SDK" icon="python" href="https://github.com/modelcontextprotocol/python-sdk">
|
||||
Check out the Python SDK on GitHub
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
@ -1,599 +0,0 @@
|
|||
---
|
||||
title: "TypeScript"
|
||||
description: "Create a simple MCP server in TypeScript in 15 minutes"
|
||||
---
|
||||
|
||||
Let's build your first MCP server in TypeScript! We'll create a weather server that provides current weather data as a resource and lets Claude fetch forecasts using tools.
|
||||
|
||||
<Note>
|
||||
This guide uses the OpenWeatherMap API. You'll need a free API key from [OpenWeatherMap](https://openweathermap.org/api) to follow along.
|
||||
</Note>
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<Steps>
|
||||
<Step title="Install Node.js">
|
||||
You'll need Node.js 18 or higher:
|
||||
|
||||
```bash
|
||||
node --version # Should be v18 or higher
|
||||
npm --version
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Create a new project">
|
||||
You can use our [create-typescript-server](https://github.com/modelcontextprotocol/create-typescript-server) tool to bootstrap a new project:
|
||||
|
||||
```bash
|
||||
npx @modelcontextprotocol/create-server weather-server
|
||||
cd weather-server
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Install dependencies">
|
||||
```bash
|
||||
npm install --save axios dotenv
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Set up environment">
|
||||
Create `.env`:
|
||||
```bash
|
||||
OPENWEATHER_API_KEY=your-api-key-here
|
||||
```
|
||||
Make sure to add your environment file to `.gitignore`
|
||||
```bash
|
||||
.env
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Create your server
|
||||
|
||||
<Steps>
|
||||
<Step title="Define types">
|
||||
Create a file `src/types.ts`, and add the following:
|
||||
|
||||
```typescript
|
||||
export interface OpenWeatherResponse {
|
||||
main: {
|
||||
temp: number;
|
||||
humidity: number;
|
||||
};
|
||||
weather: Array<{
|
||||
description: string;
|
||||
}>;
|
||||
wind: {
|
||||
speed: number;
|
||||
};
|
||||
dt_txt?: string;
|
||||
}
|
||||
|
||||
export interface WeatherData {
|
||||
temperature: number;
|
||||
conditions: string;
|
||||
humidity: number;
|
||||
wind_speed: number;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface ForecastDay {
|
||||
date: string;
|
||||
temperature: number;
|
||||
conditions: string;
|
||||
}
|
||||
|
||||
export interface GetForecastArgs {
|
||||
city: string;
|
||||
days?: number;
|
||||
}
|
||||
|
||||
// Type guard for forecast arguments
|
||||
export function isValidForecastArgs(args: any): args is GetForecastArgs {
|
||||
return (
|
||||
typeof args === "object" &&
|
||||
args !== null &&
|
||||
"city" in args &&
|
||||
typeof args.city === "string" &&
|
||||
(args.days === undefined || typeof args.days === "number")
|
||||
);
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add the base code">
|
||||
Replace `src/index.ts` with the following:
|
||||
|
||||
```typescript
|
||||
#!/usr/bin/env node
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
ListResourcesRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
CallToolRequestSchema,
|
||||
ErrorCode,
|
||||
McpError
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import axios from "axios";
|
||||
import dotenv from "dotenv";
|
||||
import {
|
||||
WeatherData,
|
||||
ForecastDay,
|
||||
OpenWeatherResponse,
|
||||
isValidForecastArgs
|
||||
} from "./types.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const API_KEY = process.env.OPENWEATHER_API_KEY;
|
||||
if (!API_KEY) {
|
||||
throw new Error("OPENWEATHER_API_KEY environment variable is required");
|
||||
}
|
||||
|
||||
const API_CONFIG = {
|
||||
BASE_URL: 'http://api.openweathermap.org/data/2.5',
|
||||
DEFAULT_CITY: 'San Francisco',
|
||||
ENDPOINTS: {
|
||||
CURRENT: 'weather',
|
||||
FORECAST: 'forecast'
|
||||
}
|
||||
} as const;
|
||||
|
||||
class WeatherServer {
|
||||
private server: Server;
|
||||
private axiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.server = new Server({
|
||||
name: "example-weather-server",
|
||||
version: "0.1.0"
|
||||
}, {
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {}
|
||||
}
|
||||
});
|
||||
|
||||
// Configure axios with defaults
|
||||
this.axiosInstance = axios.create({
|
||||
baseURL: API_CONFIG.BASE_URL,
|
||||
params: {
|
||||
appid: API_KEY,
|
||||
units: "metric"
|
||||
}
|
||||
});
|
||||
|
||||
this.setupHandlers();
|
||||
this.setupErrorHandling();
|
||||
}
|
||||
|
||||
private setupErrorHandling(): void {
|
||||
this.server.onerror = (error) => {
|
||||
console.error("[MCP Error]", error);
|
||||
};
|
||||
|
||||
process.on('SIGINT', async () => {
|
||||
await this.server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
private setupHandlers(): void {
|
||||
this.setupResourceHandlers();
|
||||
this.setupToolHandlers();
|
||||
}
|
||||
|
||||
private setupResourceHandlers(): void {
|
||||
// Implementation continues in next section
|
||||
}
|
||||
|
||||
private setupToolHandlers(): void {
|
||||
// Implementation continues in next section
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
|
||||
// Although this is just an informative message, we must log to stderr,
|
||||
// to avoid interfering with MCP communication that happens on stdout
|
||||
console.error("Weather MCP server running on stdio");
|
||||
}
|
||||
}
|
||||
|
||||
const server = new WeatherServer();
|
||||
server.run().catch(console.error);
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add resource handlers">
|
||||
Add this to the `setupResourceHandlers` method:
|
||||
|
||||
```typescript
|
||||
private setupResourceHandlers(): void {
|
||||
this.server.setRequestHandler(
|
||||
ListResourcesRequestSchema,
|
||||
async () => ({
|
||||
resources: [{
|
||||
uri: `weather://${API_CONFIG.DEFAULT_CITY}/current`,
|
||||
name: `Current weather in ${API_CONFIG.DEFAULT_CITY}`,
|
||||
mimeType: "application/json",
|
||||
description: "Real-time weather data including temperature, conditions, humidity, and wind speed"
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
this.server.setRequestHandler(
|
||||
ReadResourceRequestSchema,
|
||||
async (request) => {
|
||||
const city = API_CONFIG.DEFAULT_CITY;
|
||||
if (request.params.uri !== `weather://${city}/current`) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidRequest,
|
||||
`Unknown resource: ${request.params.uri}`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await this.axiosInstance.get<OpenWeatherResponse>(
|
||||
API_CONFIG.ENDPOINTS.CURRENT,
|
||||
{
|
||||
params: { q: city }
|
||||
}
|
||||
);
|
||||
|
||||
const weatherData: WeatherData = {
|
||||
temperature: response.data.main.temp,
|
||||
conditions: response.data.weather[0].description,
|
||||
humidity: response.data.main.humidity,
|
||||
wind_speed: response.data.wind.speed,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
return {
|
||||
contents: [{
|
||||
uri: request.params.uri,
|
||||
mimeType: "application/json",
|
||||
text: JSON.stringify(weatherData, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InternalError,
|
||||
`Weather API error: ${error.response?.data.message ?? error.message}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Add tool handlers">
|
||||
Add these handlers to the `setupToolHandlers` method:
|
||||
|
||||
```typescript
|
||||
private setupToolHandlers(): void {
|
||||
this.server.setRequestHandler(
|
||||
ListToolsRequestSchema,
|
||||
async () => ({
|
||||
tools: [{
|
||||
name: "get_forecast",
|
||||
description: "Get weather forecast for a city",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
city: {
|
||||
type: "string",
|
||||
description: "City name"
|
||||
},
|
||||
days: {
|
||||
type: "number",
|
||||
description: "Number of days (1-5)",
|
||||
minimum: 1,
|
||||
maximum: 5
|
||||
}
|
||||
},
|
||||
required: ["city"]
|
||||
}
|
||||
}]
|
||||
})
|
||||
);
|
||||
|
||||
this.server.setRequestHandler(
|
||||
CallToolRequestSchema,
|
||||
async (request) => {
|
||||
if (request.params.name !== "get_forecast") {
|
||||
throw new McpError(
|
||||
ErrorCode.MethodNotFound,
|
||||
`Unknown tool: ${request.params.name}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!isValidForecastArgs(request.params.arguments)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
"Invalid forecast arguments"
|
||||
);
|
||||
}
|
||||
|
||||
const city = request.params.arguments.city;
|
||||
const days = Math.min(request.params.arguments.days || 3, 5);
|
||||
|
||||
try {
|
||||
const response = await this.axiosInstance.get<{
|
||||
list: OpenWeatherResponse[]
|
||||
}>(API_CONFIG.ENDPOINTS.FORECAST, {
|
||||
params: {
|
||||
q: city,
|
||||
cnt: days * 8 // API returns 3-hour intervals
|
||||
}
|
||||
});
|
||||
|
||||
const forecasts: ForecastDay[] = [];
|
||||
for (let i = 0; i < response.data.list.length; i += 8) {
|
||||
const dayData = response.data.list[i];
|
||||
forecasts.push({
|
||||
date: dayData.dt_txt?.split(' ')[0] ?? new Date().toISOString().split('T')[0],
|
||||
temperature: dayData.main.temp,
|
||||
conditions: dayData.weather[0].description
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify(forecasts, null, 2)
|
||||
}]
|
||||
};
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: `Weather API error: ${error.response?.data.message ?? error.message}`
|
||||
}],
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Build and test">
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Connect to Claude Desktop
|
||||
|
||||
<Steps>
|
||||
<Step title="Update Claude config">
|
||||
If you didn't already connect to Claude Desktop during project setup, add to `claude_desktop_config.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"weather": {
|
||||
"command": "node",
|
||||
"args": ["/path/to/weather-server/build/index.js"],
|
||||
"env": {
|
||||
"OPENWEATHER_API_KEY": "your-api-key",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
|
||||
<Step title="Restart Claude">
|
||||
1. Quit Claude completely
|
||||
2. Start Claude again
|
||||
3. Look for your weather server in the 🔌 menu
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Try it out!
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Check Current Weather" active>
|
||||
Ask Claude:
|
||||
```
|
||||
What's the current weather in San Francisco? Can you analyze the conditions?
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Get a Forecast">
|
||||
Ask Claude:
|
||||
```
|
||||
Can you get me a 5-day forecast for Tokyo and tell me if I should pack an umbrella?
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Compare Weather">
|
||||
Ask Claude:
|
||||
```
|
||||
Can you analyze the forecast for both Tokyo and San Francisco and tell me which city will be warmer this week?
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Understanding the code
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Type Safety">
|
||||
```typescript
|
||||
interface WeatherData {
|
||||
temperature: number;
|
||||
conditions: string;
|
||||
humidity: number;
|
||||
wind_speed: number;
|
||||
timestamp: string;
|
||||
}
|
||||
```
|
||||
TypeScript adds type safety to our MCP server, making it more reliable and easier to maintain.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Resources">
|
||||
```typescript
|
||||
this.server.setRequestHandler(
|
||||
ListResourcesRequestSchema,
|
||||
async () => ({
|
||||
resources: [{
|
||||
uri: `weather://${DEFAULT_CITY}/current`,
|
||||
name: `Current weather in ${DEFAULT_CITY}`,
|
||||
mimeType: "application/json"
|
||||
}]
|
||||
})
|
||||
);
|
||||
```
|
||||
Resources provide data that Claude can access as context.
|
||||
</Tab>
|
||||
|
||||
<Tab title="Tools">
|
||||
```typescript
|
||||
{
|
||||
name: "get_forecast",
|
||||
description: "Get weather forecast for a city",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
city: { type: "string" },
|
||||
days: { type: "number" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Tools let Claude take actions through your server with type-safe inputs.
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Best practices
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card
|
||||
title="Error Handling"
|
||||
icon="shield"
|
||||
>
|
||||
When a tool encounters an error, return the error message with `isError: true`, so the model can self-correct:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const response = await axiosInstance.get(...);
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
return {
|
||||
content: {
|
||||
mimeType: "text/plain",
|
||||
text: `Weather API error: ${error.response?.data.message ?? error.message}`
|
||||
},
|
||||
isError: true,
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
|
||||
For other handlers, throw an error, so the application can notify the user:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const response = await this.axiosInstance.get(...);
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
throw new McpError(
|
||||
ErrorCode.InternalError,
|
||||
`Weather API error: ${error.response?.data.message}`
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
```
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="Type Validation"
|
||||
icon="check"
|
||||
>
|
||||
```typescript
|
||||
function isValidForecastArgs(args: any): args is GetForecastArgs {
|
||||
return (
|
||||
typeof args === "object" &&
|
||||
args !== null &&
|
||||
"city" in args &&
|
||||
typeof args.city === "string"
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
<Tip>You can also use libraries like [Zod](https://zod.dev/) to perform this validation automatically.</Tip>
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Available transports
|
||||
|
||||
While this guide uses stdio to run the MCP server as a local process, MCP supports other [transports](/docs/concepts/transports) as well.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<Info>
|
||||
The following troubleshooting tips are for macOS. Guides for other platforms are coming soon.
|
||||
</Info>
|
||||
|
||||
### Build errors
|
||||
```bash
|
||||
# Check TypeScript version
|
||||
npx tsc --version
|
||||
|
||||
# Clean and rebuild
|
||||
rm -rf build/
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Runtime errors
|
||||
Look for detailed error messages in the Claude Desktop logs:
|
||||
```bash
|
||||
# Monitor logs
|
||||
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
|
||||
```
|
||||
|
||||
### Type errors
|
||||
```bash
|
||||
# Check types without building
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Architecture overview"
|
||||
icon="sitemap"
|
||||
href="/docs/concepts/architecture"
|
||||
>
|
||||
Learn more about the MCP architecture
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
title="TypeScript SDK"
|
||||
icon="square-js"
|
||||
href="https://github.com/modelcontextprotocol/typescript-sdk"
|
||||
>
|
||||
Check out the TypeScript SDK on GitHub
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Note>
|
||||
Need help? Ask Claude! Since it has access to the MCP SDK documentation, it can help you debug issues and suggest improvements to your server.
|
||||
</Note>
|
||||
|
|
@ -34,11 +34,11 @@ MCP provides several tools for debugging at different levels:
|
|||
|
||||
The Claude.app interface provides basic server status information:
|
||||
|
||||
1. Click the 🔌 icon to view:
|
||||
1. Click the <img src="/images/claude-desktop-mcp-plug-icon.svg" style={{display: 'inline', margin: 0, height: '1.3em'}} /> icon to view:
|
||||
- Connected servers
|
||||
- Available prompts and resources
|
||||
|
||||
2. Click the 🔨 icon to view:
|
||||
2. Click the <img src="/images/claude-desktop-mcp-hammer-icon.svg" style={{display: 'inline', margin: 0, height: '1.3em'}} /> icon to view:
|
||||
- Tools made available to the model
|
||||
|
||||
### Viewing logs
|
||||
|
|
|
|||
123
examples.mdx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
title: Examples
|
||||
description: 'A list of example servers and implementations'
|
||||
---
|
||||
|
||||
This page showcases various Model Context Protocol (MCP) servers that demonstrate the protocol's capabilities and versatility. These servers enable Large Language Models (LLMs) to securely access tools and data sources.
|
||||
|
||||
## Reference implementations
|
||||
|
||||
These official reference servers demonstrate core MCP features and SDK usage:
|
||||
|
||||
### Data and file systems
|
||||
- **[Filesystem](src/filesystem)** - Secure file operations with configurable access controls
|
||||
- **[PostgreSQL](src/postgres)** - Read-only database access with schema inspection capabilities
|
||||
- **[SQLite](src/sqlite)** - Database interaction and business intelligence features
|
||||
- **[Google Drive](src/gdrive)** - File access and search capabilities for Google Drive
|
||||
|
||||
### Development tools
|
||||
- **[Git](src/git)** - Tools to read, search, and manipulate Git repositories
|
||||
- **[GitHub](src/github)** - Repository management, file operations, and GitHub API integration
|
||||
- **[GitLab](src/gitlab)** - GitLab API integration enabling project management
|
||||
- **[Sentry](src/sentry)** - Retrieving and analyzing issues from Sentry.io
|
||||
|
||||
### Web and browser automation
|
||||
- **[Brave Search](src/brave-search)** - Web and local search using Brave's Search API
|
||||
- **[Fetch](src/fetch)** - Web content fetching and conversion optimized for LLM usage
|
||||
- **[Puppeteer](src/puppeteer)** - Browser automation and web scraping capabilities
|
||||
|
||||
### Productivity and communication
|
||||
- **[Slack](src/slack)** - Channel management and messaging capabilities
|
||||
- **[Google Maps](src/google-maps)** - Location services, directions, and place details
|
||||
- **[Memory](src/memory)** - Knowledge graph-based persistent memory system
|
||||
|
||||
### AI and specialized tools
|
||||
- **[EverArt](src/everart)** - AI image generation using various models
|
||||
- **[Sequential Thinking](src/sequentialthinking)** - Dynamic problem-solving through thought sequences
|
||||
- **[AWS KB Retrieval](src/aws-kb-retrieval)** - Retrieval from AWS Knowledge Base using Bedrock Agent Runtime
|
||||
|
||||
## Official integrations
|
||||
|
||||
These MCP servers are maintained by companies for their platforms:
|
||||
|
||||
- **[Axiom](https://github.com/axiomhq/mcp-server-axiom)** - Query and analyze logs, traces, and event data using natural language
|
||||
- **[Browserbase](https://github.com/browserbase/mcp-server-browserbase)** - Automate browser interactions in the cloud
|
||||
- **[Cloudflare](https://github.com/cloudflare/mcp-server-cloudflare)** - Deploy and manage resources on the Cloudflare developer platform
|
||||
- **[E2B](https://github.com/e2b-dev/mcp-server)** - Execute code in secure cloud sandboxes
|
||||
- **[Neon](https://github.com/neondatabase/mcp-server-neon)** - Interact with the Neon serverless Postgres platform
|
||||
- **[Obsidian Markdown Notes](https://github.com/calclavia/mcp-obsidian)** - Read and search through Markdown notes in Obsidian vaults
|
||||
- **[Qdrant](https://github.com/qdrant/mcp-server-qdrant/)** - Implement semantic memory using the Qdrant vector search engine
|
||||
- **[Raygun](https://github.com/MindscapeHQ/mcp-server-raygun)** - Access crash reporting and monitoring data
|
||||
- **[Search1API](https://github.com/fatwang2/search1api-mcp)** - Unified API for search, crawling, and sitemaps
|
||||
- **[Tinybird](https://github.com/tinybirdco/mcp-tinybird)** - Interface with the Tinybird serverless ClickHouse platform
|
||||
|
||||
## Community highlights
|
||||
|
||||
A growing ecosystem of community-developed servers extends MCP's capabilities:
|
||||
|
||||
- **[Docker](https://github.com/ckreiling/mcp-server-docker)** - Manage containers, images, volumes, and networks
|
||||
- **[Kubernetes](https://github.com/Flux159/mcp-server-kubernetes)** - Manage pods, deployments, and services
|
||||
- **[Linear](https://github.com/jerhadf/linear-mcp-server)** - Project management and issue tracking
|
||||
- **[Snowflake](https://github.com/datawiz168/mcp-snowflake-service)** - Interact with Snowflake databases
|
||||
- **[Spotify](https://github.com/varunneal/spotify-mcp)** - Control Spotify playback and manage playlists
|
||||
- **[Todoist](https://github.com/abhiz123/todoist-mcp-server)** - Task management integration
|
||||
|
||||
> **Note:** Community servers are untested and should be used at your own risk. They are not affiliated with or endorsed by Anthropic.
|
||||
|
||||
For a complete list of community servers, visit the [MCP Servers Repository](https://github.com/modelcontextprotocol/servers).
|
||||
|
||||
## Getting started
|
||||
|
||||
### Using reference servers
|
||||
|
||||
TypeScript-based servers can be used directly with `npx`:
|
||||
|
||||
```bash
|
||||
npx -y @modelcontextprotocol/server-memory
|
||||
```
|
||||
|
||||
Python-based servers can be used with `uvx` (recommended) or `pip`:
|
||||
|
||||
```bash
|
||||
# Using uvx
|
||||
uvx mcp-server-git
|
||||
|
||||
# Using pip
|
||||
pip install mcp-server-git
|
||||
python -m mcp_server_git
|
||||
```
|
||||
|
||||
### Configuring with Claude
|
||||
|
||||
To use an MCP server with Claude, add it to your configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"memory": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-memory"]
|
||||
},
|
||||
"filesystem": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/files"]
|
||||
},
|
||||
"github": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-github"],
|
||||
"env": {
|
||||
"GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Additional resources
|
||||
|
||||
- [MCP Servers Repository](https://github.com/modelcontextprotocol/servers) - Complete collection of reference implementations and community servers
|
||||
- [Awesome MCP Servers](https://github.com/punkpeye/awesome-mcp-servers) - Curated list of MCP servers
|
||||
- [MCP CLI](https://github.com/wong2/mcp-cli) - Command-line inspector for testing MCP servers
|
||||
- [MCP Get](https://mcp-get.com) - Tool for installing and managing MCP servers
|
||||
|
||||
Visit our [GitHub Discussions](https://github.com/orgs/modelcontextprotocol/discussions) to engage with the MCP community.
|
||||
BIN
images/available-mcp-tools.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
3
images/claude-desktop-mcp-hammer-icon.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M31.4175 14L22.985 5.51002C20.7329 3.26243 17.6811 2.00012 14.4993 2.00012C11.3176 2.00012 8.26581 3.26243 6.01372 5.51002L6.00247 5.52127L4.28122 7.30002C4.10292 7.49163 4.00685 7.74552 4.01364 8.00717C4.02043 8.26883 4.12954 8.51739 4.31754 8.6995C4.50554 8.88161 4.75745 8.98276 5.01919 8.98122C5.28092 8.97968 5.53163 8.87558 5.71747 8.69127L7.43372 6.91877C8.12421 6.22842 8.91217 5.64303 9.77247 5.18127L15.585 11L3.58497 23C3.39921 23.1857 3.25185 23.4062 3.15131 23.6489C3.05077 23.8916 2.99902 24.1517 2.99902 24.4144C2.99902 24.6771 3.05077 24.9372 3.15131 25.1799C3.25185 25.4225 3.39921 25.643 3.58497 25.8288L6.17122 28.415C6.35694 28.6008 6.57744 28.7481 6.82012 28.8487C7.06281 28.9492 7.32291 29.001 7.5856 29.001C7.84828 29.001 8.10839 28.9492 8.35107 28.8487C8.59375 28.7481 8.81425 28.6008 8.99997 28.415L21 16.415L22.7925 18.2075L25 20.4125C25.1857 20.5983 25.4062 20.7456 25.6489 20.8462C25.8916 20.9467 26.1517 20.9985 26.4143 20.9985C26.677 20.9985 26.9371 20.9467 27.1798 20.8462C27.4225 20.7456 27.643 20.5983 27.8287 20.4125L31.415 16.8263C31.7897 16.4516 32.0005 15.9436 32.0009 15.4137C32.0014 14.8838 31.7915 14.3753 31.4175 14ZM7.58497 27L4.99997 24.4138L13.5 15.9138L16.085 18.5L7.58497 27ZM20.2925 14.29L17.5 17.0838L14.9137 14.5L17.7075 11.7063C17.8004 11.6134 17.8742 11.5031 17.9245 11.3817C17.9749 11.2603 18.0008 11.1302 18.0008 10.9988C18.0008 10.8673 17.9749 10.7372 17.9245 10.6158C17.8742 10.4944 17.8004 10.3841 17.7075 10.2913L11.79 4.37502C13.4996 3.89351 15.3067 3.87606 17.0253 4.32445C18.744 4.77284 20.3122 5.67089 21.5687 6.92627L27.0962 12.49L23.5 16.0825L21.7075 14.29C21.6146 14.197 21.5043 14.1233 21.3829 14.073C21.2615 14.0226 21.1314 13.9967 21 13.9967C20.8686 13.9967 20.7384 14.0226 20.617 14.073C20.4956 14.1233 20.3853 14.197 20.2925 14.29ZM26.4175 18.9975L24.9175 17.4975L28.5 13.9063L30 15.4063L26.4175 18.9975Z" fill="#343330"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2 KiB |
3
images/claude-desktop-mcp-plug-icon.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.7076 17.2926C18.6147 17.1996 18.5044 17.1258 18.383 17.0755C18.2616 17.0252 18.1315 16.9993 18.0001 16.9993C17.8687 16.9993 17.7385 17.0252 17.6171 17.0755C17.4957 17.1258 17.3854 17.1996 17.2926 17.2926L15.0001 19.5863L12.4138 17.0001L14.7076 14.7076C14.8952 14.5199 15.0006 14.2654 15.0006 14.0001C15.0006 13.7347 14.8952 13.4802 14.7076 13.2926C14.5199 13.1049 14.2654 12.9995 14.0001 12.9995C13.7347 12.9995 13.4802 13.1049 13.2926 13.2926L11.0001 15.5863L8.70757 13.2926C8.51993 13.1049 8.26543 12.9995 8.00007 12.9995C7.7347 12.9995 7.48021 13.1049 7.29257 13.2926C7.10493 13.4802 6.99951 13.7347 6.99951 14.0001C6.99951 14.2654 7.10493 14.5199 7.29257 14.7076L8.08632 15.5001L5.17132 18.4138C4.79979 18.7852 4.50507 19.2262 4.30399 19.7116C4.10292 20.197 3.99942 20.7172 3.99942 21.2426C3.99942 21.7679 4.10292 22.2881 4.30399 22.7735C4.50507 23.2589 4.79979 23.6999 5.17132 24.0713L5.84382 24.7426L2.29257 28.2926C2.19966 28.3855 2.12596 28.4958 2.07567 28.6172C2.02539 28.7386 1.99951 28.8687 1.99951 29.0001C1.99951 29.1314 2.02539 29.2616 2.07567 29.3829C2.12596 29.5043 2.19966 29.6146 2.29257 29.7076C2.48021 29.8952 2.7347 30.0006 3.00007 30.0006C3.13146 30.0006 3.26157 29.9747 3.38296 29.9244C3.50436 29.8742 3.61466 29.8005 3.70757 29.7076L7.25757 26.1563L7.92882 26.8288C8.30026 27.2003 8.74126 27.4951 9.22662 27.6961C9.71198 27.8972 10.2322 28.0007 10.7576 28.0007C11.2829 28.0007 11.8032 27.8972 12.2885 27.6961C12.7739 27.4951 13.2149 27.2003 13.5863 26.8288L16.5001 23.9138L17.2926 24.7076C17.3855 24.8005 17.4958 24.8742 17.6172 24.9244C17.7386 24.9747 17.8687 25.0006 18.0001 25.0006C18.1315 25.0006 18.2616 24.9747 18.383 24.9244C18.5044 24.8742 18.6147 24.8005 18.7076 24.7076C18.8005 24.6146 18.8742 24.5043 18.9245 24.3829C18.9747 24.2616 19.0006 24.1314 19.0006 24.0001C19.0006 23.8687 18.9747 23.7386 18.9245 23.6172C18.8742 23.4958 18.8005 23.3855 18.7076 23.2926L16.4138 21.0001L18.7076 18.7076C18.8005 18.6147 18.8743 18.5044 18.9246 18.383C18.975 18.2616 19.0009 18.1315 19.0009 18.0001C19.0009 17.8686 18.975 17.7385 18.9246 17.6171C18.8743 17.4957 18.8005 17.3854 18.7076 17.2926ZM12.1713 25.4176C11.7963 25.7923 11.2878 26.0029 10.7576 26.0029C10.2274 26.0029 9.71885 25.7923 9.34382 25.4176L6.58632 22.6563C6.21153 22.2813 6.00099 21.7728 6.00099 21.2426C6.00099 20.7123 6.21153 20.2038 6.58632 19.8288L9.50007 16.9138L15.0863 22.5001L12.1713 25.4176ZM29.7076 2.29255C29.6147 2.19958 29.5044 2.12582 29.383 2.07549C29.2616 2.02517 29.1315 1.99927 29.0001 1.99927C28.8687 1.99927 28.7385 2.02517 28.6171 2.07549C28.4957 2.12582 28.3854 2.19958 28.2926 2.29255L24.7426 5.8438L24.0713 5.1713C23.3203 4.42249 22.3031 4.00199 21.2426 4.00199C20.1821 4.00199 19.1648 4.42249 18.4138 5.1713L15.5001 8.0863L14.7076 7.29255C14.5199 7.10491 14.2654 6.9995 14.0001 6.9995C13.7347 6.9995 13.4802 7.10491 13.2926 7.29255C13.1049 7.4802 12.9995 7.73469 12.9995 8.00005C12.9995 8.26542 13.1049 8.51991 13.2926 8.70755L23.2926 18.7076C23.3855 18.8005 23.4958 18.8742 23.6172 18.9244C23.7386 18.9747 23.8687 19.0006 24.0001 19.0006C24.1315 19.0006 24.2616 18.9747 24.383 18.9244C24.5044 18.8742 24.6147 18.8005 24.7076 18.7076C24.8005 18.6146 24.8742 18.5043 24.9245 18.3829C24.9747 18.2616 25.0006 18.1314 25.0006 18.0001C25.0006 17.8687 24.9747 17.7386 24.9245 17.6172C24.8742 17.4958 24.8005 17.3855 24.7076 17.2926L23.9138 16.5001L26.8288 13.5863C27.2003 13.2149 27.4951 12.7739 27.6961 12.2885C27.8972 11.8031 28.0007 11.2829 28.0007 10.7576C28.0007 10.2322 27.8972 9.71197 27.6961 9.22661C27.4951 8.74125 27.2003 8.30025 26.8288 7.9288L26.1563 7.25755L29.7076 3.70755C29.8005 3.61468 29.8743 3.50439 29.9246 3.38299C29.975 3.2616 30.0009 3.13147 30.0009 3.00005C30.0009 2.86864 29.975 2.73851 29.9246 2.61711C29.8743 2.49572 29.8005 2.38543 29.7076 2.29255ZM25.4138 12.1676L22.5001 15.0863L16.9138 9.50005L19.8288 6.5863C20.2039 6.21151 20.7124 6.00098 21.2426 6.00098C21.7728 6.00098 22.2813 6.21151 22.6563 6.5863L25.4138 9.3363C25.6005 9.52214 25.7487 9.74303 25.8498 9.98629C25.9509 10.2295 26.0029 10.4904 26.0029 10.7538C26.0029 11.0172 25.9509 11.2781 25.8498 11.5213C25.7487 11.7646 25.6005 11.9855 25.4138 12.1713V12.1676Z" fill="#343330"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
BIN
images/client-claude-cli-python.png
Normal file
|
After Width: | Height: | Size: 731 KiB |
BIN
images/current-weather.png
Normal file
|
After Width: | Height: | Size: 2 MiB |
BIN
images/visual-indicator-mcp-tools.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
images/weather-alerts.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
103
introduction.mdx
|
|
@ -3,53 +3,80 @@ title: Introduction
|
|||
description: 'Get started with the Model Context Protocol (MCP)'
|
||||
---
|
||||
|
||||
The Model Context Protocol (MCP) is an open protocol that enables seamless integration between LLM applications and external data sources and tools. Whether you're building an AI-powered IDE, enhancing a chat interface, or creating custom AI workflows, MCP provides a standardized way to connect LLMs with the context they need.
|
||||
MCP is an open protocol that standardizes how applications provide context to LLMs. Think of MCP like a USB-C port for AI applications. Just as USB-C provides a standardized way to connect your devices to various peripherals and accessories, MCP provides a standardized way to connect AI models to different data sources and tools.
|
||||
|
||||
## Get started with MCP
|
||||
## Why MCP?
|
||||
|
||||
MCP helps you build agents and complex workflows on top of LLMs. LLMs frequently need to integrate with data and tools, and MCP provides:
|
||||
- A growing list of pre-built integrations that your LLM can directly plug into
|
||||
- The flexibility to switch between LLM providers and vendors
|
||||
- Best practices for securing your data within your infrastructure
|
||||
|
||||
### General architecture
|
||||
|
||||
At its core, MCP follows a client-server architecture where a host application can connect to multiple servers:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph "Your Computer"
|
||||
Host["MCP Host\n(Claude, IDEs, Tools)"]
|
||||
S1["MCP Server A"]
|
||||
S2["MCP Server B"]
|
||||
S3["MCP Server C"]
|
||||
Host <-->|"MCP Protocol"| S1
|
||||
Host <-->|"MCP Protocol"| S2
|
||||
Host <-->|"MCP Protocol"| S3
|
||||
S1 <--> D1[("Local\nData Source A")]
|
||||
S2 <--> D2[("Local\nData Source B")]
|
||||
end
|
||||
subgraph "Internet"
|
||||
S3 <-->|"Web APIs"| D3[("Remote\nService C")]
|
||||
end
|
||||
```
|
||||
|
||||
- **MCP Hosts**: Programs like Claude Desktop, IDEs, or AI tools that want to access data through MCP
|
||||
- **MCP Clients**: Protocol clients that maintain 1:1 connections with servers
|
||||
- **MCP Servers**: Lightweight programs that each expose specific capabilities through the standardized Model Context Protocol
|
||||
- **Local Data Sources**: Your computer's files, databases, and services that MCP servers can securely access
|
||||
- **Remote Services**: External systems available over the internet (e.g., through APIs) that MCP servers can connect to
|
||||
|
||||
## Get started
|
||||
|
||||
Choose the path that best fits your needs:
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Quickstart"
|
||||
icon="bolt"
|
||||
href="/quickstart"
|
||||
>
|
||||
The fastest way to see MCP in action—connect example servers to Claude Desktop
|
||||
Build and connect to your first MCP server
|
||||
</Card>
|
||||
<Card
|
||||
title="Build your first server (Python)"
|
||||
icon="python"
|
||||
href="/docs/first-server/python"
|
||||
title="Examples"
|
||||
icon="grid"
|
||||
href="/examples"
|
||||
>
|
||||
Create a simple MCP server in Python to understand the basics
|
||||
</Card>
|
||||
<Card
|
||||
title="Build your first server (TypeScript)"
|
||||
icon="square-js"
|
||||
href="/docs/first-server/typescript"
|
||||
>
|
||||
Create a simple MCP server in TypeScript to understand the basics
|
||||
Check out our gallery of official MCP servers and implementations
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Development tools
|
||||
|
||||
Essential tools for building and debugging MCP servers:
|
||||
## Tutorials
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Debugging Guide"
|
||||
icon="bug"
|
||||
href="/docs/tools/debugging">
|
||||
Learn how to effectively debug MCP servers and integrations
|
||||
title="Building a MCP client"
|
||||
icon="outlet"
|
||||
href="/building-a-client"
|
||||
>
|
||||
Learn how to build your first MCP client
|
||||
</Card>
|
||||
<Card
|
||||
title="MCP Inspector"
|
||||
icon="magnifying-glass"
|
||||
href="/docs/tools/inspector"
|
||||
title="Building MCP with Claude"
|
||||
icon="comments"
|
||||
href="/building-a-client"
|
||||
>
|
||||
Test and inspect your MCP servers with our interactive debugging tool
|
||||
Learn how to use Claude to speed up your MCP development
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
|
|
@ -59,7 +86,7 @@ Dive deeper into MCP's core concepts and capabilities:
|
|||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Core Architecture"
|
||||
title="Core architecture"
|
||||
icon="sitemap"
|
||||
href="/docs/concepts/architecture"
|
||||
>
|
||||
|
|
@ -102,6 +129,26 @@ Dive deeper into MCP's core concepts and capabilities:
|
|||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Development tools
|
||||
|
||||
Essential tools for building and debugging MCP servers:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card
|
||||
title="Debugging Guide"
|
||||
icon="bug"
|
||||
href="/docs/tools/debugging">
|
||||
Learn how to effectively debug MCP servers and integrations
|
||||
</Card>
|
||||
<Card
|
||||
title="MCP Inspector"
|
||||
icon="magnifying-glass"
|
||||
href="/docs/tools/inspector"
|
||||
>
|
||||
Test and inspect your MCP servers with our interactive debugging tool
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Contributing
|
||||
|
||||
Want to contribute? Check out [@modelcontextprotocol](https://github.com/modelcontextprotocol) on GitHub to join our growing community of developers building with MCP.
|
||||
Want to contribute? Check out [@modelcontextprotocol](https://github.com/modelcontextprotocol) on GitHub to join our growing community of developers building with MCP.
|
||||
13
mint.json
|
|
@ -40,15 +40,14 @@
|
|||
"navigation": [
|
||||
{
|
||||
"group": "Get Started",
|
||||
"pages": ["introduction", "quickstart", "clients"]
|
||||
"pages": ["introduction", "quickstart", "examples"]
|
||||
},
|
||||
{
|
||||
"group": "Your First MCP Server",
|
||||
"pages": ["docs/first-server/python", "docs/first-server/typescript"]
|
||||
},
|
||||
{
|
||||
"group": "Development Tools",
|
||||
"pages": ["docs/tools/debugging", "docs/tools/inspector"]
|
||||
"group": "Tutorials",
|
||||
"pages": [
|
||||
"tutorials/building-a-client",
|
||||
"tutorials/building-mcp-with-claude"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Concepts",
|
||||
|
|
|
|||
1452
quickstart.mdx
463
tutorials/building-a-client-node.mdx
Normal file
|
|
@ -0,0 +1,463 @@
|
|||
<Tab title="Node">
|
||||
## System Requirements
|
||||
|
||||
Before starting, ensure your system meets these requirements:
|
||||
- Mac or Windows computer
|
||||
- Node.js version 16 or higher installed
|
||||
- npm (comes with Node.js)
|
||||
|
||||
## Setting Up Your Environment
|
||||
|
||||
First, create a new Node.js project:
|
||||
|
||||
```bash
|
||||
# Create project directory
|
||||
mkdir mcp-client
|
||||
cd mcp-client
|
||||
|
||||
# Initialize npm project
|
||||
npm init -y
|
||||
|
||||
# Install dependencies
|
||||
npm install @modelcontextprotocol/sdk @anthropic-ai/sdk dotenv
|
||||
npm install -D typescript @types/node
|
||||
|
||||
# Create TypeScript config
|
||||
npx tsc --init
|
||||
|
||||
# Create necessary files
|
||||
mkdir src
|
||||
touch src/client.ts
|
||||
touch .env
|
||||
```
|
||||
|
||||
Update your `package.json` to add necessary configuration:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node build/client.js"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update your `tsconfig.json` with appropriate settings:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
```
|
||||
|
||||
## Setting Up Your API Key
|
||||
|
||||
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
|
||||
|
||||
Create a `.env` file:
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=your_key_here
|
||||
```
|
||||
|
||||
Add `.env` to your `.gitignore`:
|
||||
```bash
|
||||
echo ".env" >> .gitignore
|
||||
```
|
||||
|
||||
## Creating the Client
|
||||
|
||||
First, let's set up our imports and create the basic client class in `src/client.ts`:
|
||||
|
||||
```typescript
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import dotenv from "dotenv";
|
||||
import { Tool } from "@anthropic-ai/sdk/resources/messages.js";
|
||||
import {
|
||||
CallToolResultSchema,
|
||||
ListToolsResultSchema,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import * as readline from "node:readline";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
interface MCPClientConfig {
|
||||
name?: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
class MCPClient {
|
||||
private client: Client | null = null;
|
||||
private anthropic: Anthropic;
|
||||
private transport: StdioClientTransport | null = null;
|
||||
|
||||
constructor(config: MCPClientConfig = {}) {
|
||||
this.anthropic = new Anthropic();
|
||||
}
|
||||
|
||||
// Methods will go here
|
||||
}
|
||||
```
|
||||
|
||||
## Server Connection Management
|
||||
|
||||
Next, implement the method to connect to an MCP server:
|
||||
|
||||
```typescript
|
||||
async connectToServer(serverScriptPath: string): Promise<void> {
|
||||
const isPython = serverScriptPath.endsWith(".py");
|
||||
const isJs = serverScriptPath.endsWith(".js");
|
||||
|
||||
if (!isPython && !isJs) {
|
||||
throw new Error("Server script must be a .py or .js file");
|
||||
}
|
||||
|
||||
const command = isPython ? "python" : "node";
|
||||
|
||||
this.transport = new StdioClientTransport({
|
||||
command,
|
||||
args: [serverScriptPath],
|
||||
});
|
||||
|
||||
this.client = new Client(
|
||||
{
|
||||
name: "mcp-client",
|
||||
version: "1.0.0",
|
||||
},
|
||||
{
|
||||
capabilities: {},
|
||||
}
|
||||
);
|
||||
|
||||
await this.client.connect(this.transport);
|
||||
|
||||
// List available tools
|
||||
const response = await this.client.request(
|
||||
{ method: "tools/list" },
|
||||
ListToolsResultSchema
|
||||
);
|
||||
|
||||
console.log(
|
||||
"\nConnected to server with tools:",
|
||||
response.tools.map((tool: any) => tool.name)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## Query Processing Logic
|
||||
|
||||
Now add the core functionality for processing queries and handling tool calls:
|
||||
|
||||
```typescript
|
||||
async processQuery(query: string): Promise<string> {
|
||||
if (!this.client) {
|
||||
throw new Error("Client not connected");
|
||||
}
|
||||
|
||||
// Initialize messages array with user query
|
||||
let messages: Anthropic.MessageParam[] = [
|
||||
{
|
||||
role: "user",
|
||||
content: query,
|
||||
},
|
||||
];
|
||||
|
||||
// Get available tools
|
||||
const toolsResponse = await this.client.request(
|
||||
{ method: "tools/list" },
|
||||
ListToolsResultSchema
|
||||
);
|
||||
|
||||
const availableTools: Tool[] = toolsResponse.tools.map((tool: any) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
input_schema: tool.inputSchema,
|
||||
}));
|
||||
|
||||
const finalText: string[] = [];
|
||||
let currentResponse = await this.anthropic.messages.create({
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
max_tokens: 1000,
|
||||
messages,
|
||||
tools: availableTools,
|
||||
});
|
||||
|
||||
// Process the response and any tool calls
|
||||
while (true) {
|
||||
// Add Claude's response to final text and messages
|
||||
for (const content of currentResponse.content) {
|
||||
if (content.type === "text") {
|
||||
finalText.push(content.text);
|
||||
} else if (content.type === "tool_use") {
|
||||
const toolName = content.name;
|
||||
const toolArgs = content.input;
|
||||
|
||||
// Execute tool call
|
||||
const result = await this.client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: toolName,
|
||||
args: toolArgs,
|
||||
},
|
||||
},
|
||||
CallToolResultSchema
|
||||
);
|
||||
|
||||
finalText.push(
|
||||
`[Calling tool ${toolName} with args ${JSON.stringify(toolArgs)}]`
|
||||
);
|
||||
|
||||
// Add Claude's response (including tool use) to messages
|
||||
messages.push({
|
||||
role: "assistant",
|
||||
content: currentResponse.content,
|
||||
});
|
||||
|
||||
// Add tool result to messages
|
||||
messages.push({
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "tool_result",
|
||||
tool_use_id: content.id,
|
||||
content: [
|
||||
{ type: "text", text: JSON.stringify(result.content) },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Get next response from Claude with tool results
|
||||
currentResponse = await this.anthropic.messages.create({
|
||||
model: "claude-3-5-sonnet-20241022",
|
||||
max_tokens: 1000,
|
||||
messages,
|
||||
tools: availableTools,
|
||||
});
|
||||
|
||||
// Add Claude's interpretation of the tool results to final text
|
||||
if (currentResponse.content[0]?.type === "text") {
|
||||
finalText.push(currentResponse.content[0].text);
|
||||
}
|
||||
|
||||
// Continue the loop to process any additional tool calls
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, there were no tool calls in the response
|
||||
break;
|
||||
}
|
||||
|
||||
return finalText.join("\n");
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Interactive Chat Interface
|
||||
|
||||
Add the chat loop and cleanup functionality:
|
||||
|
||||
```typescript
|
||||
async chatLoop(): Promise<void> {
|
||||
console.log("\nMCP Client Started!");
|
||||
console.log("Type your queries or 'quit' to exit.");
|
||||
|
||||
// Using Node's readline for console input
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const askQuestion = () => {
|
||||
rl.question("\nQuery: ", async (query: string) => {
|
||||
try {
|
||||
if (query.toLowerCase() === "quit") {
|
||||
await this.cleanup();
|
||||
rl.close();
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await this.processQuery(query);
|
||||
console.log("\n" + response);
|
||||
askQuestion();
|
||||
} catch (error) {
|
||||
console.error("\nError:", error);
|
||||
askQuestion();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
askQuestion();
|
||||
}
|
||||
|
||||
async cleanup(): Promise<void> {
|
||||
if (this.transport) {
|
||||
await this.transport.close();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
Finally, add the main execution logic outside the class:
|
||||
|
||||
```typescript
|
||||
// Main execution
|
||||
async function main() {
|
||||
if (process.argv.length < 3) {
|
||||
console.log("Usage: ts-node client.ts <path_to_server_script>");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new MCPClient();
|
||||
try {
|
||||
await client.connectToServer(process.argv[2]);
|
||||
await client.chatLoop();
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
await client.cleanup();
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run main if this is the main module
|
||||
if (import.meta.url === new URL(process.argv[1], "file:").href) {
|
||||
main();
|
||||
}
|
||||
|
||||
export default MCPClient;
|
||||
```
|
||||
|
||||
## Running the Client
|
||||
|
||||
To run your client with any MCP server:
|
||||
|
||||
```bash
|
||||
# Build the TypeScript code. Make sure to rerun this every time you update `client.ts`!
|
||||
npm run build
|
||||
|
||||
# Run the client
|
||||
node build/client.js path/to/server.py # for Python servers
|
||||
node build/client.js path/to/server.js # for Node.js servers
|
||||
```
|
||||
|
||||
The client will:
|
||||
1. Connect to the specified server
|
||||
2. List available tools
|
||||
3. Start an interactive chat session where you can:
|
||||
- Enter queries
|
||||
- See tool executions
|
||||
- Get responses from Claude
|
||||
|
||||
## Key Components Explained
|
||||
|
||||
#### 1. Client Initialization
|
||||
- The `MCPClient` class initializes with session management and API clients
|
||||
- Sets up the MCP client with basic capabilities
|
||||
- Configures the Anthropic client for Claude interactions
|
||||
|
||||
#### 2. Server Connection
|
||||
- Supports both Python and Node.js servers
|
||||
- Validates server script type
|
||||
- Sets up proper communication channels
|
||||
- Lists available tools on connection
|
||||
|
||||
#### 3. Query Processing
|
||||
- Maintains conversation context
|
||||
- Handles Claude's responses and tool calls
|
||||
- Manages the message flow between Claude and tools
|
||||
- Combines results into a coherent response
|
||||
|
||||
#### 4. Interactive Interface
|
||||
- Provides a simple command-line interface
|
||||
- Handles user input and displays responses
|
||||
- Includes basic error handling
|
||||
- Allows graceful exit
|
||||
|
||||
#### 5. Resource Management
|
||||
- Proper cleanup of resources
|
||||
- Error handling for connection issues
|
||||
- Graceful shutdown procedures
|
||||
|
||||
### Common Customization Points
|
||||
|
||||
1. **Tool Handling**
|
||||
- Modify `processQuery()` to handle specific tool types
|
||||
- Add custom error handling for tool calls
|
||||
- Implement tool-specific response formatting
|
||||
|
||||
2. **Response Processing**
|
||||
- Customize how tool results are formatted
|
||||
- Add response filtering or transformation
|
||||
- Implement custom logging
|
||||
|
||||
3. **User Interface**
|
||||
- Add a GUI or web interface
|
||||
- Implement rich console output
|
||||
- Add command history or auto-completion
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Error Handling**
|
||||
- Always wrap tool calls in try-catch blocks
|
||||
- Provide meaningful error messages
|
||||
- Gracefully handle connection issues
|
||||
|
||||
2. **Resource Management**
|
||||
- Use proper cleanup methods
|
||||
- Close connections when done
|
||||
- Handle server disconnections
|
||||
|
||||
3. **Security**
|
||||
- Store API keys securely in `.env`
|
||||
- Validate server responses
|
||||
- Be cautious with tool permissions
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
#### Server Path Issues
|
||||
- Double-check the path to your server script
|
||||
- Use absolute paths if relative paths aren't working
|
||||
- For Windows users, use forward slashes (/) or escaped backslashes (\\)
|
||||
- Verify the server file has the correct extension (.py or .js)
|
||||
|
||||
Example of correct path usage:
|
||||
```bash
|
||||
# Relative path
|
||||
node build/client.js ./server/weather.js
|
||||
|
||||
# Absolute path
|
||||
node build/client.js /Users/username/projects/mcp-server/weather.js
|
||||
|
||||
# Windows path (either format works)
|
||||
node build/client.js C:/projects/mcp-server/weather.js
|
||||
node build/client.js C:\\projects\\mcp-server\\weather.js
|
||||
```
|
||||
|
||||
#### Connection Issues
|
||||
- Verify the server script exists and has correct permissions
|
||||
- Check that the server script is executable
|
||||
- Ensure the server script's dependencies are installed
|
||||
- Try running the server script directly to check for errors
|
||||
|
||||
#### Tool Execution Issues
|
||||
- Check server logs for error messages
|
||||
- Verify tool input arguments match the schema
|
||||
- Ensure tool dependencies are available
|
||||
- Add debug logging to track execution flow
|
||||
</Tab>
|
||||
414
tutorials/building-a-client.mdx
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
---
|
||||
title: "Building MCP clients"
|
||||
description: "Learn how to build your first client in MCP"
|
||||
---
|
||||
|
||||
In this tutorial, you'll learn how to build a client application that connects to MCP servers. It helps to have gone through the [Quickstart tutorial](/quickstart) that guides you through the basic of building your first server.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Python">
|
||||
|
||||
## System Requirements
|
||||
|
||||
Before starting, ensure your system meets these requirements:
|
||||
- Mac or Windows computer
|
||||
- Latest Python version installed
|
||||
- Latest version of `uv` installed
|
||||
|
||||
## Setting Up Your Environment
|
||||
|
||||
First, create a new Python project with `uv`:
|
||||
|
||||
```bash
|
||||
# Create project directory
|
||||
uv init mcp-client
|
||||
cd mcp-client
|
||||
|
||||
# Create virtual environment
|
||||
uv venv
|
||||
|
||||
# Activate virtual environment
|
||||
# On Windows:
|
||||
.venv\Scripts\activate
|
||||
# On Unix or MacOS:
|
||||
source .venv/bin/activate
|
||||
|
||||
# Install required packages
|
||||
uv add mcp anthropic python-dotenv
|
||||
|
||||
# Remove boilerplate files
|
||||
rm hello.py
|
||||
|
||||
# Create our main file
|
||||
touch client.py
|
||||
```
|
||||
|
||||
## Setting Up Your API Key
|
||||
|
||||
You'll need an Anthropic API key from the [Anthropic Console](https://console.anthropic.com/settings/keys).
|
||||
|
||||
Create a `.env` file to store it:
|
||||
|
||||
```bash
|
||||
# Create .env file
|
||||
touch .env
|
||||
```
|
||||
|
||||
Add your key to the `.env` file:
|
||||
```bash
|
||||
ANTHROPIC_API_KEY=<your key here>
|
||||
```
|
||||
|
||||
Add `.env` to your `.gitignore`:
|
||||
```bash
|
||||
echo ".env" >> .gitignore
|
||||
```
|
||||
|
||||
<Warning>
|
||||
Make sure you keep your `ANTHROPIC_API_KEY` secure!
|
||||
</Warning>
|
||||
|
||||
## Creating the Client
|
||||
|
||||
### Basic Client Structure
|
||||
First, let's set up our imports and create the basic client class:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from contextlib import AsyncExitStack
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
from anthropic import Anthropic
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv() # load environment variables from .env
|
||||
|
||||
class MCPClient:
|
||||
def __init__(self):
|
||||
# Initialize session and client objects
|
||||
self.session: Optional[ClientSession] = None
|
||||
self.exit_stack = AsyncExitStack()
|
||||
self.anthropic = Anthropic()
|
||||
# methods will go here
|
||||
```
|
||||
|
||||
### Server Connection Management
|
||||
|
||||
Next, we'll implement the method to connect to an MCP server:
|
||||
|
||||
```python
|
||||
async def connect_to_server(self, server_script_path: str):
|
||||
"""Connect to an MCP server
|
||||
|
||||
Args:
|
||||
server_script_path: Path to the server script (.py or .js)
|
||||
"""
|
||||
is_python = server_script_path.endswith('.py')
|
||||
is_js = server_script_path.endswith('.js')
|
||||
if not (is_python or is_js):
|
||||
raise ValueError("Server script must be a .py or .js file")
|
||||
|
||||
command = "python" if is_python else "node"
|
||||
server_params = StdioServerParameters(
|
||||
command=command,
|
||||
args=[server_script_path],
|
||||
env=None
|
||||
)
|
||||
|
||||
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
||||
self.stdio, self.write = stdio_transport
|
||||
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
|
||||
|
||||
await self.session.initialize()
|
||||
|
||||
# List available tools
|
||||
response = await self.session.list_tools()
|
||||
tools = response.tools
|
||||
print("\nConnected to server with tools:", [tool.name for tool in tools])
|
||||
```
|
||||
|
||||
### Query Processing Logic
|
||||
|
||||
Now let's add the core functionality for processing queries and handling tool calls:
|
||||
|
||||
```python
|
||||
async def process_query(self, query: str) -> str:
|
||||
"""Process a query using Claude and available tools"""
|
||||
messages = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": query
|
||||
}
|
||||
]
|
||||
|
||||
response = await self.session.list_tools()
|
||||
available_tools = [{
|
||||
"name": tool.name,
|
||||
"description": tool.description,
|
||||
"input_schema": tool.inputSchema
|
||||
} for tool in response.tools]
|
||||
|
||||
# Initial Claude API call
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
tools=available_tools
|
||||
)
|
||||
|
||||
# Process response and handle tool calls
|
||||
tool_results = []
|
||||
final_text = []
|
||||
|
||||
for content in response.content:
|
||||
if content.type == 'text':
|
||||
final_text.append(content.text)
|
||||
elif content.type == 'tool_use':
|
||||
tool_name = content.name
|
||||
tool_args = content.input
|
||||
|
||||
# Execute tool call
|
||||
result = await self.session.call_tool(tool_name, tool_args)
|
||||
tool_results.append({"call": tool_name, "result": result})
|
||||
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
|
||||
|
||||
# Continue conversation with tool results
|
||||
if hasattr(content, 'text') and content.text:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": content.text
|
||||
})
|
||||
messages.append({
|
||||
"role": "user",
|
||||
"content": result.content
|
||||
})
|
||||
|
||||
# Get next response from Claude
|
||||
response = self.anthropic.messages.create(
|
||||
model="claude-3-5-sonnet-20241022",
|
||||
max_tokens=1000,
|
||||
messages=messages,
|
||||
)
|
||||
|
||||
final_text.append(response.content[0].text)
|
||||
|
||||
return "\n".join(final_text)
|
||||
```
|
||||
|
||||
### Interactive Chat Interface
|
||||
Now we'll add the chat loop and cleanup functionality:
|
||||
|
||||
```python
|
||||
async def chat_loop(self):
|
||||
"""Run an interactive chat loop"""
|
||||
print("\nMCP Client Started!")
|
||||
print("Type your queries or 'quit' to exit.")
|
||||
|
||||
while True:
|
||||
try:
|
||||
query = input("\nQuery: ").strip()
|
||||
|
||||
if query.lower() == 'quit':
|
||||
break
|
||||
|
||||
response = await self.process_query(query)
|
||||
print("\n" + response)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\nError: {str(e)}")
|
||||
|
||||
async def cleanup(self):
|
||||
"""Clean up resources"""
|
||||
await self.exit_stack.aclose()
|
||||
```
|
||||
|
||||
### Main Entry Point
|
||||
|
||||
Finally, we'll add the main execution logic:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python client.py <path_to_server_script>")
|
||||
sys.exit(1)
|
||||
|
||||
client = MCPClient()
|
||||
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())
|
||||
```
|
||||
|
||||
You can find the complete `client.py` file [here.](https://gist.github.com/zckly/f3f28ea731e096e53b39b47bf0a2d4b1)
|
||||
|
||||
## Key Components Explained
|
||||
|
||||
### 1. Client Initialization
|
||||
- The `MCPClient` class initializes with session management and API clients
|
||||
- Uses `AsyncExitStack` for proper resource management
|
||||
- Configures the Anthropic client for Claude interactions
|
||||
|
||||
### 2. Server Connection
|
||||
- Supports both Python and Node.js servers
|
||||
- Validates server script type
|
||||
- Sets up proper communication channels
|
||||
- Initializes the session and lists available tools
|
||||
|
||||
### 3. Query Processing
|
||||
- Maintains conversation context
|
||||
- Handles Claude's responses and tool calls
|
||||
- Manages the message flow between Claude and tools
|
||||
- Combines results into a coherent response
|
||||
|
||||
### 4. Interactive Interface
|
||||
- Provides a simple command-line interface
|
||||
- Handles user input and displays responses
|
||||
- Includes basic error handling
|
||||
- Allows graceful exit
|
||||
|
||||
### 5. Resource Management
|
||||
- Proper cleanup of resources
|
||||
- Error handling for connection issues
|
||||
- Graceful shutdown procedures
|
||||
|
||||
## Common Customization Points
|
||||
|
||||
1. **Tool Handling**
|
||||
- Modify `process_query()` to handle specific tool types
|
||||
- Add custom error handling for tool calls
|
||||
- Implement tool-specific response formatting
|
||||
|
||||
2. **Response Processing**
|
||||
- Customize how tool results are formatted
|
||||
- Add response filtering or transformation
|
||||
- Implement custom logging
|
||||
|
||||
3. **User Interface**
|
||||
- Add a GUI or web interface
|
||||
- Implement rich console output
|
||||
- Add command history or auto-completion
|
||||
|
||||
## Running the Client
|
||||
|
||||
To run your client with any MCP server:
|
||||
|
||||
```bash
|
||||
uv run client.py path/to/server.py # python server
|
||||
uv run client.py path/to/build/index.js # node server
|
||||
```
|
||||
|
||||
<Note>
|
||||
If you're continuing the weather tutorial from the quickstart, your command might look something like this: `python client.py .../weather/src/weather/server.py`
|
||||
</Note>
|
||||
|
||||
The client will:
|
||||
1. Connect to the specified server
|
||||
2. List available tools
|
||||
3. Start an interactive chat session where you can:
|
||||
- Enter queries
|
||||
- See tool executions
|
||||
- Get responses from Claude
|
||||
|
||||
Here's an example of what it should look like if connected to the weather server from the quickstart:
|
||||
|
||||
<Frame>
|
||||
<img src="/images/client-claude-cli-python.png" />
|
||||
</Frame>
|
||||
|
||||
## How It Works
|
||||
|
||||
When you submit a query:
|
||||
|
||||
1. The client gets the list of available tools from the server
|
||||
2. Your query is sent to Claude along with tool descriptions
|
||||
3. Claude decides which tools (if any) to use
|
||||
4. The client executes any requested tool calls through the server
|
||||
5. Results are sent back to Claude
|
||||
6. Claude provides a natural language response
|
||||
7. The response is displayed to you
|
||||
|
||||
## Best practices
|
||||
|
||||
1. **Error Handling**
|
||||
- Always wrap tool calls in try-catch blocks
|
||||
- Provide meaningful error messages
|
||||
- Gracefully handle connection issues
|
||||
|
||||
2. **Resource Management**
|
||||
- Use `AsyncExitStack` for proper cleanup
|
||||
- Close connections when done
|
||||
- Handle server disconnections
|
||||
|
||||
3. **Security**
|
||||
- Store API keys securely in `.env`
|
||||
- Validate server responses
|
||||
- Be cautious with tool permissions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Server Path Issues
|
||||
- Double-check the path to your server script is correct
|
||||
- Use the absolute path if the relative path isn't working
|
||||
- For Windows users, make sure to use forward slashes (/) or escaped backslashes (\\) in the path
|
||||
- Verify the server file has the correct extension (.py for Python or .js for Node.js)
|
||||
|
||||
Example of correct path usage:
|
||||
```bash
|
||||
# Relative path
|
||||
uv run client.py ./server/weather.py
|
||||
|
||||
# Absolute path
|
||||
uv run client.py /Users/username/projects/mcp-server/weather.py
|
||||
|
||||
# Windows path (either format works)
|
||||
uv run client.py C:/projects/mcp-server/weather.py
|
||||
uv run client.py C:\\projects\\mcp-server\\weather.py
|
||||
```
|
||||
|
||||
### Response Timing
|
||||
- The first response might take up to 30 seconds to return
|
||||
- This is normal and happens while:
|
||||
- The server initializes
|
||||
- Claude processes the query
|
||||
- Tools are being executed
|
||||
- Subsequent responses are typically faster
|
||||
- Don't interrupt the process during this initial waiting period
|
||||
|
||||
### Common Error Messages
|
||||
|
||||
If you see:
|
||||
- `FileNotFoundError`: Check your server path
|
||||
- `Connection refused`: Ensure the server is running and the path is correct
|
||||
- `Tool execution failed`: Verify the tool's required environment variables are set
|
||||
- `Timeout error`: Consider increasing the timeout in your client configuration
|
||||
|
||||
</Tab>
|
||||
|
||||
</Tabs>
|
||||
|
||||
## Next steps
|
||||
|
||||
<CardGroup cols={1}>
|
||||
<Card
|
||||
title="Example servers"
|
||||
icon="grid"
|
||||
href="/examples"
|
||||
>
|
||||
Check out our gallery of official MCP servers and implementations
|
||||
</Card>
|
||||
<Card
|
||||
title="Core architecture"
|
||||
icon="sitemap"
|
||||
href="/docs/concepts/architecture"
|
||||
>
|
||||
Understand how MCP connects clients, servers, and LLMs
|
||||
</Card>
|
||||
</CardGroup>
|
||||
73
tutorials/building-mcp-with-claude.mdx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
---
|
||||
title: "Building MCP with Claude"
|
||||
description: "Speed up your MCP development with Claude!"
|
||||
---
|
||||
|
||||
This guide will help you use Claude to build custom Model Context Protocol (MCP) servers and clients.
|
||||
|
||||
## Preparing the documentation
|
||||
|
||||
Before starting, gather the necessary documentation to help Claude understand MCP:
|
||||
|
||||
1. Visit https://modelcontextprotocol.io/llms-full.txt and copy the full documentation text
|
||||
2. Navigate to either the [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) or [Python SDK repository](https://github.com/modelcontextprotocol/python-sdk)
|
||||
3. Copy the README files and other relevant documentation
|
||||
4. Paste these documents into your conversation with Claude
|
||||
|
||||
## Describing your server
|
||||
|
||||
Once you've provided the documentation, clearly describe to Claude what kind of server you want to build. Be specific about:
|
||||
|
||||
- What resources your server will expose
|
||||
- What tools it will provide
|
||||
- Any prompts it should offer
|
||||
- What external systems it needs to interact with
|
||||
|
||||
For example:
|
||||
```
|
||||
Build an MCP server that:
|
||||
- Connects to my company's PostgreSQL database
|
||||
- Exposes table schemas as resources
|
||||
- Provides tools for running read-only SQL queries
|
||||
- Includes prompts for common data analysis tasks
|
||||
```
|
||||
|
||||
## Working with Claude
|
||||
|
||||
When working with Claude on MCP servers:
|
||||
|
||||
1. Start with the core functionality first, then iterate to add more features
|
||||
2. Ask Claude to explain any parts of the code you don't understand
|
||||
3. Request modifications or improvements as needed
|
||||
4. Have Claude help you test the server and handle edge cases
|
||||
|
||||
Claude can help implement all the key MCP features:
|
||||
|
||||
- Resource management and exposure
|
||||
- Tool definitions and implementations
|
||||
- Prompt templates and handlers
|
||||
- Error handling and logging
|
||||
- Connection and transport setup
|
||||
|
||||
## Best practices
|
||||
|
||||
When building MCP servers with Claude:
|
||||
|
||||
- Break down complex servers into smaller pieces
|
||||
- Test each component thoroughly before moving on
|
||||
- Keep security in mind - validate inputs and limit access appropriately
|
||||
- Document your code well for future maintenance
|
||||
- Follow MCP protocol specifications carefully
|
||||
|
||||
## Next steps
|
||||
|
||||
After Claude helps you build your server:
|
||||
|
||||
1. Review the generated code carefully
|
||||
2. Test the server with the MCP Inspector tool
|
||||
3. Connect it to Claude.app or other MCP clients
|
||||
4. Iterate based on real usage and feedback
|
||||
|
||||
Remember that Claude can help you modify and improve your server as requirements change over time.
|
||||
|
||||
Need more guidance? Just ask Claude specific questions about implementing MCP features or troubleshooting issues that arise.
|
||||