MCP Server Builder's Quick Reference

Build, debug, and ship Model Context Protocol servers in Python (FastMCP) and TypeScript — the whole loop on one page.

⚡ Maintained by protodex.io — the largest MCP directory (13,000+ servers indexed)

What MCP is, in one page

The Model Context Protocol is a JSON-RPC 2.0 wire protocol that lets a language-model client (Claude Desktop, Claude Code, an agent runtime) talk to an external server that exposes tools, resources, and prompts. The server runs in a separate process. The client launches it or connects to it. They speak JSON over a transport: stdio, Streamable HTTP, or SSE.

MCP is not an LLM API. It does not call models. It is the bridge between a model-facing client and the outside world. A server might wrap a database, a shell, a file system, a web search, a hardware device, or a SaaS API. The client decides which servers to load, and the model decides which tools to call.

The three primitives

ToolsFunctions the model can call. JSON-Schema input, free-form output. The high-impact primitive.
ResourcesRead-only data the client can attach to the model's context. URI-addressed. Optional.
PromptsNamed prompt templates the user can pick from a menu. Optional, often skipped.

If you only build one primitive, build tools. Resources and prompts are nice; tools are why MCP exists.

The handshake

  1. Client launches the server (stdio) or opens a connection (HTTP).
  2. Client sends initialize with its protocol version and capabilities.
  3. Server replies with its protocol version, capabilities, and server info.
  4. Client sends notifications/initialized. Session is now live.
  5. Client calls tools/list, resources/list, prompts/list to enumerate.
  6. From there, normal request/response (tools/call, resources/read, etc.).

Pick a language: Python or TypeScript

Both SDKs are first-party. Pick by where your existing code lives.

Python (FastMCP)Fastest to write. Decorator-based. Best if your tool wraps a Python library, a pandas pipeline, an ML model, or a CLI.
TypeScriptBest if your tool wraps a Node API, a browser automation library, or you want to ship a single-binary stdio server via npx.
Other (Go, Rust, C#)Community SDKs exist. Stable enough for personal use; for distribution prefer the two first-party SDKs until your need is specific.

Most servers published on the public registries are Python. Most servers shipped inside paid products are TypeScript (smaller image, faster cold-start). For a first server, Python.

Python: FastMCP in 30 lines

mcp[cli] is the official Python SDK. FastMCP is the high-level API on top.

# pyproject: dependencies = ["mcp[cli]>=1.2"]
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
def get_forecast(city: str, days: int = 1) -> str:
    """Return a plain-text weather forecast for a city.

    Args:
        city: City name in English.
        days: 1-7. Defaults to 1.
    """
    return fetch_forecast(city, days)   # your code

@mcp.resource("weather://{city}/current")
def current(city: str) -> str:
    return fetch_current(city)

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

Three things to notice. First, the docstring becomes the tool description the model sees. Write it for the model, not for a human reader. Second, type hints become the JSON Schema. int = 1 means optional with default. Use Literal['a','b'] for enums. Third, mcp.run() blocks on stdio. For HTTP, see section 7.

Naming

Python: tool I/O and errors

FastMCP marshals return values into MCP content blocks. Three rules.

Errors

Raise. FastMCP catches and converts to an MCP error response. The model sees the message; show it something actionable.

@mcp.tool()
def transfer(amount: float, to: str) -> str:
    if amount <= 0:
        raise ValueError("amount must be > 0")
    if amount > balance():
        raise ValueError(
            f"insufficient balance: have {balance()},"
            f" need {amount}"
        )
    return do_transfer(amount, to)

Good error text reads as an instruction to the caller: what went wrong, what would fix it. Bad error text is a stack trace or "internal error". The model can recover from the first; the second wastes a turn.

Python: Context, progress, logs

Inject an mcp.server.fastmcp.Context parameter into any tool to get access to logging, progress reporting, and the active session.

from mcp.server.fastmcp import FastMCP, Context

@mcp.tool()
async def crawl(url: str, depth: int, ctx: Context) -> str:
    pages = enumerate_pages(url, depth)
    for i, page in enumerate(pages):
        await ctx.report_progress(i, len(pages))
        ctx.info(f"fetched {page.url}")
    return summarize(pages)

report_progress is shown by graphical clients (Claude Desktop shows a bar). ctx.info / ctx.warning / ctx.error go to the client's log surface; the model does not see them. Use them for post-mortem debugging, not for routing model behavior.

TypeScript: minimal server

@modelcontextprotocol/sdk is the official TypeScript SDK. The API mirrors the Python one but is more explicit (no decorators).

import { McpServer } from
  "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from
  "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({
  name: "weather", version: "0.1.0",
});

