Runtype
PlatformPlatform

MCP server: expose your product to your customers' agents

What an MCP server is under the Model Context Protocol (hosts, tools, transports, OAuth), why hosting one is hard, and what changes when customers call it.

Last updated 7 min read

An MCP server is a program that implements the Model Context Protocol, an open standard for connecting AI applications to external tools and data. It offers tools, resources and prompts over JSON-RPC 2.0, and an AI application discovers and calls them through an MCP client. The rest of this page covers what the protocol specifies, why running a server is harder than the wrapper suggests, and what changes when the callers are your customers' agents.

What the Model Context Protocol specifies

The protocol names three roles. A host is the application the user is in (Claude Desktop, Cursor, VS Code, or an agent your own product runs). The host creates one MCP client per server it connects to, and each client talks to one server. The server is the process that answers.

A server can offer three kinds of things. Tools are functions the model decides to call, each with a name, a description and a JSON Schema for its input. Resources are data the application can read, addressed by URI, such as a file or a database row. Prompts are reusable templates the user picks from.

The client can offer something back. Elicitation lets the server ask the user a question mid-call, optional and declared as a client capability on each request. Sampling, where the server asked the host's model to complete something, was deprecated in the 2026-07-28 revision.

Two transports are defined. Over stdio the host launches the server as a subprocess and exchanges newline-delimited JSON on stdin and stdout. Over Streamable HTTP the server exposes a single endpoint that accepts POST requests and may answer with a plain JSON body or an SSE stream; the 2026-07-28 revision removed protocol-level sessions, so each request carries its own version and capabilities. It replaced the two-endpoint HTTP with SSE transport, deprecated since March 2025.

Authorization applies to HTTP transports and builds on OAuth 2.1. The server must implement protected resource metadata (RFC 9728) naming its authorization server, clients must send an RFC 8707 resource indicator on both the authorization and token requests, and a bearer token rides every call.

The lifecycle is short. Revisions through 2025-11-25 opened with an initialize handshake; 2026-07-28 dropped it, so a client states its version and capabilities in each request's _meta and the useful traffic is tools/list and tools/call. A tool result carries a content array (text, image, audio or an embedded resource), an isError flag, and a structuredContent value validated against an optional outputSchema. A longer walk through the pieces is at what is an MCP server, and the trade-offs against a plain HTTP API at MCP vs API.

Why running an MCP server is hard

The reference SDKs make a server that lists and calls tools an afternoon's work. The difficulty is what the protocol leaves to the operator.

Authorization stops at the token

The spec describes how a client gets a token and presents it. It does not describe what the token may do. Mapping a principal to the subset of tools it may call, deciding whether a read_order tool may see orders from a different account, and refusing a call whose arguments reach outside the caller's scope are all server-side decisions with no protocol primitive behind them. The spec also forbids passing the inbound token through to upstream services, so a tool that calls your own backend needs its own credential exchange rather than a forwarded header.

The tool list is a prompt

Each tool's name, description and input schema is placed in the model's context on every turn. Forty tools at roughly 150 tokens each is 6,000 tokens of context before the user has typed anything, and the model's ability to pick the right tool falls as the list grows. A description is also text a model reads and obeys, so a poorly worded one produces wrong calls and a hostile one produces injected instructions. Sizing guidance is in how many tools an agent should have and the diagnosis of a model that keeps choosing the wrong one is in wrong tool selection.

Nothing bounds call volume

The protocol has no rate-limit primitive. A host's agent loop that reads an empty result as a failure will call the same tool again until the host's own turn limit stops it, and that limit belongs to the host, which may not be you. A server that fronts a paid API or a database therefore needs its own per-caller budget, enforced at tools/call, with a clear error the model can act on rather than a silent throttle it will retry through.

Long work does not fit a single call

A tool that wraps a three-minute workflow (generate a report, run a reconciliation, provision an environment) collides with client timeouts counted in seconds: the official TypeScript SDK defaults to 60 seconds per request, and the Python SDK sets no default of its own while its stock HTTP client caps a stream read at 300 seconds. Streamable HTTP lets a server send progress notifications while it works, but only if the client supports them. The reliable pattern is a tool that starts the job and returns an id, plus a second tool that reports status, the shape described in exposing an internal workflow as an agent tool.

Cached tool lists go stale

A client lists tools once and caches the result, which tools/list now encourages with an explicit ttlMs. If a caller's scope changes, or a tool is removed, the client keeps offering the model a tool the server will refuse. notifications/tools/list_changed helps, but the server must re-check authorization on every tools/call rather than trusting what it listed earlier.

How it works

Both directions matter in a production system. A product exposes its capabilities as an MCP server so that customers' agents, IDEs and assistants can call them, and the same product's own agents consume external MCP servers as tools. The mechanics differ in who is trusting whom.

On the exposing side, the server takes a set of capabilities, gives each a tool name, a description and an input schema, and publishes them at an HTTP endpoint. A caller authenticates (OAuth for hosts that support discovery, a bearer key for automation, or nothing for a public tool), lists tools, and calls them:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "lookup_order",
    "arguments": { "orderId": "ord_8f21" }
  }
}

On the consuming side, an agent registers a server by URL and credential, discovers its tools, and attaches some or all of them to the model's tool set, with a timeout and a per-turn call cap. The table shows which pieces come from the protocol and which the operator supplies.

