Blog
 » 

Claude

 » 
How to Build a Custom MCP Server for Claude Code

How to Build a Custom MCP Server for Claude Code

13 min

 read

Learn how to build a custom MCP server for Claude Code — connect your own tools, APIs, and workflows to extend Claude's capabilities.

Jesus Vargas

By 

Jesus Vargas

Updated on

May 13, 2026

.

Reviewed by 

Why Trust Our Content

How to Build a Custom MCP Server for Claude Code

Building a custom MCP server for Claude Code is how you give the agent access to anything the official servers do not cover: your internal APIs, proprietary data sources, legacy systems, and workflow tools with no public MCP implementation.

The TypeScript MCP SDK makes this approachable. Define the tools you want Claude Code to call, implement the handlers, and register the server. This guide walks through the full build with a working example.

Key Takeaways

  • Any API becomes an MCP tool: If you can call it from Node.js, you can expose it to Claude Code as a named tool through a custom MCP server.
  • TypeScript SDK is the standard path: Anthropic maintains @modelcontextprotocol/sdk, which handles protocol communication so you write tool definitions and handlers, not transport code.
  • Zod schemas validate tool inputs: Each tool has a name, description, and Zod-based input schema. Claude Code uses the description to decide when to call the tool.
  • The server runs as a local process: Your custom MCP server runs on the developer's machine, communicates over stdio, and makes any outbound network call your handler requires.
  • Registration is one .mcp.json entry: Point the entry at your compiled build output and Claude Code treats your custom tools exactly like any official MCP tool.
  • This guide builds an internal API server: A reusable pattern that wraps a REST API and exposes its endpoints as named tools Claude Code can call during development.

Claude for Small Business

Claude for SMBs Founders

Most people open Claude and start typing. That works for one-off questions. It doesn't work for running a business. Do this once — this weekend.

Why Would You Build a Custom MCP Server?

Build a custom MCP server when no official or community server exists for your data source, or when your workflow requires business logic that no existing tool can provide.

The official servers cover common external services: GitHub, Supabase, Postgres, Notion, Brave Search, Filesystem, and Puppeteer. They do not cover internal company APIs, proprietary data stores, or legacy systems.

  • Check existing servers first: Review the official MCP servers available and the community registry before investing in a custom build.
  • Understand the MCP vs Tool Use distinction: The MCP vs Tool Use comparison clarifies when to build an MCP server versus using the Claude API's Tool Use feature directly.
  • Get the prerequisites first: If your team is new to MCP, the MCP setup fundamentals guide provides the context you need before starting a custom server build.
  • Internal API access without going public: A custom MCP server exposes internal APIs to Claude Code without requiring those APIs to be publicly accessible.
  • Business logic that cannot be composed: When your workflow requires logic specific to your organisation, a custom server is the correct abstraction layer.

The custom server path is the right choice when you have a specific internal service Claude Code needs to reach and no existing server can reach it.

What Does a Custom MCP Server Look Like Before You Build One?

A custom MCP server is a Node.js process that defines a set of named tools, handles tool call requests from Claude Code over stdio, and makes whatever outbound calls your handler code requires.

Understanding the components before writing code saves significant debugging time. The mental model is simple once the four parts are clear.

  • The MCP server: A Node.js process running on the developer's machine. It declares tools and handles tool call requests from Claude Code.
  • The transport layer: The TypeScript SDK handles this automatically. Claude Code spawns the server as a child process and communicates via stdio pipes.
  • The tool definition: Each tool has a name (the identifier Claude Code uses to call it), a description (how Claude Code decides to use it), and an input schema.
  • The tool handler: The function that runs when Claude Code calls the tool. It receives validated arguments and returns a structured text response.

This guide builds a three-tool server wrapping a hypothetical internal REST API: list_feature_flags, trigger_deployment, and get_service_logs. These three tools cover read, write, and parameterised-read patterns that appear in almost every custom server build.

How Do You Set Up the MCP Server Project?

Project setup requires Node.js 18 or later, the @modelcontextprotocol/sdk package, Zod for schema validation, and TypeScript configured for CommonJS or ESM output with a dist output directory.

This is the boilerplate that must exist before any tool code is written. Keep the structure minimal, it scales cleanly.

  • Install dependencies: Run the commands below to initialise the project and install everything needed.
  • Configure tsconfig.json: Set "module": "commonjs" or "node16", "outDir": "./dist", and "strict": true.
  • Add build scripts: Define "build": "tsc" and "start": "node dist/index.js" in package.json.
  • Keep the source structure flat: All server code lives in src/index.ts. Compiled output goes to dist/index.js.
  • The .mcp.json entry points to dist: This is the path Claude Code uses to start the server process, so it must exist before registration.

mkdir internal-api-mcp && cd internal-api-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node ts-node
npx tsc --init


Project structure after setup:

internal-api-mcp/
├── src/
│   └── index.ts
├── dist/
├── package.json
└── tsconfig.json

How Do You Define Tools in an MCP Server?

Tool definitions go in the ListTools handler. Each tool has a name, a description written for Claude Code to read, and an input schema that defines the parameters Claude Code must provide when calling the tool.

