Skip to main content
Glama

mcp-dbserver

A self-built MCP server that gives an AI agent — Claude Code, Claude Desktop, or any MCP-compatible client — read-only, security-scoped access to three database engines at once: PostgreSQL (with pgvector), DynamoDB, and MongoDB Atlas (with Atlas Vector Search). It's a personal project extending 17+ years of multi-cloud database architecture work into the AI/agentic tooling space: the goal isn't "an agent can query a database," it's demonstrating the same least-privilege, defense-in-depth discipline a production reference architecture would demand, applied to a caller that's an LLM instead of a service.

No employer data, schemas, or business logic (current or former) appears anywhere in this repo — only public or synthetic data, generated specifically for this project.

Architecture

flowchart LR
    Client["MCP client<br/>(Claude Code / Claude Desktop)"]

    subgraph Server["mcp-dbserver (stdio)"]
        direction TB
        Tools["Fixed tool surface<br/>(no generic 'run query' tool)"]
        Guard["guardrails.py + allowlist.py<br/>read-only + row-limit re-check"]
        Tools --> Guard
    end

    Client -- "MCP tool calls" --> Tools

    Guard --> PG[("PostgreSQL + pgvector<br/>RDS, IAM or password auth")]
    Guard --> DDB[("DynamoDB<br/>fixed table-target registry")]
    Guard --> Mongo[("MongoDB Atlas + Vector Search<br/>fixed collection-target registry")]

Every arrow into a database is a named, allowlisted operation — never raw SQL, a raw MongoDB filter, or a raw DynamoDB key condition. See ARCHITECTURE.md for the full design.

Related MCP server: Secure RDS Read-Only MCP Server

Security model

This is the part of the project meant to differentiate it from a typical "point an agent at a database" demo. Full detail (including two things found by actually testing the guardrails, not just designing them) lives in ARCHITECTURE.md — summary:

  1. Read-only, full stop. No write, update, or delete tool exists for any engine in v1. A write-capable version, if it ever exists, is a separate project with its own threat model.

  2. A documented boundary, found by testing it. The "no write tool" guardrail constrains what an agent can do through the MCP protocol. It cannot constrain a code-capable client (like Claude Code, as opposed to a chat-only client like Claude Desktop) that has independent access to the same credentials. In testing, Claude Code correctly found no delete tool — then wrote its own psycopg script and attempted the delete directly, bypassing the MCP server entirely. It failed only because the configured database role lacked write privilege. That makes the database-/IAM-level read-only role the real last line of defense against a code-capable client, not the absence of a write method in this code — documented explicitly rather than left implicit.

  3. No raw queries from the agent. Every operation is a named, allowlisted shape with typed parameters — a fixed SQL template (Postgres), a fixed table/collection target registry plus a typed key (DynamoDB/MongoDB) — never a filter document, key-condition expression, or SQL string built from agent input. An earlier draft of the vector search tool took a table/column name as a direct argument and was caught and fixed (a real SQL injection surface via f-string interpolation) before the server was ever wired up to a live client.

  4. Defense in depth at execution time. Even an allowlisted Postgres query is re-validated by guardrails.py before running (rejects anything that isn't SELECT/WITH, rejects stacked statements, enforces a row-limit ceiling regardless of what's requested), and every connection sets default_transaction_read_only = on at the database level.

  5. Credentials: environment variables only, never logged, never hardcoded. RDS IAM database authentication is supported and preferred over a stored Postgres password (a fresh ~15-minute token per connection via rds:GenerateDBAuthToken, no long-lived DB secret at all).

Supported engines & tools

Engine

Tools

PostgreSQL + pgvector

query_postgres, list_postgres_queries, semantic_search_documents

DynamoDB

list_dynamodb_tables, get_dynamodb_item, list_dynamodb_items, count_dynamodb_items

MongoDB Atlas + Vector Search

list_mongodb_collections, get_mongodb_document, list_mongodb_documents, count_mongodb_documents, semantic_search_mongodb

Full per-tool descriptions and the reasoning behind each one are in ARCHITECTURE.md. semantic_search_documents and semantic_search_mongodb run against the same demo dataset and the same local embedding model, specifically so pgvector and Atlas Vector Search results can be compared directly.

Setup

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env   # fill in your own, personal, non-work credentials

Run the test suite (no live database required — guardrail/allowlist logic is unit tested against fakes for all three engines):

pytest

Run the MCP server (stdio transport, for local use with Claude Code / Claude Desktop):

mcp-dbserver

