MAX guide 14 min read

How to Build an MCP Server with the Official TypeScript and Python SDKs in 2026

Specification map for building an MCP server: transports, tool capabilities, and editor host config
Before you dive in

This article is a specific deep-dive within our broader topic of Model Context Protocol.

This article assumes familiarity with:

TL;DR

  • An MCP server is a contract, not a script — it exposes tools, resources, and prompts that any compliant client can call the same way.
  • The two decisions that break most builds are transport (stdio vs Streamable HTTP — SSE is dead) and which config key each editor expects.
  • Build against the 2025-11-25 spec with the official SDKs, and treat remote auth as an OAuth 2.1 resource-server problem, not a login screen.

You built an MCP Server. It runs. The logs look clean. Then you wire it into VS Code Copilot using the config that worked in Cursor — and nothing shows up. No error. No tool. Just silence. The server isn’t broken. The spec you handed your editor was.

Before You Start

You’ll need:

  • An AI coding tool that speaks MCP — Claude Code, Cursor, or VS Code Copilot
  • One official SDK: @modelcontextprotocol/sdk for TypeScript, or mcp for Python (3.10+)
  • A working mental model of Model Context Protocol — how a MCP Host (your editor) talks to a server through an MCP Client

This guide teaches you: how to decompose an MCP server into transport, capabilities, and auth — so the AI tool you’re pairing with generates the right server instead of guessing.

The Server That Runs but Never Connects

Here’s the failure I see most. Someone types “build me an MCP server that reads my database” into Cursor, gets a file full of plausible code, runs it — and the client lists zero tools. The server starts. It just never registers anything the host can see.

That gap is almost never a bug — it’s an unspecified contract. The AI picked a transport you didn’t ask for, named capabilities the client can’t discover, or scaffolded against an API that shifted three releases ago. It worked in the demo and died in your editor, which expected a different config key than the one you copied over.

Step 1: Map the Server into Three Contracts

Before anything gets generated, decompose the server into the three things it actually owns. Every MCP server is the same three layers underneath, no matter the language.

Your server has these parts:

  • Transport — how bytes move between host and server. Two legal choices: stdio for a local process the editor launches, or Streamable HTTP for a remote service. Pick one before you write a line.
  • Capabilities — what the server exposes. Three types: Tools (actions the model can call), Resources (data the model can read), and Prompts (reusable templates). A filesystem server exposes files as Resources; a database server exposes queries as Tools.
  • Auth boundary — who is allowed in. Local stdio servers inherit your shell’s trust. Remote servers need a real boundary, and MCP defines exactly what that boundary is.

Underneath, MCP messages are JSON-RPC — typed request/response envelopes the SDK writes for you. That’s why capabilities must be declared, not improvised: the client discovers them by asking, and the server answers with a structured list.

The Architect’s Rule: If you can’t name your server’s transport, its three capability lists, and its auth boundary in one breath, the AI can’t build it either.

Step 2: Lock Down the Spec Before You Generate

This is the context the AI must have before it writes anything. Skip a line here and you get a server that runs in the demo and fails in your stack.

Context checklist:

  • SDK and exact version@modelcontextprotocol/sdk v1.29.0 for TypeScript (TypeScript SDK), or mcp v1.27.1 for Python, which needs 3.10 or newer (MCP Python SDK). Pin the version; don’t let the model guess from training data.
  • Server classMcpServer from the TypeScript package, or FastMCP imported as from mcp.server.fastmcp import FastMCP in Python (MCP Docs). Name it explicitly.
  • Spec revision — target 2025-11-25, the current stable revision.
  • Transportstdio or Streamable HTTP. Never SSE.
  • Capabilities — every Tool, Resource, and Prompt listed by name, with inputs and outputs.
  • Auth model — for remote servers, an OAuth 2.1 resource server.

The Spec Test: If your context doesn’t name the transport, the AI will scaffold Streamable HTTP when your local editor expects to launch a stdio process — and the client connects to a server it can’t speak to.

A note on auth, because it trips up web-app developers first. An MCP server is an OAuth 2.1 resource server, not an authorization server (MCP Spec) — it validates tokens, it doesn’t issue them. The spec requires it to publish RFC 9728 metadata at /.well-known/oauth-protected-resource and to reject any invalid or expired token with an HTTP 401. You’re the bouncer checking IDs, not the office that prints them.