server.tool(
  "get_forecast",
  "Return a forecast for a city.",
  {
    city: z.string(),
    days: z.number().int().min(1).max(7).default(1),
  },
  async ({ city, days }) => {
    const text = await fetchForecast(city, days);
    return { content: [{ type: "text", text }] };
  },
);

const transport = new StdioServerTransport();
await server.connect(transport);

Use Zod for input schemas. The SDK converts Zod to JSON Schema for the protocol. Return shape is { content: [...] } with one or more content blocks. Each block has a type (text, image, resource).

Package layout

Transports

stdio (default for local servers)

The client launches the server as a subprocess. Requests go in on stdin, responses on stdout, diagnostics on stderr. Zero network. Works everywhere. Default for Claude Desktop and Claude Code server configs.

Streamable HTTP (current standard for remote)

One POST endpoint. Client sends JSON-RPC requests; server streams responses as Server-Sent Events. Replaces the older SSE transport. Use for any server that runs out-of-process, on a different machine, or behind a load balancer.

# Python (FastMCP) -- HTTP server on :8000
mcp.run(
    transport="streamable-http",
    host="0.0.0.0",
    port=8000,
)

SSE (deprecated, but still seen)

Older two-endpoint design: GET /sse for the event stream, POST /messages for requests. New servers should not implement SSE. Existing servers should migrate when the client matrix permits.

Picking one

Local dev / personal usestdio
Shipping a public server / multi-userStreamable HTTP behind TLS
Inside a private LAN serviceStreamable HTTP, no auth or simple bearer
Connecting to a legacy MCP serverSSE, but migrate it

Authentication

Stdio servers do not authenticate; the OS process boundary is the trust boundary. The user trusts the client; the client trusts the server it launches. If a stdio server needs an API key, read it from an environment variable the client injects.

HTTP servers: pick one

Bearer tokenSimple. Header Authorization: Bearer <token>. Good for personal-use servers and behind-the-firewall deployments.
OAuth 2.1 (MCP spec)The protocol-blessed path for multi-user public servers. Client performs an OAuth flow; resulting token attached to every request. More moving parts but interoperable.
mTLSIf you control both ends and operate at scale. Not common in MCP land yet.

Secret handling

Tool descriptions: what the model actually reads

The model decides whether to call your tool based on the tool name, description, and parameter schema. Treat the description as the tool's marketing copy aimed at the model. Three rules.

  1. Lead with WHEN to use it, not what it does. "Use this to look up the current price of a stock. Returns the latest trade price in USD." beats "Fetches financial data."
  2. Name the inputs in plain language inside the description, even though they are also in the schema. The model is more reliable about parameter shapes when both signals agree.
  3. Mention what the tool does NOT do, if it is easy to confuse with another tool. "This is delete_file, not move_file — the file is gone after this call."

Test by reading the description aloud and asking: if I knew nothing else, would I know when to call this? If you have to clarify out loud, the model will also have to guess.

Schemas: get them right or pay forever

Every wrong-shape call costs the user a turn and the operator credibility. Defensive schema design pays back many times over.

Output shapes

Debugging

stdio servers

The Inspector is the official debugging tool. It is an Electron app that talks to your server like a client would and shows the wire traffic.

# launch your server under the Inspector
npx @modelcontextprotocol/inspector python my_server.py
# then open the URL it prints (usually http://localhost:5173)

Use it to confirm tools/list matches what you intended, to exercise tool calls with hand-crafted arguments, and to watch what your server actually returns.

When the client fails to load the server

When tools never get called

Deployment: Claude Desktop

Claude Desktop reads claude_desktop_config.json on launch. macOS path: ~/Library/Application Support/Claude/claude_desktop_config.json. Windows: %APPDATA%\Claude\claude_desktop_config.json.

{
  "mcpServers": {
    "weather": {
      "command": "uvx",
      "args": ["weather-mcp"],
      "env": { "WEATHER_API_KEY": "..." }
    },
    "local-py": {
      "command": "/Users/me/proj/.venv/bin/python",
      "args": ["/Users/me/proj/server.py"]
    }
  }
}

Restart Claude Desktop after editing. The slider icon (bottom-right of the prompt box) lists loaded servers. A red dot means startup failed; click it for the error.

Deployment: Claude Code

Claude Code stores MCP server configs in ~/.claude.json under the mcpServers key, with the same shape as Claude Desktop's config. The CLI also accepts ad-hoc additions:

# add a stdio server for the current project
claude mcp add weather -- uvx weather-mcp

# add a remote HTTP server
claude mcp add --transport http example https://mcp.example.com

# list servers visible to the current project
claude mcp list

Project-scoped servers live in .mcp.json at the project root and are committed to the repo, so collaborators get the same toolset. User-scoped servers live in ~/.claude.json and follow you across projects.

