Safe, read-only PostgreSQL inspection, query diagnosis, performance analysis, and database health workflows for AI agents.
An Agent Plugin (spec 1.0.0), version 0.0.2.
- Inspects PostgreSQL databases: identity, schemas, tables, columns, relationships, indexes, sizes.
- Diagnoses slow queries with
EXPLAIN (FORMAT JSON)(never executes the target statement). - Investigates performance: activity, long transactions, locks, table health,
pg_stat_statements(when available). - Produces compact health reviews.
- All through a bundled HiAI PostgreSQL MCP server — no third-party PostgreSQL MCP dependency.
Most coding agents already expose filesystem and general tools, but safe PostgreSQL inspection is a distinct capability. This plugin ships a read-only MCP that uses PostgreSQL's own server-side read-only enforcement — not a regex — as the primary safety boundary.
It is NOT:
- an AI database administrator,
- a generic arbitrary SQL executor,
- a migration tool,
- a write-capable database automation system.
The plugin is deliberately read-only. There is no write mode.
Read-only is enforced in layers (defense in depth):
- Server-side (primary): every pooled connection is created with
default_transaction_read_only=on,statement_timeout,lock_timeout, andidle_in_transaction_session_timeoutvia PostgreSQL connection options. PostgreSQL itself rejects writes. - Session reinforcement:
set_configcalls apply read-only and timeouts to the active connection. Becauseset_configcan also disable these settings, thequerytool blocks it (and every function that can change session-level read-only state) at the application layer. - Tool-level: the server exposes no write tool at all.
- Input checks: the
querytool enforces one statement, rejects forbidden keywords (DML/DDL/DCL/transaction/COPY) and dangerous functions (set_config,pg_read_file,pg_ls_dir,pg_stat_file, and other server-file / admin functions), and caps results. - Output limits: row and byte caps with an explicit
truncatedflag.
The security gate (30 cases) and the two-PostgreSQL-version matrix are
recorded in docs/SECURITY_VERIFICATION.md.
The MCP is maintained by HiAI as part of this plugin (it is not a third-party server):
agent-plugin-postgresql
└── packages/postgres-mcp (the HiAI PostgreSQL MCP, stdio)
Dependency chain (intentionally small):
HiAI Agent Plugin
→ HiAI PostgreSQL MCP (packages/postgres-mcp)
→ official MCP TypeScript SDK
→ Bun.SQL (Bun's PostgreSQL driver)
→ PostgreSQL
See docs/UPSTREAM_TRUST.md for licenses.
The MCP requires the DATABASE_URL environment variable (standard
PostgreSQL connection string), for example:
DATABASE_URL=postgresql://user:password@host:5432/database-
Set it at the client level (env for the MCP subprocess). Do not commit real values.
-
If
DATABASE_URLis unset the server refuses to start (it does not silently fall back to localhost):Error: DATABASE_URL environment variable is required. Set it to your PostgreSQL connection string. -
Credentials are never embedded in the plugin, and are never echoed: any displayed connection string is redacted (
postgresql://user:***@host:5432/database).
| Skill | Use when |
|---|---|
inspect-database |
understanding an unfamiliar database |
diagnose-query |
a query is slow or suspicious |
diagnose-performance |
database-wide performance issues |
diagnose-locks |
queries hang / lock contention |
review-database-health |
overall health assessment |
All skills default to inspect / explain / recommend — never modify, terminate, or vacuum automatically.
| Tool | Purpose |
|---|---|
database_info |
version, database, user, settings, extensions |
list_schemas |
visible schemas |
list_tables |
tables with size estimates |
describe_table |
columns, types, PK, FKs, constraints, indexes |
list_indexes |
index definitions and sizes |
query |
single read-only SQL statement (with limits) |
explain_query |
EXPLAIN (FORMAT JSON) — not executed |
database_activity |
sessions, long transactions, waits |
locks |
blocked vs blocking sessions |
slow_queries |
top statements via pg_stat_statements (optional) |
table_health |
maintenance signals |
database_sizes |
database, largest tables/indexes |
health_summary |
compact diagnostic summary |
analyze_query (EXPLAIN ANALYZE) is intentionally not exposed.
Tested against:
- PostgreSQL 14 (older supported major)
- PostgreSQL 16 (current stable major)
- PostgreSQL 15, 17, 18, and 19 beta2 (see
docs/COMPATIBILITY.mdfor the full matrix and the PG19 pre-release caveat)
See docs/COMPATIBILITY.md for details.
The plugin should be used with a least-privilege PostgreSQL role. PostgreSQL role privileges remain part of the security boundary.
The plugin's own filters are defense-in-depth; the real authority is the role
in DATABASE_URL. A least-privilege role keeps the query tool safe even for
server-file functions (pg_read_file, pg_ls_dir, ...) and sensitive catalog
relations (pg_authid), which PostgreSQL blocks unless the role is privileged.
Never use a superuser DATABASE_URL (including the default role created
by a stock docker run postgres): under a superuser role, read-only
inspection would also allow reading arbitrary server files and role password
verifiers. Do not grant pg_read_server_files, pg_write_server_files,
pg_execute_server_program, or pg_signal_backend.
Recommended least-privilege role recipe:
CREATE ROLE readonly LOGIN PASSWORD 'change-me';
GRANT CONNECT ON DATABASE <db> TO readonly;
GRANT USAGE ON SCHEMA app TO readonly; -- per application schema
GRANT SELECT ON ALL TABLES IN SCHEMA app TO readonly;
GRANT pg_monitor TO readonly; -- optional: diagnostic viewsFunctional needs:
- Read access to
pg_catalogandinformation_schema(most roles have this). - Monitoring views (
pg_stat_activity,pg_stat_user_tables,pg_stat_user_indexes,pg_locks) require the role to see them — some require elevated privileges. slow_queriesrequirespg_stat_statementsand access to it.
Insufficient privileges return a structured available: false + reason —
never a crash. The plugin does not recommend granting superuser casually.
slow_queries uses pg_stat_statements when available. If the extension
is not installed or not accessible, it returns:
{
"available": false,
"reason": "pg_stat_statements is not installed or accessible"
}It never fails the plugin and never installs the extension.
- Read-only enforced server-side (primary) + tool/input/output layers.
- One statement per query; forbidden keywords and dangerous functions
(
set_config, server-file / admin functions) rejected. - The plugin should be used with a least-privilege PostgreSQL role; PostgreSQL role privileges remain part of the security boundary.
- Row/byte limits; explicit
truncated. - Credential redaction in logs, errors, and diagnostics.
- Permission-aware: missing privileges are handled gracefully.
See docs/SECURITY.md and docs/SECURITY_VERIFICATION.md.
- Read-only by design; no write mode.
- No
EXPLAIN ANALYZE(deliberately omitted). DATABASE_URLis required; the server will not start without it.pg_stat_statementsoptional and environment-dependent.- Health signals are signals, not exact diagnoses.
bun(runtime for the bundled MCP).- A reachable PostgreSQL server.
DATABASE_URLat the client level (required; there is no default host).
Copy the plugin's runtime files (plugin.json, mcp.json, skills/,
packages/postgres-mcp/, README.md, LICENSE) into your Agent Plugins
client's plugin folder, or use your client's plugin install flow. Do not
copy the authoring sources (plugin.yml, skills-src/, docs/) — see the
collection README for the full boundary. Set DATABASE_URL for the MCP
subprocess. The plugin launches its bundled MCP via ${PLUGIN_ROOT} — no
global install required.
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"postgresql": {
"type": "stdio",
"command": "bun",
"args": ["run", "${PLUGIN_ROOT}/packages/postgres-mcp/src/index.ts"]
}
}
}See CHANGELOG.md.
MIT. See LICENSE.