News
🤖 AI Tutorials Add a Custom MCP Server to Claude Code

Add a Custom MCP Server to Claude Code

Expose your own tools, APIs, or data sources to Claude Code by running a local MCP server it can call.

An MCP (Model Context Protocol) server is a small program that exposes tools or data to an LLM client like Claude Code. Once it is registered, Claude Code can call the server's tools the same way it calls built-in tools like Read or Bash.

This tutorial walks through registering a server, then building a very small one yourself so you understand what is actually happening.

What you need

  • Claude Code installed and authenticated
  • Node.js 20+ or Python 3.11+ (we'll use Node in the example)
  • A terminal

Part 1 — Register an existing MCP server

Before writing your own, get a known-good one running. Claude Code ships with the filesystem MCP server as a reference implementation. Register it against a directory you want to expose:

claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /tmp/playground

Breaking that down:

  • filesystem — the name you'll see inside Claude Code
  • Everything after -- is the command Claude Code will spawn to start the server
  • /tmp/playground is the only directory the server will let tools touch

Confirm it registered:

claude mcp list

Start a Claude Code session in any directory. The new tools (mcp__filesystem__read_file, mcp__filesystem__list_directory, etc.) will appear in the tool list. Ask Claude Code to list files in /tmp/playground — it should reach for the MCP tool instead of shelling out.

If you want to remove it later:

claude mcp remove filesystem

Part 2 — Write your own MCP server

Now the interesting part. Say you want Claude Code to be able to check the status of your internal deploy system, which lives behind an API at https://deploy.internal/status.

Create a new directory and initialize it:

mkdir deploy-mcp && cd deploy-mcp
npm init -y
npm install @modelcontextprotocol/sdk

Create server.js:

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';

const server = new Server(
  { name: 'deploy-status', version: '0.1.0' },
  { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'check_deploy',
      description: 'Check the current deploy status of a service.',
      inputSchema: {
        type: 'object',
        properties: {
          service: { type: 'string', description: 'Service name' },
        },
        required: ['service'],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  if (req.params.name !== 'check_deploy') {
    throw new Error(`Unknown tool: ${req.params.name}`);
  }

  const { service } = req.params.arguments;
  const res = await fetch(`https://deploy.internal/status?service=${service}`);
  const data = await res.json();

  return {
    content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
  };
});

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

Add "type": "module" to package.json if it is not already there.


Part 3 — Register your server

claude mcp add deploy -- node /absolute/path/to/deploy-mcp/server.js

Use the absolute path. Claude Code does not spawn the server from your project directory, so a relative path will break on the next session.

Open a Claude Code session and ask something like "check the deploy status for the api service". Claude Code should pick up the mcp__deploy__check_deploy tool and call it with service: "api".


Common issues

The tool does not show up. Check claude mcp list to confirm registration, then look at the server's stderr — Claude Code logs MCP server output to ~/.claude/logs/ on most systems. A crash at startup will be there.

Claude Code calls the tool but the response is empty. Your CallToolRequestSchema handler must return { content: [...] }. Returning just the raw data silently produces an empty tool result.

Tool works once, then stops. The server process may be crashing between calls. Stdio-based MCP servers are long-lived — they need to keep running across multiple requests in the same session. Catch errors inside your handler and return them as text, do not throw.

Permissions keep prompting. Add the tool to your allowlist in ~/.claude/settings.json:

{
  "permissions": {
    "allow": ["mcp__deploy__check_deploy"]
  }
}

When to use MCP vs. a shell script

MCP is not a replacement for Bash. If Claude Code can call curl once and move on, it is almost never worth the ceremony of a custom server. MCP pays off when:

  • The tool needs structured input/output that a CLI would turn into brittle string parsing
  • You want to expose the same tool across Claude Code, Claude Desktop, and other MCP clients
  • The underlying call needs auth, rate limiting, or caching that you do not want the model reasoning about

For one-off internal glue, a shell script is lighter. For something your whole team will use across surfaces, MCP is the right shape.