Skip to content

Commit f25a192

Browse files
authored
docs: recommend Postgres + Valkey MCP servers for agent DX (#113)
Adds a Reference page and an opt-in .mcp.json.example wiring read-only Postgres (crystaldba/postgres-mcp, --access-mode=restricted) and the official Redis MCP (redis-mcp-server) at the local dev stack, so a coding agent can read the live schema, rows, cache keys, and queue state instead of guessing. The dev overlay already publishes 5432/6379 on the host (prod never does), so the config works after `dev.sh up` + `cp .mcp.json.example .mcp.json`. Both servers run via uvx (the postgres one pinned to --python 3.13 to dodge a missing pglast wheel on newer Pythons). .mcp.json is gitignored; the example is the source. Fills the ROADMAP Workstream-D 'AI MCP recommendation' item. Validated: both servers install + run via the exact commands; the example's connection strings authenticate against the running dev Postgres and Valkey; docs build:ci clean.
1 parent 23036fc commit f25a192

5 files changed

Lines changed: 151 additions & 1 deletion

File tree

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,8 @@ coverage/
1515
.pnpm-store/
1616
.bun-install/
1717
.audit/
18-
ROADMAP.md
18+
ROADMAP.md
19+
20+
# Local MCP client config — copied from .mcp.json.example and may carry
21+
# machine-specific paths or credentials. The committed example is the source.
22+
.mcp.json

.mcp.json.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"mcpServers": {
3+
"postgres": {
4+
"command": "uvx",
5+
"args": ["--python", "3.13", "postgres-mcp", "--access-mode=restricted"],
6+
"env": {
7+
"DATABASE_URI": "postgresql://app:app_dev_password@localhost:5432/app"
8+
}
9+
},
10+
"valkey": {
11+
"command": "uvx",
12+
"args": [
13+
"--from",
14+
"redis-mcp-server@latest",
15+
"redis-mcp-server",
16+
"--url",
17+
"redis://localhost:6379/0"
18+
]
19+
}
20+
}
21+
}

