Building Your First MCP Server for Claude Code (and What to Connect First)

•By Blacdisk Team

Building Your First MCP Server for Claude Code (and What to Connect First)

Most developers who try MCP hit the same wall: the protocol is simple, but the first hour is spent figuring out which SDK version to install, why nothing shows up in the client, and whether to build a server or just connect one that already exists. This guide answers all three. You will build a small server, wire it into Claude Code, and then decide what else deserves a slot in your setup.

Here is the short version of what you will do: write one tool in about 40 lines of TypeScript, register it with a single command, and verify it with the MCP Inspector before Claude ever touches it.

Key Takeaways

  • An MCP server is a program that exposes tools a model can call. In the official TypeScript SDK you register a tool with one Zod schema, and the SDK derives the JSON Schema and validates arguments for you.
  • Start with a local stdio server and test it in the MCP Inspector first. Only move to HTTP when you need one endpoint shared by many clients.
  • Before you build, check whether a server already exists. Connect one hosted server with no sign-in first, then add one that matches your daily workflow.
  • Keep the number of servers small and scope access down. Every server adds to your context and your attack surface.

[INTERNAL-LINK: what Claude Code is and how to install it → Claude Code getting-started guide]

Should You Build a Server or Connect an Existing One?

Connect an existing server when a service you already use publishes one; build your own when the tool is specific to your team, your data, or your internal API. That single decision saves most people a weekend.

The Model Context Protocol lets Claude Code use tools beyond its built-in set, such as searching an issue tracker, querying a database, or controlling a web browser, and Claude Code's MCP quickstart describes those tools as coming from servers that run on your machine or as hosted services. Hosted services like Sentry, Linear, and Notion run their MCP servers behind OAuth, so connecting one is a matter of adding a URL and signing in through your browser.

Build your own when:

What You Need Before You Start

You need Node.js 20 or later and a terminal. The official first-server tutorial states that requirement directly and adds that nothing else is required. Claude Code should already be installed and signed in.

One version note matters here, because many tutorials online are out of date. The official TypeScript SDK repository now describes its main branch as v2, published as @modelcontextprotocol/server and @modelcontextprotocol/client, and states that v2 is the stable release line, released alongside the 2026-07-28 spec (MCP TypeScript SDK repository, retrieved 2026-09-20). Older posts import from @modelcontextprotocol/sdk instead. That older v1 line keeps receiving bug fixes and security updates for at least six months after v2's release, so existing servers keep working, but new projects should start on v2. If a snippet you find online imports from @modelcontextprotocol/sdk/server/mcp.js, it is v1 code.

Step 1: Set Up the Project

Create a folder, initialize it, and install the SDK plus Zod and tsx:

mkdir notes-server && cd notes-server
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir src

Two details are easy to miss. The type=module line matters because the SDK ships ES modules only, and tsx runs TypeScript directly, so there is no build step. Both points come straight from the official tutorial.

Step 2: Register Your First Tool

Create src/index.ts. This example gives Claude a tool that searches a folder of local markdown notes, a realistic "narrow, read-only view" of your own data:

