Architecture Protocol•LLM Integration

MCP in Production: Architecting Narrow Data Channels for LLMs

P

Panneer

Systems Engineer

2026-10-08

8 min read

Most guidance on the Model Context Protocol covers what it is, not how to run it safely once the caller is an autonomous agent rather than a person. The key insight is simple and often missed: in an MCP setup, the real security boundary is the database role backing the server, not the list of tools you register. A tool description that says "read-only" is an application-level label. The database role is the enforcement mechanism.

1The Constraint

AI agents accessing enterprise data through MCP inherit whatever database permissions the server runs under. Most implementations reuse the main application credential "temporarily" to get something working — and the temporary setup becomes the production setup.

Hard Constraints

  • The database role must enforce least privilege — not just the tool allow-list
  • Read and write capabilities must be in separate MCP servers with separate credentials
  • Every tool call must be audited, not just the final output
  • Tool call arguments must be sanitized — the agent's reasoning is not trustworthy input

What would fail:An MCP server running under the application's main database credential gives any agent that calls it effective full database access, regardless of which tools are registered. A prompt injection that convinces the model to make an unexpected tool call can reach any data the credential can access.

2Architectural Decision

Separate read and write MCP servers with separate least-privilege database roles. Read server: SELECT-only role, schema-scoped. Write server: one narrow tool per specific write action. Both servers implement statement timeouts, row limits, and durable audit logging.

Why

The confused deputy problem: an MCP server runs under one identity but is driven by an agent that has no recognized identity of its own. The database role is the real enforcement boundary. Everything the agent can do is bounded by what the database role allows — not by what tools are registered.

Trade-off

More infrastructure: two servers instead of one, two database roles, two credential rotation schedules. The complexity is the price of a real security boundary.

System Layers

Read MCP Server

MCP SDK / Node.js

Exposes structured read tools (list_tables, describe_table). Runs under mcp_reader role — SELECT only, schema-scoped.

Write MCP Server

MCP SDK / Node.js

Exposes one narrow write tool per specific action. Runs under mcp_writer role — INSERT on specific tables only.

Database Layer

PostgreSQL with role-based access control

Enforces permissions at the database level. Application-level checks are defense-in-depth, not the primary boundary.

Audit Log

Durable log store

Records every tool call: name, sanitized arguments, duration, outcome.

3Decision Log

Separate database roles per MCP server, not shared application credential

Why

The confused deputy problem means the real security boundary is the database role, not the tool allow-list. Reusing the application credential is the single most common MCP security mistake.

Trade-off

Two roles to create and rotate. Slightly higher operational overhead.

Structured tools (list_tables, describe_table) instead of a generic run_sql tool

Why

A generic run_sql tool that accepts arbitrary queries is difficult to audit meaningfully and easy to abuse. Structured tools have known shapes — they are easier to validate, easier to log, and harder to misuse via prompt injection.

Trade-off

More tools to register and maintain. Each new query type requires a new tool definition.

Statement timeouts and row limits on every tool handler

Why

A read-only role does not prevent a tool call from scanning a huge table or returning a result set that exceeds the model's context window. Every tool must self-limit regardless of what the caller asked for.

Trade-off

Legitimate large-result queries are rejected and must be paginated or redesigned.

Credentials in environment variables, not command-line arguments

Why

Command-line arguments appear in ps aux output — visible to any process on the host. Environment variables are not exposed in the process list.

Trade-off

Slightly more complex deployment configuration. Well worth the trade-off.

4Integration Boundaries

Systems: MCP SDK (read server) ↔ MCP SDK (write server) ↔ PostgreSQL with least-privilege roles ↔ Agent framework (Claude, GPT, or custom) ↔ Durable audit log

Boundaries: Read and write channels are separate processes with separate credentials. An agent session that only needs read access should not be configured with access to the write server. The AI agent framework is not trusted to enforce this separation — the server separation enforces it.

Auth Model: Dedicated database role per MCP server. Credentials passed via environment variables (never command-line arguments — they appear in ps output). Credentials rotated on a schedule.

5Evaluation

Method: Tool calls tested outside of agent context (unit tests against the MCP server directly). Permission boundary verified: write tool calls against the read server must fail at the database level, not the application level.

Regression Gate: Any tool that allows a write operation through the read server is a P0 security regression. Tool calls that bypass audit logging are a P0 regression.

6Production Ops

Observability: Every tool call logged with: tool name, sanitized arguments, duration, outcome. Log is append-only and treated as an API access log. Anomalous patterns (unusual tool call frequency, unexpected argument shapes) alert the operator.

Human Gate: High-impact write actions (anything that modifies critical data) route through a pending approval queue before the write MCP server executes. [VERIFY — depends on deployment configuration]

Rollback: MCP server processes can be stopped independently without affecting the main application. Database roles can be revoked immediately, cutting off all agent data access.

Verified Technical Signals

Database role scope

SELECT-only on public schema for read server; INSERT on specific tables only for write server

Database-level enforcement, not application-level.

Statement timeout

2000ms per tool call

Prevents long-running queries from consuming the agent's context budget.

Audit coverage

Every tool call logged with name, sanitized args, duration, outcome

Required for incident reconstruction when the caller is autonomous.

7Engineering Takeaways

  • The database role is the real security boundary in MCP setups, not the tool allow-list. Tool descriptions are labels; database permissions are enforcement.
  • Separate read and write MCP servers with separate credentials. A single misbehaving agent session should never reach a write path it was not explicitly given.
  • Structured tools (list_tables, describe_table) are safer than generic query tools. They are easier to validate, log, and reason about.
  • Audit every tool call. When an autonomous agent is the caller, the audit log is the most important forensic tool you have.
  • Pass database credentials via environment variables. Command-line arguments appear in process lists.

8What a CTO Should Verify

Honest questions for due diligence when evaluating similar systems:

Q1. Is the read MCP server's database role strictly SELECT-only at the database level, or is it application-level only?
Q2. Are the read and write MCP servers running as separate processes with separate credentials, or is it one server with conditional logic?
Q3. What is the credential rotation schedule for the MCP server database roles?
Q4. Are tool call logs written to a durable store that the MCP server process itself cannot modify?
Q5. How is prompt injection handled — is there input sanitization on tool call arguments before they reach the database?

Discuss an AI Engineering Engagement

Vasiva builds production AI systems and software products for organizations globally. Evaluate an opportunity with us.

Start a Conversation