CM

cvsubs74/mcp

Developer tools
0 stars 0 forks Quality 38 Trend 0

This guide outlines a practical design pattern to implement, test, and deploy an MCP (Model Context Protocol) server and a matching client using FastMCP.

Overview

This guide outlines a practical design pattern to implement, test, and deploy an MCP (Model Context Protocol) server and a matching client using FastMCP. It is framework- and cloud-agnostic with concrete examples where helpful. You will learn: - The recommended project layout - Core dependencies and environment setup - Server design patterns (tools, transports, serialization) - Client design patterns (connect, list, call) - Testing patterns (in-memory unit tests and HTTP integration tests) - Containerization and deployment options - server.py — FastMCP server, tools, and HTTP transport startup - client.py — Minimal FastMCP client example - test_mcp_inmemory.py — In‑memory, pytest-based unit tests that import the server directly - test_mcp_server.py — Simplified integration tests against the deployed HTTP server - Dockerfile — Container build for Cloud Run - pyproject.toml — Python project and dependency metadata - requirements.

README

Building an MCP Server and Client

This guide outlines a practical design pattern to implement, test, and deploy an MCP (Model Context Protocol) server and a matching client using FastMCP. It is framework- and cloud-agnostic with concrete examples where helpful.

You will learn:

  • The recommended project layout
  • Core dependencies and environment setup
  • Server design patterns (tools, transports, serialization)
  • Client design patterns (connect, list, call)
  • Testing patterns (in-memory unit tests and HTTP integration tests)
  • Containerization and deployment options
  • server.py — FastMCP server, tools, and HTTP transport startup
  • client.py — Minimal FastMCP client example
  • test_mcp_inmemory.py — In‑memory, pytest-based unit tests that import the server directly
  • test_mcp_server.py — Simplified integration tests against the deployed HTTP server
  • Dockerfile — Container build for Cloud Run
  • pyproject.toml — Python project and dependency metadata
  • requirements.txt — Pinned runtime dependencies for Docker image

Server Design Pattern

Implement the server as a module that exports a single FastMCP instance and decorates Python functions as MCP tools. Keep business logic pure and testable, and return JSON-serializable results.

Core pattern:

from typing import Any, Dict, List
from fastmcp import FastMCP

mcp = FastMCP("Your MCP Server Name")

@mcp.tool()
def your_tool(arg1: str, limit: int = 10) -> List[Dict[str, Any]]:
    """Describe what this tool does in one sentence."""
    # Validate inputs (FastMCP also validates based on type hints)
    # Query/compute your results
    return [{"ok": True, "arg1": arg1, "limit": limit}]

Transports:

  • In-memory: ideal for testing via Client(mcp)
  • HTTP (streamable): ideal for remote consumption by HTTP clients
  • Other transports: depending on FastMCP support and your needs

HTTP startup:

# server.py
import asyncio, os

if __name__ == "__main__":
    asyncio.run(
        mcp.run_async(
            transport="streamable-http",
            host="0.0.0.0",
            port=os.getenv("PORT", 8080),
        )
    )

Notes:

  • Prefer explicit, typed function signatures. FastMCP generates JSON Schema automatically.
  • Ensure all return values are JSON-serializable (dict, list, str, int, float, bool, None).
  • If you expose HTTP, document the base path (e.g., /mcp/) and any auth requirements.

Client Design Pattern

Use the asynchronous context manager to connect, list tools, and call tools. When testing in-memory, pass the server instance directly; when testing remotely, pass the server URL.

Basic patterns:

from fastmcp import Client

# Remote HTTP client
async def main_remote():
    async with Client("https://your-server.example.com/mcp/") as client:
        await client.ping()
        tools = await client.list_tools()
        result = await client.call_tool("your_tool", {"arg1": "value", "limit": 5})
        data = result.data

# In-memory client (great for unit tests)
async def main_inmemory(mcp):
    async with Client(mcp) as client:
        result = await client.call_tool("your_tool", {"arg1": "value"})
        assert result.data

Tips:

  • Treat result.data as the canonical output. Avoid assuming dict-like access for transport-wrapped items.
  • Prefer small, composable tools with clear inputs/outputs.

Local Development

  • Python 3.11+ recommended (3.13 supported)
  • Use a virtual environment
  • Install dependencies via uv or pip

Example setup:

python3 -m venv .venv
source .venv/bin/activate
uv pip install fastmcp fastapi uvicorn pytest pytest-asyncio  # adjust as needed
# or: pip install ...

# Run server locally (HTTP on localhost:8080)
python server.py

# Quick smoke test (replace URL if needed)
python - <<'PY'
import asyncio
from fastmcp import Client

async def main():
    async with Client("http://localhost:8080/mcp/") as c:
        await c.ping()
        print("OK: ping")

asyncio.run(main())
PY

Containerization and Deployment

Containerize your server to run anywhere. Common targets:

  • Cloud Run (serverless containers)
  • Kubernetes (GKE, EKS, AKS)
  • Docker on VM or edge devices

Minimal Dockerfile (Python slim + requirements):

FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "server.py"]

Example: deploy to Cloud Run (public access):

docker build -t gcr.io/PROJECT_ID/mcp-server:latest .
docker push gcr.io/PROJECT_ID/mcp-server:latest

gcloud run deploy mcp-server \
  --image gcr.io/PROJECT_ID/mcp-server:latest \
  --region us-central1 \
  --platform managed \
  --allow-unauthenticated

Auth notes:

  • Private services require roles/run.invoker and ID tokens on requests
  • Always document your service base path (e.g., /mcp/) and enforce trailing slashes to avoid redirects

Testing Strategy

Adopt a two-layer testing approach:

  1. In-memory unit tests (pytest)
  • Import the mcp instance directly from your server module
  • Use Client(mcp) to call tools without HTTP
  • Validate return shapes and edge cases deterministically
  1. Integration tests (unittest or pytest)
  • Target the exposed HTTP endpoint
  • Check liveness (ping), tool discovery, and one or two happy-path tool calls
  • Avoid over-asserting internal shapes if the transport may wrap results

Result handling tips:

  • Client.call_tool() returns CallToolResult, use .data for the payload
  • Over HTTP, items may be wrapped (e.g., Root); assert invariants rather than direct key access unless you unwrap downstream
  • Prefer separate, narrower tools (e.g., a get_details(name) tool) for deterministic verification

Common Pitfalls and Tips

  • Trailing slash: If your HTTP transport mounts at /mcp/, ensure clients use the trailing slash to avoid 30x redirects
  • Auth: Prefer unauthenticated endpoints for internal testing; add ID token or reverse proxy auth for production
  • Serialization: Keep tool return values JSON-serializable and stable across versions
  • Versioning: Introduce new tools or fields rather than breaking existing ones
  • Observability: Add logging around tool entry/exit and validate inputs early

Extending the Server

  1. Add a new tool function with precise type hints
  2. Decorate with @mcp.tool()
  3. Keep logic pure and test via in-memory client
  4. Document inputs/outputs in the docstring so clients know what to pass/expect

License

This guide is provided as-is for demonstration and educational purposes.

View the full README on GitHub

Install

This server does not publish a one-line install command.

Open the repository installation guide New to MCP? Start with the mcpskill.org guide →