Skip to content
Go back

MCP Servers: Why Read-Only is the Only Way

Published: Aug 26, 2025
Toronto, Canada

I covered MCP servers on my YouTube channel a while back: https://youtu.be/SDqB1uvBI8A

Since then, something interesting happened. Supabase got hacked through their MCP server. Now their official documentation reads like a security advisory:

We recommend read-only mode to prevent the agent from making unintended changes to your database. Note that read-only mode applies only to database operations.

This isn’t a bug. It’s a feature. Let me explain why MCP servers should never have write permissions in the first place.

The Permission Architecture of Trust

Here’s the uncomfortable truth about enterprise adoption of agentic AI: trust is binary. You either trust a system with production data or you don’t. There’s no middle ground.

The biggest barrier to enterprise adoption isn’t capability—it’s trust. And trust doesn’t come from promises or protocols. It comes from constraints.

When 43% of production MCP servers contain critical command injection vulnerabilities, we’re not talking about a security issue. We’re talking about an architectural failure. The problem isn’t that MCP servers can be exploited. The problem is that they have the permissions to cause damage when they are.

MCP Servers as Views: The MVC Perspective

In the Model-View-Controller pattern, the View has one job: presentation. It reads from the Model and displays to the user. It never writes. It never modifies. It observes.

MCP servers are Views in a distributed MVC architecture:

  • Model: Your databases, APIs, and source-of-truth systems
  • View: MCP servers that read and present data to AI agents
  • Controller: The actual business logic that validates and executes changes
// This is what MCP servers should be
class MCPView {
    async getData() { 
        return await database.query("SELECT * FROM users"); 
    }
    // No update(), delete(), or insert() methods
}

// Not this security nightmare
class MCPController {
    async modifyData(input) {
        // 43% chance of command injection here
        return await database.exec(`DELETE FROM users WHERE ${input}`);
    }
}

When you give an MCP server write permissions, you’re not building a View anymore. You’re building a Controller. And Controllers require authentication, authorization, validation, sanitization—all the things that 43% of MCP servers fail at catastrophically.

MCP Servers as Read Models: The Event Sourcing Perspective

Event Sourcing offers an even cleaner mental model. In Event Sourcing, you have:

  1. Command Side: Writes events to the event store
  2. Query Side: Read models (projections) optimized for queries

MCP servers are Query Side components—read models that project your data for AI consumption. They should never emit commands. They should never write events. They are projections, not producers.

// MCP servers should be projections
interface ReadModel {
    project(events: Event[]): QueryableState;
    query(state: QueryableState): ReadOnlyData;
}

// Not command handlers
interface CommandHandler {
    execute(command: Command): Event; // MCP servers should NEVER do this
}

This isn’t about being conservative. It’s about recognizing what MCP servers actually are: specialized read interfaces for AI agents. The moment you add write capabilities, you’re not building an MCP server anymore. You’re building a backdoor.

The Security Apocalypse: 43% Critical Vulnerability Rate

Let me put this in perspective. In my threat intelligence analysis of the MCP ecosystem, I found:

  • 43% of production MCP servers contain command injection vulnerabilities
  • 7.2% contain general security vulnerabilities
  • 5.5% exhibit AI-specific tool poisoning flaws

These aren’t edge cases. The official PostgreSQL and SQLite MCP servers—the reference implementations—had SQL injection vulnerabilities that allowed bypassing read-only restrictions.

Here’s what a typical vulnerable MCP server looks like:

// Real vulnerability found in production
async function executeQuery(userInput) {
    // 43% of MCP servers do something like this
    const query = `SELECT * FROM data WHERE condition = ${userInput}`;
    return await db.query(query); // SQL injection
}

async function runCommand(filename) {
    // Or this monstrosity
    exec(`process_file.sh ${filename}`); // Command injection
}

But here’s the thing: if these servers only had read permissions, the blast radius shrinks dramatically. A SQL injection in a read-only connection can leak data, but it can’t drop tables. A command injection without write permissions is annoying, not apocalyptic.

