Build an MCP Server from Scratch: A Complete Developer Guide

About 22 min read · MACCOME

If you are a Python or TypeScript developer who wants Claude, Cursor, or Gemini to query your database, call internal APIs, or read project files—but you keep hitting brittle one-off integrations—this guide delivers a working MCP Server end to end. ① You will understand Host / Client / Server roles and JSON-RPC transport in under ten minutes; ② you will build a FastMCP Hello World, then add Tools, Resources, and Prompts; ③ you will debug with MCP Inspector, expose HTTP+SSE for remote clients, Docker-deploy to production, and ship a personal knowledge-base Server. For the protocol-level "why MCP" story, read our companion piece on why MCP is becoming the HTTP of the AI era.

Six MCP Server mistakes developers make in 2026

  1. Skipping protocol basics and jumping straight to SDK decorators: without understanding tools/list and JSON-RPC, debugging "tool not found" errors wastes hours.
  2. Using REST handlers instead of MCP primitives: wrapping an existing Flask route does not give AI dynamic discovery or JSON Schema self-description.
  3. Putting everything in Tools: read-only file content belongs in Resources; reusable workflow templates belong in Prompts—mixing them causes permission and caching problems.
  4. Testing only inside Cursor without MCP Inspector: Inspector isolates Server bugs from Host configuration issues and shows raw RPC payloads.
  5. Deploying HTTP+SSE on a sleeping laptop: SSE sessions drop when the lid closes; production Servers need a node with stable uptime.
  6. Hard-coding secrets in Server source: API keys should flow through environment variables or Host-managed auth, not committed JSON configs.

MCP protocol basics: what you are actually building

Model Context Protocol (MCP) is an open standard—Anthropic released it in November 2024, and by 2026 OpenAI, Google, and Microsoft have adopted it. Three roles matter:

  • Host: Claude Desktop, Cursor, VS Code—the app the user talks to
  • Client: embedded inside the Host; maintains a 1:1 session with each Server
  • Server: your code; exposes Tools, Resources, and Prompts over JSON-RPC 2.0

Communication uses methods like initialize, tools/list, tools/call, resources/list, resources/read, and prompts/get. The Host never talks to your database directly—it asks the Server, which enforces permissions and returns structured results.

For the full N×M integration argument, REST vs MCP tables, and 2026 ecosystem timeline, see the MCP protocol deep dive. MCP complements—not replaces—Agent Skills (SKILL.md): Skills package in-host prompts; MCP standardizes cross-host tool access.

Primitive RPC methods Typical use
Toolstools/list, tools/callSide-effect actions: query DB, create ticket, send webhook
Resourcesresources/list, resources/readRead-only context: files, config snapshots, record URIs
Promptsprompts/list, prompts/getReusable multi-step templates the Host injects into chat

Environment setup: Python FastMCP stack

This tutorial uses FastMCP (Python 3.10+) because it ships STDIO and HTTP+SSE transports, auto-generates JSON Schema from type hints, and works with Cursor and Claude Desktop out of the box. TypeScript teams can mirror the same structure with @modelcontextprotocol/sdk.

bash
mkdir my-mcp-server && cd my-mcp-server
python3 -m venv .venv
source .venv/bin/activate          # Windows: .venv\Scripts\activate
pip install "mcp[cli]" fastmcp uvicorn

# Verify MCP CLI and Inspector
npx @modelcontextprotocol/inspector --version

Create server.py as the entry point. Keep secrets in .env (load with python-dotenv if needed)—never commit API keys.

Hello World: your first MCP Server

A minimal Server registers a name, exposes one Tool, and runs over STDIO—the default for local Cursor and Claude Desktop integration.

python
from fastmcp import FastMCP

mcp = FastMCP("hello-mcp")

@mcp.tool()
def greet(name: str) -> str:
    """Return a greeting for the given name."""
    return f"Hello, {name}! MCP is working."

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

Run locally with Inspector:

bash
npx @modelcontextprotocol/inspector python server.py

In the Inspector UI, call tools/list—you should see greet with a JSON Schema describing the name parameter. Then invoke tools/call with {"name": "greet", "arguments": {"name": "MACCOME"}}.

Cursor MCP configuration

Add to ~/.cursor/mcp.json (or project-level .cursor/mcp.json):

json
{
  "mcpServers": {
    "hello-mcp": {
      "command": "/path/to/my-mcp-server/.venv/bin/python",
      "args": ["/path/to/my-mcp-server/server.py"]
    }
  }
}

Tools: callable actions with JSON Schema