ConcernProvided by the protocolSupplied by the operator
Discovery and invocationinitialize, tools/list, tools/call, schemasTool naming, descriptions, which capabilities to publish at all
Transportstdio, Streamable HTTP, per-request metadataHosting, TLS, state storage for a remote server
AuthenticationOAuth 2.1 flow, bearer token presentationIssuing keys, choosing OAuth vs key vs public per server, revocation
AuthorizationNothing beyond "the token is valid"Per-caller tool scopes, argument-level checks, refusing out-of-scope data
LimitsNothingPer-key rate limits, per-turn call caps, tool timeouts, spend budgets
Long-running workProgress notifications (client support varies)Start and poll tool pairs, durable job state
Attribution and auditNothingWhich customer called which tool with which arguments, and what it cost

The right-hand column is the work. A server that does only the left-hand column is fine for a single developer's editor and unsafe for a customer.

What changes when the server is customer-facing and multi-tenant

An MCP server built for your own team has one trust boundary: your engineers, on your machines, with your data. The caller and the operator are the same organization, so a scoping mistake is an inconvenience.

A server your customers connect to has a different caller on every session. The token identifies a customer, and each tool call has to run as that customer: the lookup_order tool above must resolve ord_8f21 inside the caller's tenant and return nothing if the id belongs to another one, however the argument was phrased. Per-tenant keys make revocation possible without affecting other customers, and per-key scopes let a customer who bought one capability see only that tool in tools/list.

Limits become a product decision rather than a safety net. One customer's agent stuck in a retry loop should exhaust that customer's per-minute budget and receive an error the model can read, without slowing the others. The same applies to cost: a tool that triggers a model call or a paid API on your side needs its spend recorded against the tenant that called it, or the bill lands on you.

Tool descriptions turn into a public interface. The model reading your tool list belongs to the customer's host, not to you, so every description is documentation you are shipping to someone else's model and cannot patch with a system prompt. Renaming a tool or tightening a schema breaks every connected client that cached the old list, which argues for boring, stable names. The broader platform questions behind this, from identity to evals, are gathered at the AI agent platform overview.

Where Runtype fits

Runtype runs both directions of this protocol, and the exposing side is a surface on a product rather than a service to write. An agent you already run comes in four ways that stack.

  • Serve tools from your product. A product's agents, flows, records and tools are published as an MCP server surface, each becoming a tool whose name and description you can override per surface. The surface runs in OAuth, API key or public mode; keys are issued per customer or integration, carry an optional per-minute rate limit, and can be scoped so tools/list shows a caller only what it may call. Setup and scoping are at setting up an MCP surface and scoping API keys to capabilities.
  • Register the agent. An external agent whose endpoint speaks Runtype's unified stream or A2A is called by Runtype, added to a product as a capability and reached through that MCP surface alongside flows, with cost recorded per run.
  • Send traces. An OpenTelemetry-instrumented loop exports OTLP over HTTP to https://api.runtype.com/v1/otel for the Runs view, token usage and a display-only cost estimate. Point exactly one instrumentation at it, because two doubles tokens and cost.
  • Rebuild when it earns it. Port one capability to a hosted flow or agent once an eval suite harvested from real runs can prove parity, after which runtype eval run returns a non-zero exit code in CI on a regression.

On the consuming side the limits are explicit: tool calls time out at 30 seconds by default with a 60-second cap for MCP tools, a turn is bounded by maxToolCalls (default 10, maximum 100), a run by maxTurns and an optional cost ceiling, and tool search activates once an agent carries more than 20 tools. A tool that should not run unattended sits behind an approval gate, and the approver sees the agent's own stated reason, which is never read as a control signal.

These layers are built for a software company's customers. Each resource declares a tenancy strategy (internal, tenant-isolated or end-user-isolated) and an assurance floor of asserted or verified, evaluated before any tool runs, and every call in either direction lands in a trace with its arguments, result, latency and cost, filed under the tenant and end user it ran for.

A team that only needs a local stdio server for its own editor does not need any of this.

Frequently asked questions

What is an MCP server?
An MCP server is a program that speaks the Model Context Protocol and offers tools, resources and prompts to an AI application over JSON-RPC 2.0. The application (the host) runs an MCP client that connects to the server, lists what it offers, and calls tools when a model asks for them. Servers run locally over stdio or remotely over Streamable HTTP.
Is an MCP server the same as a REST API?
No. A REST API is called by code that already knows which endpoint it wants. An MCP server is called by a model that chooses a tool from a list of names and descriptions at runtime, so the description is part of the interface and the server has to expect calls it did not plan for. Many MCP servers are thin wrappers over an existing API, with the extra work being the wrapper, the auth mapping and the limits.
Does the Model Context Protocol handle authentication?
Partly. The authorization part of the spec describes how a client obtains an OAuth 2.1 access token for a remote server and presents it as a bearer token on every request. It does not say which tools a given token may call, how many calls a caller gets, or how a call is attributed to a customer. Those are decisions the server operator makes.
How many tools should one MCP server expose?
Fewer than you first think. Every tool's name, description and input schema is loaded into the model's context on each turn, and selection accuracy falls as the list grows. A server that exposes a few well-named tools per job, scoped per caller, beats a server that mirrors every endpoint of an API.
Can one system both expose an MCP server and consume other MCP servers?
Yes, and most production agents do both. The agent registers external MCP servers as tools it can call, and the same product publishes its own capabilities as an MCP server that customers' agents call. As a consumer you trust the server's tool descriptions; as a server you authorize and rate-limit the caller.