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

Grok Best Practices for Academic Research and Literature Discovery: Leveraging X/Twitter for Scholarly Intelligence Best Practices Grok Best Practices for Content Strategy: Identify Trending Topics Before They Peak and Create Content That Captures Demand Best Practices Grok Case Study: How a DTC Beauty Brand Used Real-Time Social Listening to Save Their Product Launch Case Study Grok Case Study: How a Pharma Company Tracked Patient Sentiment During a Drug Launch and Caught a Safety Signal 48 Hours Before the FDA Case Study Grok Case Study: How a Disaster Relief Nonprofit Used Real-Time X/Twitter Monitoring to Coordinate Emergency Response 3x Faster Case Study Grok Case Study: How a Political Campaign Used X/Twitter Sentiment Analysis to Reshape Messaging and Win a Swing District Case Study How to Use Grok for Competitive Intelligence: Track Product Launches, Pricing Changes, and Market Positioning in Real Time How-To Grok vs Perplexity vs ChatGPT Search for Real-Time Information: Which AI Search Tool Is Most Accurate in 2026? Comparison How to Use Grok for Crisis Communication Monitoring: Detect, Assess, and Respond to PR Emergencies in Real Time How-To How to Use Grok for Product Improvement: Extract Customer Feedback Signals from X/Twitter That Your Support Team Misses How-To How to Use Grok for Conference Live Monitoring: Extract Event Insights and Identify Networking Opportunities in Real Time How-To How to Use Grok for Influencer Marketing: Discover, Vet, and Track Influencer Partnerships Using Real X/Twitter Data How-To How to Use Grok for Job Market Analysis: Track Industry Hiring Trends, Layoff Signals, and Salary Discussions on X/Twitter How-To How to Use Grok for Investor Relations: Track Earnings Sentiment, Analyst Reactions, and Shareholder Concerns in Real Time How-To How to Use Grok for Recruitment and Talent Intelligence: Identifying Hiring Signals from X/Twitter Data How-To How to Use Grok for Startup Fundraising Intelligence: Track Investor Sentiment, VC Activity, and Funding Trends on X/Twitter How-To How to Use Grok for Regulatory Compliance Monitoring: Real-Time Policy Tracking Across Industries How-To NotebookLM Best Practices for Financial Analysts: Due Diligence, Investment Research & Risk Factor Analysis Across SEC Filings Best Practices NotebookLM Best Practices for Teachers: Build Curriculum-Aligned Lesson Plans, Study Guides, and Assessment Materials from Your Own Resources Best Practices NotebookLM Case Study: How an Insurance Company Built a Claims Processing Training System That Cut Errors by 35% Case Study