Protocol basics · Tools / Resources / Prompts · Debug & deploy · Personal knowledge base
Backend and AI developers with Python or TypeScript experience keep hitting the same wall: Claude and GPT are smart, but they cannot query your database, call internal APIs, or read local files—because models lack a standard channel to external tools. The Model Context Protocol (MCP), open-sourced by Anthropic, lets AI Clients (Cursor, Claude Desktop) talk to your Server over JSON-RPC. By the end of this guide you will build and deploy a production-ready MCP Server, covering: protocol architecture → environment setup → Hello World → Tools / Resources / Prompts → HTTP remote transport → debug and test → Docker deployment → a personal knowledge-base project → the 2026 ecosystem outlook.
Even the strongest models have training cutoffs. They cannot magically know your CRM customers, last night's logs, or the live state of an internal API. The industry path is clear: Function Calling → Plugins → MCP. The first two are mostly vendor-specific formats—switch models and you rewrite the integration layer. MCP standardizes how AI discovers and invokes tools, so you write once and reuse across Clients.
Pain point: You want Claude or GPT to query databases, call APIs, and manipulate files—but every vendor speaks a different format.
Scenario: In Cursor, have AI read your Markdown notes; in Claude Desktop, pull GitHub Issues on demand.
Promise: This guide takes you from an empty directory to a deployable Server, with complete code and a troubleshooting table.
Anthropic released MCP in November 2024 to standardize AI-to-tool communication, replacing N models × M tools = N×M custom adapters with one open protocol. For the broader protocol narrative, see our MCP and HTTP analogy article.
MCP uses a Client ↔ Server model: the Client lives on the AI side (Claude Desktop, Cursor); the Server is what you build, exposing three core capabilities over JSON-RPC:
Tools: Functions the AI can call—search, calculate, query databases.
Resources: Data the AI can read—files, URLs, config streams.
Prompts: Predefined, parameterizable prompt templates.
Built on JSON-RPC 2.0. Transport options: stdio (local subprocess—default for Cursor and Claude Desktop) and HTTP + SSE / Streamable HTTP (remote services). Lifecycle: initialization handshake → capability negotiation (initialize) → request/response → shutdown.
| Dimension | MCP | OpenAI Function Calling | LangChain Tools |
|---|---|---|---|
| Standardization | Open protocol | Vendor-specific | Framework-bound |
| Transport | stdio / HTTP | HTTP | HTTP |
| Cross-model | Yes | No | Partial |
| Resources / Prompts | Native support | Not supported | Not supported |
| Ecosystem | 10,000+ Servers in 2026 | Mature | Mature |
Python (official mcp SDK with FastMCP decorators) is the best starting point for newcomers. TypeScript (@modelcontextprotocol/sdk) suits frontend and full-stack teams. This guide uses Python as the primary example, with TypeScript noted where relevant.
# Python python -m venv .venv && source .venv/bin/activate pip install mcp httpx pydantic # TypeScript (reference) npm init -y && npm install @modelcontextprotocol/sdk
my-mcp-server/ ├── server.py # Main entry point ├── tools/ # calculator.py, web_search.py ├── resources/ # file_reader.py ├── prompts/ # templates.py ├── tests/test_tools.py ├── pyproject.toml └── README.md
MCP Inspector: npx @modelcontextprotocol/inspector python server.py
Claude Desktop: Edit claude_desktop_config.json to register your Server.
Cursor: Settings → MCP → add command + args.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-first-server")
@mcp.tool()
def say_hello(name: str) -> str:
"""Greet a person by name"""
return f"Hello, {name}! This is your first MCP tool."
if __name__ == "__main__":
mcp.run()Run python server.py, then in Inspector or Cursor MCP settings enter {"command":"python","args":["/path/to/server.py"]}. Refresh the tool list—you should see say_hello.
Use absolute paths for server.py and your virtualenv Python binary.
After code changes, restart the MCP connection or reopen the Cursor window.
Explicitly ask the model to call the tool in chat to confirm registration.
Your function signature is the documentation: parameter types, return types, and docstrings are exposed to the model for tool selection. Use snake_case with verb prefixes (search_web, read_file). Return structured JSON on errors instead of raw exceptions so the model can self-correct.
from pydantic import BaseModel, Field
class SearchInput(BaseModel):
query: str = Field(description="Search keywords")
max_results: int = Field(default=5, description="Maximum results to return")
language: str = Field(default="en", description="Result language")
@mcp.tool()
def web_search(input: SearchInput) -> list[dict]:
"""Run a web search and return matching results"""
...| Tool | Purpose | Caveats |
|---|---|---|
| Calculator | Evaluate math expressions | Use ast.literal_eval or a safe parser—never eval |
| File I/O | Read/write local files | Whitelist directories; normalize paths to prevent traversal |
| HTTP requests | Call external APIs | Timeouts, retries, response size limits |
| Database queries | Read-only SQL | Parameterized queries; block DDL |
| Time utilities | Current time, timezone conversion | Return ISO 8601 for easy parsing |
@mcp.tool()
async def fetch_url(url: str) -> str:
"""Fetch content from a URL"""
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url)
return response.textSet timeouts on network and I/O operations (15–30s recommended).
Apply permission checks on sensitive operations (API keys, allowed paths).
Return {"error": "...", "hint": "..."} so the model can retry intelligently.
Resource vs Tool: A Resource is a data provider (mostly read-only); a Tool is an action executor. Resources are addressed by URI: file://, http://, custom://.
@mcp.resource("config://app-settings")
def get_app_settings() -> str:
"""Return application configuration"""
return json.dumps({"version": "1.0", "env": "production"})
@mcp.resource("user://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
"""Return user profile by ID"""
return json.dumps(db.query_user(user_id))Resource types include: text (text/plain, application/json), binary (images, PDFs), and streaming (live market data). A practical project: a filesystem Server that lists directories, reads files, and optionally subscribes to file changes.
MCP Prompts are predefined prompt fragments with dynamic parameter injection, improving team consistency and maintainability.
from mcp.types import PromptMessage, TextContent
@mcp.prompt()
def code_review_prompt(language: str, code: str) -> list[PromptMessage]:
"""Code review prompt template"""
return [PromptMessage(
role="user",
content=TextContent(type="text", text=f"""Review the following {language} code:
1. Quality and readability 2. Bugs and security 3. Performance
```{language}
{code}
```""")
)]You can define multi-turn templates with user + assistant roles for interview simulations, debugging assistants, and more. Clients call prompts/get to retrieve the filled message chain.
| Feature | stdio | HTTP + SSE |
|---|---|---|
| Deployment | Local process | Remote server |
| Latency | Very low | Network-dependent |
| Multi-client | Not supported | Supported |
| Use case | Local IDE tools | SaaS / team sharing |
mcp = FastMCP("remote-server", transport="streamable-http")
if __name__ == "__main__":
mcp.run(host="0.0.0.0", port=8000)Production requirements: Bearer Token / API Key middleware, CORS allowlists, and rate limiting. Never expose an unauthenticated HTTP MCP Server to the public internet.
Inspector provides a GUI to test tools/list, tools/call, inspect JSON-RPC logs, and simulate error scenarios.
@pytest.mark.asyncio
async def test_calculator_tool():
server_params = StdioServerParameters(command="python", args=["server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool("calculate", {"expression": "2 + 2"})
assert result.content[0].text == "4"| Error | Cause | Fix |
|---|---|---|
| Tool not listed | Wrong config path | Verify absolute paths in config.json |
| JSON serialization failure | Unsupported return type | Convert to str or dict |
| Timeout disconnect | Slow tool execution | Use async + timeout controls |
| Permission denied | Restricted file path | Configure allowed directories / use VNC for TCC |
FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt COPY . . EXPOSE 8000 CMD ["python", "server.py"]
Railway / Render: One-click deploy for personal projects.
AWS Lambda / Cloud Run: Serverless—watch cold starts and stdio limitations.
Self-hosted VPS + Nginx: Reverse proxy, TLS, and rate limiting.
Structured request logs, Prometheus metrics for tool calls, Sentry error alerts, and a /health endpoint. Declare your MCP protocol version and keep tool upgrades backward compatible.
Goal: Let AI search local Markdown notes, run semantic retrieval, and create or update notes. In Cursor, ask: "What did I write about MCP last week?"
| Component | Recommendation |
|---|---|
| Vector store | ChromaDB / Qdrant (local) |
| Embedding model | text-embedding-3-small |
| File watcher | watchfiles for incremental indexing |
index_notes: Scan a directory and build a vector index.
semantic_search: Return relevant snippets by meaning.
write_note: Create or append Markdown files.
Resource: notes://{path} to read a single note directly.
The AI calls semantic_search, synthesizes answers from retrieved snippets, and saves tokens compared to stuffing your entire note library into context.
mcp-server-filesystem — Filesystem operations
mcp-server-github — GitHub repositories
mcp-server-brave-search — Web search
mcp-server-postgres — Database queries
mcp-server-slack — Slack messaging
In 2026, Cursor, Claude Desktop, OpenAI, Google Gemini, and Microsoft all support or have completed MCP integration. The MCP Marketplace and enterprise security standards (OAuth 2.1, audit trails) continue to mature. Official docs: modelcontextprotocol.io; Python SDK: github.com/modelcontextprotocol/python-sdk.
Pick a Host (Cursor / Claude Desktop) and confirm 2026 builds have native MCP support.
Develop locally over stdio; validate tools/list in Inspector.
Register in Cursor MCP config; smoke-test with the Hello World tool.
For Servers touching browsers, Keychain, or screen recording, complete TCC authorization in a macOS graphical session—SSH alone cannot click Allow.
Deploy HTTP mode in Docker with auth and monitoring; cross-link with our Agent Skill guide and browser MCP checklist.
Python (mcp + FastMCP) is the fastest on-ramp; TypeScript (@modelcontextprotocol/sdk) fits teams already on Node. The protocol layer is equivalent.
Prefer stdio for local IDE development; choose HTTP + SSE / Streamable HTTP when you need multi-user sharing or SaaS delivery—and always add authentication.
Tools execute actions (write to DB, send requests); Resources provide read-only data (config, file contents) addressed by URI templates.
Browser DevTools MCP, high-privilege file access, Keychain, and screen recording trigger macOS TCC dialogs. SSH cannot click Always Allow—you need a VNC graphical session on the same machine as the Host.
MCP is the standard protocol for AI tool integration—mastering the full path from Hello World to production deployment is a core skill for AI engineers in 2026. What tool will you build first? Share your Server design after you ship it.
Hidden cost rarely lives in writing code—it lives in the runtime environment. Windows-primary workflows plus a cloud Mac running Cursor MCP mean TCC dialogs, Python virtualenvs, and Inspector logs must be cross-checked in a graphical session on the same machine. Buying a Mac mini adds sleep, OS updates, and depreciation; 8 GB RAM chokes under vector indexing plus concurrent Servers.
To validate your MCP toolchain hourly with on-machine GUI checks for TCC and Inspector, rent a remote Mac through VNCMac: use the primary button below for the pricing page, or browse plans on the homepage first.