Model Context Protocol (MCP) and FastMCP

Expose tools to any MCP-capable client with FastMCP, and consume them from your own agents.

πŸ•ΈοΈ Module 4 7 min read Not started

Why this matters

MCP (Model Context Protocol, by Anthropic, late 2024) is the open standard that lets any LLM client (Claude Desktop, Claude Code, Cursor, ChatGPT desktop, IDEs) talk to any tool or data source. It is to AI what USB is to peripherals β€” and it has become an essential 2026 skill. The job-board hit rate for "MCP" exploded between mid-2025 and 2026.

If your tools live behind an MCP server, every LLM client can use them without changing a line of integration code on your side.

Learning objectives

  1. Understand the 3 MCP primitives: tools, resources, prompts.
  2. Build an MCP server with FastMCP 3.x.
  3. Run it over stdio (local) and http (remote).
  4. Connect the server to Claude Desktop, Claude Code, Cursor.
  5. Consume MCP servers from Python code.

1. The MCP mental model

[LLM client] ←→ MCP protocol ←→ [MCP server] ←→ your code/data

Three primitives a server exposes:

  • Tools β€” functions the model can call to do something. Model-controlled.
  • Resources β€” read-only URIs the client can fetch as context. App-controlled.
  • Prompts β€” pre-canned templates the user invokes via UI. User-controlled.

You write each as a Python function with a decorator.


2. FastMCP β€” the standard Python framework

FastMCP (now FastMCP 3.0+, January 2026) is the recommended way to build MCP servers in Python. Decorator-based, ~5x less boilerplate than the raw SDK.

powershell
uv add fastmcp

Hello, MCP

python
# server.py
from fastmcp import FastMCP

mcp = FastMCP(name="DemoServer", instructions="A demo MCP server.")

@mcp.tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

@mcp.tool
def get_weather(city: str) -> dict:
    """Return current weather for a city."""
    # imagine a real API call
    return {"city": city, "tempC": 32, "condition": "humid"}

@mcp.resource("config://app")
def get_config() -> dict:
    """Read-only application config."""
    return {"version": "1.0.0", "owner": "you"}

@mcp.resource("greetings://{name}")
def hello(name: str) -> str:
    """Personalised greeting."""
    return f"Hello {name}!"

@mcp.prompt
def summarise(url: str) -> str:
    """Reusable prompt to summarise a URL."""
    return (
        f"Please fetch {url} using available tools and summarise it in 5 bullets."
    )

if __name__ == "__main__":
    mcp.run()  # default: stdio transport

mcp.run() runs over stdio by default (the LLM client launches your script as a subprocess). Add transport="http" for remote.

Test it locally with the inspector

powershell
fastmcp dev server.py

This opens a web UI where you can call your tools, fetch resources, and check prompts. Use it during development as a unit-test harness.


3. Connect to Claude Desktop, Cursor, Claude Code

Claude Desktop

%APPDATA%\Claude\claude_desktop_config.json:

json
{
  "mcpServers": {
    "demo": {
      "command": "uv",
      "args": ["run", "python", "server.py"],
      "cwd": "C:/path/to/your/project"
    }
  }
}

Restart Claude Desktop. The hammer icon shows your server's tools. Ask Claude "What is the weather in Mumbai?" and it will call your get_weather tool.

Cursor

.cursor/mcp.json in your project (or ~/.cursor/mcp.json globally):

json
{
  "mcpServers": {
    "demo": {
      "command": "uv",
      "args": ["run", "python", "C:/path/to/your/project/server.py"]
    }
  }
}

Claude Code (CLI)

bash
claude mcp add demo "uv run python /path/to/server.py"

4. HTTP transport (remote MCP servers)

For shared/team servers, run over HTTP:

python
if __name__ == "__main__":
    mcp.run(transport="http", host="0.0.0.0", port=8765)

Clients add a remote entry:

json
{
  "mcpServers": {
    "remote-demo": { "url": "https://my-mcp.example.com/mcp" }
  }
}

Recent 2026 best practice: protect HTTP MCP servers with OAuth 2.1 / bearer tokens and rate limits. FastMCP 3 has first-class Authorization providers.


5. Async tools, errors, and progress

