# mcp-server-pgvector [![CI](https://github.com/mittalpk/mcp-server-pgvector/actions/workflows/ci.yml/badge.svg)](https://github.com/mittalpk/mcp-server-pgvector/actions/workflows/ci.yml) An [MCP](https://modelcontextprotocol.io/) server that gives LLM agents first-class access to [pgvector](https://github.com/pgvector/pgvector)-backed embedding tables in PostgreSQL: similarity search, hybrid (vector + full-text) search, upserts, and HNSW/IVFFlat index management. Generic Postgres MCP servers expose raw SQL or schema introspection; this one speaks pgvector specifically — nearest-neighbor search, distance metrics, and ANN index tuning are first-class tools, not something the model has to hand-write SQL for. ## Tools | Tool | Description | |---|---| | `list_vector_tables` | Discover every `vector` column in the database, with its dimensionality | | `describe_vector_table` | Columns, indexes, and approximate row count for a table | | `similarity_search` | k-NN search over a vector column (cosine / L2 / inner product), with structured metadata filters | | `hybrid_search` | Weighted blend of vector similarity and Postgres full-text search (`ts_rank_cd`) | | `upsert_embedding` | Insert or update a row's embedding + metadata | | `create_vector_index` | Create an HNSW or IVFFlat index with tunable parameters | | `explain_similarity_query` | `EXPLAIN ANALYZE` a similarity query to confirm the ANN index is used | ## Safety - Every table/column name is validated against `information_schema` / `pg_catalog` before being interpolated into SQL — an LLM can only ever reference identifiers that already exist. Values are always bound parameters. - Metadata filters are a closed `{column, op, value}` allowlist, not a raw SQL fragment. - Set `MCP_PGVECTOR_READ_ONLY=true` to disable `upsert_embedding` and `create_vector_index`, leaving only read/search tools available — useful when pointing the server at a production database. - Every query runs with a per-command timeout (`MCP_PGVECTOR_COMMAND_TIMEOUT_SECONDS`, default 30s) so one expensive query can't occupy a pool connection — and stall every other caller — indefinitely. Set it to `0` to disable. ## Installation ```bash uvx mcp-server-pgvector ``` Or with pip: ```bash pip install mcp-server-pgvector python -m mcp_server_pgvector ``` ## Configuration The server reads its connection string from `DATABASE_URL` (or `PGVECTOR_DATABASE_URL`): ```json { "mcpServers": { "pgvector": { "command": "uvx", "args": ["mcp-server-pgvector"], "env": { "DATABASE_URL": "postgresql://user:password@localhost:5432/mydb", "MCP_PGVECTOR_READ_ONLY": "false", "MCP_PGVECTOR_COMMAND_TIMEOUT_SECONDS": "30" } } } } ``` ## Production readiness **Covered:** - Identifier-safe SQL (every table/column checked against `pg_catalog` before use) and a closed filter-operator allowlist — no path from tool arguments to raw SQL. - Per-query timeout, so one runaway query can't monopolize the (small, 5-connection) pool. - 60+ tests, including dimension-mismatch and injection-attempt regressions, run in CI on every push/PR against a real pgvector container across Python 3.10–3.13. A separate CI job builds the package and runs `twine check` on the result. - Connection failures surface as plain `ConnectionRefusedError`/`asyncpg` exceptions — verified these don't leak the DSN's credentials into error text. **Known limitations, honestly:** - No per-tool authorization — access control is whatever the Postgres role in `DATABASE_URL` can do. If you need different agents to have different permissions, give them different connection strings backed by different Postgres roles, not different server instances of this same DSN. - `hybrid_search`'s full-text side is hardcoded to Postgres's `'english'` text search configuration; there's no parameter to change it yet. - No structured logging — failures are exceptions surfaced through the MCP error channel, not written to a log you can tail. Fine for a single-user desktop MCP client, a real gap if you're running this as a shared service. - The connection pool is fixed at 1–5 connections and isn't configurable via environment variable yet. ## Development ```bash uv sync --dev # Bring up an isolated pgvector instance for local testing docker compose -f docker-compose.dev.yml up -d export DATABASE_URL=postgresql://postgres:postgres@localhost:5434/postgres uv run pytest uv run ruff check . uv run pyright ``` ## Contributing See [CONTRIBUTING.md](CONTRIBUTING.md). See [CHANGELOG.md](CHANGELOG.md) for release history. ## License MIT — see [LICENSE](LICENSE).