Security & compatibility notes:

  • Sanitize every tool input (CVE-2026-30623). A design flaw disclosed in April 2026 lets parameter values flow into command execution on the stdio transport, and the execution model is treated as by-design — there is no SDK patch coming (The Hacker News). Validate and bound every input, and never interpolate an untrusted value into a shell command. The safety is your responsibility, by design.
  • Pin the TypeScript SDK to a patched release (CVE-2026-0621). A ReDoS flaw in the SDK’s URI-template handling affected versions at or below 1.25.1; it was fixed in v1.25.2, so the pinned v1.29.0 is already safe.
  • SSE transport is deprecated. The legacy HTTP+SSE transport was replaced by Streamable HTTP (MCP Docs). Build new servers on stdio or Streamable HTTP only — a --transport sse connection error means switch to --transport http.
  • Spec version drift. Build against the 2025-11-25 stable revision — the current stable spec. A larger revision is on the public 2026 roadmap (a stateless HTTP core, an extensions framework), but it is not yet final, so build on 2025-11-25 and your server keeps working.
  • Config key differs by host. VS Code’s .vscode/mcp.json uses the key servers; Cursor and Claude Code project config use mcpServers. Config copied between them fails silently.

These sanitization patterns are for securing and testing your own servers. Never use them for unauthorized access to systems you don’t own.

Step 3: Sequence the Build — Transport Last

Order matters in AI-assisted work, because each layer’s context depends on the one before it. Build the contract from the inside out.

Build order:

  1. Capability schemas first — define your Tools, Resources, and Prompts. They have no dependencies and they’re the whole point of the server. Get the names, inputs, and outputs right before anything else.
  2. Wire each capability to its data source — this is where “connect to a database or local file system” actually happens. A file server exposes a directory as Resources scoped to a root path; a database server exposes queries as Tools, read-only unless you intend writes. The client never sees the data source behind the capability. And that data boundary is a security boundary: on stdio, an argument can flow straight into command execution, so validate and bound every input — never interpolate it into a shell command, file path, or SQL string.
  3. Attach the transport laststdio or Streamable HTTP. It’s the wrapper, not the substance. Wiring it first is how people end up debugging plumbing before they have anything to plumb.

For each capability, your context must specify:

  • What it receives (input schema)
  • What it returns (output shape)
  • What it must NOT do (the read-only constraint, the path it can’t escape, the input it must never pass into a shell or query unescaped)
  • How it fails (the error the client should see, not a stack trace)

That last point is the one developers skip. A database Tool that throws a raw exception on a bad query hands the model garbage — so specify the failure shape, not just the happy path.

Step 4: Prove the Server Before You Trust It

Don’t trust a clean startup log. A server that boots is not a server that works. Check the contract, not the process.

Validation checklist:

  • The client lists every tool you defined — failure looks like: an empty tool list in the host, which means capabilities weren’t registered or the host loaded the wrong config key.
  • An invalid token returns 401 (remote only) — failure looks like: the server returns data to an expired token, which means your auth boundary is decorative.
  • The server starts from the host’s launch command — failure looks like: a connection error mentioning sse, which means you’re on a dead transport.
  • The config key matches the host — failure looks like: the server is invisible in VS Code but works in Cursor, which means you used mcpServers where VS Code wanted servers.
Three-layer MCP server specification showing transport choice, tool and resource capabilities, and the OAuth 2.1 auth boundary
The three contracts every MCP server must specify before an AI tool can build it: transport, capabilities, and auth.

Common Pitfalls

What You DidWhy AI FailedThe Fix
One-shot “build me an MCP server”Too many concerns at once — picks a transport, skips auth, invents capabilitiesDecompose into transport / capabilities / auth first
No SDK version pinnedScaffolds against a stale API from training dataState @modelcontextprotocol/sdk v1.29.0 or mcp v1.27.1 explicitly
Used SSE transportConnection fails — SSE was replaced by Streamable HTTPSpecify stdio or Streamable HTTP in the context
Copied Cursor config into VS CodeVS Code needs the servers key, not mcpServers — silent no-showUse the per-host config key
Database Tool with no constraintAI generates a Tool that can write rowsAdd a read-only constraint to the capability spec
Passed a tool argument into a shell callstdio lets the value reach command execution (CVE-2026-30623)Validate and bound inputs; never build a shell or SQL string from them

