MCP Tutorial: From Zero to Production Server with the 2026-07-28 Specification
Key takeaways
- 97M+ monthly MCP SDK downloads, but only 8.5% of servers use OAuth — the protocol's adoption outpaces its security posture, making the production hardening steps in this tutorial non-optional for any B2B deployment.
- MCP SDK v2 cut package size 83% and improved speed 25% — the 2026-07-28 specification shipped alongside redesigned SDKs across TypeScript, Python, Go, and C#, with migration guides for each.
- The 2026-07-28 spec removed sessions and the initialize handshake — every request is now self-contained, landing on any server instance behind a plain round-robin load balancer without shared state.
- The August 22 MCP roadmap defines five priority areas — agent identity, HTTP transport unification, agentic messaging primitives, improved tool primitives, and SDK developer experience — each addressed in this tutorial's production path.
- 82% of MCP servers are vulnerable to path traversal (per Practical DevSecOps) — the sandboxing and input-validation steps here are the difference between a demo and a deployment.
The Model Context Protocol crossed 97 million monthly SDK downloads in 2026, with the TypeScript and Python SDKs each surpassing 1 billion total downloads. The 2026-07-28 specification shipped the largest revision since launch: a stateless protocol core, first-class extensions, and three deprecations that simplify the deployment surface. Three weeks later, on August 22, the MCP maintainers published a new roadmap defining five priority areas for the next specification cycle — agent identity, HTTP transport unification, agentic messaging primitives, improved tool primitives, and SDK developer experience.
This tutorial covers the production path: building an MCP server that is stateless, horizontally scalable, identity-aware, and ready for the roadmap's enterprise priorities. The official quickstart walks through a weather server connected to Claude Desktop. This article starts where that quickstart ends — the steps between a working demo and a server you would put behind a B2B agent system in production.
Step 1 — Project setup with SDK v2
The 2026-07-28 specification shipped alongside redesigned SDKs. The TypeScript SDK v2 cut package size by approximately 83% and improved performance by 25% through a new client-server split. The Python SDK 2.0+, Go SDK, and C# SDK v2.0 all speak the 2026-07-28 protocol version as of publication day, with detailed migration notes for the breaking changes.
For this tutorial, we use Python 3.10+ with uv:
uv init mcp-server
cd mcp-server
uv venv
source .venv/bin/activate
uv add "mcp[cli]"The mcp[cli] extra brings the CLI tooling for running and inspecting servers. The SDK uses Python type hints and docstrings to generate tool definitions automatically — you define a function, decorate it, and the protocol metadata is derived from the signature.
Step 2 — Define your first tools
An MCP server exposes three capability types: tools (functions the model calls), resources (data the model reads), and prompts (templated workflows). For a B2B server, tools are the primary surface — they are how an agent searches a catalog, generates a quote, or holds inventory.
from mcp.server import MCPServer
mcp = MCPServer("catalog-server")
@mcp.tool()
async def search_catalog(query: str, supplier_id: str | None = None) -> str:
"""Search the supplier catalog by keyword, optionally filtered by supplier.
Args:
query: Search keyword (SKU, product name, or category)
supplier_id: Optional supplier filter (e.g., "s-12")
"""
results = await catalog.search(query=query, supplier_id=supplier_id)
return format_results(results)
@mcp.tool()
async def get_pricing(sku: str, quantity: int) -> str:
"""Get tiered pricing for a SKU at a given quantity.
Args:
sku: Supplier product identifier
quantity: Order quantity (determines pricing tier)
"""
price = await pricing_engine.get(sku=sku, quantity=quantity)
return f"SKU {sku}: ${price.unit_price:.2f} (tier: {price.tier_name})"Each tool's docstring becomes the description the model sees in its tool list. The type hints become the input schema. This is the MCP Module Code Standard pattern: every tool has a typed schema, a clear docstring, and a single responsibility.
STDIO logging pitfall: for STDIO-based servers, never write to stdout — it corrupts the JSON-RPC message stream. Use the standard logging module, which writes to stderr:
import logging
logger = logging.getLogger(__name__)
logger.info("Catalog search: query=%s", query) # stderr, safeStep 3 — Transport: STDIO vs Streamable HTTP
The 2026-07-28 specification makes remote MCP servers "no different from any other HTTP workload" (specification changelog). The roadmap's second priority area — HTTP-native transport unification — extends this to local servers speaking Streamable HTTP over stdio, unifying on one transport model.
For local development and desktop clients, STDIO is the default:
if __name__ == "__main__":
mcp.run(transport="stdio")For production B2B deployments — where the agent runs as a cloud workload, not a desktop app — Streamable HTTP is the production transport. The server runs behind a load balancer, accepts HTTP POST requests with Mcp-Method and Mcp-Name headers, and responds with JSON-RPC over HTTP:
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8080)The header-based routing feature means your gateway, rate limiter, or WAF can route and meter on Mcp-Method and Mcp-Name headers directly — no JSON body parsing required for routing decisions. This is the deployment shape the stateless protocol was designed for: a pool of stateless server instances behind a round-robin load balancer, with no shared session tier.
Step 4 — Explicit handles for stateful workflows
Stateless does not mean state disappears. The 2026-07-28 specification replaces hidden session state with the explicit-handle pattern: a tool mints a handle (an order_id, a quote_id, a basket_id) and the model passes it back as an ordinary argument on subsequent calls. This is covered in detail in MCP 2026-07-28: What the Stateless Protocol Means for B2B Agent Deployments.
For a quoting workflow that spans five tool calls — create request, search catalog, generate quote, hold availability, apply pricing tier — the handles thread through every call:
@mcp.tool()
async def create_request(buyer_id: str, line_items: list[dict]) -> str:
"""Create an RFQ request and return a request_id handle."""
request = await rfq_engine.create(buyer_id, line_items)
return f"request_id={request.id}"
@mcp.tool()
async def generate_quote(request_id: str, supplier_ids: list[str]) -> str:
"""Generate a quote from specified suppliers. Returns quote_id."""
quote = await rfq_engine.quote(request_id, supplier_ids)
return f"quote_id={quote.id}"Every call carries the handle it needs. No server remembers anything between calls. If the load balancer routes call 4 to a different instance than call 3, it still works — the handle is in the request. If the audit team needs to reconstruct this workflow a week later, the handles in the request arguments tell the full story.
Step 5 — Agent identity: the enterprise gap
The roadmap's third priority area — agent identity and enterprise-ready security — is the most significant for B2B deployments. The roadmap is explicit: MCP authorization today is "built around a person approving access in a browser," but "more and more of the callers are agents running as cloud workloads with their own identity, acting on a user who isn't present, or delegating narrower authority to sub-agents."
The path forward, as defined by the roadmap:
- DPoP (RFC 9449) — Demonstrating Proof of Possession binds an OAuth token to a key held by the client. A stolen token alone cannot replay requests from another process. DPoP does not decide what the agent is allowed to do; it makes the credential harder to reuse outside its intended holder.
- Workload Identity Federation — the IETF WIMSE working group is developing architecture for workload identity in multi-system environments. An agent is a workload, so it gets a workload's identity: named with a SPIFFE ID, authenticated with short-lived credentials, not a shared API key.
- Enterprise-Managed Authorization (EMA) — the EMA extension moves the access decision to an organization's identity provider. The MCP client exchanges a user identity assertion for an Identity Assertion JWT Authorization Grant (ID-JAG), then exchanges that grant for a server-specific access token. This supports central assignment and revocation.
For this tutorial's production server, the minimum baseline is:
- No shared API keys. Each agent gets a short-lived, audience-bound token.
- OAuth with DPoP. The Practical DevSecOps MCP Security Statistics 2026 report found only 8.5% of MCP servers use OAuth — the remaining 91.5% rely on API keys or no authentication at all.
- Token exchange at every trust boundary. The CoSAI token-exchange standard (published August 18) establishes token exchange as a foundational control for agentic workflows. Every
register_tools()entry point should accept a task-scoped token, not a persistent credential.
See the MCP Security Hardening Checklist for the 12 controls that verify these standards before production, and the MCP Module Code Standard for the module-level defensive posture.
Step 6 — Progressive tool discovery: solving the hundred-tools problem
The roadmap's fourth priority area — improved primitives — addresses a concrete production problem: "Connecting to a server with a hundred tools means the model pays for that entire surface before the user has asked a single question, and tool selection tends to get worse as the list grows."
The roadmap's answer is progressive discovery: a server offers a small entry point and reveals more of its catalog as the conversation narrows. Instead of dumping 100 tool schemas into the model's context window on connection, the server exposes a handful of top-level tools and dynamically expands the surface based on what the agent is doing.
The 2026-07-28 specification already provides the building blocks:
server/discoverRPC — a client can learn a server's capabilities before doing anything else, without a session handshake.tools/listwithttlMsandcacheScope— list responses carry cache hints, so clients cache tool catalogs and avoid re-fetching on every connection._metaon every request — protocol version, client info, and capabilities travel per-request, not in a negotiated session.
For a server with 50+ tools, the progressive discovery pattern looks like:
@mcp.tool()
async def discover_tools(category: str) -> str:
"""Discover available tools in a category. Start here for a guided tour.
Args:
category: Tool category — 'catalog', 'pricing', 'inventory', 'orders'
"""
available = tool_registry.by_category(category)
return format_tool_list(available)The agent calls discover_tools("catalog") first, gets a focused set of 5-8 tools, and only expands to other categories when the workflow requires it. This keeps the context window lean and tool selection accurate — the same principle behind the MCP Module Code Standard's single-responsibility tool design.
Step 7 — Deployment: stateless, horizontal, behind a load balancer
The deployment shape the 2026-07-28 specification was designed for:
┌──────────────────┐
│ Load Balancer │
│ (round-robin) │
└────────┬─────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌────────┴────┐ ┌───────┴────┐ ┌───────┴────┐
│ MCP Server │ │ MCP Server │ │ MCP Server │
│ Instance 1 │ │ Instance 2 │ │ Instance 3 │
│ (stateless) │ │ (stateless) │ │ (stateless) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
└──────────────┼──────────────┘
│
┌────────┴─────────┐
│ Upstream Systems │
│ (NetSuite, etc.) │
└──────────────────┘No shared session store. No sticky-session load balancer. No session-replay infrastructure. Every request carries what it needs — protocol version, client info, capabilities, and handles — in the request body and headers, not in server-side state.
For production hardening:
- Sandbox every server instance. The MCP Project Sandboxing Baseline (published August 16) requires OS-level sandboxing for spawned processes. Use Landlock (Linux), Seatbelt (macOS), or Windows ACLs to restrict filesystem and network access. The 82% path-traversal vulnerability rate (per Practical DevSecOps) is the evidence that input validation alone is insufficient.
- Rate-limit every tool. The MCP Module Code Standard requires per-tool rate limits. The MCP Ruby SDK DoS vulnerability (disclosed August 16) confirms that resource-exhaustion attacks are a real attack surface.
- Log to OpenTelemetry, not the MCP logging channel. The 2026-07-28 specification deprecated the Logging feature in favor of stderr and OpenTelemetry. MCP server logs integrate with existing observability pipelines (Datadog, CloudWatch, Honeycomb) without a custom transport.
- Use header-based routing for WAF and rate limiting. The
Mcp-MethodandMcp-Nameheaders let your gateway route and authorize without parsing JSON bodies.
The roadmap ahead: five priority areas
The August 22 roadmap defines the direction for the next specification cycle. This tutorial covers the production-ready parts; the roadmap names what comes next:
| Priority area | Status | Tutorial coverage |
|---|---|---|
| Agent identity and enterprise-ready security | In progress (DPoP, WIMSE, EMA) | Step 5 — baseline established; full implementation pending spec finalization |
| HTTP-native transport unification | Shipped (remote), in progress (local) | Step 3 — remote HTTP is production; local Streamable HTTP over stdio is the roadmap target |
| Agentic messaging primitives | Extensions shipping (Tasks, subscriptions) | Not covered — server-initiated events (webhooks, channels) are the next frontier |
| Improved primitives (progressive discovery) | Design phase | Step 6 — the pattern is implementable today using discover_tools; spec-level support is coming |
| Improved SDK developer experience | Shipped (SDK v2, conformance testing) | Step 1 — SDK v2 is the current production baseline |
The roadmap is direction, not a compatibility promise. The July 28 specification already delivered the stateless HTTP core and the Tasks extension. Push events, unified discovery, delegation, and cross-SDK conformance still need implementation work. For B2B deployments, the agent identity priority is the one to watch — it addresses the exact gap (API keys and long-lived tokens) that the MCP security articles and governance checklist have been flagging.
The tutorial's production path covers the five-step production checklist:
Related reading
- MCP 2026-07-28: What the Stateless Protocol Means for B2B Agent Deployments — the companion analysis covering the stateless protocol, explicit-handle pattern, and deprecations in depth
- MCP Module Code Standard — the structural pattern that makes every module production-ready: directory layout, tool registration, error handling, rate limiting, and audit logging
- MCP Security Hardening Checklist: 1,467 Exposed Servers and the Controls That Close Them — the 12 controls that verify a server before production, addressing the 82% path-traversal and 8.5% OAuth gaps
Build vignette
A mid-market distributor running NetSuite wanted to give their sales team an AI assistant that could search supplier catalogs, generate quotes, and check inventory levels without leaving the CRM. The first attempt used a single API key shared across every agent instance — the 91.5% of MCP servers that skip OAuth. A supplier price-list update exposed the key in a log file, and the team spent two days rotating credentials across 15 services.
The rebuild followed this tutorial's production path: SDK v2 with typed tool schemas, Streamable HTTP transport behind a round-robin load balancer, DPoP-bound OAuth tokens with 15-minute expiry, explicit handles for the five-step quoting workflow, and OS-level sandboxing on every server instance. The agent connects to NetSuite through an MCP module following the code standard, with progressive discovery exposing 8 catalog tools first and expanding to pricing and inventory only when the workflow requires it. Six server instances run stateless behind the load balancer. No shared session store. No sticky sessions. Every request carries its handle, its token, and its protocol version.
Request a scoped build
One-week discovery. You get a system inventory, workflow map, and fixed scope — whether or not you build with us.
Want this built for your systems?
Every document here comes from real production work. If you have a target system and a workflow in mind, we can scope a build in one week.
Request a scoped buildOne-week discovery. You get a system inventory, workflow map, and fixed scope — whether or not you build with us.