MCP Server Architecture for Database Access
A standard protocol replaces custom database connectors with composable servers.

Connecting an AI agent to a database used to mean building a one-off integration for every single data source, and that does not scale. Each connector came with its own maintenance burden, its own ways of failing, and its own ideas about what counted as safe access. Adding a second database, a third API, or a filesystem creates a tangle of custom bridges that nobody can fully audit. The deeper failure was never just the engineering overhead involved in writing all that glue code. In bespoke adapters, access control lives in the application layer, and that layer collapses the moment an agent starts constructing SQL on its own, because there is no practical way to review a query for safety before it runs. The Model Context Protocol answers this by giving every AI application one interface for reaching any compatible data source, replacing the question of "how do we build another connector" with "does this server speak the standard. Claude, Cursor, and VS Code Copilot now speak MCP natively, and ChatGPT supports it through Developer Mode on paid plans. That reach means MCP has moved past being an experimental protocol; it is the integration layer agents are now built to expect.
The three-layer host–client–server model
MCP organizes every connection into three distinct roles: host, client, and server. Treating these as interchangeable is where most implementations go wrong. The host is the application a person actually interacts with, such as Claude Desktop, Cursor, or a custom-built agent. It owns the session, enforces whatever security policy applies, and decides which servers get connected. The host never talks to a server directly. Instead, it builds a dedicated client for each server it connects to, and every tool call runs through that client.
The client itself is the connector that lives inside the host process. Each client holds exactly one connection to exactly one server, handles the protocol negotiation for that connection, and keeps that server isolated from every other server the host might also be talking to. If Claude Desktop is connected to a filesystem server, a database server, and an API server at the same time, it runs three separate clients, one per connection, not one client juggling three jobs. That one-to-one mapping is deliberate: it keeps failures contained and makes error handling far simpler than a shared connection ever could.
The server is the narrow, purpose-built process on the other end. A database server only handles database operations, and a filesystem server only handles file reads and writes. Each server declares what it can do when the connection opens, so the client knows exactly which capabilities to surface to the model. The analogy to USB-C is useful here mainly for the intuition it offers: one standard connector, many different peripherals, no need to know what is on the other end before plugging in. For database deployments, the practical result of this model is that MCP servers behave like composable microservices. Adding a new data capability means connecting another server, not rewriting the core application.
The Three MCP Primitives: Tools, Resources, and Prompts
Every MCP server, regardless of what it connects to, exposes its capabilities through exactly three primitives. Knowing what each one does separates a well-designed database server from a poorly designed one.
Tools are executable functions the model can call, and they are the primitive that carries almost all the weight in database access. Each tool has a name and a JSON Schema describing what inputs it expects, along with optional annotations that signal whether calling it is safe (read-only) or consequential (destructive). For a database server specifically, tools are how SQL queries, schema inspection, and transaction management all reach the agent. Prompts serve a different purpose: they are reusable instruction templates for specific workflows, so a server might ship a "generate monthly report" prompt that pre-structures the context and tool calls a particular analytical task needs.
Connection itself follows a handshake. When a client connects to a server, both sides declare what they support: the client states its own capabilities, the server answers with its primitives and whatever protocol extensions it implements. Only after that exchange completes does the session go active, and both sides are expected to honor what they declared for as long as the session lasts. This negotiation is what lets servers and clients evolve on separate timelines. If a server adds tool support later, it does not break a client that only ever used resources.
How database MCP servers generate specialized tools
A well-built database MCP server does not hand the model one generic "run SQL" tool and call it done. It generates a structured set of purpose-specific tools for each connected database, and that structure makes per-connection scoping and guardrails enforceable. The DB MCP Server, built on the FreePeak/cortex framework, illustrates this directly: it automatically generates specialized tools for every connected database, covering querying, executing statements, managing transactions, inspecting schemas, and analyzing performance, with each tool named using the database identifier as a suffix. So the agent always knows, unambiguously, which database a given call will touch.
That per-database naming is not a cosmetic convenience. Because of this, you can apply different access policies to different databases inside a single server instance, so a production database and a staging database on the same server can carry entirely different permission sets.
This generates a real trade-off between two deployment modes. Unified mode exposes one set of tools, and each accepts a database parameter as an argument, so the number of tools stays flat no matter how many databases are connected. Per-database mode, on the other hand, builds a full, separate tool set for every database in the pool. The practical cost appears in how much context the model has to carry: per-database mode grows with every database added, while unified mode stays flat regardless of how many connections exist. Neither mode is universally correct; the choice depends on how many databases an agent needs simultaneous access to and how much token budget that access can reasonably consume. What matters more than the mode chosen is the underlying lesson: tool architecture structures access scope, schema visibility, and write permissions before a query ever reaches the database engine.
Choosing between the two transport options for a database deployment
MCP supports two transport mechanisms, and the right one is determined by where the server physically runs relative to the client, not by personal preference.
STDIO runs the server as a local subprocess and passes JSON-RPC messages through standard input and output. It carries no network overhead, it needs no authentication configuration because the operating system already provides process-level isolation, and it starts up instantly. That makes it the right fit for local tools and local database queries where the client and server share the same machine. Its limitation is just as direct: it cannot scale past a single client connection, and the server's entire lifecycle is tied to the host process running it.
Streamable HTTP sends JSON-RPC messages over ordinary HTTP POST requests, and it can also stream from the server when operations run long. It supports remote servers, cloud deployments, and multiple clients connecting at once, and it works with the authentication methods teams already use elsewhere: OAuth, API keys, bearer tokens. Because it rides on standard HTTP infrastructure, it works with load balancers, CDNs, and proxies without any special configuration layered on top. If you are building from older tutorials, know that an earlier transport, streaming over persistent connections, is deprecated now in favor of Streamable HTTP, so building that older pattern means working against guidance the specification has already moved past.
The transport layer sits below the protocol layer, so if you switch from STDIO to Streamable HTTP, you change nothing in tool definitions or business logic. The JSON-RPC messages themselves stay identical regardless of which transport carries them. For a database deployment, you need Streamable HTTP when you want the horizontal scaling production analytics workloads need and the authentication delegation security policy requires, but STDIO still works for local development and testing.
Stateless-by-default deployment for database MCP servers
The current MCP specification revision, 2026-07-28, makes stateless operation the default deployment pattern, and that one change resolves scaling and failure-mode problems that stateful designs struggled with for years. Under the earlier model, a persistent session established at initialization carried the connection's state forward. Under the current revision, every request carries its own protocol version, client identity, and client capabilities in its metadata, eliminating the need for any long-lived session. Earlier stateful architectures needed persistent connections, and that complicated scaling and introduced single points of failure. Now the design favors ephemeral sessions, and it routes each request on its own terms.
For database deployments, this changes what a production rollout actually looks like. A stateless server, where each HTTP request is self-contained, scales horizontally behind any ordinary load balancer, with no sticky sessions, no session store, and no coordination overhead needed between instances. Tool-only servers that execute discrete operations, such as a single database query, fit this model naturally, because each query was already a self-contained unit of work before statelessness became the spec's default. Production environments route traffic through load balancers configured with health checks, while anything that needs to persist, conversation history, execution context, lives in a dedicated external database or cache layer outside the server process. That separation also simplifies compliance audits, since sensitive data never has to sit inside the execution environment to begin with.
The MCP server, in this arrangement, functions purely as a translation layer. It converts structured requests into database calls and returns normalized responses, and it owns no state of its own. That separation lets a team upgrade a storage engine or migrate a region without touching the protocol implementation, because the protocol never depended on where the state lived.
The three-layer defense model that keeps agents from touching data they should not reach
A single permission flag is not sufficient defense for a database an agent can query. The failure modes that occur in production require enforcement at three independent layers at once, because any one layer, left on its own, can be bypassed or misconfigured without the others catching it.
OWASP's agentic top-10 documents this threat, naming LLM01, Prompt Injection, as unauthorized data exfiltration carried out through malicious instructions hidden inside external documents the agent reads, and LLM03, Excessive Agency, as unintended or harmful actions that result from giving a model more functionality, permissions, or autonomy than the task in front of it actually requires.
Defense in depth for a database MCP server means enforcing read-only access at three layers that do not depend on each other. At the application level, a classifier strips comments and string literals from a statement before it executes, so it catches attempts to smuggle a write past a surface-level check. At the engine level, session defaults enforce read-only behavior inside the database itself, through settings such as PostgreSQL's default_transaction_read_only=on or MySQL's transaction_read_only=1. At the credential level, the database user the server connects as carries only the least privilege that role needs, so even a bypass of the first two layers runs into a wall the database itself enforces. Setting read_only: true on a given database connection blocks INSERT, UPDATE, DELETE, DDL statements, data-modifying CTEs, and stacked writes, across both query and execute tools, with rejection happening at the engine. An audit log appends one JSON record for every statement, executed or rejected, capturing timestamp, operation type, database, the statement itself (capped in length), duration, and any error, including attempts that were rejected against a read-only database.
Least privilege here is not just good practice that security teams recommend. A data minimization principle found in privacy regulation turns this into a direct legal obligation wherever an agent touches personal data, and the same logic carries over to data governed by other regulated-data frameworks. For any organization connecting an agent to a production database, this three-layer defense is what makes the architecture covered here, the host-client-server model, the primitives a server exposes, the transport it runs on, and its statelessness, safe to deploy.


