diff --git a/docs/first-server/python.mdx b/docs/first-server/python.mdx
deleted file mode 100644
index d48c966..0000000
--- a/docs/first-server/python.mdx
+++ /dev/null
@@ -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.
-
-
- This guide uses the OpenWeatherMap API. You'll need a free API key from [OpenWeatherMap](https://openweathermap.org/api) to follow along.
-
-
-## Prerequisites
-
-
- The following steps are for macOS. Guides for other platforms are coming soon.
-
-
-
-
- You'll need Python 3.10 or higher:
-
- ```bash
- python --version # Should be 3.10 or higher
- ```
-
-
-
- See https://docs.astral.sh/uv/ for more information.
-
- ```bash
- brew install uv
- uv --version # Should be 0.4.18 or higher
- ```
-
-
-
- ```bash
- uvx create-mcp-server --path weather_service
- cd weather_service
- ```
-
-
-
- ```bash
- uv add httpx python-dotenv
- ```
-
-
-
- Create `.env`:
-
- ```bash
- OPENWEATHER_API_KEY=your-api-key-here
- ```
-
-
-
-## Create your server
-
-
-
- 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
- ```
-
-
-
- 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")
- ```
-
-
-
- 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)}")
-
- ```
-
-
-
- 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)}")
- ```
-
-
-
- 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()
- )
- ```
-
-
-
- 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']
- ```
-
-
-
-
-
-
-## Connect to Claude Desktop
-
-
-
- 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"
- }
- }
- }
- }
- ```
-
-
-
- 1. Quit Claude completely
-
- 2. Start Claude again
-
- 3. Look for your weather server in the 🔌 menu
-
-
-
-## Try it out!
-
-
-
- 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?
- ```
-
-
-
- Ask Claude:
-
- ```
- Can you get me a 5-day forecast for Tokyo and help me plan what clothes to pack for my trip?
- ```
-
-
-
- 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?
- ```
-
-
-
-## Understanding the code
-
-
-
- ```python
- async def read_resource(self, uri: str) -> ReadResourceResult:
- # ...
- ```
-
- Python type hints help catch errors early and improve code maintainability.
-
-
-
- ```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.
-
-
-
- ```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.
-
-
-
- ```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.
-
-
-
-## Best practices
-
-
-
- ```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)}"
- )
- ```
-
-
-
- ```python
- if not isinstance(args, dict) or "city" not in args:
- raise McpError(
- ErrorCode.INVALID_PARAMS,
- "Invalid forecast arguments"
- )
- ```
-
-
-
- ```python
- if not API_KEY:
- raise ValueError("OPENWEATHER_API_KEY is required")
- ```
-
-
-
-
-## 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
-
-
-
- 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)]
- ```
-
-
-
- ```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
- ```
-
-
-
- ```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...
- ```
-
-
-
- ```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)}")
- ```
-
-
-
- ```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"
- )
- ]
- ```
-
-
-
-## Testing
-
-
-
- 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"]
- ```
-
-
- ```bash
- uv add --dev pytest
- uv run pytest
- ```
-
-
-
-## 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
-
-
-
- Learn more about the MCP architecture
-
-
-
- Check out the Python SDK on GitHub
-
-
diff --git a/docs/first-server/typescript.mdx b/docs/first-server/typescript.mdx
deleted file mode 100644
index 0f39531..0000000
--- a/docs/first-server/typescript.mdx
+++ /dev/null
@@ -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.
-
-
-This guide uses the OpenWeatherMap API. You'll need a free API key from [OpenWeatherMap](https://openweathermap.org/api) to follow along.
-
-
-## Prerequisites
-
-
-
- You'll need Node.js 18 or higher:
-
- ```bash
- node --version # Should be v18 or higher
- npm --version
- ```
-
-
-
- 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
- ```
-
-
-
- ```bash
- npm install --save axios dotenv
- ```
-
-
-
- Create `.env`:
- ```bash
- OPENWEATHER_API_KEY=your-api-key-here
- ```
- Make sure to add your environment file to `.gitignore`
- ```bash
- .env
- ```
-
-
-
-## Create your server
-
-
-
- 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")
- );
- }
- ```
-
-
-
- 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 {
- 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);
- ```
-
-
-
- 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(
- 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;
- }
- }
- );
- }
- ```
-
-
-
- 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;
- }
- }
- );
- }
- ```
-
-
-
- ```bash
- npm run build
- ```
-
-
-
-## Connect to Claude Desktop
-
-
-
- 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",
- }
- }
- }
- }
- ```
-
-
-
- 1. Quit Claude completely
- 2. Start Claude again
- 3. Look for your weather server in the 🔌 menu
-
-
-
-## Try it out!
-
-
-
- Ask Claude:
- ```
- What's the current weather in San Francisco? Can you analyze the conditions?
- ```
-
-
-
- Ask Claude:
- ```
- Can you get me a 5-day forecast for Tokyo and tell me if I should pack an umbrella?
- ```
-
-
-
- Ask Claude:
- ```
- Can you analyze the forecast for both Tokyo and San Francisco and tell me which city will be warmer this week?
- ```
-
-
-
-## Understanding the code
-
-
-
- ```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.
-
-
-
- ```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.
-
-
-
- ```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.
-
-
-
-## Best practices
-
-
-
- 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;
- }
- ```
-
-
-
- ```typescript
- function isValidForecastArgs(args: any): args is GetForecastArgs {
- return (
- typeof args === "object" &&
- args !== null &&
- "city" in args &&
- typeof args.city === "string"
- );
- }
- ```
-
- You can also use libraries like [Zod](https://zod.dev/) to perform this validation automatically.
-
-
-
-## 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
-
-
- The following troubleshooting tips are for macOS. Guides for other platforms are coming soon.
-
-
-### 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
-
-
-
- Learn more about the MCP architecture
-
-
-
- Check out the TypeScript SDK on GitHub
-
-
-
-
-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.
-