Skip to content

MCP server architecture for enterprise data: what to build and what the spec now requires

8 min readFor data engineers, architects and security reviewers putting data behind MCP

An MCP server is a program that exposes tools, resources and prompts to AI applications through the Model Context Protocol, using JSON-RPC over stdio or Streamable HTTP. For enterprise data, the protocol is the small part. The design work is deciding what the model may ask for, validating a token issued for this server, running each call under the requesting person’s authority, and returning results that state their scope. Since the July 28, 2026 specification, the protocol is stateless, so designs that rely on sessions need rework.

One request, end to end

MCP has three roles. The host is the AI application a person uses. Inside it, one client exists per connected server. The server offers capabilities and answers requests. The specification’s design principles keep the full conversation with the host and stop a server from seeing into other servers, so your server receives only the arguments of the call. That isolation helps security and costs you something, because the server can’t look back at the conversation to work out what the person meant.

Servers expose three kinds of thing, and the difference is who decides to use them. Tools are model-controlled. Resources are application-driven, so the host decides how they enter the context. Prompts are user-controlled, typically appearing as commands a person chooses. Most data servers are built almost entirely from tools, because a tool is the only primitive the model can reach for on its own. A resource suits material the host should attach deliberately, such as a metric glossary. A prompt suits a repeatable review a person starts.

POST /mcp HTTP/1.1
Authorization: Bearer <token issued for this server>
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: query_metric

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "query_metric",
    "arguments": { "metric": "net_revenue", "period": "2026-Q3" },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {}
    }
  }
}

Many guides describe a protocol that has changed

Much of what ranks for this topic still explains an initialize handshake and session IDs. The July 28, 2026 revision removed both. Every request now carries its protocol version and client capabilities, and the session header is gone from Streamable HTTP. For an enterprise team this is mostly good news, because any replica behind a load balancer can answer any request. It also removes assumptions older server code tends to make, so treat this table as a migration list. Clients may still speak earlier revisions, and the specification describes how to detect them, so support both if your users’ assistants have not moved.

If your design assumesThe July 28, 2026 realityWhat to do
A session holds state between callsNo protocol session existsReturn an explicit handle and take it as an argument; check the caller against it on every call
tools/list can vary per connectionIt may vary only by the authorization on the requestDerive the list from the token’s scopes
The server can call back for input mid-requestInput requests travel inside a result, and the client retriesReturn input_required and protect any requestState you rely on
A dropped stream can resumeResumption was removed; the client reissues as a new requestMake every write safe to receive twice
Sampling lets the server use the client’s modelSampling, Roots and Logging are deprecatedCall a model provider directly; log through OpenTelemetry or stderr
Dynamic client registration is the defaultDeprecated in favor of Client ID Metadata DocumentsSupport metadata documents, and keep registration only for compatibility

Keep these layers apart

A weekend server fits in one file: a decorated function that runs a query. An enterprise server fails in ways that need separate owners, so separate the concerns even if they ship in one process. Each layer in this stack can reject a request without trusting the layer above it. A gateway can rate limit by tool name using the mirrored headers, and the server must still reject any request where a header disagrees with the body, because the specification treats that mismatch as a security problem.

GatewayOrigin checks, rate limits, routing on mirrored headersToken validationIssuer, expiry, audience is this server, scope for this toolTool contractNamed operations, schemas, typed results and failuresQuery and policy checksRead-only, tables in scope, join fan-out, limitsSource executionA separate upstream credential tied to the personAuditPerson, client, tool, arguments, query, outcomeGatewayOrigin checks, rate limits, routing onmirrored headersToken validationIssuer, expiry, audience is this server,scope for this toolTool contractNamed operations, schemas, typed results andfailuresQuery and policy checksRead-only, tables in scope, join fan-out,limitsSource executionA separate upstream credential tied to thepersonAuditPerson, client, tool, arguments, query,outcome
The model only sees the tool contract. Each layer below it can refuse a call without relying on the model or on the layer above.

Choosing what the model is allowed to say

The biggest design decision is the vocabulary of your tools. It sets your review surface, your error rate and how much of the system’s safety rests on checks you must write yourself. Four shapes are common, and they sit on two axes: how much the model composes, and how much a person has already vetted.

Anthropic’s guidance on tools for agents argues against mirroring API endpoints one for one, and favors a few tools for high-impact workflows. Snowflake’s documentation for its managed MCP server makes a related point from the data side: for governed business questions it recommends exposing a single agent, and warns that a direct SQL tool on the same server lets clients bypass that agent’s logic, so direct SQL belongs on a separate server with its own role. Treat that as a general rule. If two tools on one server have different trust levels, the model will sometimes choose the weaker one.

Many teams end up with two tiers: a named-metric tool for anything with an approved definition, and a question tool that runs through query checks for everything else. Most real questions land in the second tier, because a metric catalog rarely covers the long tail. Make sure the result tells the reader which tier answered.