Tools are functions the model chooses at runtime. FastMCP derives parameter schemas from Python type hints and docstrings—those docstrings become tool descriptions the model reads when deciding what to call.

python
import httpx
from fastmcp import FastMCP

mcp = FastMCP("weather-tools")

@mcp.tool()
async def get_weather(city: str, units: str = "metric") -> dict:
    """Fetch current weather for a city via OpenWeatherMap."""
    api_key = os.environ["OPENWEATHER_API_KEY"]
    url = f"https://api.openweathermap.org/data/2.5/weather"
    async with httpx.AsyncClient() as client:
        r = await client.get(url, params={"q": city, "appid": api_key, "units": units})
        r.raise_for_status()
        data = r.json()
    return {"city": city, "temp": data["main"]["temp"], "desc": data["weather"][0]["description"]}
warning

Side effects: mark destructive Tools clearly in the docstring ("Deletes the record permanently"). Consider input validation and rate limits—AI will call Tools aggressively during multi-step tasks.

Resources: read-only context the model can pull

Resources expose URIs like file:///docs/readme.md or kb://article/42. The model reads them via resources/read without executing arbitrary code—ideal for documentation, config snapshots, or database row previews.

python
from pathlib import Path
from fastmcp import FastMCP

mcp = FastMCP("docs-resources")
DOCS = Path("./docs")

@mcp.resource("docs://{filename}")
def read_doc(filename: str) -> str:
    """Expose markdown files under ./docs as MCP resources."""
    path = (DOCS / filename).resolve()
    if not str(path).startswith(str(DOCS.resolve())):
        raise ValueError("Path traversal blocked")
    return path.read_text(encoding="utf-8")

Prompts: reusable workflow templates

Prompts return structured message templates—useful for "code review checklist," "incident postmortem," or "SQL query assistant" workflows the user selects from a menu rather than typing from scratch.

python
from fastmcp import FastMCP
from mcp.types import PromptMessage, TextContent

mcp = FastMCP("review-prompts")

@mcp.prompt()
def code_review(language: str = "python") -> list[PromptMessage]:
    """Structured code review prompt for the given language."""
    return [
        PromptMessage(
            role="user",
            content=TextContent(
                type="text",
                text=f"Review this {language} code for bugs, security issues, and style. "
                     "Output: Critical / Warning / Suggestion sections."
            ),
        )
    ]

HTTP transport: expose your Server over the network

STDIO suits local dev; HTTP + SSE lets teammates, CI pipelines, or remote Hosts connect without spawning a subprocess on each machine. FastMCP supports SSE via mcp.run(transport="sse") or mounting on Starlette/Uvicorn.

python
# server_http.py — same Tools/Resources/Prompts, SSE transport
from fastmcp import FastMCP

mcp = FastMCP("my-remote-server")
# ... register tools, resources, prompts ...

if __name__ == "__main__":
    mcp.run(transport="sse", host="0.0.0.0", port=8000)
Transport Best for Trade-offs
STDIOLocal Cursor, Claude Desktop, InspectorZero network setup; one Client per subprocess; dies when Host closes
HTTP + SSETeam-shared Servers, cloud deploy, remote AgentsRequires TLS + auth in production; SSE needs session affinity behind load balancers

Debug and test: MCP Inspector workflow

Always test Servers in isolation before wiring them into Cursor:

  1. Launch Inspector: npx @modelcontextprotocol/inspector python server.py
  2. Verify handshake: initialize returns server capabilities
  3. List primitives: confirm tools/list, resources/list, prompts/list match expectations
  4. Call each Tool with valid and invalid arguments—check error messages are model-readable
  5. Read Resources and confirm MIME types / text encoding
  6. Fetch Prompts and inspect returned message structure

For HTTP mode, point Inspector at http://localhost:8000/sse (path varies by SDK version—check FastMCP docs). Log JSON-RPC payloads during development; strip verbose logging in production.

Production deployment: Docker and process supervision

Package the Server as a container with a non-root user, health checks, and environment-injected secrets.

dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
HEALTHCHECK CMD curl -f http://localhost:8000/health || exit 1
CMD ["python", "server_http.py"]

Run behind reverse proxy TLS (nginx, Caddy, or cloud load balancer). Add OAuth 2.1 or API-key middleware before exposing HTTP+SSE to the public internet—roughly 1,000 MCP Servers were publicly exposed without auth in early 2026 surveys.

Project walkthrough: personal knowledge-base MCP Server