The Partial Solution Problem

Even if we solve the security issues, there’s a deeper problem: MCP servers provide partial solutions while official APIs provide complete functionality.

Take Salesforce. Their API surface is dynamic, metadata-driven, with effectively infinite endpoints that adapt to each organization’s schema. When you create a custom object called Shipment__c, you instantly get:

  • /services/data/vXX.X/sobjects/Shipment__c/ (create, read)
  • /services/data/vXX.X/sobjects/Shipment__c/{recordId} (read, update, delete)
  • /services/data/vXX.X/sobjects/Shipment__c/describe/ (metadata)

Plus composite operations, bulk APIs, streaming endpoints, and more. The API surface is complete, coherent, and secure.

Now look at the Salesforce MCP server. It provides… a subset. A convenience wrapper. A partial view. It’s the difference between having a city map and having turn-by-turn directions to three specific locations.

MCP servers are nice. Official APIs are complete. When you need nice, use MCP. When you need complete, use the API directly.

Why Read-Only is Non-Negotiable

Supabase learned this the hard way. After their security incident, their documentation now explicitly recommends:

  1. Use read-only mode by default
  2. Never use MCP servers on production databases
  3. Treat MCP servers as untrusted interfaces

This isn’t Supabase being paranoid. This is Supabase being honest about the threat model.

The ‘Lethal Trifecta’ attack on Supabase showed exactly what happens when an MCP server has too many permissions:

  1. Privileged Access: Agent connects with service_role key (bypasses all security)
  2. Untrusted Input: Agent reads a support ticket with hidden prompt injection
  3. Exfiltration Channel: Agent writes stolen secrets to a public table

If the MCP server only had read permissions, step 3 becomes impossible. The attack fails.

The Trust Equation

For enterprises, the trust equation is simple:

Trust = (Capability × Security) / Risk

When MCP servers have write permissions:

  • Capability goes up marginally (you can update records)
  • Security goes down catastrophically (43% vulnerability rate)
  • Risk goes up exponentially (data corruption, breaches, compliance violations)

The math doesn’t work.

When MCP servers are read-only:

  • Capability is sufficient (AI agents excel at analysis and recommendation)
  • Security improves dramatically (read-only can’t corrupt)
  • Risk becomes manageable (worst case: data leakage, not destruction)

Now the math works.

Building Trust Through Constraint

The path forward isn’t to make MCP servers more secure. It’s to make them less capable—deliberately.

# This is the future of enterprise MCP adoption
mcp_server_config:
  permissions: READ_ONLY
  allowed_operations:
    - SELECT
    - DESCRIBE
    - GET
  forbidden_operations:
    - INSERT
    - UPDATE
    - DELETE
    - EXECUTE
    - DROP
  audit_log: REQUIRED
  rate_limiting: ENABLED

This isn’t a limitation. It’s a feature. It’s the feature that makes MCP servers trustworthy enough for enterprise adoption.

Conclusion: The Read-Only Revolution

MCP servers have a choice: be trustworthy or be powerful. They can’t be both.

The ecosystem is choosing trustworthy. Supabase made the call. Salesforce’s Agentforce explicitly adds security layers. Every enterprise that’s serious about adoption is implementing read-only constraints.

The revolution isn’t in making AI agents more powerful. It’s in making them safe enough to use. And safety comes from constraint, not capability.

MCP servers should be views, not controllers. Read models, not command handlers. Observers, not actors.

The 43% vulnerability rate isn’t a bug to be fixed. It’s a design flaw to be eliminated. And the elimination is simple: remove write permissions.

Read-only isn’t a restriction. It’s the only architecture that makes sense.

The enterprises that understand this will adopt. The ones that don’t will get hacked.

Choose wisely.

  Let an Agentic AI Expert Review Your Code

I hope you found this article helpful. If you want to take your agentic AI to the next level, consider booking a consultation or subscribing to premium content.