
For years, connecting Large Language Models to internal databases, local filesystems, and developer tools required writing bespoke function-calling schemas for each LLM provider. Every time you switched models or updated an agent framework, you had to re-implement tool definitions, authorization boundaries, and serialization logic.
The Model Context Protocol (MCP), introduced by Anthropic and adopted broadly across Claude Desktop, Cursor, and modern AI developer environments, solves this fragmentation. MCP is the universal, open standard - essentially the "USB-C of AI integrations" - that enables LLMs to discover, inspect, and invoke local and remote tools through a standardized JSON-RPC interface.
In this guide, we break down the architecture of MCP, build a production-grade Python MCP server from scratch using FastMCP, implement secure tools and dynamic resources, and wire it directly into Claude Desktop.
Configure stdio & SSE servers with Claude, Cursor & Python export
Configure and validate Model Context Protocol (MCP) servers with stdio and SSE transports. Export ready-to-run configurations for Claude Desktop, Claude Code CLI, Cursor, and TypeScript/Python SDKs.
To understand how MCP functions, examine the three core participants in the protocol:
stdin/stdout). This is the standard, zero-overhead mode for local developer machines.uvWe'll use uv, the blazing-fast Python package manager, to initialize our project and install the official Python MCP SDK:
# Create project directory
mkdir local-dev-mcp && cd local-dev-mcp
# Initialize Python workspace
uv init
uv add mcp pydantic
FastMCPThe Python SDK provides FastMCP, a high-level framework modeled after FastAPI. It automatically extracts parameter schemas, type hints, and documentation strings into valid JSON-RPC tool schemas.
Create server.py:
# server.py: Production Local Developer MCP Server
import os
from pathlib import Path
from pydantic import BaseModel, Field
from mcp.server.fastmcp import FastMCP
# Initialize the MCP Server with identity and version
mcp = FastMCP("Developer-Workspace-Tools", version="1.0.0")
# Define a safe base directory to prevent directory traversal attacks
SAFE_WORKSPACE = Path(os.getenv("MCP_WORKSPACE_DIR", "./workspace")).resolve()
SAFE_WORKSPACE.mkdir(parents=True, exist_ok=True)
def resolve_safe_path(relative_path: str) -> Path:
"""Guarantees file operations remain strictly inside the authorized workspace."""
resolved = (SAFE_WORKSPACE / relative_path).resolve()
if not str(resolved).startswith(str(SAFE_WORKSPACE)):
raise PermissionError(f"Access denied: '{relative_path}' attempts path traversal.")
return resolved
# ====================================================================
# Primitive 1: Tools (Callable Actions)
# ====================================================================
@mcp.tool()
def save_code_snippet(filename: str, code: str, language: str = "python") -> str:
"""
Saves a code snippet to the authorized local workspace.
Args:
filename: Name of the file including extension (e.g., 'auth_utils.py')
code: Full code content to write
language: Programming language identifier for metadata
"""
target_path = resolve_safe_path(filename)
target_path.parent.mkdir(parents=True, exist_ok=True)
with open(target_path, "w", encoding="utf-8") as f:
f.write(code)
return f"Successfully wrote {len(code)} bytes to {filename} (Language: {language})"
@mcp.tool()
def list_workspace_files() -> list[dict]:
"""Lists all files in the developer workspace with file size and modified timestamps."""
files = []
for p in SAFE_WORKSPACE.rglob("*"):
if p.is_file():
stat = p.stat()
files.append({
"relative_path": str(p.relative_to(SAFE_WORKSPACE)),
"size_bytes": stat.st_size,
"modified_timestamp": stat.st_mtime,
})
return files
# ====================================================================
# Primitive 2: Resources (Context Streams)
# ====================================================================
@mcp.resource("workspace://logs/{log_name}")
def get_system_log(log_name: str) -> str:
"""Reads a designated log file from the workspace for debugging context."""
log_path = resolve_safe_path(f"logs/{log_name}.log")
if not log_path.exists():
return f"Log file '{log_name}' not found."
with open(log_path, "r", encoding="utf-8") as f:
# Return the last 50 lines to conserve context window
lines = f.readlines()
return "".join(lines[-50:])
if __name__ == "__main__":
# Runs the Stdio transport event loop
mcp.run()
To allow Claude Desktop to discover and invoke your new MCP server, configure its JSON configuration file:
~/Library/Application Support/Claude/claude_desktop_config.json~/.config/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonAdd your server definition under mcpServers:
{
"mcpServers": {
"developer-workspace": {
"command": "uv",
"args": [
"run",
"--directory",
"/absolute/path/to/local-dev-mcp",
"server.py"
],
"env": {
"MCP_WORKSPACE_DIR": "/absolute/path/to/workspace"
}
}
}
}
Restart Claude Desktop. You will notice a new 🔌 hammer/tool icon appear in the bottom-right corner of the chat input box. Clicking it displays your registered tools: save_code_snippet and list_workspace_files.
Before connecting to Claude Desktop, you can interactively test and debug your MCP server using the official browser-based MCP Inspector:
# Launch the interactive MCP debugger
npx @modelcontextprotocol/inspector uv run server.py
The inspector launches a local web UI where you can:
pathlib.Path.resolve(). Never trust relative path strings passed directly by an LLM.SELECT-only privileges.print() statements for debugging in an MCP server. Because standard output is reserved strictly for JSON-RPC messages, arbitrary text output will corrupt the wire protocol. Use standard Python logging configured to write to sys.stderr.A developer is building an MCP server to integrate a proprietary internal database with Claude Desktop. They need to ensure minimal latency for local development and maximum security, preventing external access to the database. Which MCP transport mechanism should they prioritize for this scenario?
Yes. An MCP server is simply standard code. You can use httpx or requests inside your tool implementations to call GitHub APIs, Slack webhooks, Jira tickets, or internal Kubernetes clusters.
Claude inspects the function signature and docstrings advertised by your MCP server. Writing descriptive docstrings (explaining what the tool does, what parameters mean, and when it should be called) is the single most important factor for reliable tool calling.
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

What Claude's free tier actually gives you: Sonnet model, 200K context, Projects, and local MCP tools via Claude Desktop, and how to stretch the message limit further than you'd expect.
Read more
Architecting, securing, and deploying production remote Model Context Protocol (MCP) servers with Streamable HTTP, DNS rebinding mitigation, OAuth 2.1 bearer tokens, and FastAPI.
Read more
Step-by-step guide to developing production MCP servers in TypeScript: input validation with Zod, secure tool handler patterns, and Claude integration.
Read more