Tutorial··11 min
How to build an MCP server with Claude Code — practical guide
MCP (Model Context Protocol) lets you give Claude access to custom tools, APIs, and data sources. Here's how to build your own MCP server in TypeScript and connect it to Claude Code.
Model Context Protocol (MCP) is Anthropic's open standard for connecting AI models to tools and data sources. When Claude has an MCP server connected, it can call functions — query a database, fetch an API, read a file system — as part of a conversation. This guide shows you how to build one from scratch.
What MCP actually does
------------------------
Without MCP, Claude's knowledge is static — it knows what was in its training data plus what you paste into the conversation. With MCP, Claude can call live functions. Examples:
- A "weather" MCP server: Claude can fetch current weather for any city
- A "database" MCP server: Claude can query your Postgres database by natural language
- A "GitHub" MCP server: Claude can list PRs, read issues, create branches
- A "Figma" MCP server: Claude can read component names and export CSS
The pattern is always the same: you define tools (functions with typed inputs), your MCP server exposes them, and Claude decides when to call them during a conversation.
What you need
--------------
- Node.js 18+, TypeScript
- Claude Code (or Claude Desktop for a GUI-based workflow)
- The MCP TypeScript SDK: @modelcontextprotocol/sdk
Step 1 — Scaffold the MCP server
----------------------------------
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
claude
Install a skill to help:
claude skills add nodejs-express-api
Then ask Claude Code to scaffold:
> create an MCP server in TypeScript. the entry point is src/index.ts. it should use @modelcontextprotocol/sdk's Server class and StdioServerTransport for communication (this makes it work with Claude Code and Claude Desktop). define one tool to start: get_weather, which accepts a city string and returns a mock weather object with temperature and condition. define the tool schema using zod.
Step 2 — Understand the generated structure
---------------------------------------------
The agent will produce something like this:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const server = new Server(
{ name: "my-mcp-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "get_weather") {
const { city } = z.object({ city: z.string() }).parse(request.params.arguments);
return {
content: [{ type: "text", text: JSON.stringify({ city, temperature: 22, condition: "sunny" }) }]
};
}
throw new Error('Unknown tool: ' + request.params.name);
});
const transport = new StdioServerTransport();
await server.connect(transport);
Key things to understand:
- StdioServerTransport means communication happens over stdin/stdout — Claude Code spawns your server as a subprocess
- Each tool is handled in the setRequestHandler callback
- Zod validates inputs — if the caller passes wrong types, it throws before your code runs
- The content array in the return can have multiple items (text, images, embedded resources)
Step 3 — Add a real tool (GitHub PRs example)
-----------------------------------------------
> replace the mock weather tool with a real GitHub tool called list_open_prs. it should accept owner (string) and repo (string), call the GitHub REST API with a personal access token from the GITHUB_TOKEN environment variable, and return the open PR titles, numbers, and URLs as a formatted string.
Claude Code will add fetch to the implementation (or install node-fetch if needed) and handle the API call. Review that it:
- Handles HTTP errors (non-2xx responses)
- Reads GITHUB_TOKEN from process.env, not hardcodes it
- Returns meaningful error messages when the token is missing or invalid
Step 4 — Connect it to Claude Code
------------------------------------
Add your MCP server to Claude Code's config. On macOS:
nano ~/.claude/config.json
Add:
{
"mcpServers": {
"my-server": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_your_token_here"
}
}
}
}
Build your server first:
npx tsc
Then restart Claude Code and test it:
> list open PRs for vercel/next.js
Claude Code will call your list_open_prs tool, get the result, and incorporate it into its response. You'll see the tool call in the output.
Step 5 — Add more tools
-------------------------
> add a second tool: create_issue. it should accept owner, repo, title, and body strings. create a GitHub issue via the API using the GITHUB_TOKEN. return the new issue URL on success.
As your server grows, consider:
1. Grouping related tools. A GitHub server should have all GitHub tools. Don't mix GitHub and database tools in one server — separate servers are easier to debug.
2. Descriptive tool descriptions. Claude uses the tool description (in your server's tool list) to decide when to call it. "Lists the open pull requests for a GitHub repository" is better than "list PRs". Be specific.
3. Error handling that's meaningful. Claude reads your error messages. "GitHub API error: 404 Not Found (repository does not exist or is private)" is better than "Error: 404".
Real-world MCP server ideas
-----------------------------
Once you understand the pattern, the possibilities are significant:
Database explorer: expose list_tables, query_table, describe_schema as tools. Now Claude can explore your database by asking questions in plain English.
Deployment helper: expose get_deployments, rollback_deployment, get_logs as tools. Claude can diagnose production issues by reading real logs.
Project management: expose list_tasks, update_task_status, add_comment for Linear or Jira. Claude can triage your backlog during a planning conversation.
Internal documentation: expose search_docs, get_doc_by_id tools over your Notion or Confluence API. Claude can answer questions grounded in your actual internal docs.
Browse MCP-related skills at claudeskil.com/category/agent — most of them include companion MCP server templates you can fork.