Skip to content
TopInsight .co
Editorial illustration of a layered architectural cross-section — terminal layer, middle MCP layer of interconnected nodes, integrated tools above — rendered in warm amber wireframe.

Building your own MCP server for Claude Code: a working tutorial

After running ten community MCP servers for months, the next step is writing your own. Here is the start-to-finish tutorial, including the parts the docs gloss over.

C Charles Lin ·

The first ten MCP servers guide covers wiring up existing servers. This piece covers the next step: writing your own. It’s a real tutorial — start-to-finish — including the parts where the official docs are thin.

If you’ve been using Claude Code with MCP and started thinking “I wish there was an MCP server for my internal X tool,” this is the piece for you.

What you’re actually building

An MCP server is a process that:

  1. Speaks the Model Context Protocol over stdio (or HTTP)
  2. Exposes one or more “tools” (functions the model can call)
  3. Optionally exposes “resources” (data the model can read) and “prompts” (templated prompts the user can invoke)

The simplest useful MCP server is a single tool that does one thing. We’re going to build that.

The example: a “git stats” MCP server

Concrete goal: an MCP server that exposes a git_stats tool which Claude Code can call to get summary stats about the current git repo (commit count, last-commit-author, hot files, etc.). Useful for “summarise what changed this sprint” prompts.

Step 1: Project setup

mkdir mcp-git-stats && cd mcp-git-stats
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
npx tsc --init

Adjust tsconfig.json for ESM:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}

Add "type": "module" to package.json.

Step 2: The minimal server skeleton

src/index.ts:

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { execSync } from 'node:child_process';

const server = new McpServer({
  name: 'git-stats',
  version: '0.1.0',
});

server.tool(
  'git_stats',
  'Get summary statistics about the current git repository.',
  {
    repoPath: z.string().describe('Absolute path to the git repository'),
    sinceDays: z.number().optional().default(30).describe('Look back N days'),
  },
  async ({ repoPath, sinceDays }) => {
    const since = `--since="${sinceDays} days ago"`;
    const commitCount = execSync(
      `git -C ${repoPath} log ${since} --oneline | wc -l`,
    )
      .toString()
      .trim();
    const topAuthor = execSync(
      `git -C ${repoPath} shortlog -sn -e ${since} | head -1`,
    )
      .toString()
      .trim();
    const hotFiles = execSync(
      `git -C ${repoPath} log ${since} --pretty=format: --name-only | sort | uniq -c | sort -rn | head -5`,
    )
      .toString()
      .trim();

    return {
      content: [
        {
          type: 'text',
          text: `Repository: ${repoPath}\nWindow: last ${sinceDays} days\nCommits: ${commitCount}\nTop author: ${topAuthor}\nHot files:\n${hotFiles}`,
        },
      ],
    };
  },
);

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

Build: npx tsc. Result: dist/index.js, executable as a Node process.

Step 3: Wire it into Claude Code

In ~/.claude/settings.json (or your project-level .claude/settings.json):

{
  "mcpServers": {
    "git-stats": {
      "command": "node",
      "args": ["/absolute/path/to/dist/index.js"]
    }
  }
}

Restart Claude Code. Verify with claude mcp listgit-stats should appear.

Step 4: Use it

Inside Claude Code:

“Use the git_stats tool to give me a summary of the topinsight repo over the last 14 days.”

The model recognises it has a git_stats tool available, calls it with the right arguments, gets back the text response, and synthesises a summary for you.

You just built an MCP server.

The parts the docs gloss over

After building three real MCP servers, here are the things I wish I’d known earlier:

1. Tool descriptions matter more than you think

The model decides whether to call your tool based on the description. Be specific. Bad: “Gets git info.” Good: “Get summary statistics about the current git repository — commit count, top author, hot files. Use this when the user asks about repo activity over a time window.”

Spend time on this. Bad descriptions = the model never uses your tool.

2. Schema design matters too

The Zod schema becomes the JSON schema the model sees. Use .describe() liberally on every parameter. The model uses these descriptions to know what to pass.

3. Errors should be model-readable

When your tool fails, don’t just throw. Return a structured error in the response:

if (!fs.existsSync(repoPath)) {
  return {
    content: [{
      type: 'text',
      text: `Error: repository path does not exist: ${repoPath}`,
    }],
    isError: true,
  };
}

The model can read this and decide what to do (try a different path, ask the user, give up gracefully).

4. Sub-process safety matters

The execSync example above is fine for trusted personal use. For a real server you’d want to validate repoPath against a whitelist, escape arguments properly, or use a structured git library (isomorphic-git, etc.). MCP servers expose your machine to the LLM — treat them with appropriate paranoia.

5. Performance: keep responses small

The model has a context budget. A tool that returns 100KB of text on every call burns context fast. Return concise text; let the model ask for more if needed.

6. Naming convention

Tool names should be snake_case verbs or noun-verbs: get_commit_log, search_codebase, run_tests. Avoid generic names like query or execute.

When to write your own MCP server

Write one when:

  • You repeatedly want to give Claude Code access to the same internal tool / API / data
  • An existing MCP server doesn’t cover your use case
  • You want strict control over what data the model can access (auditable code)

Don’t write one when:

  • An existing MCP server already does what you need
  • The integration is “call OpenAI API” — that’s not what MCP is for
  • You’d be better off with a slash command or shell script

What r/ClaudeAI says about building MCP

The community signal from mid-2025:

  • Strong “Anthropic donating MCP to Linux Foundation” enthusiasm — see this thread (1510 ups). The community sees MCP as the long-term standard.
  • The Boris setup thread (2993 ups) treats custom MCP servers as a power-user move worth investing in.
  • Recurring patterns: people build MCP servers for their internal docs, internal APIs, deploy systems, and specialised tooling. The OSS ecosystem of community MCP servers grows but stays narrow — most real-world MCP servers are private.

The bigger pattern

MCP is the actual interesting part of Claude Code as a platform. The CLI and model are commodities; the wrapper of “what tools can I plug in?” is what produces compounding value.

Writing your own MCP server is the unlock that turns Claude Code from a generic agent into one that knows about your specific work. It takes an afternoon for a simple server, a weekend for a real one. The leverage is meaningful — once you have it, your agent is genuinely smarter about your domain than any out-of-the-box tool can be.

For the broader Claude Code workflow, see our Claude Code review and the essential MCP servers list.

Sources

Every reference behind this piece. If we make a claim, it's because at least one of these said so — or we lived it ourselves.

  1. Firsthand Built three production MCP servers across personal and client projects
  2. Docs Model Context Protocol specification — MCP working group
  3. Docs MCP TypeScript SDK — MCP
  4. Docs Claude Code MCP integration documentation — Anthropic
  5. Blog r/ClaudeAI — Anthropic donates MCP to Linux Foundation — r/ClaudeAI
  6. Blog r/ClaudeAI — Boris setup thread on MCP patterns — r/ClaudeAI
  7. YouTube AI Jason and IndyDevDan MCP server tutorials — Various