A technical reference for building and configuring MCP servers. Covers transport, configuration, tool schemas, resource types, and security patterns.
MCP connects Claude to external tools and data through a client-server protocol. Claude is the client; your MCP server is the bridge to external systems.
The server process communicates via stdin/stdout. Best for local tools.
# In ~/.claude/settings.json
{
"mcpServers": {
"my-db": {
"command": "node",
"args": ["/path/to/server.js"],
"env": {
"DATABASE_URL": "postgres://localhost/mydb"
}
}
}
}
The server runs as an HTTP service. Best for shared team servers or cloud-hosted integrations.
{
"mcpServers": {
"team-tools": {
"url": "https://mcp.company.internal/sse",
"headers": {
"Authorization": "Bearer ${TEAM_MCP_TOKEN}"
}
}
}
}
Tools are the primary way MCP servers expose actions to Claude.
{
"name": "search_documents",
"description": "Search the internal knowledge base for documents matching a query. Use this when the user asks about company policies, procedures, or internal documentation.",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query. Use natural language."
},
"limit": {
"type": "integer",
"description": "Maximum number of results to return. Default 5, max 20.",
"default": 5
},
"category": {
"type": "string",
"enum": ["policy", "procedure", "technical", "all"],
"description": "Filter by document category. Use all if unsure.",
"default": "all"
}
},
"required": ["query"]
}
}
Write 3-4 sentences in the description field. State what the tool does, when to use it, and any important limits. Vague descriptions lead to wrong tool selection.
Resources expose readable content — like files or database views — that Claude can access on demand.
# Resource URI format mcp://server-name/resource-type/identifier # Examples mcp://my-db/schema/users mcp://docs-server/page/onboarding-guide mcp://config-server/env/production
MCP servers can expose named prompt templates that Claude users can invoke directly.
{
"name": "analyze_pr",
"description": "Analyze a GitHub pull request for quality and potential issues",
"arguments": [
{
"name": "pr_url",
"description": "The full GitHub PR URL",
"required": true
}
]
}
from mcp import Server
from mcp.types import Tool, TextContent
app = Server("my-server")
@app.list_tools()
async def list_tools():
return [Tool(
name="get_weather",
description="Get current weather for a city",
inputSchema={"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}
)]
@app.call_tool()
async def call_tool(name, arguments):
if name == "get_weather":
city = arguments["city"]
# fetch weather
return [TextContent(type="text", text=f"Weather in {city}: 72F, sunny")]
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new Server(
{ name: "my-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler("tools/call", async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_user") {
const user = await db.getUser(args.userId);
return { content: [{ type: "text", text: JSON.stringify(user) }] };
}
});
await server.connect(new StdioServerTransport());
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "${DATABASE_URL}"]
},
"team-server": {
"url": "https://mcp.internal.co/sse",
"headers": {
"X-API-Key": "${TEAM_MCP_KEY}"
}
}
}
}