Claude MCP Server Setup Guide: Build Custom Tool Integrations for Claude Code and Claude Desktop

What Is MCP and Why It Matters for Claude Users

The Model Context Protocol (MCP) is Anthropic’s open standard for connecting Claude to external tools, data sources, and services. Think of it as a USB port for AI — a universal interface that lets Claude interact with databases, APIs, file systems, and custom tools through a standardized protocol.

Before MCP, integrating Claude with external services meant building custom API wrappers, managing authentication, and writing glue code for every integration. MCP standardizes this: you build an MCP server once, and it works with Claude Code, Claude Desktop, and any other MCP-compatible client.

For developers, MCP unlocks three capabilities:

  • Tools: functions Claude can call to perform actions (query a database, create a file, send a message)
  • Resources: data Claude can read (documentation, configuration files, database schemas)
  • Prompts: reusable prompt templates that Claude can access

This guide focuses on the practical setup — getting MCP servers running with Claude Code and Claude Desktop.

MCP Architecture: How the Pieces Fit Together

Client-Server Model

Claude Code (client) <--> MCP Server <--> External Service
Claude Desktop (client) <--> MCP Server <--> Database
                                       <--> API
                                       <--> File System

The MCP client (Claude Code or Claude Desktop) communicates with MCP servers over stdio (standard input/output) or HTTP with server-sent events. Each server exposes tools, resources, and prompts that Claude can use during a conversation.

Transport Types

stdio (recommended for local development): The MCP server runs as a child process. Claude Code launches it and communicates via stdin/stdout. Simple, fast, no network configuration.

HTTP + SSE (for remote servers): The MCP server runs as a web service. Claude connects via HTTP. Use this for shared team servers or cloud-hosted tools.

Setting Up Your First MCP Server (TypeScript)

Step 1: Initialize the Project

mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node
npx tsc --init

Step 2: Create the Server

Create src/index.ts:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

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

// Define a tool
server.tool(
  "get-weather",
  "Get current weather for a city",
  {
    city: z.string().describe("City name, e.g. 'San Francisco'"),
    units: z.enum(["celsius", "fahrenheit"]).default("celsius")
      .describe("Temperature units"),
  },
  async ({ city, units }) => {
    // Your implementation here
    const temp = units === "celsius" ? "22°C" : "72°F";
    return {
      content: [
        {
          type: "text",
          text: `Weather in ${city}: ${temp}, partly cloudy`,
        },
      ],
    };
  }
);

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);

Step 3: Build and Test

# Add to package.json scripts
"build": "tsc",
"start": "node dist/index.js"

# Build
npm run build

Step 4: Configure in Claude Code

Add to your project’s .claude/mcp.json:

{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["path/to/my-mcp-server/dist/index.js"],
      "env": {
        "API_KEY": "your-api-key-here"
      }
    }
  }
}

Or add to global settings at ~/.claude/mcp.json for all projects.

Step 5: Verify

Launch Claude Code and check:

claude

# Claude should show your MCP server as connected
# Ask Claude to use your tool:
# "What's the weather in San Francisco?"

Claude will call the get-weather tool and display the result.

Setting Up an MCP Server (Python)

Step 1: Initialize

mkdir my-mcp-server-python
cd my-mcp-server-python
pip install mcp

Step 2: Create the Server

Create server.py:

from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import json

server = Server("my-python-tools")

@server.list_tools()
async def list_tools():
    return [
        Tool(
            name="query-database",
            description="Execute a read-only SQL query against the project database",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "SQL SELECT query to execute"
                    }
                },
                "required": ["query"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict):
    if name == "query-database":
        query = arguments["query"]
        # Validate it's a SELECT query
        if not query.strip().upper().startswith("SELECT"):
            return [TextContent(
                type="text",
                text="Error: Only SELECT queries are allowed"
            )]
        # Execute query (replace with your database logic)
        results = execute_query(query)
        return [TextContent(
            type="text",
            text=json.dumps(results, indent=2)
        )]

async def main():
    async with stdio_server() as (read_stream, write_stream):
        await server.run(read_stream, write_stream)

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

Step 3: Configure

In .claude/mcp.json:

