{"slug": "let-an-ai-agent-query-your-prodution-database-safely-with-read-only-sql-over-mcp", "title": "Let an AI agent query your prodution database safely with read-only SQL over MCP", "summary": "Railyard published a tutorial showing how to give an AI agent read-only SQL access to a production database through its `list_tables` and `query_database` MCP tools, with five guard rails: a write-scoped connection requirement, an admin/owner-only role cap, a read-only transaction that is always rolled back on Postgres (read-only mode on MySQL and ClickHouse), a statement timeout, and a 200-row cap flagged as `truncated` when exceeded. The tutorial also states that SQL text is never written into the MCP audit entry, so queries containing an email address are not copied into the activity log. The setup matters because it lets agents answer plain-language business, data-quality and performance questions without production credentials that can modify data.", "body_md": "[← Blog](https://railyard.run/blog/)Guides & tutorials • 4 min read\n\n# Let an AI agent query your production database safely with read-only SQL over MCP\n\n Follow this on your own server. [Start free, no card needed](https://app.railyard.run/signup) \n\n“How many teams signed up this week?” “Which customers have more than three failed payments?” “Is the orders table actually using that index?” These questions come up every day, and every answer means opening a database console with production credentials. An agent can answer them in plain language, as long as it can’t change anything while it looks.\n\nThis tutorial sets that up on Railyard with the `list_tables` and `query_database` MCP tools, and explains each guard rail so you know exactly what an agent can and can’t do.\n\n## The guard rails, in order\n\nBefore the how-to, here’s why it’s safe. Every query passes through five layers.\n\n1. **Scope.**`query_database` and`list_tables` need a** write** -scoped connection, even though they only read. Your data is the most sensitive thing in the account, so a read-only dashboard connection doesn’t get it. Agents that only check deploys never see rows.\n2. **Role cap.** Only admins and owners can grant`write` . A viewer’s or deployer’s agent can’t query data, whatever scope it asked for. Members limited to some apps can’t reach other apps’ databases.\n3. **Read-only transaction.** On Postgres the query runs inside a`READ ONLY` transaction that is always rolled back. On MySQL it runs in a read-only transaction, and ClickHouse runs it in read-only mode. An`INSERT` ,`UPDATE` ,`DELETE` or DDL statement fails at the database, not just in a filter that could be bypassed.\n4. **Time limit.** A statement timeout stops runaway queries, so an accidental cross join can’t pin your primary.\n5. **Row cap.** At most 200 rows come back, flagged as`truncated` when there were more. Aggregate in SQL and the agent gets a precise answer instead of a sample.\n\nAnd one rule outside the database: the SQL text is never written into the MCP audit entry, so a query that happens to contain an email address doesn’t get copied into your activity log.\n\n## Step 1: Connect with write access\n\nWrite access is for admins and owners. In Claude, go to *Customize → Connectors → Railyard → Connect* and choose **Full (write)** on the consent page. In Claude Code:\n\n```\nclaude mcp add --transport http railyard https://app.railyard.run/api/mcp\n```\n\nThen run `/mcp` → **railyard** → **Authenticate**, and pick write.\n\nIf you’d rather give a specific agent data access without dashboard sign-in, create a `write` API token under *Settings → Access & tokens*, set an expiry, and pass it as a Bearer header. The same rules apply.\n\n## Step 2: Let it learn the schema\n\n```\n> What tables does the shop app's database have?\n```\n\n`list_tables` returns each table with approximate row counts and sizes, largest first. That’s usually enough for the agent to orient itself:\n\n```\norders        1.2M rows   840 MB\norder_items   3.9M rows   610 MB\ncustomers     210K rows    95 MB\npayments      1.3M rows   120 MB\n```\n\nWhen it needs columns, it asks the database the standard way:\n\n```\nSELECT column_name, data_type\nFROM information_schema.columns\nWHERE table_name = 'orders'\nORDER BY ordinal_position;\n```\n\nIf an app has more than one database (say Postgres and ClickHouse), name it with `engine: \"clickhouse\"` or just say “in ClickHouse”.\n\n## Step 3: Ask real questions\n\n**Business questions**\n\n```\n> How many new customers signed up each day this week?\nSELECT date_trunc('day', created_at) AS day, count(*)\nFROM customers\nWHERE created_at >= now() - interval '7 days'\nGROUP BY 1 ORDER BY 1;\n```\n\n**Data-quality questions**, often while [debugging a failed deploy](https://railyard.run/blog/debug-failed-deploy-with-claude/):\n\n```\n> Are there duplicate emails in orders? A unique index migration just failed.\nSELECT email, count(*) FROM orders GROUP BY email HAVING count(*) > 1 ORDER BY 2 DESC LIMIT 50;\n```\n\n**Performance questions**\n\n```\n> Is the orders lookup by customer_id using an index?\nEXPLAIN SELECT * FROM orders WHERE customer_id = 42;\n```\n\nPlain `EXPLAIN` is fine. `EXPLAIN ANALYZE` actually executes the query, so on a big table the time limit may stop it, which is the point.\n\n## Step 4: Write queries that fit the limits\n\nThe 200-row cap shapes good habits, and Claude follows them quickly when you say so once:\n\n- **Aggregate in SQL.**`count` ,`sum` ,`avg` ,`percentile_cont` and`GROUP BY` return a handful of rows that answer the question exactly.\n- **Filter early.** Put a date range on large tables. It’s faster and stays under the time limit.\n- **Order and limit on purpose.** “Top 20 by revenue” beats “all customers”.\n- **Look at the `truncated` flag.** When it’s`true` , the answer is a sample. Ask for an aggregate instead.\n\n## What it can’t do, and what to do instead\n\n- **Change data.** No writes, ever, through this tool. To fix bad data, have Claude draft the`UPDATE` and run it yourself in the dashboard’s database console, or ship it as a migration so it’s reviewed and repeatable.\n- **Restore a backup.**`restore_backup` exists, but it always waits for an admin’s approval in Railyard, because it replaces today’s data.\n- **Export everything.** Bounded results are deliberate. For a full export, download a backup from the dashboard.\n\n## Backups, from the same conversation\n\nThe agent can also see and start backups:\n\n```\n> When was the shop database last backed up? Take one now before I run the migration.\n```\n\n`list_backups` shows recent backups with status and size. `create_backup` starts one immediately (it needs `write`, and it’s additive, so no approval). It’s a good habit before any risky migration, and now it’s one sentence.\n\n## Questions people ask\n\n**Does the agent see my database password?** Not through queries; they run through Railyard, and no connection string is needed or returned. But a write connection can read variable values with `get_env`, and that can include a `DATABASE_URL`. Seal sensitive variables and they stay masked.\n\n**Can a prompt injection make it write?** No. The read-only transaction is enforced by the database itself, not by checking the SQL text. A malicious instruction hidden in a row the agent reads can’t turn a read into a write.\n\n**Which databases are supported?** Postgres, MySQL and ClickHouse that Railyard manages for your app.\n\nThe full tool reference is in the [MCP docs](https://railyard.run/docs/mcp/).\n\n## Why people pick Railyard for this\n\n-  **Your own cloud account.** Deploy to DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, or any Linux server over SSH, billed directly by your provider with no markup.\n-  **One flat price per team.** Free is free forever: one server plus a database server, three apps, no card. Pro is $19 a month for up to five servers and unlimited apps. Team is $99 a month with unlimited servers. No per-app, per-deploy, per-seat or per-gigabyte charges.\n-  **One-click templates.** Deploy open-source apps, like databases, analytics and dev tools, to your own server in one click.\n-  **Managed databases.** Postgres, MySQL and Redis with scheduled, restore-verified backups and point-in-time recovery.\n-  **Push-to-deploy.** Deploy from GitHub, GitLab or the CLI, with automatic HTTPS and zero-downtime releases.", "url": "https://wpnews.pro/news/let-an-ai-agent-query-your-prodution-database-safely-with-read-only-sql-over-mcp", "canonical_source": "https://railyard.run/blog/ai-agent-read-only-sql/", "published_at": "2026-11-13 14:36:34+00:00", "updated_at": "2026-11-13 14:51:33.562713+00:00", "lang": "en", "topics": ["ai-agents", "agent-protocols", "ai-tools", "mlops"], "entities": ["Railyard", "Claude", "Claude Code", "Postgres", "MySQL", "ClickHouse", "MCP"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/let-an-ai-agent-query-your-prodution-database-safely-with-read-only-sql-over-mcp", "markdown": "https://wpnews.pro/news/let-an-ai-agent-query-your-prodution-database-safely-with-read-only-sql-over-mcp.md", "text": "https://wpnews.pro/news/let-an-ai-agent-query-your-prodution-database-safely-with-read-only-sql-over-mcp.txt", "jsonld": "https://wpnews.pro/news/let-an-ai-agent-query-your-prodution-database-safely-with-read-only-sql-over-mcp.jsonld"}}