FastMCP: The Complete Guide to Building MCP Servers and Clients

FastMCP: The Complete Guide to Building MCP Servers and Clients

A comprehensive tutorial covering FastMCP 2.0, the fast and Pythonic way to build Model Context Protocol (MCP) servers and clients. Learn how to create tools, resources, and prompts for LLM applications.

Lesson 4 2025-07-03 06:59

Integrating FastMCP with Claude Code

Integrating FastMCP with Claude Code

Claude Code is Anthropic's official CLI tool that supports MCP servers, allowing you to extend Claude's capabilities with your custom FastMCP servers. This integration enables powerful development workflows where Claude can access your tools, resources, and domain-specific functionality.

Why Integrate with Claude Code?

Claude Code integration offers several compelling benefits:

  • Enhanced Development Workflow: Access your custom tools directly from Claude
  • Context-Aware Assistance: Claude can read your project resources and configuration
  • Automated Tasks: Execute complex operations through your MCP tools
  • Local and Remote Support: Connect to both local development servers and deployed services
  • Real-time Interaction: Dynamic tool discovery and execution

Setting Up Your FastMCP Server

Creating a Development Server

Let's create a comprehensive FastMCP server designed for development workflows:

# dev_server.py
import os
import subprocess
import json
from pathlib import Path
from datetime import datetime
from fastmcp import FastMCP, Context

mcp = FastMCP(
    name="Development Assistant",
    instructions="""
    This server provides development tools including:
    - Project analysis and file operations
    - Git operations and repository management
    - Code quality checks and testing
    - Environment and dependency management
    """
)

@mcp.tool
def run_command(command: str, cwd: str = ".") -> dict:
    """Execute a shell command and return the result."""
    try:
        result = subprocess.run(
            command.split(),
            cwd=cwd,
            capture_output=True,
            text=True,
            timeout=30
        )
        return {
            "command": command,
            "returncode": result.returncode,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "success": result.returncode == 0
        }
    except subprocess.TimeoutExpired:
        return {"error": "Command timed out after 30 seconds"}
    except Exception as e:
        return {"error": str(e)}

@mcp.tool
def analyze_project_structure(path: str = ".") -> dict:
    """Analyze the structure of a project directory."""
    project_path = Path(path)

    if not project_path.exists():
        return {"error": f"Path {path} does not exist"}

    structure = {
        "root": str(project_path.absolute()),
        "files": [],
        "directories": [],
        "python_files": [],
        "config_files": [],
        "total_size": 0
    }

    config_extensions = {".json", ".yaml", ".yml", ".toml", ".ini", ".cfg"}

    for item in project_path.rglob("*"):
        if item.is_file():
            size = item.stat().st_size
            structure["total_size"] += size

            file_info = {
                "path": str(item.relative_to(project_path)),
                "size": size,
                "modified": datetime.fromtimestamp(item.stat().st_mtime).isoformat()
            }

            structure["files"].append(file_info)

            if item.suffix == ".py":
                structure["python_files"].append(file_info)
            elif item.suffix in config_extensions:
                structure["config_files"].append(file_info)

        elif item.is_dir():
            structure["directories"].append(str(item.relative_to(project_path)))

    return structure

@mcp.tool
async def check_code_quality(file_path: str, ctx: Context) -> dict:
    """Check code quality using various linting tools."""
    await ctx.info(f"Checking code quality for {file_path}...")

    if not os.path.exists(file_path):
        return {"error": f"File {file_path} not found"}

    results = {"file": file_path, "checks": {}}

    # Check with different tools
    tools = {
        "flake8": ["flake8", file_path],
        "pylint": ["pylint", file_path, "--output-format=json"],
        "mypy": ["mypy", file_path]
    }

    for tool, command in tools.items():
        try:
            result = subprocess.run(
                command,
                capture_output=True,
                text=True,
                timeout=15
            )
            results["checks"][tool] = {
                "returncode": result.returncode,
                "output": result.stdout,
                "errors": result.stderr
            }
        except (subprocess.TimeoutExpired, FileNotFoundError):
            results["checks"][tool] = {"error": f"{tool} not available or timed out"}

    return results

@mcp.resource("project://config")
def get_project_config() -> dict:
    """Get project configuration from various config files."""
    config = {"found_files": [], "configurations": {}}

    config_files = [
        "pyproject.toml", "setup.py", "requirements.txt", 
        "package.json", "Dockerfile", ".env", "config.yaml"
    ]

    for config_file in config_files:
        if os.path.exists(config_file):
            config["found_files"].append(config_file)
            try:
                with open(config_file, 'r') as f:
                    content = f.read()
                config["configurations"][config_file] = {
                    "content": content[:1000] + ("..." if len(content) > 1000 else ""),
                    "size": len(content)
                }
            except Exception as e:
                config["configurations"][config_file] = {"error": str(e)}

    return config

@mcp.resource("git://status")
def get_git_status() -> dict:
    """Get current Git repository status."""
    try:
        # Check if we're in a git repository
        subprocess.run(["git", "rev-parse", "--git-dir"], 
                      check=True, capture_output=True)

        # Get various git information
        commands = {
            "branch": ["git", "branch", "--show-current"],
            "status": ["git", "status", "--porcelain"],
            "last_commit": ["git", "log", "-1", "--pretty=format:%H|%an|%ad|%s"],
            "remote": ["git", "remote", "-v"]
        }

        result = {}
        for key, command in commands.items():
            try:
                output = subprocess.run(
                    command, capture_output=True, text=True, check=True
                ).stdout.strip()
                result[key] = output
            except subprocess.CalledProcessError:
                result[key] = "unavailable"

        return result

    except subprocess.CalledProcessError:
        return {"error": "Not a git repository"}

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

