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

Table of Contents
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-25spec 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/sdkfor TypeScript, ormcpfor 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:
stdiofor 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/sdkv1.29.0 for TypeScript (TypeScript SDK), ormcpv1.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 class —
McpServerfrom the TypeScript package, orFastMCPimported asfrom mcp.server.fastmcp import FastMCPin Python (MCP Docs). Name it explicitly. - Spec revision — target
2025-11-25, the current stable revision. - Transport —
stdioor 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
stdioprocess — 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
stdiotransport, 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
stdioor Streamable HTTP only — a--transport sseconnection error means switch to--transport http.- Spec version drift. Build against the
2025-11-25stable 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 on2025-11-25and your server keeps working.- Config key differs by host. VS Code’s
.vscode/mcp.jsonuses the keyservers; Cursor and Claude Code project config usemcpServers. 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:
- 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.
- 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. - Attach the transport last —
stdioor 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
mcpServerswhere VS Code wantedservers.

Common Pitfalls
| What You Did | Why AI Failed | The Fix |
|---|---|---|
| One-shot “build me an MCP server” | Too many concerns at once — picks a transport, skips auth, invents capabilities | Decompose into transport / capabilities / auth first |
| No SDK version pinned | Scaffolds against a stale API from training data | State @modelcontextprotocol/sdk v1.29.0 or mcp v1.27.1 explicitly |
| Used SSE transport | Connection fails — SSE was replaced by Streamable HTTP | Specify stdio or Streamable HTTP in the context |
| Copied Cursor config into VS Code | VS Code needs the servers key, not mcpServers — silent no-show | Use the per-host config key |
| Database Tool with no constraint | AI generates a Tool that can write rows | Add a read-only constraint to the capability spec |
| Passed a tool argument into a shell call | stdio 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