Building Your Own MCP Server
When there’s no existing MCP server for a service you use, you can build your own. This is simpler than it sounds.
When to Build Your Own
Section titled “When to Build Your Own”- Your company has internal APIs Claude should access
- You use a niche service without a community MCP server
- You want to expose a database or tool in a controlled way
- You need custom logic between Claude and an external service
MCP Server Architecture
Section titled “MCP Server Architecture”An MCP server is a program that:
- Communicates with Claude Code via stdio (stdin/stdout)
- Declares what tools/resources it provides
- Handles tool calls and returns results
Building with the TypeScript SDK
Section titled “Building with the TypeScript SDK”The official @modelcontextprotocol/sdk makes this straightforward.
mkdir my-mcp-servercd my-mcp-servernpm init -ynpm install @modelcontextprotocol/sdk zodnpm install -D typescript @types/nodeBasic Server
Section titled “Basic Server”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-custom-server", version: "1.0.0",});
// Define a toolserver.tool( "get_weather", "Get current weather for a city", { city: z.string().describe("City name"), }, async ({ city }) => { // Your logic here — call an API, query a database, etc. const response = await fetch( `https://api.weather.example/current?city=${encodeURIComponent(city)}` ); const data = await response.json();
return { content: [ { type: "text", text: JSON.stringify(data, null, 2), }, ], }; });
// Start the serverconst transport = new StdioServerTransport();await server.connect(transport);Compile and Run
Section titled “Compile and Run”{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./dist", "strict": true }, "include": ["src/"]}npx tscnode dist/index.jsRegistering Your Server
Section titled “Registering Your Server”Point Claude Code at your server in settings:
{ "mcpServers": { "my-server": { "command": "node", "args": ["/path/to/my-mcp-server/dist/index.js"], "env": { "API_KEY": "your-key" } } }}Real-World Example: WordPress REST API Server
Section titled “Real-World Example: WordPress REST API Server”Here’s a practical example — an MCP server that connects Claude to a WordPress site:
server.tool( "wp_get_posts", "Get posts from WordPress", { post_type: z.string().default("posts"), per_page: z.number().default(10), }, async ({ post_type, per_page }) => { const response = await fetch( `${process.env.WP_URL}/wp-json/wp/v2/${post_type}?per_page=${per_page}`, { headers: { Authorization: `Basic ${btoa( `${process.env.WP_USER}:${process.env.WP_APP_PASSWORD}` )}`, }, } ); const posts = await response.json(); return { content: [{ type: "text", text: JSON.stringify(posts, null, 2) }], }; });
server.tool( "wp_create_post", "Create a new WordPress post", { title: z.string(), content: z.string(), status: z.enum(["draft", "publish"]).default("draft"), }, async ({ title, content, status }) => { const response = await fetch( `${process.env.WP_URL}/wp-json/wp/v2/posts`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Basic ${btoa( `${process.env.WP_USER}:${process.env.WP_APP_PASSWORD}` )}`, }, body: JSON.stringify({ title, content, status }), } ); const post = await response.json(); return { content: [{ type: "text", text: `Created post #${post.id}: ${post.link}` }], }; });Testing Your Server
Section titled “Testing Your Server”The MCP Inspector tool lets you test servers without Claude Code:
npx @modelcontextprotocol/inspector node dist/index.jsThis opens a web UI where you can call tools and see responses.
Exercise
Section titled “Exercise”- Create a simple MCP server with one tool (e.g., a random quote generator)
- Register it in your Claude Code settings
- Restart Claude and verify the tool appears
- Ask Claude to use the tool
- Expand the server with additional tools