mcp-server-pgvector
mcp-server-pgvector
An MCP server that gives LLM agents first-class access to 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 |
| Discover every |
| Columns, indexes, and approximate row count for a table |
| k-NN search over a vector column (cosine / L2 / inner product), with structured metadata filters |
| Weighted blend of vector similarity and Postgres full-text search ( |
| Insert or update a row's embedding + metadata |
| Create an HNSW or IVFFlat index with tunable parameters |
|
|
Safety
Every table/column name is validated against
information_schema/pg_catalogbefore 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=trueto disableupsert_embeddingandcreate_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 to0to disable.
Installation
uvx mcp-server-pgvectorOr with pip:
pip install mcp-server-pgvector
python -m mcp_server_pgvectorConfiguration
The server reads its connection string from DATABASE_URL (or PGVECTOR_DATABASE_URL):
{
"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_catalogbefore 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 checkon the result.Connection failures surface as plain
ConnectionRefusedError/asyncpgexceptions — 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_URLcan 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
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 pyrightContributing
See CONTRIBUTING.md. See CHANGELOG.md for release history.
License
MIT — see LICENSE.