What an MCP server is (and where it fits)
Model Context Protocol (MCP) is an open protocol for connecting AI clients to external context and actions in a consistent, tool-like way. An MCP server is the program that exposes capabilities (tools, resources, prompts) over MCP.
Core roles
- MCP Host: the AI app runtime (Claude Desktop, IDE assistants, OpenClaw-style orchestrators).
- MCP Client: the connection layer inside that host, one per server connection.
- MCP Server: your integration endpoint exposing capabilities.
What servers expose
- Tools: callable functions (usually highest value).
- Resources: structured/readable context (files, docs, records).
- Prompts: reusable prompt templates.
Host app → MCP client connection → MCP server → your local/remote systems.
MCP supports both local servers (commonly stdio) and remote servers (commonly Streamable HTTP). Same protocol semantics, different transport and auth posture.
Prerequisites and assumptions
Required
- Comfort with JSON config and terminal workflows.
- Node.js 20+ or Python 3.10+.
- An MCP-capable client (e.g., Claude Desktop or another MCP host).
- A target system to expose (API, DB, filesystem, internal service).
Scope in this guide
- Recommended path: local dev server first, then harden.
- Examples for both Node and Python.
- Client config example in Claude Desktop format.
- Best-practice checklist for production readiness.
Build a simple MCP server and connect a client
Step 1) Pick your server SDK
- TypeScript:
@modelcontextprotocol/server(+zod). - Python:
mcp[cli]withFastMCP.
Step 2) Create a minimal tool (Node/TypeScript skeleton)
# package install (example)
npm install @modelcontextprotocol/server zod
# then implement a simple MCP server with one tool, e.g. "ping" or "search_docs"
# expose strict input schema and deterministic output structure
Step 3) Create a minimal tool (Python / FastMCP skeleton)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo-server")
@mcp.tool()
def ping(message: str) -> str:
"""Return a deterministic echo for connectivity checks."""
return f"pong: {message}"
if __name__ == "__main__":
mcp.run(transport="stdio")
Step 4) Configure a client to launch your local server
Example Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"demo-server": {
"command": "python",
"args": ["/ABSOLUTE/PATH/to/server.py"]
}
}
}
stdout; use stderr or a logger.Step 5) Restart client and confirm tool discovery
- Restart your MCP client app completely.
- Open tool selector / MCP indicator.
- Run a known-safe test prompt that triggers your demo tool.
- Confirm request/response payload shape matches your schema.
Step 6) Move from local to remote (when needed)
Switch to Streamable HTTP only when you need multi-user access, shared infrastructure, or centralized auth/rate control.
Best practices (the part people skip and regret)
Security
- Principle of least privilege (scoped directories, scoped tokens).
- Require explicit user approval for state-changing tools.
- Use OAuth/token rotation for remote servers.
- Treat tool inputs as untrusted: validate + sanitize.
Schema design
- Use narrow, explicit schemas with enums where possible.
- Avoid free-form "do anything" tools.
- Return consistent JSON-shaped outputs for downstream automation.
- Version breaking schema changes.
Logging + observability
- Add request IDs and tool-call timing.
- Log tool name, args hash, result status, and error class.
- For stdio transport, log to stderr/file only.
- Keep sensitive payloads redacted by default.
Reliability
- Set timeouts for all external calls.
- Implement retries with jitter for transient errors.
- Rate-limit expensive tools and respect upstream quotas.
- Return graceful errors that help the model recover.
Success checks
- Client loads server without startup errors.
- Your tool appears in the available MCP tools list.
- One test call succeeds with predictable output.
- Error cases return structured failures (not stack traces).
- Logs show timing + request IDs without leaking secrets.
Troubleshooting
- Tool never appears: bad command/path in client config; verify absolute path and executable.
- Server crashes on first call: schema mismatch or missing dependency; run server directly first.
- Random JSON parse failures: stdout pollution in stdio mode; move all logs to stderr.
- Slow/timeout behavior: add per-tool timeout and cache stable reads.
- Permission errors: reduce scope, then re-grant only required paths/tokens.