Skip to main content
Glama

MCP Server Boilerplate

mcp-guide.md9 kB
# MCP Server Development Guide ## Overview Build an MCP weather server with two tools: `get_alerts` and `get_forecast`. MCP servers provide: 1. **Resources**: File-like data 2. **Tools**: Functions callable by LLMs 3. **Prompts**: Pre-written templates ## Python Implementation ### Setup ```bash curl -LsSf https://astral.sh/uv/install.sh | sh uv init weather && cd weather uv venv && source .venv/bin/activate uv add "mcp[cli]" httpx touch weather.py ``` ### Code ```python from typing import Any import httpx from mcp.server.fastmcp import FastMCP mcp = FastMCP("weather") NWS_API_BASE = "https://api.weather.gov" USER_AGENT = "weather-app/1.0" async def make_nws_request(url: str) -> dict[str, Any] | None: headers = {"User-Agent": USER_AGENT, "Accept": "application/geo+json"} async with httpx.AsyncClient() as client: try: response = await client.get(url, headers=headers, timeout=30.0) response.raise_for_status() return response.json() except Exception: return None def format_alert(feature: dict) -> str: props = feature["properties"] return f""" Event: {props.get('event', 'Unknown')} Area: {props.get('areaDesc', 'Unknown')} Severity: {props.get('severity', 'Unknown')} Description: {props.get('description', 'No description available')} Instructions: {props.get('instruction', 'No specific instructions provided')} """ @mcp.tool() async def get_alerts(state: str) -> str: """Get weather alerts for a US state. Args: state: Two-letter US state code (e.g. CA, NY) """ url = f"{NWS_API_BASE}/alerts/active/area/{state}" data = await make_nws_request(url) if not data or "features" not in data: return "Unable to fetch alerts or no alerts found." if not data["features"]: return "No active alerts for this state." alerts = [format_alert(feature) for feature in data["features"]] return "\n---\n".join(alerts) @mcp.tool() async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ points_url = f"{NWS_API_BASE}/points/{latitude},{longitude}" points_data = await make_nws_request(points_url) if not points_data: return "Unable to fetch forecast data for this location." forecast_url = points_data["properties"]["forecast"] forecast_data = await make_nws_request(forecast_url) if not forecast_data: return "Unable to fetch detailed forecast." periods = forecast_data["properties"]["periods"] forecasts = [] for period in periods[:5]: forecast = f""" {period['name']}: Temperature: {period['temperature']}°{period['temperatureUnit']} Wind: {period['windSpeed']} {period['windDirection']} Forecast: {period['detailedForecast']} """ forecasts.append(forecast) return "\n---\n".join(forecasts) if __name__ == "__main__": mcp.run(transport='stdio') ``` Run: `uv run weather.py` ## TypeScript Implementation ### Setup ```bash mkdir weather && cd weather npm init -y npm install @modelcontextprotocol/sdk zod npm install -D @types/node typescript mkdir src && touch src/index.ts ``` ### package.json ```json { "type": "module", "bin": {"weather": "./build/index.js"}, "scripts": {"build": "tsc && chmod 755 build/index.js"}, "files": ["build"] } ``` ### tsconfig.json ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` ### Code (src/index.ts) ```typescript import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; const NWS_API_BASE = "https://api.weather.gov"; const USER_AGENT = "weather-app/1.0"; const server = new McpServer({ name: "weather", version: "1.0.0", capabilities: { resources: {}, tools: {} } }); async function makeNWSRequest<T>(url: string): Promise<T | null> { const headers = {"User-Agent": USER_AGENT, Accept: "application/geo+json"}; try { const response = await fetch(url, { headers }); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return (await response.json()) as T; } catch (error) { console.error("Error making NWS request:", error); return null; } } interface AlertFeature { properties: { event?: string; areaDesc?: string; severity?: string; status?: string; headline?: string; }; } function formatAlert(feature: AlertFeature): string { const props = feature.properties; return [ `Event: ${props.event || "Unknown"}`, `Area: ${props.areaDesc || "Unknown"}`, `Severity: ${props.severity || "Unknown"}`, `Status: ${props.status || "Unknown"}`, `Headline: ${props.headline || "No headline"}`, "---" ].join("\n"); } server.tool( "get_alerts", "Get weather alerts for a state", { state: z.string().length(2).describe("Two-letter state code (e.g. CA, NY)") }, async ({ state }) => { const stateCode = state.toUpperCase(); const alertsUrl = `${NWS_API_BASE}/alerts?area=${stateCode}`; const alertsData = await makeNWSRequest<{features: AlertFeature[]}>(alertsUrl); if (!alertsData) { return { content: [{ type: "text", text: "Failed to retrieve alerts data" }] }; } const features = alertsData.features || []; if (features.length === 0) { return { content: [{ type: "text", text: `No active alerts for ${stateCode}` }] }; } const formattedAlerts = features.map(formatAlert); const alertsText = `Active alerts for ${stateCode}:\n\n${formattedAlerts.join("\n")}`; return { content: [{ type: "text", text: alertsText }] }; } ); server.tool( "get_forecast", "Get weather forecast for a location", { latitude: z.number().min(-90).max(90).describe("Latitude of the location"), longitude: z.number().min(-180).max(180).describe("Longitude of the location") }, async ({ latitude, longitude }) => { const pointsUrl = `${NWS_API_BASE}/points/${latitude.toFixed(4)},${longitude.toFixed(4)}`; const pointsData = await makeNWSRequest<{properties: {forecast?: string}}>(pointsUrl); if (!pointsData) { return { content: [{ type: "text", text: "Failed to retrieve grid point data" }] }; } const forecastUrl = pointsData.properties?.forecast; if (!forecastUrl) { return { content: [{ type: "text", text: "Failed to get forecast URL" }] }; } const forecastData = await makeNWSRequest<{properties: {periods: any[]}}>(forecastUrl); if (!forecastData) { return { content: [{ type: "text", text: "Failed to retrieve forecast data" }] }; } const periods = forecastData.properties?.periods || []; const formattedForecast = periods.map((period: any) => [ `${period.name || "Unknown"}:`, `Temperature: ${period.temperature || "Unknown"}°${period.temperatureUnit || "F"}`, `Wind: ${period.windSpeed || "Unknown"} ${period.windDirection || ""}`, `${period.shortForecast || "No forecast available"}`, "---" ].join("\n") ); const forecastText = `Forecast for ${latitude}, ${longitude}:\n\n${formattedForecast.join("\n")}`; return { content: [{ type: "text", text: forecastText }] }; } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Weather MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); }); ``` Build: `npm run build` ## Claude Desktop Configuration Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: **Python:** ```json { "mcpServers": { "weather": { "command": "uv", "args": ["--directory", "/ABSOLUTE/PATH/TO/weather", "run", "weather.py"] } } } ``` **TypeScript:** ```json { "mcpServers": { "weather": { "command": "node", "args": ["/ABSOLUTE/PATH/TO/weather/build/index.js"] } } } ``` Restart Claude Desktop after saving. ## Testing Ask Claude: - "What's the weather in Sacramento?" - "What are the active weather alerts in Texas?" Look for the tools icon in Claude Desktop to verify server connection. ## Important Notes - **STDIO Servers**: Never use `print()`, `console.log()`, or stdout - use stderr for logging - **Paths**: Use absolute paths in config - **US Only**: NWS API only works for US locations - **Restart**: Always restart Claude Desktop after config changes ## Troubleshooting Check logs: `tail -f ~/Library/Logs/Claude/mcp*.log` Common issues: - Incorrect absolute paths - JSON syntax errors in config - Server build failures - Missing dependencies

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CaptainCrouton89/mongo-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server