Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# AGENTS.md — postgres-mcp

Universal instructions for AI agents contributing to this project.

**Scope:** Development conventions, CI commands, and safety rules for the postgres-mcp codebase. Does NOT cover how to use the MCP tools as an end user (see `.claude/skills/postgres-mcp-usage.md` for that).

## Safety Guardrails

**Hard rule: every new tool MUST call `get_sql_driver()` to obtain its SQL driver.** This is the only mechanism that enforces the server's access mode (UNRESTRICTED vs RESTRICTED). Bypassing it — by instantiating `SqlDriver` directly — silently disables safety protections.

- **UNRESTRICTED mode** — full read/write SQL access. Intended for development.
- **RESTRICTED mode** — read-only transactions via `SafeSqlDriver`, 30-second query timeout. Intended for production.
- `get_sql_driver()` returns a `SqlDriver` or `SafeSqlDriver` depending on `current_access_mode`.
- `execute_sql` is dynamically registered with different MCP annotations per mode. Follow this pattern for any new tool whose behavior changes with access mode.

## CI Commands

Run these locally before pushing. They mirror `.github/workflows/build.yml`:

```bash
uv sync # install dependencies
uv run ruff format --check . # format check
uv run ruff check . # lint
uv run pyright # type check
uv run pytest -v --log-cli-level=INFO # tests (unit + integration)
```

All five must pass for CI to be green.

## Project Structure

```
src/postgres_mcp/
server.py # MCP server entry point, tool definitions (~700 lines)
sql/ # SQL driver, SafeSqlDriver, parameterized queries, extensions
index/ # Index tuning (DTA algorithm, LLM optimizer)
explain/ # EXPLAIN plan analysis
database_health/ # Health checks (index, connection, vacuum, sequence, replication, buffer, constraint)
top_queries/ # pg_stat_statements query analysis
artifacts.py # Response types (ExplainPlanArtifact, ErrorResult)

tests/
unit/ # Fast, no database required
integration/ # Requires a running PostgreSQL instance (Docker)
conftest.py # Shared fixtures
```

Key entry point: `server.py` — contains all `@mcp.tool` definitions and the `main()` function.

## Code Conventions

See `pyproject.toml` for full ruff/pyright configuration. Key points:

- **Async throughout** — all tool handlers and SQL operations are `async`. Use `await` for database calls.
- **Line length:** 150 characters.
- **Quotes:** double quotes (ruff format).
- **Imports:** force-single-line (`from x import y`, one per line). Enforced by ruff isort.
- **Type checking:** pyright in standard mode, Python 3.12. Ruff lint targets Python 3.9 for compatibility.
- **Lint rules:** E, F, I, B, W, N, UP, RUF. See `pyproject.toml [tool.ruff]` for active ignores.

### Patterns to Follow

- Use `Field(description=..., default=...)` from pydantic for tool parameter definitions.
- Return `ResponseType` (list of `TextContent | ImageContent | EmbeddedResource`) from tool handlers.
- Use `format_text_response()` and `format_error_response()` helpers in server.py.
- Parameterized queries: use `SafeSqlDriver.execute_param_query(driver, sql, params)` with `{}` placeholders (not `%s` or `$1`).

## Testing

- **Unit tests** (`tests/unit/`): test logic without a database. Mock SQL drivers as needed.
- **Integration tests** (`tests/integration/`): run against a real PostgreSQL instance via Docker. See `tests/Dockerfile.postgres-hypopg` for the test image with pg_stat_statements and HypoPG pre-installed.
- Test runner: `uv run pytest -v --log-cli-level=INFO`
- Async tests use `pytest-asyncio`.

## Dependencies

- **Package manager:** `uv` (not pip/pipx for development).
- **Build system:** hatchling.
- **Key libraries:** FastMCP (`mcp` package), psycopg3 (async PostgreSQL), pglast (SQL parsing for safety), pydantic (validation), instructor (LLM integration).
- Add dev dependencies to `[dependency-groups] dev` in `pyproject.toml`.
60 changes: 60 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# CLAUDE.md — postgres-mcp

Claude-specific instructions for contributing to this project. Read AGENTS.md first for shared rules (CI commands, safety guardrails, code conventions).

**Scope:** Claude Code tool preferences, MCP-aware development workflows, and commit conventions. Does NOT duplicate content from AGENTS.md.

## Tool Preferences

Prefer Claude Code built-in tools over shell equivalents:

| Prefer | Over |
|--------|------|
| `Read` | `cat`, `head`, `tail` |
| `Edit` | `sed`, `awk` |
| `Grep` | `grep`, `rg` |
| `Glob` | `find`, `ls` for file search |
| `Write` | `echo >`, heredoc |

Use `Bash` only for commands that require shell execution (git, uv, pytest, etc.).

## MCP-Aware Development

When connected to a postgres-mcp instance (tools visible in MCP tool list):

- **Use MCP tools to validate changes.** After modifying a tool handler, call the tool through MCP to verify it works. For example, after editing `list_schemas`, call `list_schemas` via MCP to confirm the output.
- **Test access mode behavior.** If your change touches SQL execution paths, verify behavior in both UNRESTRICTED and RESTRICTED modes.
- **Use `execute_sql` to inspect schema** when writing tests or debugging query-related code.

When NOT connected to an MCP instance:

- Run unit tests: `uv run pytest tests/unit/ -v`
- For integration tests, start the Docker test database first (see `tests/Dockerfile.postgres-hypopg`).
- Review SQL strings manually — the MCP tools won't be available for live validation.

## Commit Style

This project uses a mixed commit style. Follow these conventions:

- **Prefix with type when applicable:** `feat:`, `fix:`, `refactor:`, `chore:`, `docs:` — lowercase, no scope.
- **Imperative mood for the subject line.** Example: `fix: Support PostgreSQL 12 in get_top_queries`
- **Include PR number** if merging via GitHub: `Add streamable HTTP transport support (#134)`
- **Keep subject line under 72 characters.**
- No body required for small changes. Add a body for non-obvious context.

## Development Workflow

1. Read the relevant source files before making changes. Start with `server.py` for tool definitions.
2. Run the full CI suite before considering work complete (see AGENTS.md for commands).
3. If adding a new MCP tool:
- Define it in `server.py` with `@mcp.tool` decorator.
- Use `get_sql_driver()` — this is a hard safety rule (see AGENTS.md).
- Add unit tests in `tests/unit/` and integration tests in `tests/integration/`.

## Project-Specific Context

- The codebase is async throughout (psycopg3 async, FastMCP).
- `execute_sql` is dynamically registered (not decorated) — see `main()` in server.py.
- Index tuning uses the DTA algorithm by default. LLM optimization (`method="llm"`) is experimental and requires `OPENAI_API_KEY`.
- SQL safety parsing uses `pglast` to reject COMMIT/ROLLBACK in restricted mode.
- Parameterized queries use `{}` placeholders via `SafeSqlDriver.execute_param_query()`, not `%s` or `$1`.