How Do You Build a Production-Ready MCP Server? An Enterprise Guide for 2026
Build a production-ready MCP server: transport selection, OAuth 2.1 auth, RBAC, tool design, audit logging, rate limiting, and multi-tenant deployment.
The Model Context Protocol (MCP) has become the standard interface for connecting AI agents to external data sources, internal APIs, and enterprise tools — but most guides stop at a hello-world server that works in one developer's local environment and fails the moment a second user connects. For engineering teams building MCP servers that must serve a fleet of AI agents, survive production load, and satisfy enterprise security requirements, the gap between a demo server and a production-grade MCP server is substantial.
The deployment surface of an MCP server is broader than it appears. A server that exposes customer data, executes privileged operations, or calls paid third-party APIs needs authentication, tool-level authorization, audit logging, rate limiting, and observability before it goes anywhere near production. For teams already thinking about what happens when AI agents call your MCP server without permission, our MCP tool poisoning prevention guide covers the attack surface — this guide covers the implementation patterns that close it.
This guide covers transport selection, OAuth 2.1 implementation, tool design for reliability, RBAC enforcement, rate limiting, audit logging, and multi-tenant deployment — the full production checklist for an enterprise MCP server in 2026.
What an MCP Server Does and When to Build Your Own
An MCP server is a JSON-RPC 2.0 process that exposes three primitives to any compliant MCP client: tools (functions the AI model can call), resources (data the model can read), and prompts (reusable prompt templates). MCP clients — Claude Desktop, Cursor, VS Code Copilot, your own agent code — discover and invoke these primitives over a standardized protocol, meaning any conforming server works with any conforming client without custom integration code.
- →Build your own MCP server when you need to expose internal APIs, proprietary databases, or business logic that no public MCP server covers. This includes CRM data, internal knowledge bases, ERP systems, and custom tools specific to your business processes.
- →Consume existing public MCP servers when the tool is a well-supported external service — GitHub, Slack, Notion, Stripe — and you do not need custom authentication or access control beyond what the public server provides.
- →Wrap a public server with your own when you need to add enterprise controls — RBAC, audit logs, rate limits — to a server you do not control. A thin gateway MCP server proxies to the upstream while adding your compliance layer.
- →Build your own when multi-tenancy is required: public servers are single-tenant by design. If different users within your system must see different scopes of data from the same underlying source, you need a server that enforces tenant isolation at the tool level.
Transport Selection: STDIO vs Streamable HTTP
The transport layer is the first production architecture decision, and it determines whether your server can be deployed, monitored, and secured at scale. MCP currently supports two active transports: STDIO and Streamable HTTP. A legacy SSE-only transport existed in the 2024 spec and was deprecated in spec 2025-03-26 — do not build new servers with it.
- →STDIO: the client launches your server as a child process and communicates over stdin/stdout using newline-delimited JSON-RPC. No network, no authentication layer, minimal latency. Correct for local developer tools and single-user desktop agents. Not deployable as a shared service — 50 developers connecting to a single logical server means 50 child processes, each with its own memory footprint and impossible to centrally monitor or audit.
- →Streamable HTTP (the production standard): introduced in spec 2025-03-26. A single HTTP endpoint accepts POST requests for client-to-server messages and GET requests to open an SSE stream for server-initiated notifications. Stateless servers are fully supported — no sticky sessions required. Authentication headers flow naturally at the HTTP layer. Every tool call is an HTTP request, visible to gateways, proxies, and load balancers. This is the transport for any server that serves more than one user, runs remotely, or must be audited.
- →The migration path is clean: the official TypeScript SDK (@modelcontextprotocol/sdk) abstracts transport behind a connect() call. Switch from StdioServerTransport to StreamableHTTPServerTransport without changing your tool implementations.
- →Session management under Streamable HTTP: the spec supports stateful sessions via a Mcp-Session-Id header. The server issues a session ID on the initial POST to the MCP endpoint; subsequent requests from the same client include it. For multi-instance deployments, session state must be shared — Redis with a short TTL is the standard pattern. Stateless servers ignore the session header entirely and are simpler to operate.
Designing Enterprise-Grade MCP Tools
Tools are the primitive AI models interact with most. Tool design quality determines whether your server is reliable or whether it generates hallucinations and data leaks. The MCP spec does not enforce quality — that is the server author's responsibility.
- →Name and description are the interface: the model selects tools based on their names and JSON Schema descriptions. Write descriptions as if explaining to a senior engineer what the tool does, what its limitations are, and when not to use it. A vague description produces incorrect tool selection; an incorrect tool call may execute a privileged operation the model did not intend.
- →Validate all inputs with a typed schema: use Zod (TypeScript) or Pydantic (Python) to define input schemas with strict types, string length limits, enum constraints, and required field annotations. Return a clean structured error for invalid inputs rather than passing raw model output to downstream systems. The model's output is untrusted user input at the tool boundary.
- →Design for idempotency: AI agents retry failed tool calls. A tool that creates a record on every invocation will produce duplicate records when the agent retries a timeout. Use idempotency keys — a UUID the caller provides — and deduplicate on the server side.
- →Paginate results by default: models have finite context windows. A tool that returns all 50,000 matching records to a broad query will overflow context and produce unpredictable behavior. Return a bounded page (20–50 items by default) with a cursor field the model can pass to fetch the next page.
- →Separate read tools from write tools explicitly: a read-only tool (list_customers, get_order) carries minimal risk if called unexpectedly. A write tool (create_order, delete_record, send_email) is irreversible. Name them with distinct prefixes and apply separate RBAC scopes to each, so a read-only token cannot accidentally trigger a write.
OAuth 2.1 and Authentication for Remote MCP Servers
The MCP spec mandates OAuth 2.1 with PKCE as the authentication standard for remote MCP servers that implement authorization. For enterprise teams, this is the correct default: it integrates with existing IdP infrastructure, supports machine-to-machine flows for agent-to-server authentication, and provides the token-based identity model required for RBAC and audit logging.
- →Discovery endpoints (RFC 9728): your server must expose a /.well-known/oauth-protected-resource endpoint returning the authorization server URL and supported scopes. MCP clients use this to discover where to obtain tokens before making any tool call. Without this endpoint, compliant clients cannot authenticate at all.
- →Authorization Server Metadata (RFC 8414): the authorization server must expose /.well-known/oauth-authorization-server with its endpoints, supported grant types, and PKCE requirements. For enterprise deployments, this is typically your existing IdP (Okta, Azure AD, Auth0) extended with MCP-specific scopes.
- →Token validation on every request: validate the Authorization: Bearer token before executing any tool logic. Verify the signature against your IdP's JWKS endpoint, check the exp and nbf claims, and verify the aud claim matches your server's resource URI. Cache the JWKS with a 15-minute TTL — do not call the JWKS endpoint on every individual request.
- →Machine-to-machine authentication for agent-to-server calls: agent services calling your MCP server use the OAuth 2.1 Client Credentials flow, not PKCE. The agent authenticates with a client ID and secret and receives a short-lived access token. Rotate client secrets on a schedule (90-day maximum) and store them in a secrets manager — not in environment variables visible in deployment configs.
- →Session vs per-request tokens: for interactive human-in-the-loop flows, short-lived access tokens (15-minute TTL) with rotating refresh tokens are appropriate. For fully automated agent-to-server calls, use client credentials tokens with a TTL matched to your job duration. Refresh token rotation must be enabled — single-use refresh tokens prevent a stolen token from being replayed indefinitely.
Tool-Level RBAC and Scope Enforcement
Authentication tells you who the caller is. Authorization — specifically tool-level RBAC — determines which tools that caller is permitted to invoke. Without explicit scope enforcement, a token issued for read-only access can call a write tool if the server does not check. This is a common gap in early MCP server implementations. Our MCP governance guide covers the organizational policy layer; this section covers the server-side implementation.
- →Define scopes at the tool category level: a minimal scope model uses three scopes per resource — mcp:customers:read, mcp:customers:write, mcp:customers:admin. Read scope grants access to list and get tools; write scope adds create and update tools; admin scope adds delete and bulk operations. Scopes are embedded in the OAuth token and checked server-side on every tool call.
- →Enforce scopes in middleware, not in individual tool handlers: a single middleware function runs before every tool handler and compares the tool name against a scope-to-tool mapping. Putting the check inside each tool handler means forgetting to add it to a newly added tool is the default failure mode. Middleware enforcement creates a single point of control.
- →Filter the tools/list response by the caller's scopes: when a client calls tools/list to discover available tools, return only the tools the caller's scopes permit. An agent that cannot see a tool cannot attempt to call it, reducing the attack surface and eliminating permission-denied noise in agent traces.
- →Read the tenant identifier from the JWT, not the request body: in multi-tenant deployments, the tenant claim must come from the validated token — a custom claim set by your IdP during authentication. Never accept a tenant_id from the client in the request body. A compromised agent could impersonate another tenant's context if the server trusts client-supplied tenant identifiers.
Rate Limiting, Cost Controls, and Quota Management
AI agents are non-deterministic callers. A model in an agentic loop can invoke a tool hundreds of times in a single run if it is not making forward progress — an expensive and potentially harmful behavior. Rate limiting is not an optional enhancement for production MCP servers; it is the cost control layer and the availability guarantee.
- →Rate limit at the tool level, not just the server level: a global server-level rate limit still allows an agent to exhaust quota on a single expensive tool. Define per-tool limits — calls per minute and maximum concurrent calls — separately. An external API that charges per call needs a tighter limit than an internal read-only lookup.
- →Use a token bucket with a sliding window: fixed-window counters (N calls per minute, resetting at :00) create burst problems — an agent that hits the limit at :59 and retries at :00 doubles the instantaneous load. A token bucket with a sliding window smooths burst behavior and is more predictable for agent retry loops.
- →Return Retry-After headers on 429 responses: when a tool call is rate-limited, return HTTP 429 with a Retry-After header indicating when the next call is permitted. MCP clients and agent frameworks that implement the spec correctly will respect this header and back off automatically.
- →Expose quota state in tool responses: include a metadata object in successful tool responses showing the remaining quota for the current window. Agent orchestrators can use this signal to throttle their call rate before hitting a hard limit, reducing failures in long-running agent tasks.
Audit Logging Every Tool Call
An audit log is not an application log. Application logs capture what happened technically — request latency, error codes, stack traces. An audit log captures what happened semantically — who called which tool, with what inputs, on whose data, and with what outcome. For enterprise MCP servers, the audit log is what you produce when a security incident occurs, a compliance audit runs, or an agent behaves unexpectedly and you need to reconstruct its exact action sequence.
- →Log these fields on every tool call: caller identity (sub and email from the JWT), the client application (OAuth client_id or aud), the tool name, sanitized input parameters (remove secrets and PII), the tenant identifier, the resource identifier accessed, the outcome (success, error, permission-denied), response latency, and a correlation ID that links the tool call to the agent run that triggered it.
- →Write audit logs to an immutable append-only store: the audit log must not be writable by the application servers that generate it. Ship immediately to a SIEM (Splunk, Elastic, Datadog) or an append-only object store (S3 with Object Lock). Audit logs co-located in the same writable database as application data can be altered or deleted in a breach.
- →Alert on anomalous patterns in real time: baseline the call volume and tool distribution per agent client over rolling 24-hour windows. Alert when an agent calls a write or delete tool it has never called before, calls any write tool more than N times per hour, or accesses tenant data outside its normal tenant set. These patterns are the primary indicators of a compromised agent or an active prompt injection attack.
- →Retain for the duration your compliance framework requires: SOC 2 Type II requires a minimum of one year of audit evidence. GDPR audits can look back three years. Set an explicit retention policy in your log store and periodically verify that you can actually query and export logs from the start of the retention window — log pipeline failures discovered during an audit are worse than a documented gap.
Deployment, Multi-Tenancy, and Observability
Deploying a Streamable HTTP MCP server to production follows the same patterns as any stateless HTTP service — a deliberate design decision in the protocol. Horizontal scaling, health checks, rolling deploys, and observability all work with standard infrastructure tooling. The operational complexity compared to STDIO is low; the compliance and security posture improvement is significant.
- →Stateless servers scale horizontally without sticky sessions: each HTTP request is independent. Deploy behind a load balancer with multiple instances. Health check on a dedicated /health endpoint, not the MCP endpoint itself (which requires auth). Auto-scale on p99 request latency — tool calls that block on a slow downstream API will back up if you scale on CPU utilization alone.
- →Containerize with a minimal image: use a multi-stage Docker build. The final image should contain only the compiled server binary and its runtime dependencies — no build tools, no source code, no dev dependencies. Run as a non-root user with a read-only root filesystem. Scan the image in CI with Trivy or Grype before every deploy.
- →Store all credentials in a secrets manager: the MCP server's downstream API keys, database connection strings, and JWT signing secrets must be fetched from a secrets manager (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) at startup. Never bake them into the image or into environment variable fields that are visible in deployment manifests or CI logs.
- →Instrument with OpenTelemetry: emit a span for every tool call with the tool name, tenant ID, caller identity, and outcome as span attributes. Propagate the W3C TraceContext header from incoming requests so the MCP server's spans appear as children of the upstream agent's root span. This makes it possible to view the complete agent-to-tool call tree in a single trace in Datadog, Honeycomb, or Grafana Tempo.
- →Use versioned tool names for breaking changes: adding a required input field or changing a response schema breaks existing agents without warning. Roll out breaking changes by introducing a new tool version alongside the old (v2_create_order alongside create_order), deprecate the old version with a deprecation notice in the tool description, and remove it only after verifying no active agents still call it.
Frequently Asked Questions
What is the difference between STDIO and Streamable HTTP for MCP servers?
STDIO launches the server as a child process and communicates over stdin/stdout. It requires no network, has no authentication layer, and is limited to one client per process. It is the correct transport for local developer tools but cannot be deployed as a shared remote service. Streamable HTTP runs the server as a standard HTTP service where authentication headers flow at the transport layer and multiple clients connect simultaneously. Any server that needs to serve more than one user, run remotely, or be audited should use Streamable HTTP.
Does every MCP server need OAuth 2.1?
The MCP spec mandates OAuth 2.1 with PKCE for remote servers that implement authorization. A local STDIO server running on a developer's machine with no network exposure does not require OAuth. Any Streamable HTTP server accessible over the network — even only internally — requires authentication. For enterprise deployments, OAuth 2.1 is the correct default: it integrates with existing IdP infrastructure and provides the token model needed for RBAC and audit logging.
How do you test an MCP server before deploying to production?
Test at three levels. At the unit level, test each tool handler function directly with mocked downstream clients — no MCP protocol involved. At the integration level, use the MCP Inspector (the official dev tool at modelcontextprotocol.io) and the @modelcontextprotocol/sdk Client class to call your server over the actual protocol and verify tool responses, error shapes, and schema compliance. At the contract level, maintain a golden dataset of representative inputs and expected outputs per tool, run it in CI on every pull request, and fail the build on regression. Test authentication and RBAC explicitly: verify that a read-scoped token cannot invoke write tools, and that a cross-tenant request returns a permission error rather than another tenant's data.
Can one MCP server serve multiple AI agent clients simultaneously?
Yes, with Streamable HTTP transport. Each client connection is a standard HTTP request; the server handles concurrent tool calls from multiple agents in parallel, bounded only by your infrastructure capacity and rate limits. Stateful sessions (via Mcp-Session-Id) are scoped per client, stored in a shared Redis cache, and do not block other clients. STDIO servers are inherently single-client and cannot be shared across agents without a process-per-client topology that scales poorly.
What should every MCP server audit log record?
Every tool call should log: the caller's identity (user sub and email from the JWT), the client application (OAuth client_id), the tool name, sanitized input parameters with PII and secrets removed, the tenant identifier, the resource identifier accessed, the outcome (success, error, or permission-denied), response latency in milliseconds, and a correlation ID linking the call to the agent run that triggered it. Audit logs should be written to an immutable append-only store separate from your application database, retained for at least one year for SOC 2 compliance.
How Belsoft Helps Teams Build Production MCP Infrastructure
Building an MCP server that passes a security review and scales to enterprise production is a different problem from building one that runs locally. The authentication layer, RBAC model, audit pipeline, and deployment infrastructure each require decisions that compound — a wrong choice in transport, for example, closes off the authentication patterns available at the tool layer. Belsoft designs and builds production MCP server infrastructure for engineering teams, from the initial architecture decision through deployment and observability. Explore our AI & Automation services or book a technical conversation to scope what your use case requires.
Teams that have already deployed MCP servers and are encountering security or governance concerns — unauthorized cross-tenant access, missing audit trails, agents consuming unbounded tool quota — should address those controls before expanding the MCP surface area. A server that functions is not the same as a server that is safe to expose to external agents or regulated data.
“A production MCP server is not a function with a JSON interface — it is a security boundary. Design it that way from the first commit.”
Written by
Belsoft Team
More from the blog
Ready to build?
Let's talk about your project.
30 minutes. No pitch. We map your requirements and tell you honestly what it will take.
Book a Strategy Call