Connecting to Claude Code

Running Your Server

First, start your FastMCP server:

# Start the server in HTTP mode
python dev_server.py

Your server will be running at http://127.0.0.1:8000/mcp/

Adding the Server to Claude Code

Use Claude Code's MCP management commands to add your server:

# Add your FastMCP server
claude mcp add dev-assistant --transport http http://localhost:8000/mcp/

# Verify the server is connected
claude mcp list

Alternative: STDIO Transport

For local development, you might prefer STDIO transport:

# dev_server_stdio.py
# ... (same server code as above)

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

Add it to Claude Code:

claude mcp add dev-assistant --transport stdio "python dev_server_stdio.py"

Using Your Server with Claude Code

Once connected, you can interact with your MCP server through Claude Code:

Basic Tool Usage

# Claude will automatically discover and use your tools
claude "Analyze the structure of my current project"

# Claude will call your analyze_project_structure tool
# Response will include file counts, sizes, and organization

Resource Access

Claude can access your server's resources using @ mentions:

# Access project configuration
claude "What configuration files does this project have? Use @dev-assistant:project://config"

# Check git status
claude "What's the current git status? Use @dev-assistant:git://status"

Complex Workflows

Combine multiple tools for sophisticated operations:

claude "Please check the code quality of all Python files in the src/ directory and summarize the issues"

# Claude will:
# 1. Use analyze_project_structure to find Python files
# 2. Use check_code_quality on each file
# 3. Summarize the results

Advanced Configuration Examples

Development Server with Database Tools

# db_server.py
import sqlite3
from fastmcp import FastMCP

mcp = FastMCP("Database Tools")

@mcp.tool
def query_database(query: str, db_path: str = "app.db") -> dict:
    """Execute a SQL query on the database."""
    try:
        conn = sqlite3.connect(db_path)
        cursor = conn.cursor()
        cursor.execute(query)

        if query.strip().upper().startswith("SELECT"):
            results = cursor.fetchall()
            columns = [description[0] for description in cursor.description]
            return {
                "columns": columns,
                "rows": results,
                "count": len(results)
            }
        else:
            conn.commit()
            return {"affected_rows": cursor.rowcount}

    except Exception as e:
        return {"error": str(e)}
    finally:
        conn.close()

@mcp.resource("db://schema")
def get_database_schema() -> dict:
    """Get the database schema information."""
    # Implementation to read database schema
    pass

if __name__ == "__main__":
    mcp.run(transport="http", port=8001)

API Testing Server

# api_server.py
import requests
from fastmcp import FastMCP, Context

mcp = FastMCP("API Testing Tools")

@mcp.tool
async def test_api_endpoint(url: str, method: str = "GET", 
                          headers: dict = None, data: dict = None,
                          ctx: Context = None) -> dict:
    """Test an API endpoint and return detailed results."""
    await ctx.info(f"Testing {method} {url}...")

    try:
        response = requests.request(
            method=method.upper(),
            url=url,
            headers=headers or {},
            json=data,
            timeout=10
        )

        return {
            "url": url,
            "method": method.upper(),
            "status_code": response.status_code,
            "headers": dict(response.headers),
            "response_time": response.elapsed.total_seconds(),
            "body": response.text[:1000] if response.text else None,
            "success": 200 <= response.status_code < 300
        }

    except Exception as e:
        return {
            "url": url,
            "method": method.upper(),
            "error": str(e),
            "success": False
        }

if __name__ == "__main__":
    mcp.run(transport="http", port=8002)

Multiple Server Setup

You can connect multiple FastMCP servers to Claude Code:

# Add multiple servers
claude mcp add dev-tools --transport http http://localhost:8000/mcp/
claude mcp add db-tools --transport http http://localhost:8001/mcp/
claude mcp add api-tools --transport http http://localhost:8002/mcp/

# Claude can now access all servers
claude "Check the project structure, then test the API endpoints defined in the config"

Troubleshooting Common Issues

Server Not Responding

# Check if server is running
curl http://localhost:8000/mcp/

# Remove and re-add the server
claude mcp remove dev-assistant
claude mcp add dev-assistant --transport http http://localhost:8000/mcp/

Tool Not Found

Ensure your server is properly registered and tools are decorated:

# Make sure tools are properly decorated
@mcp.tool  # This decorator is required
def my_tool():
    pass

Resource Access Issues

Check resource URI format:

# Correct format
claude "Use @server-name:resource://uri"

# Not @server-name/resource://uri

Best Practices for Claude Code Integration

  1. Clear Tool Names: Use descriptive, self-explanatory tool names
  2. Good Documentation: Include comprehensive docstrings
  3. Error Handling: Provide meaningful error messages
  4. Resource Organization: Use logical URI schemes for resources
  5. Performance: Keep tool execution times reasonable
  6. Security: Validate inputs and restrict dangerous operations

Next Steps

With your FastMCP server integrated into Claude Code, you can:

  • Build domain-specific development assistants
  • Automate complex project management tasks
  • Create custom code analysis and quality tools
  • Develop specialized testing and deployment workflows

In the final section, we'll explore advanced patterns, production deployment, and scaling strategies for your FastMCP servers.