All Posts
AI Infrastructure September 8, 2026

How to Build an MCP Server: A Practical TypeScript Guide (2026)

The Model Context Protocol went from a promising idea to the default way agents reach tools and data, which means building an MCP server is now a routine engineering task rather than a research project. The concepts are small: a server exposes tools, resources, and prompts, and a client running inside an agent calls them over a transport. The hard parts are not the hello world, they are the decisions after it: what to expose as a tool versus a resource, which transport fits local versus remote deployment, how to test the thing without a full agent in the loop, and how to ship it so it is observable and safe. This guide builds a minimal server in TypeScript, then walks the decisions that turn a demo into something you would actually run in production.

How to Build an MCP Server: A Practical TypeScript Guide (2026)

The Model Context Protocol went from a promising idea to the default way agents reach tools and data. By 2026 building an MCP server is a routine engineering task, not a research project. The core concepts are small: a server exposes capabilities, and a client running inside an agent calls them over a transport. What takes judgment is everything after the hello world: what belongs as a tool versus a resource, which transport fits your deployment, how you test without a full agent in the loop, and how you ship it so it is observable and safe. This guide builds a minimal server in TypeScript, then walks the decisions that separate a demo from production.

The three things a server exposes

An MCP server offers three kinds of capability, and picking the right one for each piece of functionality is the first real design decision.

Tools are actions the model can choose to invoke, like "create an order" or "search the catalog." They are model controlled: the agent decides when to call them, so their descriptions matter as much as their code, because the description is the only thing the model reads before deciding. Resources are data the server exposes for the client to read, like a file, a record, or a config document. They are application controlled: the host decides what to pull into context. Prompts are reusable templates the user can invoke deliberately, like a slash command that assembles a structured request.

The common mistake is to make everything a tool. If the agent only needs to read a document, that is a resource, and exposing it as a tool invites the model to call it at odd moments and burn tokens. Reserve tools for actions with side effects or real computation, expose passive data as resources, and use prompts for user initiated workflows. Getting this taxonomy right is what makes a server pleasant for an agent to use rather than a pile of callable functions.

A minimal server in TypeScript

Start with the official SDK. Create a project, install the SDK and a schema library, and write the smallest server that does something real. The TypeScript SDK handles the protocol framing so you write handlers, not wire format.

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: "orders", version: "1.0.0" });

