Overview
The Model Context Protocol (MCP) is an open standard for connecting AI applications to external tools and data. An MCP server is a small program you write that exposes capabilities; an MCP host (Claude Code, Claude Desktop, and other clients) connects to it and lets Claude use what it offers. Once a server is connected to Claude Code, you can ask Claude to query your database, read an issue tracker, or call an API directly instead of pasting data into the chat.
This guide walks through the official MCP quickstart: a small weather server written in Python that exposes two tools, then how to connect it to Claude Code with claude mcp add. The same flow applies whether you write the server in Python, TypeScript, or another supported SDK.
What a server can expose
MCP servers provide three kinds of capabilities. Most servers start with tools.
| Primitive |
What it is |
Who triggers it |
Typical use |
| Tools |
Functions the model can call (with user approval) |
The LLM, during a task |
Run a query, call an API, perform an action |
| Resources |
File-like data identified by a URI that clients can read |
The user / client (e.g. @server:protocol://path in Claude Code) |
API responses, file contents, schemas |
| Prompts |
Pre-written templates that help accomplish a task |
The user (surface as /mcp__server__prompt commands) |
Reusable workflows and canned instructions |
Build a minimal server (Python)
Requirements
- Python 3.10 or higher
- The Python MCP SDK version 1.2.0 or higher
uv for project and dependency management
When implementing a stdio-based server, never write to stdout (for example with print()): stdout carries the JSON-RPC messages, and writing to it corrupts the protocol and breaks the server. Log to stderr instead (print(..., file=sys.stderr) or the logging module).
Set up the project
After installing uv, create and set up the project:
# Create a new directory for our project
uv init weather
cd weather
# Create virtual environment and activate it
uv venv
source .venv/bin/activate
# Install dependencies
uv add "mcp[cli]" httpx
# Create our server file
touch weather.py
Write the server
FastMCP uses Python type hints and docstrings to generate tool definitions automatically, so each @mcp.tool() function becomes a callable tool. Put the following in weather.py. It queries the US National Weather Service API and exposes get_alerts and get_forecast:
from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP
# Initialize FastMCP server
mcp = FastMCP("weather")
# Constants
NWS_API_BASE = "https://api.weather.gov"
USER_AGENT = "weather-app/1.0"
async def make_nws_request(url: str) -> dict[str, Any] | None:
"""Make a request to the NWS API with proper error handling."""
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:
"""Format an alert feature into a readable string."""
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
"""
# First get the forecast grid endpoint
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."
# Get the forecast URL from the points response
forecast_url = points_data["properties"]["forecast"]
forecast_data = await make_nws_request(forecast_url)
if not forecast_data:
return "Unable to fetch detailed forecast."
# Format the periods into a readable forecast
periods = forecast_data["properties"]["periods"]
forecasts = []
for period in periods[:5]: # Only show next 5 periods
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)
def main():
# Initialize and run the server
mcp.run(transport="stdio")
if __name__ == "__main__":
main()
Start it locally with uv run weather.py. The server runs over stdio and listens for messages from an MCP host.
TypeScript instead? The quickstart's TypeScript version uses @modelcontextprotocol/sdk and zod, registers tools with server.registerTool(...), and runs over StdioServerTransport. The structure mirrors the Python version above.
Choosing a transport
A transport is how the host and server exchange JSON-RPC messages. The minimal server above uses stdio. Remote servers use HTTP.
| Transport |
How it runs |
Best for |
Notes |
| stdio |
Local process the host launches and talks to over stdin/stdout |
Local tools, scripts, anything needing direct system access |
Never log to stdout; Claude Code does not auto-reconnect local processes |
| HTTP (Streamable HTTP) |
Remote server reached over HTTP |
Cloud-hosted services |
Recommended remote transport; supports OAuth; streamable-http is the spec name and an accepted alias for http |
| SSE |
Remote server using Server-Sent Events |
Legacy remote servers |
Deprecated in Claude Code; use HTTP where available |
Connect the server to Claude Code
Add the server with claude mcp add. For a local stdio server, everything after the -- separator is the command Claude Code runs to launch your server:
claude mcp add weather -- uv --directory /ABSOLUTE/PATH/TO/weather run weather.py
The -- (double dash) separates Claude Code's own options (--transport, --env, --scope) from the command that starts the server. Everything after -- is passed through untouched. Use the absolute path to your project directory.
Pass secrets to the process with --env:
claude mcp add --env API_KEY=your-key weather -- uv --directory /ABSOLUTE/PATH/TO/weather run weather.py
When using --env, place at least one other option between --env and the server name. If the name comes directly after --env, the CLI reads it as another KEY=value pair and rejects it.
For a remote server, add it by URL instead:
# Recommended remote transport
claude mcp add --transport http my-remote-server https://mcp.example.com/mcp
Choose a scope
--scope controls where the configuration is stored and who sees it:
| Scope |
Loads in |
Shared with team |
Stored in |
local (default) |
Current project only |
No |
~/.claude.json |
project |
Current project only |
Yes, via version control |
.mcp.json in project root |
user |
All your projects |
No |
~/.claude.json |
# Share the server with your team via a checked-in .mcp.json
claude mcp add --scope project weather -- uv --directory /ABSOLUTE/PATH/TO/weather run weather.py
For security, Claude Code prompts for approval before using project-scoped servers from a .mcp.json file.
Manage and verify
# List all configured servers
claude mcp list
# Get details for one server
claude mcp get weather
# Remove a server
claude mcp remove weather
Inside a Claude Code session, run /mcp to check connection status and the tool count for each server. With tools connected, you can ask Claude to use them in plain language, for example: "What are the active weather alerts in CA?"
Using resources and prompts in Claude Code
If your server exposes resources, reference them with @ mentions using the format @server:protocol://resource/path, for example @github:issue://123. Resources appear in the @ autocomplete alongside files and are fetched automatically when referenced.
If your server exposes prompts, they show up as slash commands in the form /mcp__servername__promptname. Pass arguments space-separated after the command, for example /mcp__github__pr_review 456.
Security notes
- Tools run with the privileges of the process you launch, so only connect servers you trust. Servers that fetch external content can expose you to prompt-injection risk.
- Tool calls require user approval before they execute.
- Remote servers often require authentication. Claude Code supports OAuth 2.0: add the server, then run
/mcp and complete the browser login. Tokens are stored securely and refreshed automatically.
- Have Claude scaffold a server for you with the official
mcp-server-dev plugin: /plugin install mcp-server-dev@claude-plugins-official, then /mcp-server-dev:build-mcp-server.
References