pgops-mcp 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pgops_mcp-0.1.1/LICENSE +21 -0
- pgops_mcp-0.1.1/PKG-INFO +233 -0
- pgops_mcp-0.1.1/README.md +193 -0
- pgops_mcp-0.1.1/pyproject.toml +103 -0
- pgops_mcp-0.1.1/pyproject.toml.orig +110 -0
- pgops_mcp-0.1.1/src/pgops/__init__.py +4 -0
- pgops_mcp-0.1.1/src/pgops/__main__.py +702 -0
- pgops_mcp-0.1.1/src/pgops/approval.py +126 -0
- pgops_mcp-0.1.1/src/pgops/audit.py +153 -0
- pgops_mcp-0.1.1/src/pgops/auth.py +193 -0
- pgops_mcp-0.1.1/src/pgops/classifier.py +140 -0
- pgops_mcp-0.1.1/src/pgops/completions.py +118 -0
- pgops_mcp-0.1.1/src/pgops/config.py +159 -0
- pgops_mcp-0.1.1/src/pgops/connections.py +149 -0
- pgops_mcp-0.1.1/src/pgops/errors.py +113 -0
- pgops_mcp-0.1.1/src/pgops/function_safety.py +160 -0
- pgops_mcp-0.1.1/src/pgops/guardrails.py +178 -0
- pgops_mcp-0.1.1/src/pgops/middleware.py +216 -0
- pgops_mcp-0.1.1/src/pgops/migrations/__init__.py +0 -0
- pgops_mcp-0.1.1/src/pgops/migrations/diff.py +419 -0
- pgops_mcp-0.1.1/src/pgops/migrations/ledger.py +245 -0
- pgops_mcp-0.1.1/src/pgops/migrations/lock_analysis.py +425 -0
- pgops_mcp-0.1.1/src/pgops/migrations/rollback.py +418 -0
- pgops_mcp-0.1.1/src/pgops/observability.py +296 -0
- pgops_mcp-0.1.1/src/pgops/plan_analysis.py +389 -0
- pgops_mcp-0.1.1/src/pgops/prompts.py +129 -0
- pgops_mcp-0.1.1/src/pgops/py.typed +0 -0
- pgops_mcp-0.1.1/src/pgops/replay.py +269 -0
- pgops_mcp-0.1.1/src/pgops/resources.py +137 -0
- pgops_mcp-0.1.1/src/pgops/sampling.py +155 -0
- pgops_mcp-0.1.1/src/pgops/serialize.py +51 -0
- pgops_mcp-0.1.1/src/pgops/timing.py +45 -0
- pgops_mcp-0.1.1/src/pgops/tools/__init__.py +0 -0
- pgops_mcp-0.1.1/src/pgops/tools/advisor.py +277 -0
- pgops_mcp-0.1.1/src/pgops/tools/environment.py +588 -0
- pgops_mcp-0.1.1/src/pgops/tools/explain.py +217 -0
- pgops_mcp-0.1.1/src/pgops/tools/health.py +228 -0
- pgops_mcp-0.1.1/src/pgops/tools/migrations.py +475 -0
- pgops_mcp-0.1.1/src/pgops/tools/query.py +90 -0
- pgops_mcp-0.1.1/src/pgops/tools/schema.py +182 -0
- pgops_mcp-0.1.1/src/pgops/tools/write.py +223 -0
pgops_mcp-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arsh Zakee Chowhan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pgops_mcp-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pgops-mcp
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Production-grade MCP server: safe, audited PostgreSQL operations + Docker environment awareness for AI agents.
|
|
5
|
+
Keywords: mcp,model-context-protocol,postgres,postgresql,database,docker
|
|
6
|
+
Author: Arsh Zakee Chowhan
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Database
|
|
17
|
+
Classifier: Topic :: Database :: Database Engines/Servers
|
|
18
|
+
Classifier: Topic :: System :: Systems Administration
|
|
19
|
+
Requires-Dist: fastmcp>=2.3
|
|
20
|
+
Requires-Dist: asyncpg>=0.29
|
|
21
|
+
Requires-Dist: sqlparse>=0.5
|
|
22
|
+
Requires-Dist: docker>=7.0
|
|
23
|
+
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: testcontainers[postgres]>=4.0 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: hypothesis>=6.165 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: jsonschema>=4.0 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.5 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: mypy>=1.10 ; extra == 'dev'
|
|
30
|
+
Requires-Dist: opentelemetry-sdk>=1.25 ; extra == 'otel'
|
|
31
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-grpc>=1.25 ; extra == 'otel'
|
|
32
|
+
Requires-Dist: aiohttp>=3.9 ; extra == 'otel'
|
|
33
|
+
Requires-Python: >=3.12
|
|
34
|
+
Project-URL: Homepage, https://github.com/arzharch/pgops-mcp
|
|
35
|
+
Project-URL: Repository, https://github.com/arzharch/pgops-mcp
|
|
36
|
+
Project-URL: Issues, https://github.com/arzharch/pgops-mcp/issues
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Provides-Extra: otel
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# pgops-mcp
|
|
42
|
+
|
|
43
|
+
<!-- mcp-name: io.github.arzharch/pgops-mcp -->
|
|
44
|
+
|
|
45
|
+
> A production-grade MCP server that gives AI agents safe, audited, expert-level control
|
|
46
|
+
> over a real PostgreSQL database and the Docker stack around it — no shell commands,
|
|
47
|
+
> no Python scripts, just tools.
|
|
48
|
+
|
|
49
|
+
## Why
|
|
50
|
+
|
|
51
|
+
Existing Postgres MCP servers are thin query wrappers: introspect + SELECT. None handle
|
|
52
|
+
migrations with lock-impact analysis, none diagnose performance from `EXPLAIN` +
|
|
53
|
+
`pg_stat_statements`, none understand the containerized environment the database lives in.
|
|
54
|
+
Agents operating databases today are flying blind and unsafe.
|
|
55
|
+
|
|
56
|
+
`pgops-mcp` is the operations brain: **schema intelligence → guarded queries → migration
|
|
57
|
+
engine → performance diagnosis → environment awareness**, with a safety architecture that
|
|
58
|
+
makes every action classifiable, confirmable, and auditable.
|
|
59
|
+
|
|
60
|
+
## Tool surface
|
|
61
|
+
|
|
62
|
+
| Group | Tools |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Schema | `schema.inspect` |
|
|
65
|
+
| Queries | `query.read`, `query.write` (guarded), `query.explain` (parsed plan + verdict) |
|
|
66
|
+
| Performance | `index.advise`, `db.health` |
|
|
67
|
+
| Migrations | `migration.plan` (dry-run + lock analysis), `migration.describe` (plain English), `migration.apply`, `migration.rollback`, `migration.history` |
|
|
68
|
+
| Environment | `env.topology`, `env.correlate`, `container.logs`, `container.stats` |
|
|
69
|
+
| Gated | `container.restart`*, `container.exec`* |
|
|
70
|
+
|
|
71
|
+
\* Not registered at all unless the server runs with `--approval-mode`, and even then
|
|
72
|
+
each call needs a confirmation token. `container.exec` additionally enforces a read-only
|
|
73
|
+
diagnostic command allowlist — it does not offer a shell. The Docker socket is
|
|
74
|
+
root-equivalent on the host, so the default is read-only access.
|
|
75
|
+
|
|
76
|
+
## Safety model (the core differentiator)
|
|
77
|
+
|
|
78
|
+
- Separate read-only / read-write connection roles; tools bind to the right role
|
|
79
|
+
- Statement classification before execution — unbounded `DELETE`/`UPDATE` blocked
|
|
80
|
+
- Destructive actions require explicit confirmation tokens
|
|
81
|
+
- Every executed statement lands in an append-only audit log with timing and verdict
|
|
82
|
+
- Runaway-query cancellation with timeout tiers
|
|
83
|
+
|
|
84
|
+
## MCP surface
|
|
85
|
+
|
|
86
|
+
| Primitive | What's here |
|
|
87
|
+
|---|---|
|
|
88
|
+
| **Tools** | 17 — schema, query, explain, advise, migrate, environment |
|
|
89
|
+
| **Resources** | `pgops://schema`, `schema/summary`, `schema/{table}`, `health`, `migrations`, `audit/recent`, `config` |
|
|
90
|
+
| **Prompts** | `diagnose-slow-query`, `plan-safe-migration`, `incident-triage`, `review-index-health`, `explain-safety-model` |
|
|
91
|
+
| **Elicitation** | Dangerous actions ask the **user** directly, not via the agent; confirmation tokens are the fallback |
|
|
92
|
+
| **Sampling** | `migration.describe` turns English into a plan using *your* model — this server ships no API key |
|
|
93
|
+
| **Completions** | Table-name autocomplete for `pgops://schema/{table}` |
|
|
94
|
+
| **Progress / logging** | Best-effort notifications during long operations |
|
|
95
|
+
|
|
96
|
+
## Remote access & agent tokens
|
|
97
|
+
|
|
98
|
+
stdio needs no auth — the server is a subprocess your client spawns, with no open port.
|
|
99
|
+
HTTP does, so it refuses to start without a key:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pgops-mcp keygen # RS256 keypair
|
|
103
|
+
pgops-mcp issue-token --subject my-agent # read-only by default
|
|
104
|
+
pgops-mcp issue-token --subject deploy-bot --scope pgops:read --scope pgops:write
|
|
105
|
+
pgops-mcp scopes # which scope each tool needs
|
|
106
|
+
|
|
107
|
+
pgops-mcp --transport http --public-key ~/.pgops/keys/pgops_public.pem
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The server holds only the **public** key, so it can verify tokens but never mint them.
|
|
111
|
+
Scopes (`pgops:read` / `pgops:write` / `pgops:admin`) map to the same danger tiers as the
|
|
112
|
+
guardrails, and a tool with no scope entry requires `admin` — deny by default. Binds
|
|
113
|
+
loopback unless you say otherwise.
|
|
114
|
+
|
|
115
|
+
## Install
|
|
116
|
+
|
|
117
|
+
`pgops-mcp` is an MCP server, not a Python library — nothing in it is meant to be
|
|
118
|
+
imported, and `pgops.*` carries no API-stability promise. You install it the way you
|
|
119
|
+
install any MCP server: point your client at it.
|
|
120
|
+
|
|
121
|
+
**Claude Desktop / Cursor / VS Code:**
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"mcpServers": {
|
|
126
|
+
"pgops": {
|
|
127
|
+
"command": "uvx",
|
|
128
|
+
"args": ["pgops-mcp"],
|
|
129
|
+
"env": { "PGOPS_DSN": "postgresql://user:pass@localhost:5432/mydb" }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`uvx` fetches and runs it in a throwaway environment — nothing to install first, and
|
|
136
|
+
nothing added to your own project's dependencies.
|
|
137
|
+
|
|
138
|
+
**Or run the container**, if you would rather not put a Python toolchain on the machine
|
|
139
|
+
that talks to your database:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"mcpServers": {
|
|
144
|
+
"pgops": {
|
|
145
|
+
"command": "docker",
|
|
146
|
+
"args": [
|
|
147
|
+
"run", "-i", "--rm",
|
|
148
|
+
"-e", "PGOPS_DSN",
|
|
149
|
+
"-v", "pgops-audit:/var/lib/pgops",
|
|
150
|
+
"ghcr.io/arzharch/pgops-mcp:latest"
|
|
151
|
+
],
|
|
152
|
+
"env": { "PGOPS_DSN": "postgresql://user:pass@host.docker.internal:5432/mydb" }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Two things the container changes: mount a volume at `/var/lib/pgops` or the audit log
|
|
159
|
+
dies with the container, and `localhost` inside a container is the container itself —
|
|
160
|
+
use `host.docker.internal` or a compose service name.
|
|
161
|
+
|
|
162
|
+
**Check the connection before wiring a client to it:**
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
uvx pgops-mcp --selfcheck --dsn "postgresql://user:pass@localhost:5432/mydb"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Both paths install the same server and are listed together in the
|
|
169
|
+
[MCP Registry](https://registry.modelcontextprotocol.io) entry — they fail for different
|
|
170
|
+
people. `uvx` needs nothing preinstalled but assumes the host may run Python; the
|
|
171
|
+
container assumes only Docker.
|
|
172
|
+
|
|
173
|
+
See **[SETUP.md](SETUP.md)** for configuration, HTTP transport, agent tokens and
|
|
174
|
+
troubleshooting, and [CONTRIBUTING.md](CONTRIBUTING.md) to run it from a source checkout.
|
|
175
|
+
|
|
176
|
+
## Docs
|
|
177
|
+
|
|
178
|
+
**For users:**
|
|
179
|
+
|
|
180
|
+
- **[docs/API.md](docs/API.md)** — full tool catalog: parameters, returns, error codes, scopes
|
|
181
|
+
- [docs/BENCHMARKS.md](docs/BENCHMARKS.md) — what the benchmarks measure and what they are compared against
|
|
182
|
+
- [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) — first 15 minutes, guided tour
|
|
183
|
+
- [SETUP.md](SETUP.md) — full setup guide: config, clients, HTTP auth, observability, troubleshooting
|
|
184
|
+
- [.env.example](.env.example) — every environment variable, documented
|
|
185
|
+
|
|
186
|
+
**Internal (design & process):**
|
|
187
|
+
|
|
188
|
+
- [internal/PRD.md](internal/PRD.md), [internal/SPEC.md](internal/SPEC.md) — product requirements & phased spec
|
|
189
|
+
- [internal/ARCHITECTURE.md](internal/ARCHITECTURE.md), [internal/SYSTEM_DESIGN.md](internal/SYSTEM_DESIGN.md) — design + rendered diagrams
|
|
190
|
+
- [internal/adr/](internal/adr/) — architecture decision records
|
|
191
|
+
- [internal/flow.md](internal/flow.md) — living progress log
|
|
192
|
+
- [internal/interview_prep.md](internal/interview_prep.md) — Q&A companion
|
|
193
|
+
|
|
194
|
+
## Status
|
|
195
|
+
|
|
196
|
+
**Phases 0–6f complete** (436 tests, every guardrail, verdict and lock-impact rule proven
|
|
197
|
+
against real Postgres via testcontainers — no mocks — plus end-to-end suites driving the
|
|
198
|
+
server as a real MCP subprocess over stdio and as an authenticated HTTP server, verified
|
|
199
|
+
through the MCP Inspector).
|
|
200
|
+
|
|
201
|
+
| Phase | State | Tools |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| 0 · Bootstrap | ✅ | seeded dev stack (1.2M-row `orders`), CI, lint/type gates |
|
|
204
|
+
| 1 · Connection core + read path | ✅ | `schema.inspect`, `query.read`, `db.health` |
|
|
205
|
+
| 2 · Write path + safety | ✅ | `query.write`, guardrails, confirmation tokens, audit log |
|
|
206
|
+
| 3 · Performance brain | ✅ | `query.explain` (plan verdicts), `index.advise` |
|
|
207
|
+
| 4 · Migration engine | ✅ | `migration.plan` (lock analysis + dry run), `apply`, `rollback`, `history` |
|
|
208
|
+
| 5 · Docker layer | ✅ | `env.topology`, `env.correlate`, `container.logs/stats/restart/exec` |
|
|
209
|
+
| 6a · MCP completeness | ✅ | resources, prompts, elicitation, sampling, completions, progress |
|
|
210
|
+
| 6b · Remote + auth | ✅ | HTTP transport, JWT, per-tool scope enforcement, keygen CLI |
|
|
211
|
+
| 6c · Observability | ✅ | OTel spans/metrics, liveness/readiness endpoints (all optional) |
|
|
212
|
+
| 6d · Adversarial testing | ✅ | red-team suite, property-based tests, live evals in CI |
|
|
213
|
+
| 6e · Forensics | ✅ | `pgops-mcp replay` — the audit log as an executable record |
|
|
214
|
+
| 6f · Distribution | ✅ | PyPI package, container image, `server.json` for the MCP Registry |
|
|
215
|
+
|
|
216
|
+
Sample of what `migration.plan` returns for a type change on the 1.2M-row `orders`:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
ALTER TABLE "orders" ALTER COLUMN "total_cents" TYPE bigint
|
|
220
|
+
op=table_rewrite risk=high estimate=4800ms confidence=medium
|
|
221
|
+
why: rewrites every row and rebuilds every index, holding AccessExclusiveLock
|
|
222
|
+
SAFER: add a new column of the target type, backfill in batches, sync with a
|
|
223
|
+
trigger, swap the names, then drop the old column
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Quickstart the dev database (host port **5435**, to avoid colliding with a local
|
|
227
|
+
Postgres on 5432):
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
docker compose up -d
|
|
231
|
+
export PGOPS_DSN="postgresql://pgops:pgops_dev@localhost:5435/pgops_demo"
|
|
232
|
+
uv run pgops-mcp --selfcheck
|
|
233
|
+
```
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# pgops-mcp
|
|
2
|
+
|
|
3
|
+
<!-- mcp-name: io.github.arzharch/pgops-mcp -->
|
|
4
|
+
|
|
5
|
+
> A production-grade MCP server that gives AI agents safe, audited, expert-level control
|
|
6
|
+
> over a real PostgreSQL database and the Docker stack around it — no shell commands,
|
|
7
|
+
> no Python scripts, just tools.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
Existing Postgres MCP servers are thin query wrappers: introspect + SELECT. None handle
|
|
12
|
+
migrations with lock-impact analysis, none diagnose performance from `EXPLAIN` +
|
|
13
|
+
`pg_stat_statements`, none understand the containerized environment the database lives in.
|
|
14
|
+
Agents operating databases today are flying blind and unsafe.
|
|
15
|
+
|
|
16
|
+
`pgops-mcp` is the operations brain: **schema intelligence → guarded queries → migration
|
|
17
|
+
engine → performance diagnosis → environment awareness**, with a safety architecture that
|
|
18
|
+
makes every action classifiable, confirmable, and auditable.
|
|
19
|
+
|
|
20
|
+
## Tool surface
|
|
21
|
+
|
|
22
|
+
| Group | Tools |
|
|
23
|
+
|---|---|
|
|
24
|
+
| Schema | `schema.inspect` |
|
|
25
|
+
| Queries | `query.read`, `query.write` (guarded), `query.explain` (parsed plan + verdict) |
|
|
26
|
+
| Performance | `index.advise`, `db.health` |
|
|
27
|
+
| Migrations | `migration.plan` (dry-run + lock analysis), `migration.describe` (plain English), `migration.apply`, `migration.rollback`, `migration.history` |
|
|
28
|
+
| Environment | `env.topology`, `env.correlate`, `container.logs`, `container.stats` |
|
|
29
|
+
| Gated | `container.restart`*, `container.exec`* |
|
|
30
|
+
|
|
31
|
+
\* Not registered at all unless the server runs with `--approval-mode`, and even then
|
|
32
|
+
each call needs a confirmation token. `container.exec` additionally enforces a read-only
|
|
33
|
+
diagnostic command allowlist — it does not offer a shell. The Docker socket is
|
|
34
|
+
root-equivalent on the host, so the default is read-only access.
|
|
35
|
+
|
|
36
|
+
## Safety model (the core differentiator)
|
|
37
|
+
|
|
38
|
+
- Separate read-only / read-write connection roles; tools bind to the right role
|
|
39
|
+
- Statement classification before execution — unbounded `DELETE`/`UPDATE` blocked
|
|
40
|
+
- Destructive actions require explicit confirmation tokens
|
|
41
|
+
- Every executed statement lands in an append-only audit log with timing and verdict
|
|
42
|
+
- Runaway-query cancellation with timeout tiers
|
|
43
|
+
|
|
44
|
+
## MCP surface
|
|
45
|
+
|
|
46
|
+
| Primitive | What's here |
|
|
47
|
+
|---|---|
|
|
48
|
+
| **Tools** | 17 — schema, query, explain, advise, migrate, environment |
|
|
49
|
+
| **Resources** | `pgops://schema`, `schema/summary`, `schema/{table}`, `health`, `migrations`, `audit/recent`, `config` |
|
|
50
|
+
| **Prompts** | `diagnose-slow-query`, `plan-safe-migration`, `incident-triage`, `review-index-health`, `explain-safety-model` |
|
|
51
|
+
| **Elicitation** | Dangerous actions ask the **user** directly, not via the agent; confirmation tokens are the fallback |
|
|
52
|
+
| **Sampling** | `migration.describe` turns English into a plan using *your* model — this server ships no API key |
|
|
53
|
+
| **Completions** | Table-name autocomplete for `pgops://schema/{table}` |
|
|
54
|
+
| **Progress / logging** | Best-effort notifications during long operations |
|
|
55
|
+
|
|
56
|
+
## Remote access & agent tokens
|
|
57
|
+
|
|
58
|
+
stdio needs no auth — the server is a subprocess your client spawns, with no open port.
|
|
59
|
+
HTTP does, so it refuses to start without a key:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pgops-mcp keygen # RS256 keypair
|
|
63
|
+
pgops-mcp issue-token --subject my-agent # read-only by default
|
|
64
|
+
pgops-mcp issue-token --subject deploy-bot --scope pgops:read --scope pgops:write
|
|
65
|
+
pgops-mcp scopes # which scope each tool needs
|
|
66
|
+
|
|
67
|
+
pgops-mcp --transport http --public-key ~/.pgops/keys/pgops_public.pem
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The server holds only the **public** key, so it can verify tokens but never mint them.
|
|
71
|
+
Scopes (`pgops:read` / `pgops:write` / `pgops:admin`) map to the same danger tiers as the
|
|
72
|
+
guardrails, and a tool with no scope entry requires `admin` — deny by default. Binds
|
|
73
|
+
loopback unless you say otherwise.
|
|
74
|
+
|
|
75
|
+
## Install
|
|
76
|
+
|
|
77
|
+
`pgops-mcp` is an MCP server, not a Python library — nothing in it is meant to be
|
|
78
|
+
imported, and `pgops.*` carries no API-stability promise. You install it the way you
|
|
79
|
+
install any MCP server: point your client at it.
|
|
80
|
+
|
|
81
|
+
**Claude Desktop / Cursor / VS Code:**
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"pgops": {
|
|
87
|
+
"command": "uvx",
|
|
88
|
+
"args": ["pgops-mcp"],
|
|
89
|
+
"env": { "PGOPS_DSN": "postgresql://user:pass@localhost:5432/mydb" }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`uvx` fetches and runs it in a throwaway environment — nothing to install first, and
|
|
96
|
+
nothing added to your own project's dependencies.
|
|
97
|
+
|
|
98
|
+
**Or run the container**, if you would rather not put a Python toolchain on the machine
|
|
99
|
+
that talks to your database:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"mcpServers": {
|
|
104
|
+
"pgops": {
|
|
105
|
+
"command": "docker",
|
|
106
|
+
"args": [
|
|
107
|
+
"run", "-i", "--rm",
|
|
108
|
+
"-e", "PGOPS_DSN",
|
|
109
|
+
"-v", "pgops-audit:/var/lib/pgops",
|
|
110
|
+
"ghcr.io/arzharch/pgops-mcp:latest"
|
|
111
|
+
],
|
|
112
|
+
"env": { "PGOPS_DSN": "postgresql://user:pass@host.docker.internal:5432/mydb" }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Two things the container changes: mount a volume at `/var/lib/pgops` or the audit log
|
|
119
|
+
dies with the container, and `localhost` inside a container is the container itself —
|
|
120
|
+
use `host.docker.internal` or a compose service name.
|
|
121
|
+
|
|
122
|
+
**Check the connection before wiring a client to it:**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uvx pgops-mcp --selfcheck --dsn "postgresql://user:pass@localhost:5432/mydb"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Both paths install the same server and are listed together in the
|
|
129
|
+
[MCP Registry](https://registry.modelcontextprotocol.io) entry — they fail for different
|
|
130
|
+
people. `uvx` needs nothing preinstalled but assumes the host may run Python; the
|
|
131
|
+
container assumes only Docker.
|
|
132
|
+
|
|
133
|
+
See **[SETUP.md](SETUP.md)** for configuration, HTTP transport, agent tokens and
|
|
134
|
+
troubleshooting, and [CONTRIBUTING.md](CONTRIBUTING.md) to run it from a source checkout.
|
|
135
|
+
|
|
136
|
+
## Docs
|
|
137
|
+
|
|
138
|
+
**For users:**
|
|
139
|
+
|
|
140
|
+
- **[docs/API.md](docs/API.md)** — full tool catalog: parameters, returns, error codes, scopes
|
|
141
|
+
- [docs/BENCHMARKS.md](docs/BENCHMARKS.md) — what the benchmarks measure and what they are compared against
|
|
142
|
+
- [docs/GETTING_STARTED.md](docs/GETTING_STARTED.md) — first 15 minutes, guided tour
|
|
143
|
+
- [SETUP.md](SETUP.md) — full setup guide: config, clients, HTTP auth, observability, troubleshooting
|
|
144
|
+
- [.env.example](.env.example) — every environment variable, documented
|
|
145
|
+
|
|
146
|
+
**Internal (design & process):**
|
|
147
|
+
|
|
148
|
+
- [internal/PRD.md](internal/PRD.md), [internal/SPEC.md](internal/SPEC.md) — product requirements & phased spec
|
|
149
|
+
- [internal/ARCHITECTURE.md](internal/ARCHITECTURE.md), [internal/SYSTEM_DESIGN.md](internal/SYSTEM_DESIGN.md) — design + rendered diagrams
|
|
150
|
+
- [internal/adr/](internal/adr/) — architecture decision records
|
|
151
|
+
- [internal/flow.md](internal/flow.md) — living progress log
|
|
152
|
+
- [internal/interview_prep.md](internal/interview_prep.md) — Q&A companion
|
|
153
|
+
|
|
154
|
+
## Status
|
|
155
|
+
|
|
156
|
+
**Phases 0–6f complete** (436 tests, every guardrail, verdict and lock-impact rule proven
|
|
157
|
+
against real Postgres via testcontainers — no mocks — plus end-to-end suites driving the
|
|
158
|
+
server as a real MCP subprocess over stdio and as an authenticated HTTP server, verified
|
|
159
|
+
through the MCP Inspector).
|
|
160
|
+
|
|
161
|
+
| Phase | State | Tools |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| 0 · Bootstrap | ✅ | seeded dev stack (1.2M-row `orders`), CI, lint/type gates |
|
|
164
|
+
| 1 · Connection core + read path | ✅ | `schema.inspect`, `query.read`, `db.health` |
|
|
165
|
+
| 2 · Write path + safety | ✅ | `query.write`, guardrails, confirmation tokens, audit log |
|
|
166
|
+
| 3 · Performance brain | ✅ | `query.explain` (plan verdicts), `index.advise` |
|
|
167
|
+
| 4 · Migration engine | ✅ | `migration.plan` (lock analysis + dry run), `apply`, `rollback`, `history` |
|
|
168
|
+
| 5 · Docker layer | ✅ | `env.topology`, `env.correlate`, `container.logs/stats/restart/exec` |
|
|
169
|
+
| 6a · MCP completeness | ✅ | resources, prompts, elicitation, sampling, completions, progress |
|
|
170
|
+
| 6b · Remote + auth | ✅ | HTTP transport, JWT, per-tool scope enforcement, keygen CLI |
|
|
171
|
+
| 6c · Observability | ✅ | OTel spans/metrics, liveness/readiness endpoints (all optional) |
|
|
172
|
+
| 6d · Adversarial testing | ✅ | red-team suite, property-based tests, live evals in CI |
|
|
173
|
+
| 6e · Forensics | ✅ | `pgops-mcp replay` — the audit log as an executable record |
|
|
174
|
+
| 6f · Distribution | ✅ | PyPI package, container image, `server.json` for the MCP Registry |
|
|
175
|
+
|
|
176
|
+
Sample of what `migration.plan` returns for a type change on the 1.2M-row `orders`:
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
ALTER TABLE "orders" ALTER COLUMN "total_cents" TYPE bigint
|
|
180
|
+
op=table_rewrite risk=high estimate=4800ms confidence=medium
|
|
181
|
+
why: rewrites every row and rebuilds every index, holding AccessExclusiveLock
|
|
182
|
+
SAFER: add a new column of the target type, backfill in batches, sync with a
|
|
183
|
+
trigger, swap the names, then drop the old column
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Quickstart the dev database (host port **5435**, to avoid colliding with a local
|
|
187
|
+
Postgres on 5432):
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
docker compose up -d
|
|
191
|
+
export PGOPS_DSN="postgresql://pgops:pgops_dev@localhost:5435/pgops_demo"
|
|
192
|
+
uv run pgops-mcp --selfcheck
|
|
193
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pgops-mcp"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Production-grade MCP server: safe, audited PostgreSQL operations + Docker environment awareness for AI agents."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
keywords = [
|
|
10
|
+
"mcp",
|
|
11
|
+
"model-context-protocol",
|
|
12
|
+
"postgres",
|
|
13
|
+
"postgresql",
|
|
14
|
+
"database",
|
|
15
|
+
"docker",
|
|
16
|
+
]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 4 - Beta",
|
|
19
|
+
"Environment :: Console",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"Intended Audience :: System Administrators",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Database",
|
|
26
|
+
"Topic :: Database :: Database Engines/Servers",
|
|
27
|
+
"Topic :: System :: Systems Administration",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"fastmcp>=2.3",
|
|
31
|
+
"asyncpg>=0.29",
|
|
32
|
+
"sqlparse>=0.5",
|
|
33
|
+
"docker>=7.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[[project.authors]]
|
|
37
|
+
name = "Arsh Zakee Chowhan"
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/arzharch/pgops-mcp"
|
|
41
|
+
Repository = "https://github.com/arzharch/pgops-mcp"
|
|
42
|
+
Issues = "https://github.com/arzharch/pgops-mcp/issues"
|
|
43
|
+
|
|
44
|
+
[project.scripts]
|
|
45
|
+
pgops-mcp = "pgops.__main__:main"
|
|
46
|
+
|
|
47
|
+
[project.optional-dependencies]
|
|
48
|
+
otel = [
|
|
49
|
+
"opentelemetry-sdk>=1.25",
|
|
50
|
+
"opentelemetry-exporter-otlp-proto-grpc>=1.25",
|
|
51
|
+
"aiohttp>=3.9",
|
|
52
|
+
]
|
|
53
|
+
dev = [
|
|
54
|
+
"pytest>=8.0",
|
|
55
|
+
"pytest-asyncio>=0.23",
|
|
56
|
+
"testcontainers[postgres]>=4.0",
|
|
57
|
+
"hypothesis>=6.165",
|
|
58
|
+
"jsonschema>=4.0",
|
|
59
|
+
"ruff>=0.5",
|
|
60
|
+
"mypy>=1.10",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
[build-system]
|
|
64
|
+
requires = ["uv_build>=0.7,<1.0"]
|
|
65
|
+
build-backend = "uv_build"
|
|
66
|
+
|
|
67
|
+
[tool.uv.build-backend]
|
|
68
|
+
module-name = "pgops"
|
|
69
|
+
|
|
70
|
+
[tool.pytest.ini_options]
|
|
71
|
+
asyncio_mode = "auto"
|
|
72
|
+
testpaths = ["tests"]
|
|
73
|
+
markers = [
|
|
74
|
+
"slow: spawns the server as a subprocess (stdio transport end-to-end)",
|
|
75
|
+
"live: boots a real HTTP server and evaluates it end-to-end incl. benchmarks; run explicitly with -m live",
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[tool.ruff]
|
|
79
|
+
line-length = 100
|
|
80
|
+
target-version = "py312"
|
|
81
|
+
|
|
82
|
+
[tool.mypy]
|
|
83
|
+
strict = true
|
|
84
|
+
|
|
85
|
+
[[tool.mypy.overrides]]
|
|
86
|
+
module = "asyncpg.*"
|
|
87
|
+
ignore_missing_imports = true
|
|
88
|
+
|
|
89
|
+
[[tool.mypy.overrides]]
|
|
90
|
+
module = "sqlparse.*"
|
|
91
|
+
ignore_missing_imports = true
|
|
92
|
+
|
|
93
|
+
[[tool.mypy.overrides]]
|
|
94
|
+
module = "docker.*"
|
|
95
|
+
ignore_missing_imports = true
|
|
96
|
+
|
|
97
|
+
[[tool.mypy.overrides]]
|
|
98
|
+
module = "opentelemetry.*"
|
|
99
|
+
ignore_missing_imports = true
|
|
100
|
+
|
|
101
|
+
[[tool.mypy.overrides]]
|
|
102
|
+
module = "aiohttp.*"
|
|
103
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pgops-mcp"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Production-grade MCP server: safe, audited PostgreSQL operations + Docker environment awareness for AI agents."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [{ name = "Arsh Zakee Chowhan" }]
|
|
10
|
+
keywords = ["mcp", "model-context-protocol", "postgres", "postgresql", "database", "docker"]
|
|
11
|
+
|
|
12
|
+
# pgops-mcp is an MCP *server*, not a Python library: nothing here is meant to be
|
|
13
|
+
# imported by other code, and `pgops.*` carries no API stability promise. PyPI is the
|
|
14
|
+
# delivery channel — it is what makes `uvx pgops-mcp` work, and the MCP Registry stores
|
|
15
|
+
# only metadata and needs a real package to point at. Hence "Application", and no
|
|
16
|
+
# "Libraries :: Python Modules" topic, which would advertise an import surface that
|
|
17
|
+
# does not exist.
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Intended Audience :: Developers",
|
|
22
|
+
"Intended Audience :: System Administrators",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Topic :: Database",
|
|
27
|
+
"Topic :: Database :: Database Engines/Servers",
|
|
28
|
+
"Topic :: System :: Systems Administration",
|
|
29
|
+
]
|
|
30
|
+
urls = { Homepage = "https://github.com/arzharch/pgops-mcp", Repository = "https://github.com/arzharch/pgops-mcp", Issues = "https://github.com/arzharch/pgops-mcp/issues" }
|
|
31
|
+
dependencies = [
|
|
32
|
+
"fastmcp>=2.3",
|
|
33
|
+
"asyncpg>=0.29",
|
|
34
|
+
"sqlparse>=0.5",
|
|
35
|
+
# Required, not optional: six of the seventeen tools (env.*, container.*) import it,
|
|
36
|
+
# and it previously reached the venv only as a transitive dependency of
|
|
37
|
+
# testcontainers — a dev-only package. A user installing from PyPI would have had
|
|
38
|
+
# every environment tool fail on ImportError.
|
|
39
|
+
"docker>=7.0",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
pgops-mcp = "pgops.__main__:main"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
otel = [
|
|
47
|
+
"opentelemetry-sdk>=1.25",
|
|
48
|
+
"opentelemetry-exporter-otlp-proto-grpc>=1.25",
|
|
49
|
+
"aiohttp>=3.9",
|
|
50
|
+
]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=8.0",
|
|
53
|
+
"pytest-asyncio>=0.23",
|
|
54
|
+
"testcontainers[postgres]>=4.0",
|
|
55
|
+
# Property-based tests of the safety core. Test-only — it was previously declared as
|
|
56
|
+
# a runtime dependency (with a non-existent `[dev]` extra), which shipped a testing
|
|
57
|
+
# library to every user of the server.
|
|
58
|
+
"hypothesis>=6.165",
|
|
59
|
+
# Validates server.json against the vendored MCP Registry schema, so a manifest that
|
|
60
|
+
# would be rejected at publish time fails in CI instead.
|
|
61
|
+
"jsonschema>=4.0",
|
|
62
|
+
"ruff>=0.5",
|
|
63
|
+
"mypy>=1.10",
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[build-system]
|
|
67
|
+
requires = ["uv_build>=0.7,<1.0"]
|
|
68
|
+
build-backend = "uv_build"
|
|
69
|
+
|
|
70
|
+
[tool.uv.build-backend]
|
|
71
|
+
module-name = "pgops"
|
|
72
|
+
|
|
73
|
+
[tool.pytest.ini_options]
|
|
74
|
+
asyncio_mode = "auto"
|
|
75
|
+
testpaths = ["tests"]
|
|
76
|
+
markers = [
|
|
77
|
+
"slow: spawns the server as a subprocess (stdio transport end-to-end)",
|
|
78
|
+
"live: boots a real HTTP server and evaluates it end-to-end incl. benchmarks; run explicitly with -m live",
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
[tool.ruff]
|
|
82
|
+
line-length = 100
|
|
83
|
+
target-version = "py312"
|
|
84
|
+
|
|
85
|
+
[tool.mypy]
|
|
86
|
+
strict = true
|
|
87
|
+
|
|
88
|
+
[[tool.mypy.overrides]]
|
|
89
|
+
module = "asyncpg.*"
|
|
90
|
+
ignore_missing_imports = true
|
|
91
|
+
|
|
92
|
+
[[tool.mypy.overrides]]
|
|
93
|
+
module = "sqlparse.*"
|
|
94
|
+
ignore_missing_imports = true
|
|
95
|
+
|
|
96
|
+
[[tool.mypy.overrides]]
|
|
97
|
+
module = "docker.*"
|
|
98
|
+
ignore_missing_imports = true
|
|
99
|
+
|
|
100
|
+
# Optional at runtime and absent from the default dev environment: observability.py
|
|
101
|
+
# imports these lazily and degrades to a no-op when they are missing, which is the whole
|
|
102
|
+
# design. Without these overrides `mypy src` fails for anyone who has not installed the
|
|
103
|
+
# `otel` extra — including CI, which syncs only `--extra dev`.
|
|
104
|
+
[[tool.mypy.overrides]]
|
|
105
|
+
module = "opentelemetry.*"
|
|
106
|
+
ignore_missing_imports = true
|
|
107
|
+
|
|
108
|
+
[[tool.mypy.overrides]]
|
|
109
|
+
module = "aiohttp.*"
|
|
110
|
+
ignore_missing_imports = true
|