python
@mcp.tool
async def fetch_url(url: str) -> str:
    """Fetch text content of a URL."""
    import httpx
    async with httpx.AsyncClient(timeout=10) as c:
        r = await c.get(url)
        r.raise_for_status()
        return r.text[:5000]

For long-running tools you can stream progress to the client via ctx.report_progress(...):

python
from fastmcp import Context

@mcp.tool
async def long_task(n: int, ctx: Context) -> str:
    for i in range(n):
        await ctx.report_progress(i, n)
        # do work
    return "done"

Raise standard Python exceptions for errors β€” FastMCP serialises them as MCP error payloads.


6. Consuming MCP servers from Python

You can also be the client β€” write Python that talks to an MCP server.

python
from fastmcp import Client

async def main():
    async with Client("server.py") as client:
        tools = await client.list_tools()
        print([t.name for t in tools])
        out = await client.call_tool("add", {"a": 2, "b": 3})
        print(out)

import asyncio; asyncio.run(main())

LangChain and LangGraph have an MCP adapter (langchain-mcp-adapters) that loads MCP tools as LangChain tools β€” useful for plugging an existing ecosystem of MCP servers into your agent.

python
# uv add langchain-mcp-adapters
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
    "files": {"command": "uv", "args": ["run", "python", "files_server.py"], "transport": "stdio"},
    "github": {"url": "http://localhost:8765/mcp", "transport": "http"},
})
tools = await client.get_tools()
agent = create_react_agent(model="openai:gpt-4.1-mini", tools=tools)

7. Picking what to expose

Good MCP server candidates:

  • Internal databases / data warehouses (read tools).
  • Ticketing systems (Jira, Linear) β€” create/update tickets.
  • Filesystem operations.
  • Company-specific APIs (CRM, billing).
  • Code-search / repo browsing.
  • Cloud control planes (carefully gated).

Bad ones:

  • Anything that takes irreversible high-impact actions without confirmation.
  • High-cardinality APIs with thousands of operations β€” split into multiple narrow servers.

8. Security and governance (mandatory in 2026)

  • Auth: OAuth 2.1 with scopes; never run public HTTP MCP servers without auth.
  • Allow-lists: restrict which clients can register the server.
  • Side-effect gating: every mutating tool requires confirm: bool (see Lesson 3.5).
  • Audit logs: log every tool call with client_id, user_id, args, result.
  • Rate limits: per-tool, per-client.
  • PII / secret redaction: sanitise outputs before returning.
  • Sandbox shell tools: never expose raw shell to a model. Whitelist commands.

OWASP and the LLM security community have already published 2026 guidance covering MCP β€” read it before exposing anything to production.


Hands-on lab (6 hours)

Build a personal-knowledge MCP server that exposes your local Markdown notes:

Tools:

  • search_notes(query: str, top_k: int) β€” vector search over your notes (Chroma).
  • add_note(title, content, tags) β€” append a new note.
  • get_note(slug) β€” fetch by slug.

Resources:

  • notes://{slug} β€” read a note as a resource.
  • notes://recent β€” last 10 modified notes.

Prompts:

  • daily_review() β€” returns a templated prompt asking Claude to summarise today's notes.

Then:

  1. Wire it into Claude Desktop, Cursor, and Claude Code.
  2. Add OAuth bearer for HTTP mode.
  3. Add structured logging and rate limit (5 calls/min per client).
  4. Build a Python client that calls it from a create_react_agent LangGraph agent.

This is one of the highest-signal portfolio pieces in 2026 because it shows you understand the protocol, not just one framework.


Common pitfalls

  1. Forgetting mcp.run() β€” server starts but never serves.
  2. Mixing stdio and http transports in one process. Pick one.
  3. Tools that print to stdout in stdio mode. Stdio is the protocol channel β€” corrupting it breaks everything. Log to stderr or a file.
  4. No auth on HTTP servers β€” anyone with the URL can run your tools.
  5. Returning huge resources. Cap output; paginate.
  6. One mega-server. Many small purpose-built servers are easier to maintain and audit.

Self-check

  1. What is the difference between an MCP tool and an MCP resource?
  2. Why is stdio fragile to print() calls?
  3. When do you pick HTTP transport over stdio?
  4. How does langchain-mcp-adapters help an existing LangGraph project?
  5. What 3 governance controls should every public MCP server have?

References

Sign in to save your progress and earn badges.