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.
tools/list and JSON-RPC, debugging "tool not found" errors wastes hours.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:
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 |
|---|---|---|
| Tools | tools/list, tools/call | Side-effect actions: query DB, create ticket, send webhook |
| Resources | resources/list, resources/read | Read-only context: files, config snapshots, record URIs |
| Prompts | prompts/list, prompts/get | Reusable multi-step templates the Host injects into chat |
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.
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.
A minimal Server registers a name, exposes one Tool, and runs over STDIO—the default for local Cursor and Claude Desktop integration.
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:
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"}}.
Add to ~/.cursor/mcp.json (or project-level .cursor/mcp.json):
{
"mcpServers": {
"hello-mcp": {
"command": "/path/to/my-mcp-server/.venv/bin/python",
"args": ["/path/to/my-mcp-server/server.py"]
}
}
}
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.
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"]}
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 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.
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 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.
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."
),
)
]
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.
# 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 |
|---|---|---|
| STDIO | Local Cursor, Claude Desktop, Inspector | Zero network setup; one Client per subprocess; dies when Host closes |
| HTTP + SSE | Team-shared Servers, cloud deploy, remote Agents | Requires TLS + auth in production; SSE needs session affinity behind load balancers |
Always test Servers in isolation before wiring them into Cursor:
npx @modelcontextprotocol/inspector python server.pyinitialize returns server capabilitiestools/list, resources/list, prompts/list match expectationsFor 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.
Package the Server as a container with a non-root user, health checks, and environment-injected secrets.
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.
Combine all three primitives into a Server that indexes your markdown notes and lets Cursor search them during coding sessions.
~/notes/**/*.md at startup; build an in-memory title + path mapnote://{slug} via resources/readsearch_notes: full-text search returning matching slugs and snippetsappend_note: append timestamped entries (with user confirmation in Host)daily_standup: template that pulls yesterday's notes and asks for today's planWire 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).
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.
tools/list in Inspector.mcp.json) or Claude Desktop; test in a real coding session.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.