Tools are only registered for engines whose required environment variables are set — e.g. with only POSTGRES_DSN set, only the Postgres tools appear. See .env.example for every variable, per engine.

Postgres demo dataset

data/demo_documents.jsonl is a small, synthetic set of ~30 short software/infra explainer snippets (written for this project). Load it and generate embeddings locally (fastembed's ONNX runtime — offline, no external API key, no torch/torchvision dependency):

python scripts/load_demo_dataset.py
python scripts/smoke_test_postgres.py      # connectivity + read-only guardrail
python scripts/verify_demo_dataset.py      # row count + semantic search sanity check

DynamoDB

Tables aren't configured via environment variables — accessible tables come from a fixed registry in engines/dynamodb.py (_TABLE_TARGETS). Set AWS_REGION (and standard AWS credentials via env vars/profile/ instance role, scoped to dynamodb:GetItem/Scan/DescribeTable on the registered table ARN(s)) to enable the *_dynamodb_* tools.

MongoDB Atlas demo dataset

Mirrors the Postgres setup exactly — same dataset, same embedding model — so results are directly comparable. Set MONGODB_URI/MONGODB_DATABASE (Atlas user with the built-in read role, not readWrite), then:

python scripts/load_demo_dataset_mongodb.py    # upserts data + creates the Atlas Vector Search index
python scripts/verify_demo_dataset_mongodb.py  # index builds asynchronously; re-run if search comes back empty

Project layout

src/mcp_dbserver/
  guardrails.py        # read-only + row-limit enforcement, engine-agnostic
  allowlist.py          # named, parameterized Postgres query registry
  config.py              # env-var credential loading, per engine
  engines/
    postgres.py           # allowlisted queries + pgvector semantic search
    dynamodb.py             # fixed table-target registry, get/scan/count
    mongodb.py                # fixed collection-target registry, get/list/count/$vectorSearch
  server.py             # MCP entrypoint, registers tools per configured engine
tests/                   # guardrail/allowlist/engine unit tests, all three engines (no live DB needed)
scripts/                 # demo dataset loaders/verifiers, Postgres smoke test

What I'd do differently at production scale

Being explicit about what v1 deliberately doesn't solve signals more than pretending it's production-complete:

  • Client ↔ server authentication. v1 runs over stdio, launched directly by the client as a subprocess — the OS process boundary is the trust boundary, which is fine for local, single-user use and not fine for anything else. A networked deployment (HTTP/SSE, reachable by more than one client) needs per-client API keys scoped per-engine, and TLS termination in front of the server, before it's anything but a demo.

  • Observability. No query logging or metrics yet. At minimum, before any networked deployment: which named query/operation was called, when, and whether it succeeded — deliberately never parameter values or row contents, to avoid quietly building a second copy of the data in logs.

  • Rate limiting. Not implemented; only matters once the server is reachable by more than a single local stdio client, but it's a gap worth naming rather than discovering under load.

  • MySQL. Explicitly out of scope for v1. Would follow the same allowlist + guardrail pattern as Postgres if added — no new design needed, just the fourth engine's worth of plumbing.

  • The DynamoDB/MongoDB filter-shape question got simpler than planned, not more complex. The original design considered a typed schema per allowlisted filter for DynamoDB/MongoDB. What shipped instead is smaller: a fixed target registry plus a fixed, small set of named operations per engine, with no generic find(filter) or query(key_condition) tool at all. Worth calling out because the instinct to build a validation DSL was the more "impressive-sounding" option, and the simpler one turned out to close the same gap more reliably — there's no permissive shape for $where or an arbitrary key condition to hide in, because there's no field for one.

A
license - permissive license
Not graded
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides read-only access to PostgreSQL databases via MCP, enforcing least-privilege roles, row-level security, masked views, and SQL AST guardrails to prevent data leakage and unauthorized operations, enabling AI agents to safely query sensitive production data.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables read-only access to company data across PostgreSQL, MongoDB Atlas, and flat files through MCP tools, allowing AI assistants to query and retrieve information via natural language.
  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides governed, read-only PostgreSQL access for AI agents via MCP. Enforces schema/table allowlists, query limits, and audit events.
    MIT

View all related MCP servers

Related MCP Connectors

  • Analytical memory for AI agents: a real Postgres queried in plain English over MCP. One command.

  • Connect MCP clients to 2,000+ AI models without managing provider API keys.

  • A paid remote MCP for AI SDK data query MCP, built to return verdicts, receipts, usage logs, and aud

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/stanisraja/mcp_model'

If you have feedback or need assistance with the MCP directory API, please join our Discord server