{
  "mcpServers": {
    "db-tools": {
      "command": "python",
      "args": ["path/to/server.py"],
      "env": {
        "DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
      }
    }
  }
}

Common MCP Server Patterns

Database Query Tool

A read-only database query tool that lets Claude explore your data:

server.tool(
  "query-db",
  "Execute a read-only SQL query against the project database",
  {
    query: z.string().describe("SQL SELECT query"),
  },
  async ({ query }) => {
    // Safety: only allow SELECT
    if (!query.trim().toUpperCase().startsWith("SELECT")) {
      return { content: [{ type: "text", text: "Only SELECT queries allowed" }] };
    }
    const results = await db.query(query);
    return {
      content: [{ type: "text", text: JSON.stringify(results.rows, null, 2) }],
    };
  }
);

API Integration Tool

Connect Claude to an external API:

server.tool(
  "search-jira",
  "Search Jira issues by JQL query",
  {
    jql: z.string().describe("JQL query string"),
    maxResults: z.number().default(10).describe("Maximum results to return"),
  },
  async ({ jql, maxResults }) => {
    const response = await fetch(
      `${JIRA_URL}/rest/api/3/search?jql=${encodeURIComponent(jql)}&maxResults=${maxResults}`,
      { headers: { Authorization: `Basic ${JIRA_TOKEN}` } }
    );
    const data = await response.json();
    const summary = data.issues.map(i => ({
      key: i.key,
      summary: i.fields.summary,
      status: i.fields.status.name,
      assignee: i.fields.assignee?.displayName || "Unassigned",
    }));
    return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
  }
);

File System Tool

Give Claude controlled access to specific directories:

server.tool(
  "read-config",
  "Read a configuration file from the config directory",
  {
    filename: z.string().describe("Config filename (e.g., 'database.yml')"),
  },
  async ({ filename }) => {
    // Safety: prevent path traversal
    const safeName = path.basename(filename);
    const filepath = path.join(CONFIG_DIR, safeName);
    if (!fs.existsSync(filepath)) {
      return { content: [{ type: "text", text: `Config file '${safeName}' not found` }] };
    }
    const content = fs.readFileSync(filepath, "utf-8");
    return { content: [{ type: "text", text: content }] };
  }
);

Configuring Claude Desktop

Claude Desktop uses a different configuration file:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"],
      "env": {
        "API_KEY": "your-key"
      }
    }
  }
}

After saving, restart Claude Desktop. Your tools appear in the tool list when you start a new conversation.

Security Best Practices

Input Validation

Always validate inputs before executing:

// Validate SQL queries
if (query.includes(";") && query.split(";").length > 2) {
  throw new Error("Multiple statements not allowed");
}

// Validate file paths
const resolved = path.resolve(basePath, userInput);
if (!resolved.startsWith(basePath)) {
  throw new Error("Path traversal detected");
}

// Validate URLs
const url = new URL(userInput);
if (!ALLOWED_HOSTS.includes(url.hostname)) {
  throw new Error("Host not in allowlist");
}

Principle of Least Privilege

  • Database tools should use read-only database credentials
  • File system tools should be scoped to specific directories
  • API tools should use tokens with minimal required permissions
  • Never expose admin endpoints through MCP

Environment Variable Management

  • Store secrets in environment variables, never in code
  • Use .env files for local development (add to .gitignore)
  • Document required environment variables in README
  • Use the env field in mcp.json to pass variables securely

Debugging MCP Servers

Common Issues

Server does not appear in Claude:

  • Check that the path in mcp.json is correct (use absolute paths)
  • Verify the server builds and runs without errors: node dist/index.js
  • Check Claude Code logs for MCP connection errors

Tool calls fail silently:

  • Add console.error logging to your tool handlers (stderr does not interfere with stdio transport)
  • Check that input schema matches what Claude sends
  • Verify environment variables are set correctly

Server crashes after first call:

  • Ensure async handlers do not throw unhandled exceptions
  • Wrap tool handlers in try-catch blocks
  • Check for missing dependencies

Logging

Use stderr for debug logging (stdout is reserved for MCP protocol):

