MCP Inspector with mcp dev
The MCP Inspector is a developer tool that connects to a server and shows its tools, resources and prompts, so you see what a server offers before any model does.
Last updated: 29 Sep, 2026 · MCP 2.2
You have built a server and called it from code. Before you connect a server to a host, you want to see what it exposes and try a tool by hand. MCP gives you two ways to do that: the Inspector web app launched by mcp dev, and the same listing calls from your own code.
The MCP crash course runs the Inspector on its weather server with uv run mcp dev server/weather.py. The Inspector opens as a local web page and asks for a transport type. With STDIO it starts the server as a command, connects, lists the tools, and runs get_alerts with the state CA. The result is the list of active weather alerts for California, fetched from the US National Weather Service.
The Inspector in the video offers STDIO and SSE. SSE was the older HTTP transport; the current specification defines stdio and Streamable HTTP, and this course uses those two. The video's server also imports FastMCP from mcp.server.fastmcp, which is MCPServer from mcp.server in mcp 2.2.
Running the weather server from the videoOptional
The server calls api.weather.gov, a free US government API that needs no key, only a User-Agent header. It makes the request with httpx, so install that first:
pip install "httpx==0.28.1"Save the video's server as nws_weather.py; the video calls it server/weather.py, and a different name keeps it apart from the other weather server later in the course. Only the import and the class name differ from the video. The echo resource at the end returns in the resource templates lesson.
from typing import Any
import httpx
from mcp.server import MCPServer
# Initialize the MCP server
mcp = MCPServer("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.resource("echo://{message}")
def echo_resource(message: str) -> str:
"""Echo a message as a resource"""
return f"Resource echo: {message}"
The Inspector's run, get_alerts for CA, written as a client call:
import asyncio
from mcp import Client
from nws_weather import mcp
async def main():
async with Client(mcp) as client:
result = await client.call_tool("get_alerts", {"state": "CA"})
print(result.content[0].text[:500])
asyncio.run(main())
Event: Dense Fog Advisory
Area: Northern Salinas Valley/Hollister Valley and Carmel Valley; Southern Monterey Bay and Big Sur Coast
Severity: Moderate
Description: Widespread dense fog has ended. However, patchy dense fog will linger
through 930 AM.
Instructions: None
---
Event: Coastal Flood Advisory
Area: San Diego County Coastal Areas; Orange County Coastal
Severity: Minor
Description: * WHAT...Minor coastal flThe alerts are live data, so your run prints whatever is active in California when you run it, or No active alerts for this state. The first 500 characters are printed here; drop [:500] to see every alert. Above the alerts your terminal also shows a log line, INFO HTTP Request: GET https://api.weather.gov/.... MCPServer logs to standard error at INFO level, and later lessons print more of these lines, such as a rejected argument or the traceback of a failed tool; the outputs on these pages show only what your script prints.
The rest of this lesson inspects the shop server, first with mcp dev, then with the same listing calls in code.
The mcp dev command
mcp dev loads your server file and opens the Inspector, a local web app, in your browser. There you pick a tool, fill its arguments in a form, and read the result, with no host and no model involved. It needs the [cli] extra from the setup lesson, Node.js for the web app, and uv, because the Inspector starts your server with uv run in a fresh environment. That environment gets mcp without the [cli] extra, so --with typer adds the one package the server command is missing; without it, Connect fails with Connection closed. The first run asks npx to install the Inspector package, and it opens in your browser, so it is shown here as a command rather than captured output.
mcp dev shop.py --with typerThe page lists the server's tools, resources and prompts, and lets you call each one and watch the JSON-RPC messages go back and forth. It is the MCP counterpart of a run transcript: a window on what the server did.
Listing a server in code
The same four listing calls the Inspector makes are methods on the in-memory client. They run keyless, with no browser, so they belong in a small script you can keep.
tools = await client.list_tools()
resources = await client.list_resources()
templates = await client.list_resource_templates()
prompts = await client.list_prompts()View the code here
from mcp.server import MCPServer
mcp = MCPServer("Shop support")
ORDERS = {
"A17": {"item": "blue mug", "status": "shipped", "total": 12.50},
"B42": {"item": "desk lamp", "status": "processing", "total": 48.00},
}
@mcp.tool()
def lookup_order(order_id: str) -> str:
"""Look up an order by its id and say where it is."""
order = ORDERS[order_id]
return f"Order {order_id}: {order['item']}, {order['status']}."
Inspecting the shop server in code
Run the four listings against the server as it stands after the last lesson: one tool, with no resources or prompts yet.
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
print("tools:", [t.name for t in tools.tools])
resources = await client.list_resources()
print("resources:", [str(r.uri) for r in resources.resources])
templates = await client.list_resource_templates()
print("templates:", [t.uri_template for t in templates.resource_templates])
prompts = await client.list_prompts()
print("prompts:", [p.name for p in prompts.prompts])
asyncio.run(main())tools: ['lookup_order'] resources: [] templates: [] prompts: []
What the listing shows now
- tools holds
lookup_order, the one tool registered so far. - resources and templates are empty until Resources and Resource templates add them.
- prompts is empty until Prompts adds one. Run this script again after those lessons and the same four lines fill in.
Reading one tool's schema
Listing gives more than names. Each tool carries its description and input schema, the same fields a host reads to decide when and how to call it.
import asyncio
from mcp import Client
from shop import mcp
async def main():
async with Client(mcp) as client:
tools = await client.list_tools()
for tool in tools.tools:
print(tool.name, "-", tool.description)
print(" args:", list(tool.input_schema["properties"]))
asyncio.run(main())lookup_order - Look up an order by its id and say where it is. args: ['order_id']
The Inspector vs listing in code
| mcp dev Inspector | Listing in code | |
|---|---|---|
| Runs where | A web app in your browser | In a Python script |
| Needs | The [cli] extra, Node.js and uv | The mcp package only |
| Best for | Poking a server by hand | A repeatable check you save |
| Calls a tool | Through a form you fill | With call_tool |
When to inspect a server
- Right after adding a tool, to confirm its name, description and arguments are what you meant.
- Before connecting a server to Claude Code or another host, to see what that host will be offered.
- When a host cannot see a tool, to check whether the server lists it at all.
mcp dev loads your server file, so an error while importing it means the Inspector shows nothing until you fix the import. The in-memory listing fails the same way, which is why running it as a script catches the problem early.Related
- Previous: MCP client
- Next: Tool arguments
- Reference: MCP Inspector
- Add a
count_orders() -> inttool toshop.pyand run the listing again. - Run
mcp dev shop.py --with typerif you have the[cli]extra, Node and uv, and calllookup_orderfrom the form. - Break an import in
shop.py: mistypeMCPServer, run the listing, and read the error.
Every expert started right here.