The description field is the most important part of any tool definition. Claude Code uses it to decide which tool to call. Write it as an instruction, not documentation.

  • Server initialisation first: Create the server instance with its name, version, and tools capability before defining any handlers.
  • ListTools handler declares all tools: Return the full array of tool objects from this handler. Claude Code reads this list at session start to know what tools exist.
  • Description quality determines selection accuracy: A precise, action-oriented description ("Use this when the user wants to deploy code") produces more reliable tool selection than a vague one.
  • Input schema drives argument construction: Tools with underspecified schemas produce ambiguous tool calls. Define every parameter with a type and description. See Claude Code agentic workflows for how tool precision affects session reliability.
  • Required fields control validation: List every parameter the handler cannot function without in the required array. Optional parameters should have defaults documented in their description.

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

const server = new Server(
 { name: "internal-api-server", version: "1.0.0" },
 { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
 return {
   tools: [
     {
       name: "list_feature_flags",
       description: "Returns all current feature flags and their enabled/disabled status. Use this when the user asks about which features are active.",
       inputSchema: { type: "object", properties: {}, required: [] }
     },
     {
       name: "trigger_deployment",
       description: "Triggers a deployment for a specified service and environment. Use this when the user wants to deploy code to staging or production.",
       inputSchema: {
         type: "object",
         properties: {
           service: { type: "string", description: "The service name to deploy" },
           environment: { type: "string", enum: ["staging", "production"], description: "Target environment" }
         },
         required: ["service", "environment"]
       }
     },
     {
       name: "get_service_logs",
       description: "Retrieves recent logs for a specified service. Use this when debugging issues or checking service health.",
       inputSchema: {
         type: "object",
         properties: {
           service: { type: "string", description: "The service name" },
           lines: { type: "number", description: "Number of log lines to retrieve (default: 50)" }
         },
         required: ["service"]
       }
     }
   ]
 };
});

How Do You Implement Tool Handlers?

The CallTool handler routes incoming tool calls to individual handler functions by tool name. Each handler validates its inputs with Zod, calls the relevant API or service, and returns a structured text response.

Return errors in the content field rather than throwing unhandled exceptions. Claude Code recovers from returned error text more gracefully than from a server crash.

  • Switch on tool name: The CallToolRequestSchema handler receives the tool name and arguments. Route each name to its dedicated handler function.
  • Validate inputs with Zod: Parse the args parameter with a Zod schema in every handler that accepts parameters. This catches type errors before your API call.
  • Use environment variables for credentials: Pass Authorization headers using process.env.INTERNAL_API_TOKEN, never a hardcoded value in the handler source.
  • Return format is always content array: Every handler must return { content: [{ type: "text", text: string }] }. Use JSON.stringify for structured data.
  • Wrap all handler logic in try/catch: Return the error message in the content field. An unhandled exception crashes the server process and breaks all subsequent tool calls.

server.setRequestHandler(CallToolRequestSchema, async (request) => {
 const { name, arguments: args } = request.params;
 switch (name) {
   case "list_feature_flags":
     return await handleListFeatureFlags();
   case "trigger_deployment":
     return await handleTriggerDeployment(args);
   case "get_service_logs":
     return await handleGetServiceLogs(args);
   default:
     throw new Error(`Unknown tool: ${name}`);
 }
});

async function handleListFeatureFlags() {
 const response = await fetch("https://internal-api.company.com/features", {
   headers: { Authorization: `Bearer ${process.env.INTERNAL_API_TOKEN}` }
 });
 const data = await response.json();
 return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}

const DeploymentSchema = z.object({
 service: z.string(),
 environment: z.enum(["staging", "production"])
});

async function handleTriggerDeployment(args: unknown) {
 const { service, environment } = DeploymentSchema.parse(args);
 const response = await fetch("https://internal-api.company.com/deployments", {
   method: "POST",
   headers: {
     "Content-Type": "application/json",
     Authorization: `Bearer ${process.env.INTERNAL_API_TOKEN}`
   },
   body: JSON.stringify({ service, environment })
 });
 const data = await response.json();
 return { content: [{ type: "text", text: `Deployment triggered: ${JSON.stringify(data)}` }] };
}

How Do You Register the Custom Server With Claude Code?

Registration requires building the TypeScript to dist/index.js, adding a single entry to .mcp.json with the absolute path to the compiled output, and setting the required environment variable.

Use an absolute path in the .mcp.json entry. Relative paths resolve inconsistently depending on how Claude Code starts the server process.

  • Build first, then register: Run npm run build and confirm dist/index.js exists before editing .mcp.json.
  • Use absolute path in args: Point the args array to the full filesystem path of dist/index.js, not a relative path.
  • Pass credentials as env references: Use the "INTERNAL_API_TOKEN": "${INTERNAL_API_TOKEN}" pattern in the env field. Never hardcode the value.
  • Verify with a test prompt: Start a new Claude Code session and ask "List all feature flags." Claude Code should call list_feature_flags and return the response.
  • Debug by running the server directly: If the server does not load, run node /path/to/dist/index.js in your terminal. Startup errors appear there and are usually missing environment variables or import path issues.

