Enterprise DNA

Omni by Enterprise DNA

Enterprise DNA Resources

Step-by-step how-tos. Practical AI operating-system thinking for owners, operators, and teams doing real work.

220k+

Data professionals

Omni

AI agents and apps

Audit

Map the manual work

Guide Intermediate General

Build an MCP Server for Claude: A Practical Guide

Learn how to build an MCP server that connects Claude to your data and tools. A practical, step-by-step tutorial for developers.

Sam McKay |
Build an MCP Server for Claude: A Practical Guide

What MCP Server Build actually is

MCP stands for Model Context Protocol. It is an open protocol Anthropic released to standardize how AI assistants like Claude connect to external tools, data sources, and prompts. When someone says “build an MCP server,” they mean writing a small program that speaks this protocol and exposes a set of capabilities to an MCP-aware client.

Strip the marketing and the technical reality is this. An MCP server is a process that runs on your machine or a remote host. It speaks JSON-RPC over a transport, either standard input and output for local servers, or HTTP with server-sent events for remote ones. The client, in most cases Claude Desktop or another IDE plugin, connects to the server and discovers what tools, resources, and prompts the server provides. After discovery, the model can call those tools during a conversation and receive structured results back.

Two terms come up constantly. The first is the host, which is the AI application the user is talking to, like Claude Desktop. The second is the client, which lives inside the host and handles the protocol. From the model’s point of view, calling an MCP tool looks like calling a function. From your point of view as a developer, you are writing a server that registers a list of callable tools, each with a name, a description, an input schema, and a handler function.

The official SDKs ship for Python and TypeScript. Community SDKs exist for Go, Rust, Java, C#, and a few others. If you are reading this guide, you will most likely use Python or TypeScript because that is where the documentation and examples are most complete.

Why this matters is the part the marketing usually skips. MCP gives you a stable contract between the model and whatever you want the model to touch. Once that contract exists, you can swap servers, add servers, and combine servers without rewriting prompt logic. The model just sees a unified tool list.

Setup and authentication

You need three things to get going. A working installation of either Python 3.10 or higher, or Node.js 18 or higher. An MCP-aware client, the most common being Claude Desktop. And the MCP SDK for your chosen language.

For Python, the install command in a clean virtual environment is pip install mcp. For TypeScript, the equivalent is npm install @modelcontextprotocol/sdk. Both packages include the server primitives, transport handlers, and CLI helpers you will need.

Claude Desktop stores its MCP configuration in a JSON file. The path on macOS is ~/Library/Application Support/Claude/claude_desktop_config.json. On Windows it lives in %APPDATA%\Claude\claude_desktop_config.json. The config file lists servers by name and points each one at a launch command. For a local Python server you register a server named “demo” with command “python” and args that include the absolute path of your server script. For a Node server, the command is “node” and the args point at your JavaScript entry point.

Authentication in the standard MCP flow is process-level rather than user-level. When Claude Desktop launches your server, the server inherits whatever environment variables and credentials the desktop app can see. For local development, that often means tokens stored in your shell profile or a local .env file. For remote servers exposed over HTTP, the protocol supports OAuth 2.0 and bearer tokens, but the implementation details depend on the client and the reverse proxy in front of your server.

A quick note on secrets. Do not hardcode API keys in server source. Read them from environment variables at startup and refuse to start if they are missing. This pattern is not specific to MCP but it catches a lot of accidental leaks in early builds.

After you save the config, restart Claude Desktop. Open the developer settings and confirm the server shows up with a green indicator. If it shows red, the most common causes are a wrong absolute path in the config, a missing dependency in the server’s environment, or a Python or Node version mismatch.

First working example

A complete MCP server in TypeScript is small. The minimum you need is an import of the SDK, a server instance, a tool registration with a schema, and a transport connection.

The structure goes like this. Import Server from the SDK along with the StdioServerTransport class. Create a new Server instance with a name, a version, and a capabilities object that declares you support tools. Register at least one tool using server.setRequestHandler with the ListToolsRequest schema. The handler returns an array of tool definitions, each with a name, a description, and an input JSON schema. Register a second handler for CallToolRequest that switches on the tool name and returns a result object with a content array of type “text” and a string payload.

For the actual tool, pick something boring and self-contained for the first run. A “sum” tool that takes two numbers and returns their total is a good choice. A “get_time” tool that returns the current ISO timestamp works too. The point is to prove the round trip from Claude Desktop to your server and back.

Launch the server, open Claude Desktop, and ask the model to use the tool. A prompt like “Use the sum tool to add 47 and 58” should produce the answer through your server. If it works, you have the foundation for everything else in this guide.

Once the trivial example runs, replace the body of the handler with a real call. A common next step is wiring the tool to a third-party API. Use the fetch library, call the endpoint, parse the JSON, and return the relevant field. Wrap the call in a try-catch and return an error object with isError set to true so the model can see the failure and recover.

Key settings that matter

Most early MCP servers work but quietly waste money or hit rate limits. The dials below are the ones that move the needle.

The first is tool description quality. The model reads the description to decide when to call the tool. A vague description leads to missed calls. A description that mentions the specific inputs, the units, and an example produces sharper behavior. Treat the description like a piece of prompt engineering because that is what it is.

The second is input schema strictness. The more specific your JSON schema, the less the model has to guess. Mark required fields as required, use enums for known value sets, and add minimum and maximum constraints for numbers. Sloppy schemas produce hallucinated parameters that the server then has to reject.

The third is transport choice. Stdio is lower latency and easier to debug because the server runs in the same process tree as the client. HTTP with server-sent events is required for remote or shared servers but adds network overhead and needs proper auth and CORS handling. Start with stdio for development and switch to HTTP only when you have a reason to.

The fourth is error handling shape. Returning a generic exception string makes the model guess what went wrong. Returning a structured error with a clear message and a category lets the model retry, switch tools, or tell the user what happened. Most production servers include a small error wrapper that classifies failures into validation, transport, and upstream categories.

The fifth is response size. A tool that returns tens of thousands of tokens of raw text burns through the context window and slows the model down. Where possible, summarize, paginate, or pre-filter before returning. A reasonable target is to keep tool responses under a few thousand tokens for normal use.

The sixth is concurrency and timeouts. Stdio servers handle one request at a time by design. If your tool does network I/O, run several handlers in parallel within a single request or batch the work yourself. The SDK does not enforce a timeout, so a hung upstream call will hang the tool call too. Add your own timeouts in the 10 to 30 second range for typical APIs at this tier.

The seventh is logging. Pipe logs to stderr, not stdout, when using the stdio transport. The protocol uses stdout for messages and any log line you write there will be parsed as JSON-RPC and break the