Combine all three primitives into a Server that indexes your markdown notes and lets Cursor search them during coding sessions.

  1. Index: scan ~/notes/**/*.md at startup; build an in-memory title + path map
  2. Resource: expose each note as note://{slug} via resources/read
  3. Tool search_notes: full-text search returning matching slugs and snippets
  4. Tool append_note: append timestamped entries (with user confirmation in Host)
  5. Prompt daily_standup: template that pulls yesterday's notes and asks for today's plan

Wire it in Cursor alongside your codebase MCP Servers. The same Server binary works unchanged if you later switch from Claude to GPT— that portability is why enterprises report 38–55% integration cost reductions when consolidating on MCP (see our protocol analysis).

MCP ecosystem outlook in 2026

  • 10,000+ community Servers on GitHub and npm/PyPI—filesystem, PostgreSQL, Slack, GitHub, Google Drive, and more
  • AAIF governance under the Linux Foundation—spec versioning and security baselines maturing through 2026
  • Managed offerings from AWS, Azure, and Google Cloud wrapping internal services as MCP endpoints
  • A2A complement: MCP handles model-to-tools vertical integration; Agent-to-Agent protocol handles horizontal multi-agent orchestration

Start by forking a community Server close to your domain, then replace internals with your APIs. Publishing a well-documented Server to the ecosystem is increasingly valuable resume signal for AI infrastructure roles.

Ten-step build checklist: local dev to production

  1. Read protocol basics: Host / Client / Server and the three primitives (this post + protocol primer).
  2. Install FastMCP in a dedicated virtualenv; verify Inspector launches.
  3. Ship Hello World with one Tool; confirm tools/list in Inspector.
  4. Add real Tools wrapping your highest-value API or DB query; document side effects.
  5. Expose Resources for read-only docs and config the model should cite, not mutate.
  6. Register Prompts for repeatable workflows your team uses weekly.
  7. Configure Cursor (mcp.json) or Claude Desktop; test in a real coding session.
  8. Switch to HTTP+SSE when teammates need shared access; add TLS and auth.
  9. Dockerize with health checks; deploy to a 24/7 host—not a laptop that sleeps.
  10. Iterate on schemas: tighten Tool descriptions and parameter types based on misfired calls in logs.

Three hard data points for your build vs buy decision

  • 10,000+ MCP Servers (2026 ecosystem)—network effects mean your Server works across Cursor, Claude Desktop, VS Code Continue, and any MCP-compatible Host without per-vendor adapters.
  • 38–55% enterprise integration cost reduction when consolidating N×M custom glue into one MCP layer that survives LLM vendor switches (Claude → GPT → Gemini).
  • Sub-50 ms typical STDIO round-trip for local Tool calls on Apple Silicon (M4 class hardware)—remote HTTP+SSE adds 5–30 ms LAN latency; plan transport choice accordingly for latency-sensitive Agents.

Where to run your MCP Server in production

Local STDIO on a dev laptop is fine for Hello World. Production breaks down in three predictable ways: lid-close kills STDIO subprocesses and SSE streams, shared dev machines drift Python versions and break tools/call schemas, and multi-step Agent workflows need hours of uninterrupted uptime that consumer sleep policies forbid.

A dedicated Mac mini node—always on, fixed environment, remote desktop when you need to debug—eliminates those failure modes. For teams running HTTP+SSE Servers plus Cursor Agents around the clock, MACCOME Mac cloud hosting is typically cheaper than fighting laptop sleep and VPN tunnel instability. See tiers on the Mac Mini rental rates page and setup walkthrough in the support center.

FAQ

Should I start with Python FastMCP or TypeScript MCP SDK?

FastMCP is the fastest path for backend developers: decorators for Tools/Resources/Prompts, built-in STDIO and HTTP+SSE, strong Cursor compatibility. TypeScript SDK suits Node-first teams wrapping existing npm APIs.

What is the difference between MCP Tools, Resources, and Prompts?

Tools perform actions with side effects. Resources supply read-only context via URIs. Prompts return reusable message templates the Host injects into conversation—see sections 5–7 above.

How does this relate to Agent Skills (SKILL.md)?

Skills are in-host capability packages; MCP is a cross-host tool protocol. Use both: Skills for workflow prompts inside Cursor, MCP for shared database/API access. Details in our Agent Skill guide.

Where should I run an MCP Server in production?

Avoid laptops that sleep and break STDIO or SSE sessions. MACCOME offers M4/M4 Pro dedicated cloud Mac nodes for 24/7 MCP workloads. Compare plans on the rental rates page.