Deployment: hosted HTTP server

If you are shipping a public server, the bare minimum stack is:

  1. A small VM or container platform (Fly, Railway, Render, Cloudflare Workers if your runtime fits).
  2. TLS in front of your server. The MCP HTTP spec assumes TLS for remote use; do not run plain HTTP on the public internet.
  3. A health endpoint (GET /health returning 200) so the platform can route traffic only to live instances.
  4. Structured logs to stderr or a log drain. Include the JSON-RPC id of each request so you can correlate across multi-turn sessions.
  5. Rate limits at the edge. A single misbehaving client can otherwise melt your upstream.

Auth checklist

Distribution: how users find you

Patterns and anti-patterns

Patterns that work

Anti-patterns to avoid

Security checklist

Testing

Three layers, in order of importance:

  1. Unit tests on the tool functions themselves, called directly. This catches 90% of bugs at near-zero cost.
  2. Integration tests using the SDK's in-process client. Both Python and TypeScript SDKs ship a test client you can wire to the server in the same process — no transport involved.
  3. End-to-end tests with the Inspector or a script that drives the actual stdio transport. Catches transport bugs (stray stdout writes, malformed JSON-RPC).
# Python: in-process integration test
import pytest
from mcp.shared.memory import (
    create_connected_server_and_client_session,
)
from my_server import mcp

@pytest.mark.asyncio
async def test_get_forecast():
    async with create_connected_server_and_client_session(
        mcp._mcp_server
    ) as (_, client):
        result = await client.call_tool(
            "get_forecast", {"city": "Mumbai"}
        )
        assert "Mumbai" in result.content[0].text

Performance

Versioning and breaking changes

Treat your tool surface like a public API. Models trained against an old surface still work against a new one only if you add, not remove.

Adding a toolSafe. Bump minor.
Adding an optional parameterSafe. Bump minor.
Adding a required parameterBreaking. Bump major OR provide a default and keep optional.
Renaming a toolBreaking. Keep the old name as an alias for one major version.
Removing a toolBreaking. Bump major. Announce in CHANGELOG.
Changing return shapeBreaking, even if it 'looks compatible' — the model has memorized your old shape.

If you must break: document the migration in the README, and consider shipping the old and new tool side-by-side for one release so users can update at their pace.

Glossary

ClientThe model-facing application (Claude Desktop, Claude Code, agent runtime) that loads servers.
ServerAn external process that exposes tools/resources/prompts via MCP.
ToolA function the model can call. Has a name, description, and JSON-Schema input.
ResourceA read-only blob of data the client can attach to the model's context, addressed by URI.
PromptA named, parameterized prompt template offered to the user.
TransportHow bytes move between client and server. Stdio, Streamable HTTP, SSE (deprecated).
CapabilityA protocol feature a side declares it supports during the initialize handshake.
InspectorThe official Electron debugger for MCP servers.
FastMCPThe decorator-based high-level API in the Python SDK.
JSON-RPC 2.0The wire format MCP uses. Requests have id/method/params; responses have id and result-or-error.

Further reading

Bookmark all six. You will reference them while building, every time.

Don't want to build it yourself?

Send me your REST or OpenAPI API. I ship a production MCP server — auth, error handling, rate limiting, security-scanned, deployed — in about 5 days for a $900 flat fee. Built by the maintainer of this directory.

Get a production MCP server →

FAQ

What is an MCP server?

An MCP (Model Context Protocol) server is a JSON-RPC 2.0 program that exposes tools, resources, and prompts to an AI client like Claude. The client calls your tools; your server runs the logic and returns content blocks the model reads.

Should I build my MCP server in Python or TypeScript?

Both SDKs are first-party and equally capable. Pick by where your existing code lives: Python (FastMCP, the mcp[cli] package) if your API/logic is already in Python; TypeScript (@modelcontextprotocol/sdk) if it's in Node.

How do I connect an MCP server to Claude Desktop?

Add a stdio entry to claude_desktop_config.json with the command and args that launch your server, then restart Claude Desktop. The Deployment: Claude Desktop section above has the exact JSON.

What makes a good MCP tool description?

The model only acts on what the description says. Write it for the model, not a human: state what the tool does, when to use it, and the exact format of each argument. Vague descriptions cause the model to call tools wrong or not at all.

Can I hire someone to build a production MCP server for me?

Yes. Protodex builds production MCP servers from your existing REST/OpenAPI API — auth, error handling, rate limiting, security-scanned, deployed — for a $900 flat fee in about 5 days. Send your API docs on the build page.

A free reference from protodex.io — browse 13,000+ MCP servers, or have one built for you.
← Back to the MCP directory