{
 "mcpServers": {
   "internal-api": {
     "command": "node",
     "args": ["/absolute/path/to/internal-api-mcp/dist/index.js"],
     "env": {
       "INTERNAL_API_TOKEN": "${INTERNAL_API_TOKEN}"
     }
   }
 }
}

If Claude Code does not call your tool, ask it: "What tools do you have available?" This lists all loaded MCP tools and confirms whether the server registered successfully.

What Are Real-World Custom MCP Server Use Cases?

Custom MCP servers are most valuable for internal tooling that your team queries repeatedly during development: feature flags, CI pipelines, internal docs, monitoring systems, and custom database tooling.

The three-tool pattern from this guide scales directly to each of these use cases. The only change is which API or data source the handlers call.

  • Feature flag management: Expose LaunchDarkly, Unleash, or a custom flag service as MCP tools. Claude Code can read flag states, toggle flags, and create new flags during sessions.
  • CI/CD pipeline control: Wrap Jenkins, CircleCI, or Buildkite APIs as MCP tools. Claude Code can trigger runs, check build status, and retrieve logs without leaving the terminal.
  • Internal documentation search: Connect Claude Code to Confluence or a private wiki. Claude Code can search and retrieve internal docs as context during any coding session.
  • Monitoring and alerting: Expose Datadog, Grafana, or PagerDuty APIs as MCP tools. Claude Code can query alert status, fetch metrics, or acknowledge incidents from the terminal.
  • Custom migration tracking: For teams using custom migration frameworks, build an MCP server that reads your migration history table and applies pending migrations.

For teams running Claude Code across multiple engineers with shared internal API access, the enterprise Claude Code patterns guide covers governance and credential management for shared MCP server deployments.

Conclusion

Building a custom MCP server closes the gap between Claude Code's built-in capabilities and your team's specific internal tools.

The pattern is consistent regardless of what you are wrapping: define tools with precise descriptions, implement handlers with validated inputs, build the server, and register it. Once running, Claude Code treats your custom tools exactly like any official MCP server tool.

Start with one internal API endpoint your team queries repeatedly during development. One tool working correctly is more valuable than five tools half-implemented.

Claude for Small Business

Claude for SMBs Founders

Most people open Claude and start typing. That works for one-off questions. It doesn't work for running a business. Do this once — this weekend.

Need a Custom MCP Server Built for Your Internal Tools?

Most internal tools your team needs during development have no official MCP implementation. That gap either slows your developers down every session or gets solved once with a custom server build.

At LowCode Agency, we are a strategic product team, not a dev shop. We design and build custom MCP servers that connect Claude Code to your internal APIs, proprietary data sources, and legacy systems, so your development team has a capable agentic tool that fits your actual stack.

  • Internal API integration: We wrap your internal REST APIs as fully typed MCP tools with Zod validation and error handling built in.
  • Tool definition design: We write the tool descriptions and input schemas that produce reliable, accurate tool selection inside Claude Code sessions.
  • Authentication setup: We configure secure credential handling using your existing secrets management infrastructure, not hardcoded values.
  • Legacy system connectors: We build MCP handlers that bridge Claude Code to older internal systems that lack modern API surfaces.
  • Team deployment: We structure the server for shared team use, with documentation, environment variable conventions, and a .mcp.json template every developer can use.
  • Testing and validation: We run the full suite of tool call tests before handoff, including edge cases and error conditions your handlers will encounter in real sessions.
  • Custom AI agent development: We go beyond MCP servers to build complete agentic workflows when your use case requires more than a single tool layer.

We have built 350+ products for clients including Coca-Cola, American Express, and Medtronic. We know the difference between an MCP server that works in a demo and one that holds up across a full development team.

If you want a custom MCP server that connects Claude Code to your internal tools, discuss your MCP build.

Last updated on 

May 13, 2026

.

Jesus Vargas

Jesus Vargas

 - 

Founder

Jesus is a visionary entrepreneur and tech expert. After nearly a decade working in web development, he founded LowCode Agency to help businesses optimize their operations through custom software solutions. 

Custom Automation Solutions

Save Hours Every Week

We automate your daily operations, save you 100+ hours a month, and position your business to scale effortlessly.

FAQs

What is an MCP server and how does it work with Claude Code?

Do you need coding experience to build a custom MCP server?

What tools or APIs can you connect to Claude Code via a custom MCP server?

How long does it take to build a basic custom MCP server for Claude Code?

What is the difference between a local and remote MCP server for Claude Code?

Is the Model Context Protocol open source and free to use?

Watch the full conversation between Jesus Vargas and Kristin Kenzie

Honest talk on no-code myths, AI realities, pricing mistakes, and what 330+ apps taught us.
We’re making this video available to our close network first! Drop your email and see it instantly.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Why customers trust us for no-code development

Expertise
We’ve built 330+ amazing projects with no-code.
Process
Our process-oriented approach ensures a stress-free experience.
Support
With a 30+ strong team, we’ll support your business growth.