import { readdir, readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';

const NOTES_DIR = process.env.NOTES_DIR ?? './notes';

function createServer(): McpServer {
    const server = new McpServer({ name: 'notes', version: '1.0.0' });

    server.registerTool(
        'search-notes',
        {
            description: 'Search markdown notes for a keyword and return matching lines',
            inputSchema: z.object({
                keyword: z.string().min(2).describe('Word or phrase to look for')
            })
        },
        async ({ keyword }) => {
            const files = (await readdir(NOTES_DIR)).filter(f => f.endsWith('.md'));
            const hits: string[] = [];

            for (const file of files) {
                const text = await readFile(join(NOTES_DIR, file), 'utf8');
                for (const line of text.split('\n')) {
                    if (line.toLowerCase().includes(keyword.toLowerCase())) {
                        hits.push(`${file}: ${line.trim()}`);
                    }
                }
            }

            if (hits.length === 0) {
                return { content: [{ type: 'text', text: `No notes mention "${keyword}".` }] };
            }
            return { content: [{ type: 'text', text: hits.slice(0, 20).join('\n') }] };
        }
    );

    return server;
}

void serveStdio(createServer);
console.error('notes MCP server running on stdio');

The shape follows the official tutorial: a createServer factory that builds an McpServer, registers a tool with registerTool(name, config, handler), and hands the factory to serveStdio. The tutorial explains that inputSchema is a Zod schema and that it is the only schema you write; from that one schema the SDK derives the JSON Schema the model sees, validates arguments before your handler runs, and infers the handler's argument types (MCP TypeScript SDK first-server guide, retrieved 2026-09-20).

That validation is a real safety net. If a model passes a bad argument, the SDK rejects the call before your handler runs, and the model sees the validation error and can correct itself.

Two rules that trip up almost everyone:

  1. Never write to stdout. The official guide warns that stdout is the protocol channel and that a single console.log corrupts the JSON-RPC stream. Use console.error for logging, as the example does.
  2. Return errors, don't throw them. For a failed call, return isError: true in the result so the model can read the failure and react, instead of crashing the connection.

[INTERNAL-LINK: designing tools an AI agent can actually use → guide on writing effective MCP tool descriptions]

Step 3: Test It in the MCP Inspector (Before Claude)

Debug your server on its own first. The MCP Inspector is a local web app that launches your command and lets you call tools directly, and it connects over stdio. Run it from the project root:

mkdir notes && echo "- Ship the MCP post on Friday" > notes/todo.md
npx @modelcontextprotocol/inspector npx tsx src/index.ts

In the browser tab it opens, click Connect, open the Tools tab, select search-notes, enter a keyword like MCP, and run it. What you see in the result is the same content a model would receive. If this works, any remaining problem is in the Claude Code wiring, not your server. That separation saves a lot of guessing.

Step 4: Connect It to Claude Code

A local stdio server is a program Claude Code starts as a subprocess on your machine. Register it with the claude mcp add command, using -- to separate Claude's flags from the command that starts your server:

claude mcp add notes --env NOTES_DIR=/absolute/path/to/notes -- npx tsx /absolute/path/to/notes-server/src/index.ts

Per Claude Code's MCP quickstart (retrieved 2026-09-20), local servers use the default stdio transport so there is no --transport flag, and everything after the -- separator is the command Claude Code runs to start the server. Use absolute paths, because Claude Code launches the process from its own working directory, not yours.

Then verify:

claude mcp list

You want to see ✔ Connected. The same guide notes that a first check can show ✘ Failed to connect while npx downloads a package, and that the status changes once the download finishes, so wait a moment and run it again before troubleshooting. Now start a session and ask for something your tool can answer:

Use the notes server to find anything I wrote about MCP

The tool call in Claude's output is labeled with the server name, which is how you confirm the answer came from your server rather than Claude's built-in knowledge.

Pick the Right Scope

The scope you choose decides who can use the server. According to the official docs, claude mcp add defaults to local scope, which is private to you and active only in the current project. Add --scope user to register it for all your projects, or --scope project to write it to .mcp.json in the project root and share it with teammates.

Scope Stored in Available to
local (default) ~/.claude.json, under this project Only you, only this project
project .mcp.json in the project root Everyone who clones the repo
user ~/.claude.json, top-level mcpServers Only you, all projects

If your server needs a token, keep it out of the committed file. In .mcp.json you can reference an environment variable with the ${API_KEY} syntax, which expands at runtime, so the secret never lands in version control. The first time Claude Code sees a project-scoped server it asks you to approve it, which stops a repository you clone from launching processes on your machine without consent.

Troubleshooting: The Four Failures You Will Actually Hit

"No MCP servers configured." You probably ran claude mcp add from a different project. Local-scoped servers are tied to the project where you added them, so re-add from the right directory or use --scope user.

"Failed to connect." Run the exact command you registered directly in your terminal. If it starts and waits for input, the server works and the registration is wrong, most often a missing -- separator. If it errors, the message names what is missing.

Connection times out at startup. The default startup timeout is 30 seconds, and a first run that downloads packages can exceed it. Raise it with MCP_TIMEOUT=60000 claude (the value is in milliseconds).

Connects, but no tools appear. Open /mcp in a session and select the server to see its tool list. An empty list usually means a required environment variable such as an API key is missing.

What to Connect First

Connect one no-auth hosted server, then one server tied to your daily work, and stop there. The temptation after your first success is to install a dozen servers. Resist it.

1. A no-sign-in docs server, to prove your setup works. The Claude Code docs describe their own documentation server as hosted, searchable, and free of authentication or special configuration, which makes it a good first server for testing the setup flow:

claude mcp add --transport http claude-code-docs https://code.claude.com/docs/mcp

2. The server for the system you touch every day. Pick by workflow, not by popularity:

If your day involves... Connect first Why it earns a slot
Errors and incidents Sentry (OAuth) Claude can look up projects and errors instead of you pasting stack traces
Tickets and planning Linear or Jira Pulls issue detail into the conversation
Front-end changes Playwright (local, no account) Gives Claude a browser to check a page still renders after a change
Application data A database server, read-only Lets Claude answer data questions with real queries

The Playwright server is the low-friction pick from that table: the official quickstart describes it as needing no account, and it runs through npx, so it requires Node.js 18 or later.

3. A read-only database, if you need one. The Claude Code documentation demonstrates DBHub for Postgres and adds an important caution: DBHub's execute_sql tool runs whatever SQL the agent emits, including writes, unless you restrict it. Their fix is to set readonly = true in the DBHub configuration file, which makes DBHub reject INSERT, UPDATE, DELETE, and DDL statements (Claude Code docs: MCP in the Agent SDK, retrieved 2026-09-20). Take that pattern seriously for any database server: default to read-only and open writes only when you have a reason.

How Many Servers Is Too Many?

Fewer than you think, though the cost is lower than it used to be. Claude Code documents that when you have many MCP tools configured, tool definitions can consume a significant portion of your context window, and that tool search solves this by withholding tool definitions from context and loading only the ones Claude needs for each turn. It states that tool search is enabled by default (same source, retrieved 2026-09-20).

That is good news, but it does not make servers free. Two costs remain:

You can see what your servers cost at any point with the /context command, which breaks your session's context into system prompt, tools, MCP tools, and messages, and with /mcp, which shows the detail per server. The docs also offer a plain-language rule worth adopting: removing servers you no longer use keeps context free, and claude mcp remove <name> does it in one line.

Keep It Safe: Treat Every Server as Code You Are Running

MCP security is the part of this topic where the ecosystem has moved faster than the habits. One 2026 developer guide reports that researchers disclosed more than 30 MCP-related CVEs in the first four months of 2026, including a CVSS 9.6 remote code execution flaw in the mcp-remote package (env.dev guide to building MCP servers, April 2026). Treat that as a single secondary source rather than a settled statistic, but the direction of travel is clear and the mitigations are cheap.

A short checklist that follows from the official docs and common practice:

Going Further: From stdio to a Shared Server

Your notes server speaks stdio because Claude Code launches it as a local process and owns its lifetime. When you want one endpoint your whole team connects to, serve the same createServer factory over HTTP instead. The official SDK tutorial points to its HTTP guide for exactly this hand-off, and notes that stdio is a local-host arrangement while HTTP suits a single endpoint many clients connect to. The env.dev guide adds a useful rule of thumb: start with stdio, since it needs zero network configuration, and move to Streamable HTTP only when you need remote access or multi-user deployment. It also notes the older HTTP+SSE transport was deprecated in favor of Streamable HTTP, so avoid building new servers on it.

Frequently Asked Questions

Do I need TypeScript, or can I build an MCP server in Python?

You can use either. Official SDKs exist for both languages, and the protocol itself is language-neutral. This guide uses TypeScript because the official first-server tutorial does, but the concepts (register a tool, define an input schema, connect a transport) carry over directly.

Why does my server connect in the Inspector but not in Claude Code?

The Inspector proves your server works, so the problem is registration. Check claude mcp get <name> to see the command Claude Code is actually running. The most common causes are a missing -- separator before the command, a relative path, or an environment variable set in your shell but not passed with --env.

Will adding more MCP servers slow down or bloat Claude Code?

Less than it once did. Tool search, on by default, loads tool definitions only when needed. Each server still adds a connection to maintain and code to trust, so keep the list short.

Where do I find servers other people have already built?

Start with the Anthropic Directory referenced in the Claude Code docs, and the public MCP servers repository. Prefer servers published by the service itself, and apply the security checklist above before you connect anything new.

Where to Go Next

You now have a working server, a verified connection, and a rule for what to add next. The fastest way to deepen this is to replace the toy search-notes tool with one that wraps a real internal API, keep it read-only, and share it with your team through a project-scoped .mcp.json.

Recap: