Multi-Database AI Query Agent
Provides tools for querying and exploring independent PostgreSQL databases, including schema discovery, table listing, SQL execution, and query explanation with read-only access controls.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Multi-Database AI Query AgentIn ecommerce, which five products generated the most completed-order revenue?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Multi-Database AI Query Agent (MCP)
Ask questions across independent PostgreSQL databases. Claude discovers schemas through a real Model Context Protocol server, writes analytical SQL, and receives bounded results from a read-only execution layer. A Rich CLI shows the executed SQL, result tables, and the AI answer.
Portfolio demonstration using synthetic data. This is not a security-certified database gateway. Live Claude answers require your own API key and incur API charges. See the verification report for what was actually executed, including limitations.
Features
Five discoverable MCP tools:
list_databases,list_tables,get_schema,run_query,explain_query. The CLI uses MCP stdio JSON-RPC, never direct Python tool calls.Database routing, optional enforced database selection, dynamic columns, keys and indexes.
Official Anthropic SDK orchestration with multiple tool calls, error feedback and turn budgets.
SQLGlot PostgreSQL AST validation, schema authorization, approved analytics functions, read-only transactions, separate database reader roles, timeouts and result budgets.
INNER/LEFT joins, subqueries, aggregates, HAVING, nonrecursive CTEs, window functions and date aggregation, with reference queries executed against PostgreSQL.
Two different six-table datasets, Docker Compose, pinned dependencies, pytest and Ruff CI.
API-key-free
/sqland/explaindiagnostics for repeatable local demonstrations.
Related MCP server: postgres-mcp
Architecture
flowchart LR
User[Question in Rich CLI] --> Agent[Anthropic SDK tool loop]
Agent <-->|Tool definitions and tool results| Claude[Claude API]
Agent <-->|MCP stdio JSON-RPC| MCP[FastMCP server]
MCP --> Metadata[SQLAlchemy metadata discovery]
MCP --> Policy[SQLGlot PostgreSQL AST policy]
Policy --> Execution[Read-only transaction and bounded results]
Metadata --> Commerce[(E-commerce PostgreSQL)]
Metadata --> HR[(HR PostgreSQL)]
Execution --> Commerce
Execution --> HRThe MCP client launches python -m app.server as a child process and initializes a
ClientSession. It discovers tool schemas using list_tools, sends them to Claude, executes
each returned tool_use through session.call_tool, and supplies matching tool_result blocks
in the next message. Database URLs and API keys are not included in tool definitions or results.
SQL and result data are sent to Claude. The model is instructed to inspect metadata before
querying; this workflow is guidance, while SQL authorization is enforced in code and PostgreSQL.
Quick start: Windows 11 + Docker Desktop
Open PowerShell in this repository. Start Docker Desktop and wait for its engine to be ready.
powershell -ExecutionPolicy Bypass -File .\scripts\configure.ps1
code .env
# Add ANTHROPIC_API_KEY in the editor, then save. Never commit .env.
docker compose up -d --wait ecommerce hr
docker compose build agent
docker compose run --rm agentconfigure.ps1 generates four random credentials in the ignored .env. It refuses to overwrite
an existing file. The Docker image runs as a nonroot user. The server starts automatically when
the CLI connects; a separate server terminal is unnecessary for stdio transport.
At the prompt:
/use ecommerce
Which five products generated the most completed-order revenue?
/use hr
What is active headcount by department as of December 31, 2025?
/help
/exitFull instructions, including native Python setup, all testing commands, reset, troubleshooting, and publishing steps: Windows testing guide.
Reproducible example
Question: “Which five products generated the most completed-order revenue?”
SELECT p.name, SUM(i.quantity * i.unit_price) AS revenue
FROM public.products p
JOIN public.order_items i ON i.product_id = p.id
JOIN public.orders o ON o.id = i.order_id
WHERE o.status = 'completed'
GROUP BY p.id, p.name
ORDER BY revenue DESC, p.name
LIMIT 5;Product | Gross revenue (USD) |
Product 11 | 25,080.00 |
Product 08 | 16,320.00 |
Product 10 | 15,600.00 |
Product 12 | 12,960.00 |
Product 07 | 11,760.00 |
This is a verified reference query, not a recorded live Claude generation. A successful AI answer should summarize these values if it uses the same revenue definition. Seed dates are fixed in 2025; “last month” relative to today may correctly return no rows. Use explicit 2025 dates.
14 questions, SQL and database-calculated outputs include segment counts, average order values, regional revenue, inactive customers, salary history, headcount, attendance, RANK/DENSE_RANK, LAG/LEAD and running totals. Decimal values are serialized as strings to preserve precision. Dates are ISO strings. Results are arrays with a separate column list, so duplicate column names do not silently overwrite values.
Data model
Database | Tables | Synthetic volume |
ecommerce | customers, products, categories, orders, order_items, payments | 60 customers, 12 products, 720 orders, 1,440 items, 648 payments |
hr | employees, departments, positions, salaries, attendance, performance_reviews | 80 employees, 4 departments, 160 salary records, 2,400 attendance rows, 80 reviews |
Orders are either completed or cancelled. Gross revenue is the sum of completed-order item quantity times unit price; it excludes tax, refunds and discounts, which are not modeled. HR termination dates support headcount and departures; a turnover percentage additionally requires an explicitly agreed denominator. Current salary uses the latest effective date.
Configuration
Variable | Purpose / default |
| Secret, optional for diagnostics/tests; required for natural language |
| Configurable Claude model ID; default |
| JSON array of |
| Optional local JSON array file; takes precedence over JSON env value |
| Per-statement limit, default 3000; allowed 10–30000 |
| Default and maximum requested query rows; default 100, hard maximum 1000 |
| String/cell truncation; default 4000, maximum 10000 |
| Claude round-trip budget; default 12, maximum 30 |
| Compose initialization only |
| Separate per-database readers |
| Native/CI seed script only; never required by the app |
Additional databases require only configuration and a separately provisioned read-only role:
[
{
"id": "analytics",
"description": "Authorized synthetic analytics database",
"url": "postgresql+psycopg://reader:URL_ENCODED_SECRET@localhost:5432/analytics",
"schemas": ["public", "reporting"]
}
]Store this in ignored databases.local.json, set DATABASE_CONFIG_FILE=databases.local.json,
and grant SELECT/USAGE only on the intended tables/schemas. Use postgresql+psycopg URLs.
Avoid broad grants and reader membership in privileged roles. The demo Compose service sets its
own internal-hostname DATABASES_JSON; edit that override when adding containerized databases.
localhost:5541/5542 is for host Python, ecommerce:5432 and hr:5432 for the agent container.
Tests and CI
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e ".[dev]"
.\.venv\Scripts\ruff.exe check .
.\.venv\Scripts\ruff.exe format --check .
.\.venv\Scripts\python.exe -m pytest -q -m "not integration"
# After starting and configuring both seeded databases:
.\.venv\Scripts\python.exe -m pytest -q -m integrationOr use uv sync --frozen --extra dev and uv run ... for exact versions from uv.lock.
Tests do not require an Anthropic key. Standard tests mock the paid Claude boundary. Integration
tests use real PostgreSQL and real MCP stdio, including the agent loop with mocked Claude replies.
They compare all reference queries to stored PostgreSQL outputs and check ACLs even after
turning off transaction read-only in a direct test connection.
GitHub Actions runs Ruff, unit tests, seeded PostgreSQL integration tests, and a separate Docker Compose build/integration job. Workflow configuration is not evidence that a hosted run passed.
Security and limitations
SQL is parsed as PostgreSQL, normalized and executed from the validated AST. Only one SELECT/read-only WITH is accepted; DDL/DML anywhere in the tree, writable CTEs, locks, SELECT INTO and recursive CTEs are rejected. Functions fail closed against a small allowlist.
Physical tables are resolved through SQL scopes, checked against configured schemas and current SELECT privileges, and explicitly schema-qualified. Views, system tables, UDFs, extensions and cross-database SQL joins are deliberately unsupported.
Both examples use separate NOSUPERUSER/NOCREATEDB/NOCREATEROLE readers, no schema CREATE or database TEMP privilege, SELECT grants, default read-only, a 3-second role timeout and 32 MB temporary-file cap. Every execution also opens a read-only transaction. Privileged roles are rejected at connection time. Additional databases must preserve equivalent least privilege.
Pool size 2 per database, no overflow, connection and lock timeouts, 4 MB
work_mem, no parallel workers, row caps, 128-column cap, string truncation and 256 KB row payload budget bound work and responses. These are per-query controls, not global admission control or an OS memory cap.EXPLAINruns without ANALYZE. Planning itself is still subject to role/transaction controls. Indexes support customer/date, product and department access. Small demo tables may correctly use sequential scans. No claim is made that every generated query is optimal.API keys and URLs are secret configuration; errors and application logs omit raw driver exceptions, SQL literals and DSNs. Database/table descriptions and results are untrusted LLM input. Prompt injection may change the model's answer, but cannot bypass SQL/DB policy.
Schema names, comments, SQL and query results leave your machine when using Claude. Use only data you are authorized to disclose. This demo contains no real people or customer data.
stdio is local and has no network listener. Do not expose this as a remote service without authentication, authorization, rate limits, audit controls and independent security review.
Repository map
app/ configuration, metadata/execution, SQL policy, MCP server, agent, CLI
db/ deterministic schemas/data and least-privilege Docker initialization
tests/ parser, configuration, orchestration, real MCP and PostgreSQL tests
examples/ reference questions and verified outputs
scripts/ secure demo configuration, native/CI seeding, example verification, capture
docs/ Windows testing, interview walkthrough, examples, verification evidence
.github/ unit/integration and Compose CI workflowInterview walkthrough and next steps
Read the component walkthrough for the request lifecycle, schema discovery, authorization, testing strategy, tradeoffs and interview talking points.
Future work: query cost/admission budgets, richer type-safe result models, per-user/table authorization, structured audit records with redaction, independently reviewed AST policies, prompt-injection evaluations, larger randomized datasets, and SDK v2 migration. The official MCP SDK v1 FastMCP API is deliberately pinned to its compatible major version in this implementation.
MIT licensed. No hosting deployment is needed; run locally and showcase the source on GitHub.
Available Tools
5 toolsexplain_queryB
Describe SQL structure and PostgreSQL EXPLAIN estimates, without ANALYZE.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | ||
| database_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full disclosure burden, and 'without ANALYZE' does add real behavioral value: the query is planned, not executed. However, it says nothing about permissions required, side-effect safety, or how invalid SQL is handled.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single tight sentence with no filler, and the operative constraint ('without ANALYZE') is placed where it will be read. Nothing here is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return-value explanation is not required, and the ANALYZE clause covers the key semantic distinction. Still, with zero annotations the description should say more about execution safety and prerequisites for a plan-inspection tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for both parameters, and it adds no meaning for 'sql' or 'database_id'. The names are largely self-explanatory, which prevents a 1, but the description contributes nothing beyond them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb ('Describe') with two clear resources: SQL structure and PostgreSQL EXPLAIN estimates. The clause 'without ANALYZE' distinguishes it from a query-execution tool like run_query, though it never names that sibling explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is for inspecting a query plan without executing it. There is no explicit 'use this when...' or exclusion naming run_query as the alternative when actual execution is wanted.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_schemaB
Inspect columns, keys, foreign-key relationships and indexes of discovered tables.
| Name | Required | Description | Default |
|---|---|---|---|
| database_id | Yes | ||
| table_names | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full behavioral burden. 'Inspect' strongly implies a read-only operation and the description discloses what schema details are examined, but it does not explicitly confirm read-only safety, permissions, or side effects.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, front-loaded sentence with no wasted words. It efficiently communicates the tool's scope and inspected objects.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained in the description. However, with no annotations and 0% parameter description coverage, the definition leaves gaps around parameter meaning and usage context that the description could have filled.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must compensate for undocumented parameters. It implies table names via 'discovered tables' but never explains database_id or table_names, leaving both required parameters semantically underspecified.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb, 'Inspect', and names the exact schema elements returned: columns, keys, foreign-key relationships, and indexes. It is clearly distinct from list_tables in substance, but it does not explicitly name or contrast with sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'discovered tables' implies this tool is used after tables have been listed, giving some contextual usage guidance. However, it does not state when to use this versus list_tables, run_query, or explain_query, nor does it mention prerequisites or exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_databasesA
List configured database identifiers, descriptions and availability; no credentials.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden; it does disclose a meaningful trait — that no credentials are returned — plus the 'availability' dimension, which hints results may be filtered by availability. It says nothing about permissions required, ordering, pagination, or whether unavailable databases are omitted vs flagged.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single tight sentence with the resource front-loaded and the security-relevant caveat ('no credentials') appended. No filler and nothing redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
A simple, parameterless read tool with an output schema that covers the return shape; the description adds the useful 'availability' and 'no credentials' framing. What remains missing — result ordering and handling of unavailable databases — is minor given the output schema exists.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so baseline 4 applies. There is nothing parameter-wise for the description to compensate for, and the schema is trivially complete.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('List configured database identifiers') and enumerates the returned fields (identifiers, descriptions, availability). It implicitly separates itself from sibling list_tables by naming database-level rather than table-level resources, though it never names the alternative outright.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: an agent can infer this is the discovery entry point before list_tables/get_schema, but the description never says when to prefer it or what it excludes. No explicit when/when-not guidance or named alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tablesC
Discover authorized base tables, comments and approximate row counts.
| Name | Required | Description | Default |
|---|---|---|---|
| database_id | Yes | ||
| schema_name | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It does disclose that results are limited to authorized tables and that row counts are approximate, which is useful, but it omits read-only confirmation, permission requirements, and any pagination or result-size behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The definition is a single front-loaded sentence with no wasted words. It is appropriately concise, though that concision comes at the cost of missing usage and parameter context.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained. However, with no annotations and 0% parameter description coverage, the description should do more to explain how to invoke the tool and when to choose it over siblings; it currently covers only the surface purpose.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0% for two parameters, and the description does not mention database_id or schema_name at all. The parameter names are somewhat self-explanatory, but the description adds no meaning about required database selection or optional schema filtering.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: discover authorized base tables, with comments and approximate row counts. It is clear enough to distinguish from list_databases and get_schema, though it does not explicitly name those siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It implies the tool is for discovering tables, but gives no explicit when-to-use guidance, no exclusions, and no comparison to alternatives like get_schema or run_query. An agent must infer routing from the name alone.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_queryB
Execute one validated read-only SELECT/CTE, returning bounded structured results.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | ||
| max_rows | No | ||
| database_id | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries full behavioral burden and does disclose key traits: read-only, single-statement, validated, and bounded results. It says nothing about timeouts, row-cap enforcement, permission requirements, or what happens on validation failure, which are material for arbitrary SQL execution.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no filler; the operative constraints (one statement, read-only, bounded) come before the result clause. Every word earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need no explanation, and the core constraint set is present. For an arbitrary-SQL execution tool with zero annotation coverage and undocumented parameters, though, missing error/timeout behavior and database_id sourcing leave it only minimally complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must explain all three parameters and does not. 'Bounded' loosely gestures at max_rows and 'one SELECT/CTE' at sql, but database_id (the required identifier) is never mentioned nor its provenance (list_databases) indicated.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Execute) and resource (one validated read-only SELECT/CTE) with a clear output shape ('bounded structured results'). This implicitly separates it from explain_query and the list_/get_schema siblings, but no sibling is named explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'one validated read-only SELECT/CTE' implies the boundary (single statement, read-only, validation applies), which is real guidance. However, it never states when to prefer explain_query for plans, or what to do when a query is rejected, leaving usage context to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
explain_query - First observed
get_schema - First observed
list_databases - First observed
list_tables - First observed
run_query
TDQS
Scored across 5 tools
Each tool maps to a distinct stage of a read-only analytics workflow: database discovery, table discovery, schema introspection, execution, and plan explanation. The only superficially similar pair (run_query vs explain_query) is clearly separated by 'returns bounded structured results' versus 'EXPLAIN estimates, without ANALYZE'. An agent can pick the right tool without guessing.
All names are snake_case verb_noun pairs (list_*, get_*, run_*, explain_*) with a consistent, predictable structure. The verbs accurately signal the operation type, and there are no camelCase or style outliers.
Five tools is tightly scoped for a read-only query agent and every tool earns its place in the discovery→introspection→execution chain. Nothing appears redundant or bolted on.
The core lifecycle for a read-only agent (discover → inspect → execute → explain) is fully covered, and the absence of write tools is clearly intentional. Minor gaps remain: no cross-database/column search or namespace listing, so an agent must walk databases/tables sequentially to locate a field.
Maintenance
Related MCP Connectors
PostgreSQL, MySQL, and SQL Server in one session. 26 read-only MCP tools for AI agents.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Hosted PostgreSQL MCP server for Claude, ChatGPT and Cursor, with each action switched per account.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables Claude to query a Postgres analytics schema via a read-only MCP server, with tools auto-generated from live database introspection and configurable SQL queries defined in YAML.MIT
- AlicenseNot gradedqualityCmaintenanceEnables read-only, SELECT-only querying of any Postgres database through MCP-compatible clients like Claude, with schema introspection and guarded SQL execution.MIT
- FlicenseAqualityCmaintenanceEnables read-only access to a PostgreSQL database, allowing users to inspect schema, generate SQL from natural language, and execute read-only queries via MCP.5-
- AlicenseNot gradedqualityCmaintenanceEnables Claude to inspect PostgreSQL database schemas and execute read-only SQL queries through MCP tools and resources.MIT