Model picks a named operationModel composes the queryVettedmeaningNo vettedmeaningNamed metric toolSmall surface, limited to what isdefinedQuestion through checksCovers the long tail; needs real querychecksAPI endpoint wrappersEasy to generate, many overlappingchoicesRaw SQL toolFull flexibility, and every safeguardis yours to addVetted meaning · Model picks a named operationNamed metric toolSmall surface, limited to what is definedVetted meaning · Model composes the queryQuestion through checksCovers the long tail; needs real querychecksNo vetted meaning · Model picks a namedoperationAPI endpoint wrappersEasy to generate, many overlapping choicesNo vetted meaning · Model composes the queryRaw SQL toolFull flexibility, and every safeguard isyours to add
Move toward the top left for anything a decision depends on. Raw SQL can have a place, on its own server and with its own role.

Two tokens, always

The authorization rule for a data server fits in a sentence. Your server is an OAuth resource server, it must verify that a presented token was issued specifically for it, and if it calls an upstream API it uses a separate token from the upstream authorization server. The specification forbids passing through the token the client sent. It lists concrete harm from doing so: downstream controls that depend on audience get bypassed, and the audit trail can no longer tell clients apart.

Authorization in the specification stops at your server’s edge, so the next hop is your decision. A shared service account to the warehouse makes the source see one identity and forces you to rebuild every row policy in your own code. Per-person upstream credentials keep the source as the authority. The post on custom MCP auth boundaries covers the trade-offs, including token exchange and the tests to run.

Failures a demo won’t show you

Every one of these is specific to the current protocol and to data, and every one passes a happy-path test.

  • A public cache on a personal listList and resource results now carry ttlMs and cacheScope. A value of public permits any shared gateway to serve the response across authorization contexts. If tools/list varies by scope, or a resource contains anything user-specific, it must be private.
  • Handles treated as permissionA result-set handle returned to one person can reach another through a shared transcript. The specification says possession of a handle is not authentication, and advises binding it server-side to the user derived from the verified token.
  • Cell values read as instructionsQuery results are model input. A support ticket or account note in a returned row can contain text addressed to the model, and OWASP describes this as indirect prompt injection. Label returned data as data, and keep write tools behind human approval.
  • Cancellation ignoredOn Streamable HTTP, closing the response stream cancels the request, and the server should stop work. If your warehouse query keeps running, an impatient user pays for abandoned scans. Wire the disconnect through to the source’s cancel call.
  • A description that changes after approvalInvariant Labs documented tool poisoning, where hidden instructions sit in a description the model reads, and rug pulls, where a description changes after approval. As an operator, review tool definitions as code. As a buyer, ask how a client notices a change.
  • Result size eating the conversationAnthropic reports that Claude Code caps tool responses at 25,000 tokens by default. Return an aggregate with a row count and a way to narrow, and say when the result was cut.
  • Hints read as controlsAnnotations such as readOnlyHint are hints that clients must treat as untrusted unless the server is trusted. Marking a tool read-only enforces nothing; the database role does.

What real data servers expose

Two examples, making those choices differently. These reflect vendor documentation as of this writing, and vendors change them often, so check before you rely on one.

ServerShapeWorth noticing
dbt MCP serverSelf-hosted or remote; the Semantic Layer is supported in bothThe docs describe the server as read-only and say it stores no production data; remote is aimed at consumption such as querying metrics
Snowflake-managed MCP serverAgent, analyst, search, SQL and custom tools; up to 50 per serverConnecting and using a tool need separate privileges; SQL execution defaults to read-only; it does not support dynamic client registration

The build order I’d use

Build identity and the contract before you build breadth. Everything else can be added later, and those two can’t be retrofitted without changing what every client sees.

  • Choose one real questionPick something a named person asks weekly, and write down the correct answer for two people with different access.
  • Choose the transport by deploymentUse stdio for a tool on one developer’s machine, where credentials come from the environment. Use Streamable HTTP for anything shared, and implement protected resource metadata from the first commit.
  • Write the contractKeep it to one or two tools with typed input, an output schema, and distinct results for empty, denied and partial. Our post on the MCP tool contract has a template.
  • Add checks under a read-only roleParse generated SQL before running it, and connect through a role that cannot write in any case.
  • Run the two-person testCompare complete responses for both people, including counts, error text and metadata. Then revoke one person’s access and repeat.
  • Evaluate through a real clientRun multi-step tasks with verifiable outcomes and read the transcripts. Tool selection failures appear there, and unit tests of the function never show them.

How we draw these lines

Nexatron’s hosted MCP server uses Streamable HTTP and OAuth 2.1 authorization code with PKCE. A workspace administrator approves an assistant before people connect it, and every tool call runs as the person who signed in. The public tools include ask, query_metric (approved definitions only), compare_metric_across_sources, list_sources, list_metrics, explain_metric and audit, so both tiers sit on one server, with query_metric restricted to vetted meaning. No action scope comes by default. We publish the tools and scopes in the MCP reference at docs.nexatron.io, so you can test all of this against your own checklist.

Claude is a trademark of Anthropic, PBC.

Bring three business questions

Explore your sources, definitions, and review criteria with our team. Please keep credentials and sensitive records out of the inquiry.