Building Your First MCP Server

Table of content

MCP servers let you give Claude new capabilities. Build one when existing servers don’t cover your use case.

What MCP servers do

[Claude Code] ←stdio→ [Your MCP Server] → [Anything]
                                        → Database
                                        → API
                                        → Local files
                                        → Hardware

A server exposes:

PrimitivePurposeExample
ToolsActions Claude can takesend_email, query_db
ResourcesData Claude can readConfig files, database records
PromptsReusable prompt templatesSummarization formats

Tools are most common. Start there.

Prerequisites

Pick one:

RuntimeInstall SDK
Python 3.10+pip install mcp
Node.js 18+npm install @modelcontextprotocol/sdk

Minimal Python server

# my_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-server")

@mcp.tool()
def hello(name: str) -> str:
    """Say hello to someone."""
    return f"Hello, {name}!"

if __name__ == "__main__":
    mcp.run()

Run it:

python my_server.py

Minimal TypeScript server

// my-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new McpServer({ name: "my-server", version: "1.0.0" });

server.tool("hello", { name: { type: "string" } }, async ({ name }) => ({
  content: [{ type: "text", text: `Hello, ${name}!` }],
}));

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

Run it:

npx ts-node my-server.ts

Adding useful tools

Real example — a tool that queries a local SQLite database:

# db_server.py
from mcp.server.fastmcp import FastMCP
import sqlite3

mcp = FastMCP("db-server")

@mcp.tool()
def query(sql: str) -> str:
    """Run a read-only SQL query."""
    conn = sqlite3.connect("data.db")
    cursor = conn.execute(sql)
    rows = cursor.fetchall()
    conn.close()
    return str(rows)

@mcp.tool()
def tables() -> str:
    """List all tables in the database."""
    conn = sqlite3.connect("data.db")
    cursor = conn.execute(
        "SELECT name FROM sqlite_master WHERE type='table'"
    )
    names = [row[0] for row in cursor.fetchall()]
    conn.close()
    return ", ".join(names)

if __name__ == "__main__":
    mcp.run()

Connect to Claude Code

Add your server to Claude Code config:

claude mcp add my-server --command "python /path/to/my_server.py"

Or edit ~/.claude/mcp.json directly:

{
  "servers": {
    "my-server": {
      "command": "python",
      "args": ["/path/to/my_server.py"]
    }
  }
}

Verify:

claude mcp list
claude mcp status

Test your tools

claude "Use the hello tool to greet Alice"
claude "What tables are in the database?"

Claude will discover your tools automatically and use them when relevant.

Debugging

ProblemSolution
Server not foundCheck path in mcp.json is absolute
Tools not appearingRestart: claude mcp restart my-server
Server crashesRun manually: python my_server.py and check errors
Permission deniedMake script executable: chmod +x my_server.py

View logs

# Server logs
cat ~/.claude/mcp/my-server/logs/stderr.log

# Run with debug output
MCP_DEBUG=1 python my_server.py

Test without Claude

# Send JSON-RPC directly via stdin
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | python my_server.py

Project structure

For anything beyond a single file:

my-mcp-server/
├── server.py
├── requirements.txt
├── README.md
└── tools/
    ├── __init__.py
    └── database.py

Common patterns

PatternImplementation
API wrapperTool calls external API, returns formatted response
Database accessTool runs queries, returns results
File operationsTool reads/writes specific directories
System infoTool returns machine stats, processes

Security

Your MCP server runs with your user permissions. It can:

Keep tools scoped. Don’t build a “run any shell command” tool.


Next: MCP Servers Reference

Topics: mcp ai-agents architecture