Pro Tip

Keep a context file your AI tool reads before every session, and put your external contracts in it: SDK package and version, spec revision, transport, per-host config keys. These are the values that drift between the model’s training data and reality — and drift is the silent killer of AI-generated integrations. The model is great at logic and terrible at remembering which version of a fast-moving protocol is current. Pin the volatile facts once, and every server you scaffold after starts from the truth instead of a guess.

Frequently Asked Questions

Q: How to build an MCP server step by step in 2026? A: Decompose first: name your transport, your three capability lists, and your auth boundary. Then pin the SDK version and spec revision, build capability schemas before transport, and validate that the client actually lists your tools. One detail people miss — register capabilities before the transport connects, or the host’s discovery call returns nothing.

Q: How to connect an MCP server to a database or local file system? A: Expose the data through capabilities, never directly. A file system maps to Resources scoped to a root path; a database maps to Tools that wrap queries. Add a read-only constraint unless writes are intentional. Watch the path boundary — a Resource that resolves symlinks outside its root quietly leaks files the model was never meant to read.

Q: How to add an MCP server to Claude Code, Cursor, and VS Code Copilot? A: Claude Code uses claude mcp add --transport stdio my-server -- cmd args (or --transport http my-server <url>), with --scope project writing a .mcp.json (Claude Code Docs). Cursor reads .cursor/mcp.json under mcpServers. VS Code reads .vscode/mcp.json under servers — a different key, and the most common silent failure (VS Code Docs).

Your Spec Artifact

By the end of this guide, you should have:

  • A three-layer server map — transport, the three capability lists, and the auth boundary
  • A context checklist with the SDK pinned to a version, the chosen transport, and the spec revision
  • A validation list with the failure symptom for each host you target

Your Implementation Prompt

Paste this into your AI coding tool (Claude Code, Cursor, or Copilot) once you’ve filled the brackets. Every bracket maps to a constraint from Step 2 — the prompt forces the decomposition instead of leaving it to the model.

You are building an MCP server. Follow this specification exactly.

LANGUAGE & SDK:
- Language: [TypeScript | Python]
- SDK + pinned version: [@modelcontextprotocol/sdk v1.29.0 | mcp v1.27.1]
- Server class: [McpServer | FastMCP]
- Target spec revision: 2025-11-25

CAPABILITIES (expose only what I list):
- Tools: [each tool — name, inputs, output, what it must NOT do]
- Resources: [each resource — URI pattern, data source: db table or file root]
- Prompts: [named prompts, or "none"]

TRANSPORT:
- Transport: [stdio for local | Streamable HTTP for remote]
- Do NOT use SSE — it is deprecated.

AUTH (remote servers only):
- Act as an OAuth 2.1 resource server — validate tokens, do not issue them.
- Serve RFC 9728 metadata at /.well-known/oauth-protected-resource.
- Reject invalid or expired tokens with HTTP 401.

SECURITY (all servers):
- Validate and bound every tool input against its schema.
- Never interpolate an input into a shell command, file path, or SQL string.
- Pin @modelcontextprotocol/sdk to a patched v1.x release.

BUILD ORDER:
1. Define capability schemas first (tools, resources, prompts).
2. Wire each capability to its data source [db connection | file root path],
   applying read-only constraints where I marked them.
3. Attach the transport last.

VALIDATE:
- The client lists every tool I defined.
- An invalid token returns 401 (remote only).
- The server starts from my host's launch command on the chosen transport.

Ship It

You now have a mental model that survives the next protocol revision: an MCP server is three contracts — transport, capabilities, auth — and the AI’s job is to fill them, not invent them. Decompose before you generate, pin the volatile facts, and validate the contract instead of the startup log. Do that and the silent no-show disappears.

AI-assisted content, human-reviewed. Images AI-generated. Editorial Standards · Our Editors

Share: