fastmcp-mcp-builder

star 1

Guide for creating high-quality MCP (Model Context Protocol) servers using FastMCP, the Pythonic MCP framework. Use when building MCP servers to integrate external APIs or services with tools, resources, and prompts using FastMCP decorators. Includes advanced features like structured outputs, tag-based filtering, dependency injection, custom routes, and CLI development workflows.

allenli178 By allenli178 schedule Updated 3/25/2026

name: fastmcp-mcp-builder description: Guide for creating high-quality MCP (Model Context Protocol) servers using FastMCP, the Pythonic MCP framework. Use when building MCP servers to integrate external APIs or services with tools, resources, and prompts using FastMCP decorators. Includes advanced features like structured outputs, tag-based filtering, dependency injection, custom routes, and CLI development workflows. license: Complete terms in LICENSE.txt

FastMCP Server Development Guide

Overview

Create MCP (Model Context Protocol) servers using FastMCP, the standard Pythonic framework for building MCP applications. FastMCP provides a clean, Pythonic API for registering tools, resources, and prompts, with automatic schema generation and transport management. FastMCP 1.0 was incorporated into the official MCP Python SDK in 2024, and today some version of FastMCP powers 70% of MCP servers across all languages.


Process

๐Ÿš€ High-Level Workflow

Creating a high-quality FastMCP server involves four main phases:

Phase 1: Deep Research and Planning

1.1 Understand FastMCP Design

Three Pillars of FastMCP:

  • Servers: Wrap your Python functions into MCP-compliant tools, resources, and prompts
  • Clients: Connect to any server with full protocol support
  • Apps: Give your tools interactive UIs rendered directly in the conversation

Tools, Resources, and Prompts: FastMCP servers expose three types of capabilities:

  • Tools: Server-side functions that clients can execute with arguments
  • Resources: Data sources that clients can read (static or template-based)
  • Prompts: Reusable message templates that guide LLM interactions

Balance comprehensive API coverage with specialized workflow tools. When uncertain, prioritize comprehensive API coverage.

Component Naming:

  • Tools: snake_case with service prefix (e.g., github_create_issue, slack_send_message)
  • Resources: URI-based with template parameters (e.g., user://{user_id}/profile)
  • Prompts: snake_case function names (e.g., analyze_data)

Context Management: FastMCP provides a Context object for logging, progress reporting, resource access, and LLM sampling. Use it to enhance tool capabilities.

Actionable Error Messages: Error messages should guide agents toward solutions with specific suggestions and next steps.

1.2 Study FastMCP Documentation

Key Resources: FastMCP documentation is available in multiple LLM-friendly formats:

Load these documentation pages as needed:

  • Welcome & Quickstart: Use WebFetch to load https://gofastmcp.com/getting-started/welcome and https://gofastmcp.com/getting-started/quickstart
  • Server Guide: https://gofastmcp.com/servers/server
  • Tools: https://gofastmcp.com/servers/tools
  • Resources: https://gofastmcp.com/servers/resources
  • Prompts: https://gofastmcp.com/servers/prompts
  • Running Servers: https://gofastmcp.com/deployment/running-server

1.3 Plan Your Implementation

Understand the API: Review the service's API documentation to identify key endpoints, authentication requirements, and data models. Use web search and WebFetch as needed.

Capability Selection: Prioritize comprehensive API coverage. List which endpoints to implement as tools, which data to expose as resources, and what prompt templates would be useful.


Phase 2: Implementation

2.1 Set Up Project Structure

Create the following structure for FastMCP servers:

{service}_mcp/
โ”œโ”€โ”€ {service}_mcp.py    # Main entry point with FastMCP initialization
โ”œโ”€โ”€ requirements.txt     # Dependencies (fastmcp)
โ””โ”€โ”€ README.md            # Documentation

For larger projects, you can organize into modules:

{service}_mcp/
โ”œโ”€โ”€ __init__.py
โ”œโ”€โ”€ main.py              # FastMCP server initialization
โ”œโ”€โ”€ tools/               # Tool implementations
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ users.py
โ”‚   โ””โ”€โ”€ projects.py
โ”œโ”€โ”€ resources/           # Resource definitions
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ””โ”€โ”€ data.py
โ”œโ”€โ”€ prompts/             # Prompt templates
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ””โ”€โ”€ analysis.py
โ”œโ”€โ”€ requirements.txt
โ””โ”€โ”€ README.md

2.2 Implement Core Infrastructure

Server Initialization

from fastmcp import FastMCP

# Basic initialization
mcp = FastMCP(name="service_name_mcp")

# With instructions for clients
mcp = FastMCP(
    name="HelpfulAssistant",
    instructions="""
        This server provides data analysis tools.
        Call get_average() to analyze numerical data.
    """,
)

# With security and validation settings
mcp = FastMCP(
    name="SecureServer",
    strict_input_validation=False,  # Default: flexible type coercion
    mask_error_details=True,        # Mask internal error details
    on_duplicate_tools="error",     # "warn", "error", "replace", "ignore"
    on_duplicate_resources="error"  # Same options as tools
)

# Add tools, resources, and prompts here...

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

Tag-Based Filtering

New in version 2.8.0, tags let you categorize components and selectively expose them:

from fastmcp import FastMCP

mcp = FastMCP()

@mcp.tool(tags={"public", "utility"})
def public_tool() -> str:
    return "This tool is public"

@mcp.tool(tags={"internal", "admin"})
def admin_tool() -> str:
    return "This tool is for admins only"

# Only expose components tagged with "public"
mcp.enable(tags={"public"}, only=True)

# Hide components tagged as "internal" or "deprecated"
mcp.disable(tags={"internal", "deprecated"})

# Combine both: show admin tools but hide deprecated ones
mcp.enable(tags={"admin"}, only=True).disable(tags={"deprecated"})

Shared Utilities

Create shared utilities for common operations:

  • API client with authentication
  • Error handling helpers
  • Response formatting
  • Pagination support

2.3 Implement Tools

Tools are Python functions decorated with @mcp.tool. FastMCP automatically:

  • Uses the function name as the tool name
  • Uses the function's docstring as the tool description
  • Generates an input schema from function parameters and type annotations
  • Handles parameter validation and error reporting

Basic Tool Pattern:

@mcp.tool
def add(a: int, b: int) -> int:
    """Adds two integer numbers together."""
    return a + b

Tool with Custom Name and Annotations:

@mcp.tool(
    name="service_search_users",
    description="Search for users in the service by name or email",
    tags={"users", "search"},
    meta={"version": "1.2", "author": "product-team"},
    annotations={
        "title": "Search Users",
        "readOnlyHint": True,
        "destructiveHint": False,
        "idempotentHint": True,
        "openWorldHint": True
    },
    timeout=30.0  # New in 3.0.0: Tool timeout in seconds
)
async def search_users(query: str, limit: int = 20) -> list[dict]:
    """Search for users with pagination support."""
    # Implementation here
    pass

Simple Parameter Descriptions (New in 2.11.0):

from typing import Annotated

@mcp.tool
def process_image(
    image_url: Annotated[str, "URL of the image to process"],
    resize: Annotated[bool, "Whether to resize the image"] = False,
    width: Annotated[int, "Target width in pixels"] = 800,
    format: Annotated[str, "Output image format"] = "jpeg"
) -> dict:
    """Process an image with optional resizing."""
    # Implementation...
    pass

Advanced Metadata with Field:

from typing import Annotated
from pydantic import Field
from typing import Literal

@mcp.tool
def process_image(
    image_url: Annotated[str, Field(description="URL of the image to process")],
    resize: Annotated[bool, Field(description="Whether to resize the image")] = False,
    width: Annotated[int, Field(description="Target width in pixels", ge=1, le=2000)] = 800,
    format: Annotated[
        Literal["jpeg", "png", "webp"], 
        Field(description="Output image format")
    ] = "jpeg"
) -> dict:
    """Process an image with optional resizing."""
    # Implementation...
    pass

Dependency Injection (New in 2.14.0):

from fastmcp import FastMCP
from fastmcp.dependencies import Depends

mcp = FastMCP()

def get_user_id() -> str:
    return "user_123"  # Injected at runtime

@mcp.tool
def get_user_details(user_id: str = Depends(get_user_id)) -> str:
    # user_id is injected by the server, not provided by the LLM
    return f"Details for {user_id}"

Tool with Context:

from fastmcp import FastMCP, Context

@mcp.tool
async def process_data(data_uri: str, ctx: Context) -> dict:
    """Process data from a resource with progress reporting."""
    await ctx.info(f"Processing data from {data_uri}")
    
    # Read a resource
    resource = await ctx.read_resource(data_uri)
    data = resource[0].content if resource else ""
    
    # Report progress
    await ctx.report_progress(progress=50, total=100)
    
    # Example request to the client's LLM for help
    summary = await ctx.sample(f"Summarize this in 10 words: {data[:200]}")
    
    await ctx.report_progress(progress=100, total=100)
    return {
        "length": len(data),
        "summary": summary.text
    }

Structured Output (New in 2.10.0):

FastMCP automatically creates structured outputs when you add return type annotations:

from dataclasses import dataclass

@dataclass
class UserData:
    id: str
    name: str
    email: str

@mcp.tool
def get_user(user_id: str) -> UserData:
    """Get user data with structured output."""
    return UserData(id=user_id, name="Alice", email="alice@example.com")

ToolResult for Full Control:

from fastmcp.tools.tool import ToolResult
from mcp.types import TextContent

@mcp.tool
def advanced_tool() -> ToolResult:
    """Tool with full control over output."""
    return ToolResult(
        content=[TextContent(type="text", text="Human-readable summary")],
        structured_content={"data": "value", "count": 42},
        meta={"execution_time_ms": 145}
    )

Error Handling:

from fastmcp import FastMCP
from fastmcp.exceptions import ToolError

mcp = FastMCP(name="SecureServer", mask_error_details=True)

@mcp.tool
def divide(a: float, b: float) -> float:
    """Divide a by b."""
    if b == 0:
        # Error messages from ToolError are always sent to clients
        raise ToolError("Division by zero is not allowed.")
    return a / b

Using with Methods:

from fastmcp import FastMCP
from fastmcp.tools import tool

class Calculator:
    def __init__(self, multiplier: int):
        self.multiplier = multiplier

    @tool()
    def multiply(self, x: int) -> int:
        """Multiply x by the instance multiplier."""
        return x * self.multiplier

calc = Calculator(multiplier=3)
mcp = FastMCP()
mcp.add_tool(calc.multiply)  # Registers with correct schema

Tool Decorator Parameters:

  • name: Explicit tool name (overrides function name)
  • description: Explicit description (overrides docstring)
  • tags: Set of strings for categorizing tools
  • meta: Custom metadata dictionary
  • annotations: Tool behavior hints (readOnlyHint, destructiveHint, idempotentHint, openWorldHint, title)
  • timeout: Tool timeout in seconds (new in 3.0.0)
  • output_schema: Custom JSON schema for output
  • version: Component version for versioning

2.4 Implement Resources

Resources are data sources exposed via URIs. Use the @mcp.resource decorator.

Static Resource:

@mcp.resource("config://app_settings")
def get_app_settings() -> dict:
    """Returns application configuration."""
    return {"version": "1.0.0", "theme": "dark"}

Resource with Custom Configuration:

@mcp.resource(
    uri="data://app-status",
    name="ApplicationStatus",
    description="Provides the current status of the application.",
    mime_type="application/json",
    tags={"monitoring", "status"},
    meta={"version": "2.1", "team": "infrastructure"},
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
def get_application_status() -> str:
    """Provides application status."""
    return json.dumps({"status": "ok", "uptime": 12345})

Resource Template (Dynamic):

@mcp.resource("user://{user_id}/profile")
def get_user_profile(user_id: str) -> dict:
    """Retrieves a user's profile by ID."""
    return {
        "id": user_id,
        "name": f"User {user_id}",
        "status": "active"
    }

Multiple Parameters with Annotations:

@mcp.resource(
    "repos://{owner}/{repo}/info",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
def get_repo_info(owner: str, repo: str) -> dict:
    """Retrieves information about a repository."""
    return {
        "owner": owner,
        "name": repo,
        "full_name": f"{owner}/{repo}"
    }

Wildcard Parameters (New in 2.2.4):

@mcp.resource("path://{filepath*}")
def get_path_content(filepath: str) -> str:
    """Retrieves content at a specific path."""
    # Can match path://docs/server/resources.mdx
    return f"Content at path: {filepath}"

Query Parameters (New in 2.13.0):

@mcp.resource("data://{id}{?format}")
def get_data(id: str, format: str = "json") -> str:
    """Retrieve data in specified format."""
    if format == "xml":
        return f"<data id='{id}' />"
    return f'{{"id": "{id}"}}'

@mcp.resource("api://{endpoint}{?version,limit,offset}")
def call_api(endpoint: str, version: int = 1, limit: int = 10, offset: int = 0) -> dict:
    """Call API endpoint with pagination."""
    return {
        "endpoint": endpoint,
        "version": version,
        "limit": limit,
        "offset": offset
    }

ResourceResult for Full Control (New in 3.0.0):

from fastmcp import FastMCP
from fastmcp.resources import ResourceResult, ResourceContent

@mcp.resource("data://users")
def get_users() -> ResourceResult:
    return ResourceResult(
        contents=[
            ResourceContent(content='[{"id": 1}]', mime_type="application/json"),
            ResourceContent(content="# Users\n...", mime_type="text/markdown"),
        ],
        meta={"total": 1}
    )

Resource Classes:

from pathlib import Path
from fastmcp import FastMCP
from fastmcp.resources import FileResource, TextResource, DirectoryResource

mcp = FastMCP(name="DataServer")

# Exposing a static file directly
readme_path = Path("./README.md").resolve()
if readme_path.exists():
    readme_resource = FileResource(
        uri=f"file://{readme_path.as_posix()}",
        path=readme_path,
        name="README File",
        description="The project's README.",
        mime_type="text/markdown",
        tags={"documentation"}
    )
    mcp.add_resource(readme_resource)

# Exposing simple, predefined text
notice_resource = TextResource(
    uri="resource://notice",
    name="Important Notice",
    text="System maintenance scheduled for Sunday.",
    tags={"notification"}
)
mcp.add_resource(notice_resource)

Resource Decorator Parameters:

  • uri: Required URI (e.g., "resource://my-resource" or "resource://{param}")
  • name: Optional name
  • description: Optional description
  • mime_type: Optional MIME type
  • tags: Optional tags
  • annotations: Optional behavior hints
  • meta: Optional metadata

2.5 Implement Prompts

Prompts are reusable message templates. Use the @mcp.prompt decorator.

Basic Prompt Returning String:

@mcp.prompt
def ask_about_topic(topic: str) -> str:
    """Generates a user message asking for an explanation of a topic."""
    return f"Can you please explain the concept of '{topic}'?"

Prompt with Custom Configuration:

@mcp.prompt(
    name="data_analysis_request",
    description="Generates a prompt for analyzing numerical data",
    tags={"analysis", "data"}
)
def analyze_data_prompt(data_points: list[float], context: str = "") -> str:
    """Generates a data analysis request."""
    formatted_data = ", ".join(str(point) for point in data_points)
    prompt = f"Please analyze these data points: {formatted_data}"
    if context:
        prompt += f"\n\nContext: {context}"
    return prompt

Prompt Decorator Parameters:

  • name: Explicit prompt name
  • description: Explicit description
  • tags: Optional tags

2.6 Custom Routes (HTTP Transport Only)

When using HTTP transport, add custom web routes:

from fastmcp import FastMCP
from starlette.requests import Request
from starlette.responses import PlainTextResponse

mcp = FastMCP("MyServer")

@mcp.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> PlainTextResponse:
    return PlainTextResponse("OK")

if __name__ == "__main__":
    mcp.run(transport="http")  # Health check at http://localhost:8000/health

Phase 3: Review and Test

3.1 Code Quality

Review for:

  • No duplicated code (DRY principle)
  • Consistent error handling
  • Clear type hints
  • Comprehensive docstrings
  • Proper use of async/await for I/O operations

3.2 Build and Test

Run the server (stdio):

python {service}_mcp.py

Run the server (HTTP):

if __name__ == "__main__":
    mcp.run(transport="http", host="127.0.0.1", port=8000)

Using FastMCP CLI:

fastmcp run {service}_mcp.py:mcp
fastmcp run {service}_mcp.py:mcp --transport http --port 8000
fastmcp run {service}_mcp.py:mcp --reload  # Auto-reload during development
fastmcp run {service}_mcp.py:mcp --python 3.11 --with httpx --with-requirements requirements.txt

Test with MCP clients:

  • Claude Desktop (stdio)
  • Cursor (stdio, HTTP)
  • Gemini CLI (stdio, HTTP)

Phase 4: Create Evaluations

After implementing your FastMCP server, create comprehensive evaluations. Load ../mcp-builder/reference/evaluation.md for complete evaluation guidelines.


Reference Files

๐Ÿ“š Documentation Library

Load these resources as needed during development:

Core FastMCP Documentation (Load First)

  • FastMCP Welcome: Use WebFetch to load https://gofastmcp.com/getting-started/welcome
  • FastMCP Quickstart: Use WebFetch to load https://gofastmcp.com/getting-started/quickstart
  • Server Guide: โšก Server Implementation - Complete FastMCP server guide

Component Documentation (Load During Phase 2)

  • Tools Guide: Use WebFetch to load https://gofastmcp.com/servers/tools
  • Resources Guide: Use WebFetch to load https://gofastmcp.com/servers/resources
  • Prompts Guide: Use WebFetch to load https://gofastmcp.com/servers/prompts

Deployment Guide

  • Running Servers: Use WebFetch to load https://gofastmcp.com/deployment/running-server

Evaluation Guide

  • Evaluation Guide: Load from ../mcp-builder/reference/evaluation.md - Complete evaluation creation guide
Install via CLI
npx skills add https://github.com/allenli178/yuyan-skills --skill fastmcp-mcp-builder
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator