Turn any Python application into an MCP server that Claude can use. This lesson covers the FastMCP SDK, implementing tools and resources, input validation, and local testing.
The mcp Python package provides FastMCP, a high-level framework for building MCP servers with minimal boilerplate.
pip install "mcp[cli]"
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.tool()
def hello(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
Key features: Automatic JSON Schema generation from type hints, built-in stdio and SSE transports, resource URI routing, and a development inspector.
An MCP server goes through three phases:
initialize request. Server responds with capabilities.With FastMCP, the lifecycle is handled automatically. You just define your tools and resources, and the framework manages the protocol.
Note: For stdio transport, the server runs as a subprocess. It starts when the host launches it and stops when the host kills the process. For SSE, the server runs as a long-lived web service.
Tools are functions the LLM can call. FastMCP uses decorators and type hints to generate schemas automatically.
@mcp.tool()
def add_note(title: str, content: str) -> str:
"""Add a new note to the collection.
Args:
title: Short title for the note
content: Full text of the note
"""
notes[title] = content
return f"Note '{title}' saved ({len(content)} chars)"
@mcp.tool()
def list_notes() -> str:
"""List all saved notes with their titles."""
if not notes:
return "No notes yet."
return "\n".join(f"- {title}" for title in notes)
The @mcp.tool() decorator registers the function as an MCP tool. The docstring becomes the tool description. Type hints become the JSON Schema input definition.
Resources provide data that the host can load into context. They use URI patterns for routing.
@mcp.resource("notes://all")
def get_all_notes() -> str:
"""Return all notes as formatted text."""
if not notes:
return "No notes saved."
parts = [f"## {title}\n{content}" for title, content in notes.items()]
return "\n\n".join(parts)
@mcp.resource("notes://{title}")
def get_note(title: str) -> str:
"""Return a specific note by title."""
if title not in notes:
return f"Note '{title}' not found."
return notes[title]
URI templates: Use {param} in the URI pattern to capture dynamic segments. FastMCP extracts the parameter and passes it to your function.
The MCP CLI includes an Inspector for testing servers without connecting to Claude.
# Run your server with the inspector
mcp dev notes_server.py
# Or test via the CLI
mcp run notes_server.py
The Inspector provides a web UI where you can:
Workflow: Develop with mcp dev for rapid iteration. When ready, add the server to claude_desktop_config.json for integration testing with Claude.
Live demonstration: building and testing a notes MCP server.
notes_server.py with FastMCPadd_note and list_notes toolsnotes://all resourcemcp dev notes_server.pydemo/notes_server.py for the complete working server. Students can run it immediately.echo tooladd_note + list_notes toolsnotes:// resource URIsearch_notes tool with keyword matching