apps/docs/astro.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ export default defineConfig({
569569
items: [
570570
{ label: "Environment variables", link: "/reference/env-vars/" },
571571
{ label: "Commands cheatsheet", link: "/reference/commands/" },
572+
{ label: "MCP servers for agents", link: "/reference/mcp-servers/" },
572573
{
573574
label: "Scripts & tooling",
574575
link: "/reference/scripts-tooling/",

apps/docs/src/content/docs/before-you-build.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,5 +73,6 @@ Open [Quickstart](/quickstart/) and start building. With a spec that holds up to
7373
## Related
7474

7575
- [Spec loop](/skills/spec-loop/), the spec-driven workflow for working with agents.
76+
- [MCP servers for agents](/reference/mcp-servers/), so the agent reads your live database and cache instead of guessing.
7677
- [Resources](/resources/), tools and reading that pair well with building a product.
7778
- [Quickstart](/quickstart/), where the code starts once the spec is ready.
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
title: MCP servers for agents
3+
description: Point Postgres and Valkey MCP servers at the local dev stack so your coding agent can read the live schema, rows, cache keys, and queue state instead of guessing.
4+
---
5+
6+
import { Aside } from "@astrojs/starlight/components";
7+
8+
A coding agent reads your code fine. It's blind to **runtime state**: the live
9+
database schema, the actual rows, the cache keys and their TTLs, the BullMQ
10+
queue backlog. So it guesses — and guesses drift from reality.
11+
12+
[Model Context Protocol](https://modelcontextprotocol.io) servers close that
13+
gap. The dev stack already publishes Postgres and Valkey on the host (so you can
14+
`psql` and `valkey-cli` them), which means an MCP server reaches them with no
15+
extra setup. Same idea as observability: the runtime context is pre-wired —
16+
[for humans](/topics/observability/) in Grafana, and here for agents.
17+
18+
This is dev-only and opt-in. Two servers, both read-leaning:
19+
20+
| Server | Reach for it when… | What it gives the agent |
21+
| ------ | ------------------ | ----------------------- |
22+
| **Postgres** (read-only) | writing a Drizzle migration, debugging a query, checking a constraint | live schema, indexes, row samples, `EXPLAIN` plans, index/health analysis |
23+
| **Valkey / Redis** | a cache or TTL bug, a stuck job, inspecting rate-limit keys | keys, TTLs, `bull:*` queue state, `INFO` |
24+
25+
## Setup
26+
27+
1. Install [`uv`](https://docs.astral.sh/uv/) (provides `uvx`; the servers run
28+
through it — no global installs):
29+
30+
```bash
31+
brew install uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh
32+
```
33+
34+
2. Bring the dev stack up — this publishes Postgres on `5432` and Valkey on
35+
`6379` (the `development-labels` overlay; production never exposes them):
36+
37+
```bash
38+
cd infra/compose/compose && ./dev.sh up -d
39+
```
40+
41+
3. Copy the committed example into place. Your real config is gitignored, so
42+
any local edits stay local:
43+
44+
```bash
45+
cp .mcp.json.example .mcp.json
46+
```
47+
48+
4. Restart your agent (Claude Code, Cursor, any MCP client) and approve the two
49+
servers when prompted.
50+
51+
That's it — ask the agent to "list the tables" or "show the cache keys" and it
52+
queries the running stack.
53+
54+
## What ships in `.mcp.json.example`
55+
56+
```json
57+
{
58+
"mcpServers": {
59+
"postgres": {
60+
"command": "uvx",
61+
"args": ["--python", "3.13", "postgres-mcp", "--access-mode=restricted"],
62+
"env": {
63+
"DATABASE_URI": "postgresql://app:app_dev_password@localhost:5432/app"
64+
}
65+
},
66+
"valkey": {
67+
"command": "uvx",
68+
"args": [
69+
"--from",
70+
"redis-mcp-server@latest",
71+
"redis-mcp-server",
72+
"--url",
73+
"redis://localhost:6379/0"
74+
]
75+
}
76+
}
77+
}
78+
```
79+
80+
- **Postgres** uses [`postgres-mcp`](https://github.com/crystaldba/postgres-mcp)
81+
(Postgres MCP Pro). `--access-mode=restricted` is its **read-only** mode:
82+
read queries and `EXPLAIN` only, wrapped in read-only transactions. The
83+
`--python 3.13` pin sidesteps a missing wheel on newer Python builds.
84+
- **Valkey** uses the official
85+
[`redis-mcp-server`](https://github.com/redis/mcp-redis) over the Redis wire
86+
protocol (Valkey is a drop-in). The default values match the dev stack:
87+
Postgres `app` / `app_dev_password` / db `app`, and Valkey DB `0` (the API's;
88+
DB `1` is GlitchTip's).
89+
90+
<Aside type="caution" title="Dev only — never point these at production">
91+
The credentials above are the throwaway local defaults, and the `5432`/`6379`
92+
host ports only exist under the dev overlay. Production keeps data services on
93+
the internal Docker network with real secrets. Never put a production
94+
connection string in `.mcp.json` — an agent with your prod DB in context is a
95+
breach waiting to happen.
96+
</Aside>
97+
98+
<Aside type="tip" title="Read-only by default">
99+
`--access-mode=restricted` keeps the agent from mutating your dev database — it
100+
can read and explain, not write. For defense-in-depth you can also hand it a
101+
dedicated read-only role instead of `app`:
102+
103+
```sql
104+
CREATE ROLE mcp_readonly LOGIN PASSWORD 'mcp_readonly';
105+
GRANT CONNECT ON DATABASE app TO mcp_readonly;
106+
GRANT USAGE ON SCHEMA public TO mcp_readonly;
107+
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_readonly;
108+
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO mcp_readonly;
109+
```
110+
111+
Then swap the `DATABASE_URI` user to `mcp_readonly`. The Valkey server has no
112+
restricted mode; treat it as read/write and don't point it at anything you'd
113+
mind it changing.
114+
</Aside>
115+
116+
## Why this fits the stack
117+
118+
BoringStack's pitch is that [agents and humans get the same
119+
contract](/architecture/lint-as-contract/) — lint enforces the architecture for
120+
both. MCP extends that to runtime: the agent inspects the same database and
121+
cache you do, so its edits are grounded in what's actually there. Pair it with
122+
the [spec loop](/skills/spec-loop/) and the agent plans against real schema
123+
instead of an imagined one.

0 commit comments

Comments
 (0)