# fastMCP-tutorial **Repository Path**: openminds/fastMCP-tutorial ## Basic Information - **Project Name**: fastMCP-tutorial - **Description**: No description available - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-02 - **Last Updated**: 2026-06-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Building MCP Servers in Python with FastMCP A step-by-step, opinionated tutorial covering the current state of FastMCP (3.2.4, April 2026), including generative UI, clients, and the battle-tested practices for shipping servers that agents actually use well. --- ## Part 1: The Mental Model Before you write a line of code, get these three words straight. They run through every page of FastMCP's docs and every design decision you'll make. **Component.** A Tool, a Resource, or a Prompt. This is what the model sees. A Tool runs code (like a POST endpoint), a Resource returns data (like a GET endpoint), a Prompt is a reusable instruction template. **Provider.** Where components come from. A decorator on a Python function is one provider. A directory of files is another. A remote MCP server you proxy is another. A FastMCP server is itself a Provider that happens to speak the MCP protocol. **Transform.** Middleware for Providers. You can rename tools, hide them from certain users, add auth, wrap responses. Transforms let you reshape behavior without touching the original code. If you understand Component, Provider, Transform, you understand FastMCP. Everything else is a specialization. One more thing worth internalizing: MCP is a protocol, FastMCP is the framework. MCP is the agreed-upon way for LLM hosts (Claude Desktop, Cursor, ChatGPT) to talk to tool servers. FastMCP is the Python framework that handles the protocol plumbing so you write business logic instead. FastMCP 1.0 was absorbed into the official `mcp` SDK in 2024. FastMCP 2.0+ is the actively maintained standalone project that ships features far beyond the core SDK, and some version of it powers roughly 70% of MCP servers in the wild. --- ## Part 2: Installation and First Server You need Python 3.10 or higher. Install with `uv` (recommended) or pip: ```bash # Recommended uv add fastmcp # Or with pip pip install fastmcp # With Apps support (for generative UI and interactive components) uv add "fastmcp[apps]" ``` Verify: ```bash fastmcp version ``` Now the canonical first server. Create `server.py`: ```python from fastmcp import FastMCP mcp = FastMCP("Demo") @mcp.tool def add(a: int, b: int) -> int: """Add two numbers.""" return a + b if __name__ == "__main__": mcp.run() ``` That's a complete MCP server. Run it: ```bash python server.py ``` Two things are happening under the hood worth understanding. First, the `@mcp.tool` decorator inspects your function's signature and docstring and generates a JSON schema the LLM can reason over. The type hints become input validation. The docstring becomes the tool description. Second, `mcp.run()` with no arguments defaults to the STDIO transport, which means the server talks over standard input/output. That's the right default for local tools launched by a desktop client like Claude Desktop. --- ## Part 3: The Three Primitives (Tools, Resources, Prompts) ### Tools: things the LLM can do Tools execute code and produce side effects. Keep them verb-named, narrowly scoped, and clear about what they change. ```python from typing import Literal from pydantic import BaseModel class WeatherReport(BaseModel): city: str temp_c: float conditions: str @mcp.tool def get_weather( city: str, units: Literal["celsius", "fahrenheit"] = "celsius", ) -> WeatherReport: """Get current weather for a city. Use this when the user asks about temperature, rain, or conditions for a specific named city. Do not use for general climate questions. """ # ... call your weather API ... return WeatherReport(city=city, temp_c=22.0, conditions="clear") ``` Two things to notice. The Pydantic return type gives you structured output for free, which modern clients render nicely. The docstring does double duty as the tool description the LLM reads when deciding whether to call it. Spend time on that docstring. It's the most leverage you have on tool-selection quality. ### Resources: things the LLM can read Resources expose data. Think of them as GET endpoints. Static resources map a URI to fixed content. Templated resources take parameters. ```python @mcp.resource("config://app/settings") def app_settings() -> dict: """Current app settings.""" return {"theme": "dark", "region": "ap-south-1"} @mcp.resource("users://{user_id}/profile") def user_profile(user_id: str) -> dict: """Fetch a user profile by ID.""" return {"id": user_id, "name": "Siddhant"} ``` Resources are for context the LLM loads into its working memory. Use them for reference material, configuration, or anything the agent should read before acting. Do not use them for anything with side effects. That's what tools are for. ### Prompts: reusable instruction templates Prompts are parameterized message templates the host can offer as commands. ```python @mcp.prompt def code_review(language: str, code: str) -> str: """Review code for bugs, style, and performance.""" return f"Review this {language} code for bugs and style:\n\n{code}" ``` In Claude Desktop this shows up as a slash command. Useful for common workflows you want to standardize across a team. --- ## Part 4: Transports (stdio, Streamable HTTP, In-Memory) You get three transports out of the box, and picking the right one is mostly determined by where your server runs and who calls it. **STDIO** is the default. The host launches your server as a subprocess and talks over pipes. Use this for local desktop integrations: Claude Desktop, Cursor, VS Code Copilot. Zero network config, zero auth, runs on your machine. ```python if __name__ == "__main__": mcp.run() # defaults to stdio ``` **Streamable HTTP** is the production transport. Your server runs as a web service with a single `/mcp` endpoint that handles both JSON responses and Server-Sent Events for streaming. Use this for remote servers, cloud deployments, multi-user scenarios, and anything that needs horizontal scaling. ```python if __name__ == "__main__": mcp.run(transport="http", host="0.0.0.0", port=8000) ``` Set `stateless_http=True` when you construct the server if you're deploying behind a load balancer or running multiple replicas. This prevents session-stickiness bugs. ```python mcp = FastMCP("Weather", stateless_http=True) ``` SSE was the older HTTP-based transport and is now deprecated in favor of Streamable HTTP. Don't start new projects with it. **In-Memory** is the testing transport. No subprocess, no network, same Python process. Ideal for unit tests. ```python from fastmcp import FastMCP, Client mcp = FastMCP("TestServer") @mcp.tool def greet(name: str) -> str: return f"Hello, {name}" # In a test async with Client(mcp) as client: result = await client.call_tool("greet", {"name": "World"}) assert result.data == "Hello, World" ``` --- ## Part 5: Context (Progress, Logging, Sampling, Elicitation) The `Context` object is how your tool talks back to the host at runtime. Request it as a parameter and FastMCP injects it. ```python from fastmcp import FastMCP, Context @mcp.tool async def process_files(files: list[str], ctx: Context) -> str: """Process a batch of files with progress updates.""" for i, f in enumerate(files): await ctx.info(f"Processing {f}") await ctx.report_progress(i, len(files)) # ... do work ... return f"Processed {len(files)} files" ``` Four Context capabilities are worth knowing cold: **Logging.** `ctx.info()`, `ctx.warning()`, `ctx.error()` stream structured log messages to the host. Use these instead of `print()`. **Progress.** `ctx.report_progress(current, total)` lets the host render a progress bar for long-running tools. If a tool might take more than a second, report progress. **Sampling.** `ctx.sample(prompt)` asks the host's LLM to generate text on your server's behalf. This is how your server calls the model without shipping its own API key. Powerful for tools that need summarization or classification inside their logic. **Elicitation.** `ctx.elicit(...)` asks the user for input mid-tool-call via a structured form. Use when a tool needs a confirmation or an extra parameter it doesn't have. This was one of the gamechangers of the 2025 MCP spec revision. ```python from pydantic import BaseModel class Confirmation(BaseModel): confirm: bool @mcp.tool async def delete_user(user_id: str, ctx: Context) -> str: response = await ctx.elicit( f"Really delete user {user_id}? This cannot be undone.", response_type=Confirmation, ) if not response.data.confirm: return "Cancelled." # ... actually delete ... return f"Deleted {user_id}" ``` --- ## Part 6: Generative UI and MCP Apps This is the big shift from 2025 to 2026. MCP tools used to only return text. Now they can return interactive UIs that render inside the chat: charts, dashboards, forms, tables, file uploaders. This is the **MCP Apps** extension, and FastMCP 3.2 builds on it with a Python component library called **Prefab**. You have three options, ordered from least to most effort. ### Option A: Built-in providers (one line) FastMCP ships ready-made app providers. Add them to your server with `add_provider()`: ```python from fastmcp import FastMCP from fastmcp.apps.providers import FileUpload, FormInput mcp = FastMCP("MyServer") mcp.add_provider(FileUpload()) mcp.add_provider(FormInput()) ``` You now have drag-and-drop file upload and Pydantic-driven forms with no extra code. There are five built-ins in 3.2: FileUpload, FormInput, Approval (human-in-the-loop), and a couple more. ### Option B: Tool-returns-UI (the sweet spot) Most of the time you want a specific tool to return a specific UI. Mark it with `app=True` and return a Prefab component tree: ```python from fastmcp import FastMCP from prefab_ui.app import PrefabApp from prefab_ui.components import Column, Heading from prefab_ui.components.charts import BarChart, ChartSeries mcp = FastMCP("Dashboard") @mcp.tool(app=True) def revenue_chart(year: int) -> PrefabApp: """Show annual revenue as an interactive bar chart.""" data = [ {"quarter": "Q1", "revenue": 42000}, {"quarter": "Q2", "revenue": 51000}, {"quarter": "Q3", "revenue": 47000}, {"quarter": "Q4", "revenue": 63000}, ] with Column(gap=4, css_class="p-6") as view: Heading(f"{year} Revenue") BarChart( data=data, series=[ChartSeries(data_key="revenue")], x_key="quarter", ) return PrefabApp(view) ``` When the user calls this tool in Claude Desktop or any Apps-capable host, they don't see a JSON blob. They see a rendered bar chart they can hover over. The fifty rows of underlying data never touch the model's context window, which is the other big win. ### Option C: Generative UI (the LLM writes the UI) When you don't know in advance what visualization the user will want, let the model compose one on the fly: ```python from fastmcp import FastMCP from fastmcp.apps.generative import GenerativeUI mcp = FastMCP("Prefab Studio") mcp.add_provider(GenerativeUI()) ``` One line. You now have: 1. A `generate_prefab_ui` tool the LLM can call with Python code. 2. A `search_prefab_components` tool so the LLM can discover what components exist. 3. A streaming renderer that paints the UI as the model writes it. When the user asks "show me sales trends broken down by region as a heatmap," the model writes Prefab code, streams it to the renderer, and the user sees the UI build up in real time. The code runs in a server-side Pyodide sandbox for validation. No React codebase required. You stay in Python. ### When to use which Use **built-in providers** when you need a standard interaction pattern (file upload, form input, approval). Use **`app=True` on tools** when you have a known data shape and want a polished, purpose-built UI. This is the 80% case for internal dashboards and admin panels. Use **GenerativeUI** when your users ask for open-ended visualizations you can't anticipate. Data exploration tools, ad-hoc reporting, research assistants. Use **Custom HTML** (the raw MCP Apps extension) when you need a specific JavaScript framework, a map library, a 3D renderer, or some other capability Prefab doesn't cover. One tip: `fastmcp dev apps` launches a browser-based preview for app tools. You can pick a tool, pass arguments, and see the rendered UI without connecting to a real host. This is the fastest feedback loop for iterating on UIs. --- ## Part 7: Building an MCP Client Clients connect to servers and call tools. You build a client when you're the host, not the server. That's a different job from most MCP tutorials. The simplest client: ```python import asyncio from fastmcp import Client async def main(): async with Client("http://localhost:8000/mcp") as client: tools = await client.list_tools() print([t.name for t in tools]) result = await client.call_tool("add", {"a": 2, "b": 3}) print(result.data) asyncio.run(main()) ``` The `Client` constructor is smart about what you pass it: ```python # Streamable HTTP URL Client("http://localhost:8000/mcp") # Python script (infers STDIO) Client("server.py") # Explicit transport with full config from fastmcp.client.transports import StdioTransport Client(StdioTransport( command="python", args=["server.py"], env={"API_KEY": "secret"}, cwd="/path/to/server", )) # MCP config JSON (multi-server) Client({ "mcpServers": { "weather": {"command": "python", "args": ["weather.py"]}, "notion": {"url": "https://notion.example.com/mcp"}, } }) ``` Authenticated HTTP: ```python from fastmcp.client.auth import BearerAuth client = Client( "https://api.example.com/mcp", auth=BearerAuth("your-token-here"), ) ``` ### When to build a client vs. when to build a server This trips up a lot of people. Here's the decision: **Build a server** when you have a capability (a database, an API, a set of tools) that you want to expose to LLM hosts. Server is the default answer for 90% of MCP projects. **Build a client** when you're writing the thing that uses LLMs. If you're building an agent, an automation, a custom chat UI, or integrating MCP into an existing Python app that calls an LLM, you need a client. The client is how your code calls someone else's server. **Build both** when you're building a platform. Agent frameworks, orchestration layers, and multi-server proxies all need both sides. FastMCP lets you use a server as a client (wrap it in a `FastMCPToolset`) and a client as a server (via `FastMCP.as_proxy()`), which is how you chain MCPs together. --- ## Part 8: Testing The single most important testing pattern in FastMCP is **in-memory client testing**. No subprocess, no network, no flakiness. ```python import pytest from fastmcp import FastMCP, Client @pytest.fixture def server(): mcp = FastMCP("TestServer") @mcp.tool def add(a: int, b: int) -> int: return a + b return mcp @pytest.mark.asyncio async def test_add(server): async with Client(server) as client: result = await client.call_tool("add", {"a": 2, "b": 3}) assert result.data == 5 ``` Because FastMCP 3 keeps your functions callable as regular Python functions (decorators in v2 turned them into objects; v3 fixed that), you can also unit-test tools directly: ```python def test_add_direct(): assert add(2, 3) == 5 ``` Use the direct test for pure logic. Use the in-memory client when you want to exercise the protocol: serialization, validation, error paths. For interactive iteration, `fastmcp dev server.py` launches the MCP Inspector (hot-reload included), which is a web UI for poking at tools, resources, and prompts without any client code. This is where you debug tool schemas and descriptions. --- ## Part 9: Integrating with Hosts ### Claude Desktop Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the Windows equivalent: ```json { "mcpServers": { "my-server": { "command": "fastmcp", "args": ["run", "/absolute/path/to/server.py"] } } } ``` For servers that need environment variables: ```json { "mcpServers": { "notion": { "command": "fastmcp", "args": ["run", "/path/to/server.py"], "env": { "NOTION_API_KEY": "secret_xxx" } } } } ``` Claude Desktop doesn't speak HTTP directly. For remote HTTP servers, use `mcp-remote` as a bridge: ```json { "mcpServers": { "remote": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:8000/mcp"] } } } ``` A faster path: `fastmcp install stdio server.py` generates the exact command string for you. ### Claude Code, Cursor, VS Code Each ships its own config location, but the structure is identical. `fastmcp discover` scans your editor configs and lists every server you have set up, which is handy when you're working across tools. --- ## Part 10: Best Practices ### Tool design 1. **One tool, one job.** If a tool's docstring has the word "and" in the main sentence, split it. `create_user` and `delete_user`, not `manage_users`. 2. **The docstring is the API.** The LLM picks tools by reading descriptions. Spend more time on docstrings than on implementation. Say what the tool does, when to use it, and crucially, when **not** to use it. 3. **Structured output beats strings.** Return Pydantic models. Hosts render them better, and the model reasons over them more reliably. 4. **Explicit types everywhere.** `Literal["a", "b"]` for enums. `Annotated[int, Field(ge=0)]` for bounded values. Every type hint shrinks the space of things the model can get wrong. 5. **Keep tool counts low.** A server with 60 tools is worse than a server with 12 well-named tools. If you have a large catalog, look at FastMCP's CodeMode transform, which hides the catalog behind a search tool and only surfaces relevant schemas on demand. ### Server hygiene 6. **Return early on errors.** Raise `ToolError` with a message the LLM can act on, not a stack trace. `raise ToolError("User not found. Check the user_id.")` is infinitely more useful than an IndexError. 7. **Protect the context window.** Add `ResponseLimitingMiddleware` to cap tool response sizes. A tool that returns 50k tokens of JSON can poison a conversation. 8. **Be careful with resources.** Resources get loaded into context. Don't expose a resource that dumps your entire database. 9. **Use `stateless_http=True` for cloud deployments.** If you're running multiple replicas, session state in memory will bite you. ### Security 10. **Scope credentials tightly.** Read-only by default. Write access opt-in. Never wire a production database with write access to a development server. 11. **Validate user input as if it's adversarial.** MCP tools get called by LLMs, which get called by users. Assume prompt injection. A tool that takes a SQL string and runs it unsanitized is a vulnerability. 12. **Use OAuth for remote servers.** FastMCP ships first-class integrations with Google, GitHub, Azure, Auth0, WorkOS, Descope, PropelAuth, Scalekit. The 2026 MCP spec's CIMD flow (Client ID Metadata Documents) is what you want for dynamic client registration without the old DCR security holes. 13. **Beware SSRF and path traversal.** If a tool takes a URL or a file path, validate it. FastMCP 3.2 added SSRF protections to several code paths but your tool logic still needs to defend itself. ### Performance 14. **Async everything I/O-bound.** Use `async def` and `httpx.AsyncClient` for anything that talks to the network. Synchronous tools block the whole server. 15. **Cache read-heavy tools.** FastMCP ships storage backends (in-memory, disk, Redis) for exactly this. 16. **Paginate large lists.** A `list_users` tool that returns 10,000 users in one call is a bug. Take `limit` and `cursor` parameters. ### Observability 17. **Turn on OpenTelemetry in production.** FastMCP 3 has native OTEL instrumentation. Every tool call, resource read, and prompt render gets traced. This is the only way you'll find latency problems in a server with more than a handful of tools. --- ## Part 11: Useful MCP Servers (April 2026) If you're starting from zero, these are the servers worth connecting to first. Mix and match based on your stack. **Developer workflow** - **GitHub MCP** for repo management, issues, PRs, code search. The most-installed MCP server overall. - **Filesystem MCP** for local file operations. Built into most clients, safe, and indispensable. - **Git MCP** for structured repo state (branches, commits, diffs) without parsing shell output. - **Playwright MCP** for browser automation. Replaces Puppeteer MCP, which is deprecated. - **Semgrep MCP** for static analysis and security scanning on your diffs. **Data** - **PostgreSQL MCP** (use read-only mode) for database queries. - **Supabase MCP** for Supabase projects including auth and edge functions. - **Prisma Postgres MCP** (`npx prisma mcp`) for TypeScript teams. - **MindsDB MCP** for federated queries across multiple sources. **Search and research** - **Brave Search MCP** and **Exa MCP** for web search. - **Firecrawl MCP** for web scraping. - **Context7 MCP** for up-to-date library documentation inside your agent. **Productivity and workspace** - **Notion MCP** for pages, databases, blocks. OAuth-based. - **Slack MCP** (official version needs admin approval). - **Linear MCP** and **Jira MCP** for issue tracking. - **Google Drive / Gmail / Calendar** MCPs for Workspace data. **Design and frontend** - **Figma Dev Mode MCP** for design-to-code with the real design system. - **Magic UI MCP** for React + Tailwind components. **Infrastructure** - **Kubernetes MCP** for cluster status, deployments, debugging. - **Terraform MCP** (AWS-backed) for infrastructure state. - **Docker Hub MCP** for image search and pulls. - **AWS MCP Suite** for DynamoDB, Aurora, Neptune. **Payments and SaaS** - **Stripe MCP** for subscriptions, invoices, customers. **Sequential thinking / planning** - **Sequential Thinking MCP** for letting the agent plan multi-step problems explicitly. ### Finding more Three directories actually worth browsing: `mcpservers.org`, `mcpmarket.com`, and the official `modelcontextprotocol/servers` repo on GitHub. There are north of 10,000 MCP servers listed across these, most of them are weekend experiments, and the signal-to-noise ratio is poor. Stick to servers maintained by the company whose product they wrap, or by the core MCP community. --- ## Part 12: Tips, Tricks, and Gotchas 1. **`fastmcp dev server.py` is the inner-loop tool you'll use most.** Hot reload, inspector, instant feedback. Don't edit your server and restart it manually. Run dev mode and leave it on. 2. **For a quick one-off, skip `if __name__ == "__main__":`.** Just `fastmcp run server.py` finds the server object and runs it. Fewer lines, same outcome. 3. **STDIO servers don't inherit your shell env.** If you wonder why `API_KEY` isn't showing up, that's why. Pass env explicitly in your client config or load a `.env` file inside the server. 4. **`keep_alive=True` on STDIO transports reuses the subprocess across client contexts.** On by default. Turn it off if you want a fresh process per session. 5. **For production HTTP deployments, mount FastMCP inside FastAPI** and front it with a real ASGI server like uvicorn or hypercorn. `mcp.streamable_http_app()` returns a standard ASGI app. 6. **Compose servers with `mount` and `import_server`.** `main.mount("sub", sub_server)` creates a live link (changes propagate). `main.import_server(sub_server)` copies components (static snapshot). Great for organizing large servers as microservices. 7. **Proxy remote servers with `FastMCP.as_proxy()`.** Useful for bridging transports (wrap a stdio server as HTTP), adding auth, or composing multiple upstream MCPs behind one frontend. 8. **Generate MCP servers from OpenAPI specs** with `FastMCP.from_openapi()`. If you have a REST API, this is the fastest path to a usable MCP server. It won't be ideal (auto-generated tools are rarely well-named), but it's a starting point you can refine. 9. **Tag your tools** for filtering and access control: `@mcp.tool(tags=["admin"])`. Combined with authorization callables, this is how you build role-scoped servers. 10. **Icons matter more than you'd think.** FastMCP supports icons on servers, tools, resources, and prompts. Hosts render them in UI. A decent icon makes your server feel more trustworthy in a long list. 11. **Don't build an MCP server when a skill or prompt will do.** Anthropic's Skills feature (for Claude) and OpenAI's custom GPTs handle a lot of the "give the model instructions and a few tools" cases without the protocol overhead. Reach for MCP when you need a programmatic, multi-client, versioned integration. 12. **The `fastmcp.json` file** lets you declare your server's dependencies, entry point, and config in one place. Use it. It makes your server portable across hosts. --- ## Quick Reference: Minimum Viable Production Server Keep this as a starting template: ```python from fastmcp import FastMCP, Context from fastmcp.server.middleware.response_limiting import ResponseLimitingMiddleware from pydantic import BaseModel mcp = FastMCP( name="production-server", stateless_http=True, middleware=[ResponseLimitingMiddleware(max_tokens=8000)], ) class Result(BaseModel): status: str data: dict @mcp.tool async def do_work(task_id: str, ctx: Context) -> Result: """Execute task. Use this when the user asks to run task X. Do not use for status checks; use get_status instead. """ await ctx.info(f"Starting {task_id}") # ... work ... return Result(status="ok", data={"task_id": task_id}) if __name__ == "__main__": mcp.run(transport="http", host="0.0.0.0", port=8000) ``` That's a server with async I/O, structured output, context logging, response size protection, stateless HTTP for horizontal scaling, and an LLM-friendly docstring. Start here and add what you need. --- ## Further Reading - **FastMCP docs:** `gofastmcp.com` (available as `llms-full.txt` and via MCP itself at `https://gofastmcp.com/mcp`) - **MCP spec:** `modelcontextprotocol.io` - **Prefab UI:** the Python component library behind FastMCP Apps - **MCP Apps spec:** the extension standardizing interactive UIs over MCP - **Jeremiah Lowin's blog** (`jlowin.dev`) for deep dives on 3.0, 3.1, and 3.2 design decisions