← BlogGuides & tutorials • 4 min read
Follow this on your own server. Start free, no card needed
“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.
This 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.
The guard rails, in order #
Before the how-to, here’s why it’s safe. Every query passes through five layers.
- Scope.
query_databaseandlist_tablesneed 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. - 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. - Read-only transaction. On Postgres the query runs inside a
READ ONLYtransaction that is always rolled back. On MySQL it runs in a read-only transaction, and ClickHouse runs it in read-only mode. AnINSERT,UPDATE,DELETEor DDL statement fails at the database, not just in a filter that could be bypassed. - Time limit. A statement timeout stops runaway queries, so an accidental cross join can’t pin your primary.
- Row cap. At most 200 rows come back, flagged as
truncatedwhen there were more. Aggregate in SQL and the agent gets a precise answer instead of a sample.
And 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.
Step 1: Connect with write access #
Write 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:
claude mcp add --transport http railyard https://app.railyard.run/api/mcp
Then run /mcp → railyard → Authenticate, and pick write.
If 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.
Step 2: Let it learn the schema #
> What tables does the shop app's database have?
list_tables returns each table with approximate row counts and sizes, largest first. That’s usually enough for the agent to orient itself:
orders 1.2M rows 840 MB
order_items 3.9M rows 610 MB
customers 210K rows 95 MB
payments 1.3M rows 120 MB
When it needs columns, it asks the database the standard way:
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_name = 'orders'
ORDER BY ordinal_position;
If an app has more than one database (say Postgres and ClickHouse), name it with engine: "clickhouse" or just say “in ClickHouse”.
Step 3: Ask real questions #
Business questions
> How many new customers signed up each day this week?
SELECT date_trunc('day', created_at) AS day, count(*)
FROM customers
WHERE created_at >= now() - interval '7 days'
GROUP BY 1 ORDER BY 1;
Data-quality questions, often while debugging a failed deploy:
> Are there duplicate emails in orders? A unique index migration just failed.
SELECT email, count(*) FROM orders GROUP BY email HAVING count(*) > 1 ORDER BY 2 DESC LIMIT 50;
Performance questions
> Is the orders lookup by customer_id using an index?
EXPLAIN SELECT * FROM orders WHERE customer_id = 42;
Plain EXPLAIN is fine. EXPLAIN ANALYZE actually executes the query, so on a big table the time limit may stop it, which is the point.
Step 4: Write queries that fit the limits #
The 200-row cap shapes good habits, and Claude follows them quickly when you say so once:
- Aggregate in SQL.
count,sum,avg,percentile_contandGROUP BYreturn a handful of rows that answer the question exactly. - Filter early. Put a date range on large tables. It’s faster and stays under the time limit.
- Order and limit on purpose. “Top 20 by revenue” beats “all customers”.
- Look at the
truncatedflag. When it’strue, the answer is a sample. Ask for an aggregate instead.
What it can’t do, and what to do instead #
- Change data. No writes, ever, through this tool. To fix bad data, have Claude draft the
UPDATEand run it yourself in the dashboard’s database console, or ship it as a migration so it’s reviewed and repeatable. - Restore a backup.
restore_backupexists, but it always waits for an admin’s approval in Railyard, because it replaces today’s data. - Export everything. Bounded results are deliberate. For a full export, download a backup from the dashboard.
Backups, from the same conversation #
The agent can also see and start backups:
> When was the shop database last backed up? Take one now before I run the migration.
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.
Questions people ask #
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.
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.
Which databases are supported? Postgres, MySQL and ClickHouse that Railyard manages for your app.
The full tool reference is in the MCP docs.
Why people pick Railyard for this #
- 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.
- 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.
- One-click templates. Deploy open-source apps, like databases, analytics and dev tools, to your own server in one click.
- Managed databases. Postgres, MySQL and Redis with scheduled, restore-verified backups and point-in-time recovery.
- Push-to-deploy. Deploy from GitHub, GitLab or the CLI, with automatic HTTPS and zero-downtime releases.