MCP Server: Claude Desktop + Local Tools
Model Context Protocol (MCP) is Anthropic's open standard for connecting AI agents to external data and tools. MCP servers let Claude work with the file system, databases, APIs and the browser right from the chat.
MCP Architecture
MCP consists of three components: Host (Claude Desktop, Claude Code), Client (built into the Host), Server (a local process with tools). The server exposes: Tools (actions), Resources (data) and Prompts (templates).
Installing Claude Desktop
Download it from claude.ai/download. The MCP server configuration lives in:
# macOS/Linux
~/.config/claude/claude_desktop_config.json
# Windows
%APPDATA%\Claude\claude_desktop_config.jsonConnecting Ready-Made MCP Servers
# claude_desktop_config.json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
},
"sqlite": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sqlite", "--db-path", "/data/mydb.sqlite"]
},
"brave-search": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-brave-search"],
"env": {"BRAVE_API_KEY": "your-key"}
}
}
}Building Your Own MCP Server in Python
pip install mcpfrom mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp import types
import asyncio
server = Server("my-hosting-tools")
@server.list_tools()
async def list_tools():
return [
types.Tool(
name="check_domain",
description="Check domain availability",
inputSchema={
"type": "object",
"properties": {
"domain": {"type": "string", "description": "Domain name"}
},
"required": ["domain"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "check_domain":
domain = arguments["domain"]
import socket
try:
socket.gethostbyname(domain)
return [types.TextContent(type="text", text=f"Domain {domain} is active")]
except:
return [types.TextContent(type="text", text=f"Domain {domain} is unavailable")]
async def main():
async with stdio_server() as streams:
await server.run(*streams, server.create_initialization_options())
asyncio.run(main())# Add to claude_desktop_config.json
{
"mcpServers": {
"hosting-tools": {
"command": "python3",
"args": ["/path/to/my-mcp-server.py"]
}
}
}Common MCP Server Errors
| Error | Cause | Fix |
|---|---|---|
| Server disconnected right after Claude Desktop starts | The wrong path or command in the command/args field of the mcpServers section | Confirm the command runs cleanly by hand in a terminal with the same arguments |
| spawn npx ENOENT | Node.js or npx isn't installed, or isn't on the PATH of the process that launches Claude Desktop | Give the full path to npx: which npx, and put the absolute path in the command field |
| MCP error -32000: Connection closed | The server crashed with an unhandled exception right after starting | Run the server by hand and read the traceback in the console before connecting Claude Desktop |
| Changes to claude_desktop_config.json don't take effect | Claude Desktop caches the config at startup and doesn't reload it on the fly | Fully quit the Claude Desktop process (not just close the window) and relaunch it |
Comparing Ways to Connect Tools
| Method | Where it runs | Access level |
|---|---|---|
| MCP Server (local process, stdio) | On the user's machine | Full — the same as the user's own process on the system |
| Function calling via API, no MCP | On your own backend | Limited to what your code explicitly implements |
| Claude's built-in tools (web search, code execution) | On Anthropic's infrastructure | Limited to the built-in set, with no access to the user's file system |
When MCP Is Not a Good Fit
- A production service for external users is needed — MCP over stdio is built for a local desktop, not hundreds of concurrent clients
- The tool must work without a locally installed Claude Desktop — that calls for an HTTP API, not the stdio protocol
- A strict sandbox around the tool is required — a plain local MCP process runs with the current OS user's rights, with no isolation
- Guaranteed compatibility across different MCP hosts without testing each one matters — protocol implementations still differ in details between clients
If the tool connects to an external AI API rather than Claude Desktop locally, see "Claude API (Anthropic): Integrating with a VPS". Since the tool receives arbitrary input from the model, it's worth reading "AI Agent Security: Defending Against Prompt Injection" in advance.
Verifying the Result
Confirm the server is running, visible to Claude, and actually gets called:
# Run the server by hand and confirm there are no errors in stdout/stderr
python3 /path/to/my-mcp-server.py
# Check the MCP logs in Claude Desktop (macOS)
tail -f ~/Library/Logs/Claude/mcp*.log
# List tools via the official dev inspector
npx @modelcontextprotocol/inspector python3 /path/to/my-mcp-server.py
In the Claude Desktop interface, the server should show up in the connected list with a green indicator and the check_domain tool available. For automation scenarios without a Claude Desktop dialog, a ready-made platform like "n8n on a VDS: No-Code Automation with AI Agents" is sometimes more convenient.