server.tool(
  "get_order",
  "Look up a single order by its id and return its status and line items.",
  { orderId: z.string() },
  async ({ orderId }) => {
    const order = await lookupOrder(orderId);
    return { content: [{ type: "text", text: JSON.stringify(order) }] };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Three things in that snippet carry weight. The tool name is stable and machine readable. The description is a real sentence written for the model, telling it what the tool does and when to reach for it. The input schema is declared with Zod, which gives you validation and a generated JSON schema the client shows the model, so a malformed call is rejected before your handler runs. Everything else is your own code behind lookupOrder.

Choosing a transport

Transport is where local and remote servers diverge, and in 2026 the story has consolidated. A server that runs on the same machine as the client, a developer tool spawned by an editor, speaks over stdio, which is what the snippet above uses. A server that runs as a network service other agents connect to speaks over HTTP. The protocol has been unifying these around a streamable HTTP model with persistent connections, which we covered in depth in MCP Streamable HTTP and remote agent servers.

The practical rule: build the server logic transport agnostic, and swap the transport at the edge. Your tools and resources should not know or care whether they were reached over stdio or HTTP. That separation lets you develop locally over stdio and deploy the same handlers as a remote service without rewriting them. It also means the auth and network concerns of a remote deployment stay at the boundary rather than leaking into every handler.

Testing without an agent in the loop

You do not want a full agent and a frontier model in the loop every time you change a handler. That is slow, costs tokens, and makes failures ambiguous, since you cannot tell whether the model or your server misbehaved. Test the server directly.

The protocol is just JSON messages, so you can write a client harness that connects over stdio, lists the tools, and calls each one with known inputs, asserting on the output. The official tooling includes an inspector that connects to your server and lets you invoke tools and read resources by hand, which is the fastest way to sanity check descriptions and schemas. Reserve the full agent test for behavior you can only see end to end, like whether the model actually picks the right tool given your descriptions. Everything else, input validation, error shapes, resource contents, is a plain unit test against the server.

Shipping it to production

Three concerns turn a working server into a production one, and none of them is the protocol itself.

The first is authorization. A local stdio server inherits the trust of the machine it runs on, but a remote server exposed over HTTP needs real authentication and needs to know which user an agent is acting for. This is subtle enough that it deserves its own treatment, which we gave it in MCP authorization and agent identity. Do not ship a remote server without it.

The second is observability. Agents call tools in ways you did not anticipate, so log every invocation with its inputs, outputs, latency, and errors. When an agent does something surprising in production, that log is the only way to reconstruct what the model saw and chose. Treat tool calls the way you treat API requests, because that is what they are.

The third is safety at the boundary of every tool. The model can call any tool with any input the schema allows, so validation is not optional and side effects need guarding. A tool that can mutate data should be idempotent where possible, should check authorization per call rather than trusting the connection, and should return clear structured errors the model can reason about rather than throwing raw exceptions. A well behaved server makes a misbehaving agent safe; a careless one lets a confused model do damage. The same agent readable design thinking applies when you expose commerce data to agents, which we explored in the Shopify storefront MCP piece.

FAQ

What is the difference between a tool and a resource in MCP? A tool is a model controlled action the agent chooses to invoke, often with side effects. A resource is application controlled data the host reads into context. Use tools for actions, resources for passive data.

Do I have to use TypeScript to build an MCP server? No. There are official SDKs in several languages, and Python is a common choice. The concepts are identical across languages; this guide uses TypeScript because the SDK is mature and the type flow to JSON schema is clean.

Which transport should I use? stdio for a local server spawned by a client on the same machine, HTTP for a remote network service. Keep your handlers transport agnostic so you can switch without rewriting logic.

How do I test an MCP server? Test the server directly with a client harness or the inspector, asserting on tool outputs and error shapes. Reserve full agent tests for behavior you can only observe end to end, like tool selection.

What do I need before exposing a server remotely? Real authentication and per call authorization, invocation logging for observability, and input validation with guarded side effects on every tool. A local stdio server can defer these; a remote one cannot.

When this applies to your stack

If your team is wiring an agent to internal systems, the question is rarely whether MCP can do it, it is whether the server you build is one an agent uses well and an operator can run safely. That comes down to the taxonomy of tools versus resources, a transport chosen for the deployment rather than the demo, tests that do not require a model in the loop, and the production trio of auth, observability, and guarded side effects. Get those right and the server disappears into the background, which is exactly what good infrastructure does.

Designing that capability surface for a real agent, and hardening the server for production traffic, is the applied AI work we do at Contra Collective. If you are building agent integrations and want the server engineered to be safe and observable rather than just functional, we can help. Reference material worth bookmarking: the protocol docs and the specification.

[ 02 ] — Keep Reading

More from the lab.

Nov 7, 2026 AI Infrastructure

Gap and Christie's Are Running Production AI on Mac Minis: What Apple's Q4 Call Actually Revealed About Enterprise Local Inference

On its fiscal Q4 earnings call on November 2, 2026, Apple named two enterprise customers doing something more specific than the usual AI PC talking point: Gap runs AI loss prevention models across hundreds of stores entirely on Mac minis, which CFO Kevan Parekh described specifically as eliminating cloud token costs, and Christie's runs pricing estimate models on Mac Studio. Apple's pitch is that a few thousand dollar Mac can produce inference value worth tens of thousands over its life. That math is directionally right for a narrow set of workloads, and it is not a general argument against cloud inference, even though the earnings call framing invites you to read it that way.

Nov 6, 2026 AI Infrastructure

Kimi K3.1 Pro: Moonshot's Open Weight Model and What a 1M Token Context Actually Changes

Kimi K3.1 Pro went live in Moonshot's API on October 23, 2026, priced at 2 dollars per million input tokens and 10 dollars per million output tokens, with a 1 million token context window. Moonshot promised open weights by November 4. The model inherits Kimi K3's reported 2.8 trillion total parameters and 104 billion active parameters per token, which means the real question for most teams is not whether it is open but whether anything they own can actually run it.

Oct 29, 2026 AI Infrastructure

oMLX on Apple Silicon: What the SSD KV Cache Actually Buys Coding Agents

oMLX search interest has climbed fast this year, and the pull is a specific engineering claim: a coding agent's time to first token dropping from 90 seconds to as little as 3 once a long prompt's key value cache is already sitting on SSD instead of being recomputed from scratch. The architecture is real and the restore speed is measurable. The claim is also softer than the headline number suggests, and this post works through both halves.

Ready when you are

Want to discuss this topic?

Start a Conversation