
| Difficulty: Advanced | Category: Ai Tools |
Claude MCP: Connect AI to Your Local Tools in 15 Minutes
By March 2026, over 40% of AI agents are running with direct tool access—not through brittle prompt engineering, but standardized protocols. Anthropic’s Model Context Protocol (MCP) turns Claude from a chatbot into a programmable interface that can read your files, query databases, and call APIs directly.
This isn’t theoretical. Companies like Block and Apollo are already using MCP servers to let Claude access internal tools, cutting integration time from weeks to hours.
Prerequisites
Before you start, ensure you have:
- Claude Desktop app (version 0.7.0+) or API access with Claude 3.5+
- Python 3.10+ with
pipandvirtualenv - Basic understanding of JSON-RPC and async Python
- A project you want Claude to interact with (filesystem, database, or API)
Step-by-Step Guide
Step 1: Understand What MCP Actually Does
MCP is a protocol—not a library. It standardizes how AI models discover and invoke tools through three primitives:
- Resources: Read-only data Claude can access (files, database records, API responses)
- Tools: Functions Claude can execute (write files, run queries, send emails)
- Prompts: Templated workflows Claude can trigger
Think of it as OpenAPI for AI agents. Your MCP server exposes capabilities; Claude’s client discovers and uses them.
Gotcha: MCP uses JSON-RPC over stdio (standard input/output), not HTTP. Claude launches your server as a subprocess, not a web service.
Step 2: Install the MCP Python SDK
Create a new project directory and install the official SDK:
mkdir claude-mcp-filesystem
cd claude-mcp-filesystem
python3 -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
pip install mcp==0.9.0
The mcp package includes both server and client implementations. Version 0.9.0 (released February 2026) added async context managers and improved error handling.
Step 3: Build a Minimal Filesystem MCP Server
Create server.py with this complete implementation:
import asyncio
from pathlib import Path
from mcp.server import Server
from mcp.types import Tool, TextContent, Resource
import mcp.server.stdio
# Initialize MCP server
app = Server("filesystem-server")
# Define available tools
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="read_file",
description="Read contents of a file from the allowed directory",
inputSchema={
"type": "object",
"properties": {
"path": {"type": "string", "description": "Relative file path"}
},
"required": ["path"]
}
),
Tool(
name="list_directory",
description="List all files in a directory",
inputSchema={
"type": "object",
"properties": {
"path": {"type": "string", "description": "Directory path"}
}
}
)
]
# Implement tool handlers
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
base_path = Path("/Users/yourname/projects") # Change this!
if name == "read_file":
file_path = base_path / arguments["path"]
if not file_path.is_relative_to(base_path):
raise ValueError("Path traversal detected")
content = file_path.read_text()
return [TextContent(type="text", text=content)]
elif name == "list_directory":
dir_path = base_path / arguments.get("path", "")
files = [f.name for f in dir_path.iterdir()]
return [TextContent(type="text", text="\n".join(files))]
# Run server on stdio
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Pro tip: Change base_path to a real project directory. MCP servers should validate all paths to prevent directory traversal attacks—notice the is_relative_to() check.
Step 4: Configure Claude Desktop to Use Your Server
Open Claude’s config file:
- Mac:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add your server configuration:
{
"mcpServers": {
"filesystem": {
"command": "/Users/yourname/claude-mcp-filesystem/venv/bin/python",
"args": ["/Users/yourname/claude-mcp-filesystem/server.py"],
"env": {}
}
}
}
Gotcha: Use absolute paths for both command and args. Relative paths will fail silently. The command should point to your virtualenv’s Python interpreter, not system Python.
Step 5: Test the Connection
Restart Claude Desktop completely (Cmd+Q on Mac, not just closing the window). Open a new conversation and type:
What files are in my project directory?
If configured correctly, Claude will:
- Detect your
filesystem-serverMCP connection - Invoke the
list_directorytool - Parse and display the results
You should see Claude respond with actual filenames from your base_path directory.
Gotcha: If nothing happens, check Claude’s logs:
tail -f ~/Library/Logs/Claude/mcp*.log
Look for connection errors or JSON-RPC parsing issues.
Step 6: Add Database Access
Expand your server to query PostgreSQL. Install asyncpg:
pip install asyncpg==0.29.0
Add this tool to your list_tools() function:
Tool(
name="query_database",
description="Execute a read-only SQL query",
inputSchema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "SQL SELECT statement"}
},
"required": ["query"]
}
)
And implement the handler:
import asyncpg
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
# ... existing handlers ...
elif name == "query_database":
query = arguments["query"]
if not query.strip().upper().startswith("SELECT"):
raise ValueError("Only SELECT queries allowed")
conn = await asyncpg.connect(
"postgresql://user:pass@localhost/mydb"
)
rows = await conn.fetch(query)
await conn.close()
result = "\n".join([str(dict(row)) for row in rows])
return [TextContent(type="text", text=result)]
Pro tip: Use read-only database credentials. Claude will have full query access, so implement query timeouts and row limits in production.
Step 7: Implement Resources for Context
Resources let Claude proactively fetch data without invoking tools. Add this to expose your project’s README:
@app.list_resources()
async def list_resources() -> list[Resource]:
return [
Resource(
uri="file:///README.md",
name="Project README",
mimeType="text/markdown"
)
]
@app.read_resource()
async def read_resource(uri: str) -> str:
if uri == "file:///README.md":
return Path("/Users/yourname/projects/README.md").read_text()
raise ValueError(f"Unknown resource: {uri}")
Claude will now automatically read your README when discussing the project, without you explicitly asking.
Step 8: Deploy for Team Use
For team deployment, run your MCP server as a daemon and expose it via HTTP using the mcp-proxy tool:
pip install mcp-proxy==0.3.0
mcp-proxy --server ./server.py --port 8080 --auth-token your-secret-token
Team members update their Claude config to:
{
"mcpServers": {
"filesystem": {
"url": "http://your-server:8080",
"headers": {
"Authorization": "Bearer your-secret-token"
}
}
}
}
Gotcha: MCP over HTTP is still experimental in March 2026. Expect API changes before v1.0.
Practical Example: Complete Project Analysis Server
Here’s a production-ready MCP server that lets Claude analyze Python projects:
import asyncio
import ast
from pathlib import Path
from mcp.server import Server
from mcp.types import Tool, TextContent
import mcp.server.stdio
app = Server("code-analyzer")
@app.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="analyze_functions",
description="Extract all function names and docstrings from a Python file",
inputSchema={
"type": "object",
"properties": {
"file_path": {"type": "string"}
},
"required": ["file_path"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "analyze_functions":
path = Path(arguments["file_path"])
code = path.read_text()
tree = ast.parse(code)
functions = []
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
docstring = ast.get_docstring(node) or "No docstring"
functions.append(f"{node.name}: {docstring}")
return [TextContent(type="text", text="\n\n".join(functions))]
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await app.run(read_stream, write_stream, app.create_initialization_options())
if __name__ == "__main__":
asyncio.run(main())
Ask Claude: “What functions are in my main.py and what do they do?” and it’ll parse the AST and explain your codebase.
Key Takeaways
- MCP standardizes AI tool access through Resources (read-only data), Tools (executable functions), and Prompts (workflows)
- Build custom servers in ~50 lines of Python using the official
mcppackage—it handles JSON-RPC transport automatically - Claude Desktop runs servers as subprocesses via stdio, not HTTP, so use absolute paths in configuration
- Always validate inputs in tool handlers—Claude has full access to whatever permissions your server runs with
What’s Next
Now that Claude can access your local environment, learn how to build multi-agent MCP orchestrators that coordinate multiple specialized servers for complex workflows.
Key Takeaway: Model Context Protocol (MCP) lets Claude directly access your filesystem, databases, and APIs through standardized servers. You can build a custom MCP server in under 50 lines of Python and give Claude superpowers like querying your PostgreSQL database or reading project files.
New AI tutorials published daily on AtlasSignal. Follow @AtlasSignalDesk for more.
📧 Get Daily AI & Macro Intelligence
Stay ahead of market-moving news, emerging tech, and global shifts.