server.tool("my-tool", "...", schema, async (args) => {
  console.error(`[DEBUG] my-tool called with:`, JSON.stringify(args));
  try {
    const result = await doWork(args);
    console.error(`[DEBUG] my-tool result:`, result);
    return { content: [{ type: "text", text: result }] };
  } catch (error) {
    console.error(`[ERROR] my-tool failed:`, error);
    return { content: [{ type: "text", text: `Error: ${error.message}` }] };
  }
});

Distributing MCP Servers to Your Team

npm Package Distribution

Package your MCP server as an npm package:

{
  "name": "@mycompany/mcp-db-tools",
  "version": "1.0.0",
  "bin": { "mcp-db-tools": "dist/index.js" },
  "files": ["dist/"]
}

Team members install and configure:

npm install -g @mycompany/mcp-db-tools
{
  "mcpServers": {
    "db-tools": {
      "command": "mcp-db-tools",
      "env": { "DATABASE_URL": "..." }
    }
  }
}

Docker Distribution

For complex servers with dependencies:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/
ENTRYPOINT ["node", "dist/index.js"]

Frequently Asked Questions

Can I use multiple MCP servers simultaneously?

Yes. Add multiple entries to your mcp.json. Each server provides its own set of tools, and Claude can use tools from any connected server in a single conversation.

Do MCP servers work offline?

stdio-based MCP servers run locally and work offline. However, if your server calls external APIs or databases, those connections need network access.

What happens if an MCP server crashes mid-conversation?

Claude Code detects the disconnection and notifies you. The conversation continues but tools from that server become unavailable until it reconnects. Fix the issue and restart the server.

Can MCP servers maintain state between tool calls?

Yes. The server process persists for the duration of the Claude session. You can maintain in-memory state, database connections, and caches across multiple tool calls.

Is there a marketplace for MCP servers?

The MCP ecosystem is growing. Community servers are available on GitHub and npm. Anthropic maintains a list of reference implementations. Check the MCP documentation for the latest directory.

How do I update an MCP server without restarting Claude?

For Claude Code, restart the session or use the MCP management commands. For Claude Desktop, restart the application after updating the server binary.

Explore More Tools

Antigravity AI Content Pipeline Automation Guide: Google Docs to WordPress Publishing Workflow Guide Bolt.new Case Study: Marketing Agency Built 5 Client Dashboards in One Day Case Study Bolt.new Best Practices: Rapid Full-Stack App Generation from Natural Language Prompts Best Practices ChatGPT Advanced Data Analysis (Code Interpreter) Complete Guide: Upload, Analyze, Visualize Guide ChatGPT Custom GPTs Advanced Guide: Actions, API Integration, and Knowledge Base Configuration Guide ChatGPT Voice Mode Guide: Build Voice-First Customer Service and Internal Workflows Guide Claude API Production Chatbot Guide: System Prompt Architecture for Reliable AI Assistants Guide Claude Artifacts Best Practices: Create Interactive Dashboards, Documents, and Code Previews Best Practices Claude Code Hooks Guide: Automate Custom Workflows with Pre and Post Execution Hooks Guide Cursor Composer Complete Guide: Multi-File Editing, Inline Diffs, and Agent Mode Guide Cursor Case Study: Solo Founder Built a Next.js SaaS MVP in 2 Weeks with AI-Assisted Development Case Study Cursor Rules Advanced Guide: Project-Specific AI Configuration and Team Coding Standards Guide Devin AI Team Workflow Integration Best Practices: Slack, GitHub, and Code Review Automation Best Practices Devin Case Study: Automated Dependency Upgrade Across 500-Package Python Monorepo Case Study ElevenLabs Case Study: EdTech Startup Localized 200 Course Hours to 8 Languages in 6 Weeks Case Study ElevenLabs Multilingual Dubbing Guide: Automated Video Localization Workflow for Global Content Guide ElevenLabs Voice Design Complete Guide: Create Consistent Character Voices for Games, Podcasts, and Apps Guide Gemini 2.5 Pro vs Claude Sonnet 4 vs GPT-4o: AI Code Generation Comparison 2026 Comparison Gemini API Multimodal Developer Guide: Image, Video, and Document Analysis with Code Examples Guide Gemini Google Workspace Automation Guide: Docs, Sheets, and Slides AI Workflows Guide