DB-Explorer-MCP
DB-Explorer-MCP is a read-only MCP server that lets an AI client inspect, query, and audit PostgreSQL, MySQL, or SQLite databases without being able to write.
Explore schemas: list tables/views, columns, primary keys, foreign keys, indexes, row counts, and sample rows.
Execute one validated read-only
SELECTwith a row cap and structured results, including truncation metadata.Explain SQL query plans for safe
SELECTstatements.Validate schema health and report issues such as missing primary keys and unindexed foreign keys.
Suggest indexes from either a query plan or table foreign-key metadata.
Provide migration context and validate up/down migration scripts without executing them.
Enforce safety at the parser and database layers: reject writes, multiple statements, comments, blocked keywords, and locking clauses; apply row limits, timeouts, read-only transactions, and database privileges.
Support local stdio and remote HTTP deployment with bearer-token auth, named token groups, and audit logging.
Provides database exploration tools for MySQL, including schema inspection, safe read-only query execution, query plan explanation, index suggestions, and migration validation.
Provides database exploration tools for PostgreSQL, including schema inspection, safe read-only query execution, query plan explanation, index suggestions, and migration validation.
Provides database exploration tools for SQLite, including schema inspection, safe read-only query execution, query plan explanation, index suggestions, and migration validation.
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., "@DB-Explorer-MCPwhat tables are in the database, and show me the schema for the orders table"
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.
DB Explorer MCP
A read-only Model Context Protocol (MCP) server for PostgreSQL, MySQL, and SQLite that lets an AI coding assistant explore, query, and audit a relational database without ever being able to write to it.
Point your MCP client at a database and ask questions in plain language. The client's LLM writes the SQL; this server parses it, refuses anything that is not a single read-only SELECT, executes it with a row cap, and returns structured results. Schema inspection, execution plans, index recommendations, and migration review come along with it.
MCP client LLM -> FastMCP tools -> safety layer -> SQLAlchemy -> database
(writes SQL) (7 tools) (rejects writes) (any dialect)The server makes no LLM API calls of its own, so there is no API key to configure — reasoning happens in whichever client you connect. Works with SQLite, PostgreSQL, and MySQL through SQLAlchemy.
Tested and evaluated. 336 tests run on every push and pull request, plus 26 more against real PostgreSQL and MySQL in CI (Tests). In an agent evaluation, Claude completed 14 of 14 realistic database tasks through these tools with no failed tool calls, an average of 2 calls per task, and a mean independent judge score of 4.6 / 5 (Agent evals).
Why
Giving an assistant raw database credentials means one confused or prompt-injected turn can drop a table. Handing it a read-only replica loses schema context and plan analysis. This server takes the middle path: full introspection and query power, with mutation made structurally impossible at the parser level rather than by asking the model to behave.
Related MCP server: nl2sql-mcp
Architecture
┌──────────────────────────────────────────────────────────────┐
│ MCP client (Claude Code / Claude Desktop / Inspector) │
│ owns the LLM: reads schema, authors SQL, interprets results │
└───────────────────────────┬──────────────────────────────────┘
│ MCP · stdio (local)
│ · streamable HTTP + bearer token (remote)
┌───────────────────────────▼──────────────────────────────────┐
│ server.py — FastMCP instance + one engine per token group │
│ │
│ explore_schema execute_query explain_query │
│ validate_schema suggest_index migration_context │
│ validate_migration │
└──────┬───────────────────────┬───────────────────┬───────────┘
│ │ │
│ read path │ metadata path │ review path
│ │ │
┌──────▼────────────┐ ┌───────▼─────────┐ ┌──────▼──────────┐
│ safety.py │ │ inspector.py │ │ migration.py │
│ ── trust boundary │ │ schema_health.py│ │ parses up/down, │
│ sqlparse AST │ │ index_suggest.py│ │ never executes │
│ SELECT-only │ │ explain.py │ │ │
│ 1 stmt · no cmnts │ │ │ │ │
│ denylist · LIMIT │ │ │ │ │
└──────┬────────────┘ └───────┬─────────┘ └─────────────────┘
│ │
└───────────┬───────────┘
│ SQLAlchemy Core
┌──────────────────▼───────────────────────────────────────────┐
│ Target database · PostgreSQL / MySQL / SQLite │
└──────────────────────────────────────────────────────────────┘The SQL arriving at safety.py is written by the client's model and therefore untrusted, so it is parsed rather than pattern-matched, and a rejected query never reaches the database.
Mode | Transport | Auth |
Local | stdio | none needed; the client spawns the server |
Remote | streamable HTTP | bearer tokens, or OAuth at the platform edge on Prefect Horizon |
Both modes run identical tool code — only MCP_TRANSPORT changes.
Quickstart
Requires Python 3.11+ and uv.
From PyPI
No clone needed. uvx downloads the package and runs it in its own environment:
claude mcp add db-explorer --env DATABASE_URL="postgresql+psycopg2://user:pass@localhost:5432/example" -- uvx db-explorer-mcp-serverLeave out --env to try it against the bundled demo database first. In any other client's config, use "command": "uvx" and "args": ["db-explorer-mcp-server"].
From source
git clone https://github.com/AYUSH-148/DB-Explorer-MCP
cd DB-Explorer-MCP
uv sync
uv run python seed_demo_db.py # creates sample.db, a small shop with deliberate schema flaws
uv run pytest # 336 tests on SQLite; 26 more with TEST_*_URL setOn Windows, if uv is not on PATH, prefix with py -m (py -m uv sync).
You don't start the server yourself: your MCP client launches it over stdio, so go on to Connect a client. Run by hand, uv run server.py prints nothing and waits for a client on stdin; stop it with Ctrl+C.
The default database is sample.db. Point at your own with DATABASE_URL, either in the client config below or in a .env file copied from .env.example:
postgresql+psycopg2://user:password@localhost:5432/example
mysql+pymysql://user:password@localhost:3306/example
sqlite:///C:/data/example.dbPercent-encode special characters in passwords (@ → %40, # → %23, / → %2F).
Connect a client
Claude Code — local
claude mcp add db-explorer --env DATABASE_URL="postgresql+psycopg2://user:pass@localhost:5432/example" -- uv --directory "C:/path/to/DB-Explorer-MCP" run server.pyThen run /mcp in a session to confirm the 7 tools are listed. Add -s user to make it available in every project.
Claude Code — remote
claude mcp add --transport http db-explorer https://your-deployment.fastmcp.app/mcpRun /mcp → Authenticate for the OAuth flow; tokens are cached and refreshed automatically.
For a server you host yourself with a bearer token, send it as a header. Read it from an environment variable, so a .mcp.json committed for the team holds no secret:
{
"mcpServers": {
"db-explorer": {
"type": "http",
"url": "https://dbx.example.com/mcp",
"headers": { "Authorization": "Bearer ${DBX_TOKEN}" }
}
}
}Claude Desktop
Local, in claude_desktop_config.json:
{
"mcpServers": {
"db-explorer": {
"command": "uv",
"args": ["--directory", "C:/path/to/DB-Explorer-MCP", "run", "server.py"],
"env": { "DATABASE_URL": "postgresql+psycopg2://user:pass@localhost:5432/example" }
}
}
}To reach a remote deployment without a custom connector, proxy it over stdio:
{
"mcpServers": {
"db-explorer": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://your-deployment.fastmcp.app/mcp"]
}
}
}VS Code
.vscode/mcp.json is checked in and starts the server over stdio through the Windows py launcher. On macOS or Linux, change its command to uv and its args to ["run", "server.py"].
MCP Inspector
npx @modelcontextprotocol/inspectorUse transport Streamable HTTP with your /mcp URL, or stdio with uv run server.py. The Inspector shows raw tool responses and unparaphrased errors, which makes it the fastest way to tell a server problem from a client problem.
Python
import asyncio
from fastmcp import Client
async def main():
async with Client("https://your-deployment.fastmcp.app/mcp", auth="oauth") as client:
print([tool.name for tool in await client.list_tools()])
print(await client.call_tool("explore_schema", {}))
asyncio.run(main())Try it
Once connected to the demo sample.db from Quickstart, prompts like these work directly:
"What tables exist, and which ones are missing primary keys?"
"Show me 5 rows from
orderswith the highest total.""Why is this query slow?
SELECT * FROM orders WHERE created_at >= '2026-01-01'""Which foreign keys in this database lack indexes? Give me the
CREATE INDEXstatements.""Draft a migration adding a
shipped_atcolumn toorders, then validate the up and down scripts."
To watch the guardrails work, ask it to run DELETE FROM users. The call fails with Unsafe query blocked: Only SELECT queries are allowed. Got: DELETE and the database is untouched.
Agent skill
skill/db-explorer/SKILL.md is an optional agent skill that teaches the model to use these tools well: read the schema once before querying, compute totals in SQL rather than from returned rows, say when a result is truncated, and follow error hints instead of retrying. It guides the model only; the server must still be connected.
For Claude Code, copy the skill/db-explorer folder to ~/.claude/skills/db-explorer/ for every project, or to .claude/skills/db-explorer/ inside one project.
Tools
Tool | Arguments | Returns |
|
| The |
|
|
|
|
| Native execution plan plus the resolved |
|
| Schema issues with |
|
|
|
|
| Dialect plus one page of columns, keys, and indexes (no row counts), for client-side migration drafting; pages like |
|
| Parsed statement types per script; rejects |
validate_schema reports four codes: missing_primary_key, unindexed_foreign_key, wide_table (50+ columns), and no_indexes. A foreign key counts as indexed when any index, the primary key, or a unique constraint starts with its columns; partial, GIN/GiST/BRIN and FULLTEXT/SPATIAL indexes do not count.
explore_schema is cheap by default: with no arguments it returns table names and column counts. Row counts arrive only with table_name (tables only, capped at 100,000 with row_count_capped: true). Narrow a large schema with name_pattern (order matches any name containing it, order_* is a glob), page with limit/offset, and use detail=true for the columns, keys and indexes of a whole page in one call. Views and materialized views are listed alongside tables, each with a kind.
Safety model
Every execute_query, explain_query, and suggest_index call routes through safety.py before touching the database. A query is rejected unless it satisfies all of:
Single statement.
SELECT 1; DROP TABLE users→Exactly one SQL statement is requiredSELECTonly, determined from the parsed statement type rather than a string prefix →Only SELECT queries are allowed. Got: DELETENo SQL comments.
--,/*,*/are refused outside string literals and quoted names, closing the comment-smuggling route.#is refused on MySQL and MariaDB only, where it starts a comment.No blocked keywords anywhere in the token stream:
ALTER,CREATE,DELETE,DROP,EXEC,EXECUTE,GRANT,INSERT,INTO,REVOKE,TRUNCATE,UPDATE. Matched as whole tokens, soSELECT grant_date FROM permissionspasses. Four entries are load-bearing:INSERT,UPDATEandDELETEcan hide in a PostgreSQL data-modifying CTE whose statement type is stillSELECT, andSELECT * INTO archive FROM userscreates a table. The rest cannot appear in aSELECT-typed statement and stay as a second line of defense ifsqlparsetype detection regresses.No locking clause.
FOR UPDATE,FOR NO KEY UPDATE,FOR SHARE,FOR KEY SHAREand MySQL'sLOCK IN SHARE MODEare refused, because a locking read blocks other writers. Matched as a clause, not a keyword, so a column namedsharestill works.
Every query that passes is wrapped as SELECT * FROM (<your query>) AS limited_query LIMIT <row_limit + 1>, so an unbounded scan cannot flood the client's context. A LIMIT in your own query narrows the inner result, but row_limit (clamped to 1000) still caps what comes back. If the extra probe row comes back it is dropped and the result carries "truncated": true, so a capped result is never mistaken for a complete one.
Validation is only the first of three layers, because a keyword blocklist cannot see a query that is syntactically fine and still harmful:
A statement timeout.
SELECT pg_sleep(600)passes every check above, so time is bounded independently of syntax:statement_timeouton PostgreSQL (set per transaction, so it works behind PgBouncer, Supabase and Neon poolers),max_execution_timeon MySQL, and a progress-handler deadline on SQLite. Configured byQUERY_TIMEOUT_SECONDS, applied in db.py.A read-only transaction. Reads run through
BEGIN READ ONLYon PostgreSQL,START TRANSACTION READ ONLYon MySQL, andPRAGMA query_onlyon SQLite. The database refuses the write itself, which is a guarantee the blocklist cannot make.Privileges. Still the outermost boundary — see .env.example. A
SELECT-only user is what stops server-side file reads likepg_read_file()that no keyword check reliably catches.Privileges are also where to restrict what can be read. A token reads whatever its group's database user can
SELECT, so give each group its own user (see Named tokens and groups). Hide credentials and PII with grants, not with a server-side table or column blocklist: a parser cannot see the columns behindSELECT *,SELECT u FROM users u, orrow_to_json(u), but the database enforces a grant on all of them.-- PostgreSQL: whole tables, minus one GRANT SELECT ON ALL TABLES IN SCHEMA public TO explorer; REVOKE SELECT ON users FROM explorer; -- then only the safe columns of it GRANT SELECT (id, name, created_at) ON users TO explorer;MySQL takes the same column list:
GRANT SELECT (id, name) ON app.users TO 'explorer'@'%';. A query that touches a revoked column then fails withsql_error(permission denied).
Every tool call writes one line to the db_explorer.audit logger on stderr: the tool, how it ended (ok or the error code), the caller (user=<token name> group=<group> over HTTP), and its arguments, capped at 2000 characters. Arguments are logged verbatim, so treat the log as being as sensitive as the data.
Errors
A tool error is only useful to a model if it says what to do next, so every failure carries a code and a corrective hint instead of a bare message:
[table_not_found] Table not found: usrs. Did you mean: users.
Hint: Call explore_schema() to list the tables in this database.
[sql_error] The database rejected the query: no such column: totl.
Hint: Call explore_schema(table_name=...) to confirm the table and column names before retrying.
[query_timeout] The query exceeded the statement timeout of 15s: interrupted.
Hint: Add a WHERE clause, aggregate instead of scanning, or query a smaller table.Code | Cause |
| No such table. Carries the nearest matching names the database does have |
| The database rejected the query — a missing column, a type mismatch, bad syntax |
| The query ran past |
| The server could not reach the database, or the link dropped mid-query. Details go to the server log only |
| Blocked by safety.py. The hint names the specific rule that fired |
| The SQL is over 50,000 characters, or too long or deeply nested for the parser (e.g. an |
| The caller's token already has |
| The caller's token names a group the server has no database login for |
| An argument out of range, such as |
|
|
|
|
| Every database connection was in use for |
| A bug in the server. The traceback goes to the server log; the caller sees only the code |
Configuration
Variable | Default | Notes |
|
| Required when |
|
|
|
|
| HTTP transports only |
|
| HTTP transports only |
|
| Upper bound on any single statement, and the budget for a whole call: once spent, no further statement starts. Must be a positive integer |
|
| Connections kept open to the database |
|
| Extra connections opened under load, closed when returned |
|
| How long a call waits for a free connection before failing with |
|
| Tool calls one token can have running at once (per server process); stdio is not limited |
| — | One shared token. This or |
| — | Named tokens, |
| — | The database URL for one token group, for example |
|
| Explicit opt-out of the token requirement, for trusted networks or a host whose own authentication sits in front (Horizon) |
At most DB_POOL_SIZE + DB_MAX_OVERFLOW (15 by default) calls hold a connection at once; a call past that waits DB_POOL_TIMEOUT_SECONDS, then fails with server_busy. Keep the pool under the database's max_connections, counted across every server instance.
The SQLite fallback is for local development only: sample.db next to config.py, opened read-only. .env is loaded at startup (the file next to config.py first, then one in the working directory); real environment variables win over both.
Serve over HTTP
$env:MCP_TRANSPORT = "streamable-http"
$env:MCP_HOST = "0.0.0.0"
$env:MCP_PORT = "8000"
$env:DATABASE_URL = "postgresql+psycopg2://user:password@host:5432/example"
$env:MCP_AUTH_TOKEN = python -c "import secrets; print(secrets.token_urlsafe(32))"
uv run server.pyThe same on macOS or Linux:
export MCP_TRANSPORT=streamable-http MCP_HOST=0.0.0.0 MCP_PORT=8000
export DATABASE_URL="postgresql+psycopg2://user:password@host:5432/example"
export MCP_AUTH_TOKEN="$(python3 -c 'import secrets; print(secrets.token_urlsafe(32))')"
uv run server.pyAn HTTP endpoint publishes SELECT on the configured database to anyone who can reach the port, so the server fails to start without MCP_AUTH_TOKEN or MCP_AUTH_TOKENS rather than coming up unprotected. Clients send a token as Authorization: Bearer <token>; it is compared in constant time in auth.py. Set MCP_ALLOW_UNAUTHENTICATED=true to override on a genuinely trusted network — the server then warns on stderr at every startup.
A host that loads server.py:mcp directly (Horizon, fastmcp run) answers every HTTP request 401 unless a token or MCP_ALLOW_UNAUTHENTICATED=true is set.
On a loopback bind, foreign Host and Origin headers are refused, so a web page cannot reach a local server through DNS rebinding. The transport is stateless, so any replica behind a load balancer can answer any request. GET /health answers {"status": "ok"} without a token, for load-balancer probes; it never touches the database.
Named tokens and groups
Give each person or service its own token, and optionally a group whose database user decides what it can read:
MCP_AUTH_TOKENS=alice:analyst:<token>,carol:analyst:<token>,fran:finance:<token>,ci:<token>
DATABASE_URL=postgresql+psycopg2://dbx_readonly:...@db/app # tokens with no group (ci)
DATABASE_URL_ANALYST=postgresql+psycopg2://dbx_analyst:...@db/app
DATABASE_URL_FINANCE=postgresql+psycopg2://dbx_finance:...@db/appEach entry is name:token or name:group:token. A token's name goes into every audit line; its group picks the database login the call runs on, so the database's own grants (and PostgreSQL row-level security) decide what that caller can read. A lone MCP_AUTH_TOKEN behaves as one token named default with no group.
The server refuses to start on a malformed entry, a repeated name or token, a token under 32 characters, a group with no DATABASE_URL_<GROUP>, or both MCP_AUTH_TOKEN and MCP_AUTH_TOKENS set. Tokens are compared in constant time and read at startup: restart after changing them. Each group has its own connection pool.
On PostgreSQL, explore_schema lists tables a group cannot SELECT (the catalog is readable by everyone), but their rows stay protected. DEPLOYMENT.md has the GRANT statements for both databases, OAuth on Horizon, and a VM setup with HTTPS.
Hosted Supabase note: direct connections (db.<ref>.supabase.co) are IPv6-only, which fails from IPv4-only containers with an empty-looking psycopg2.OperationalError. Use the pooler host from the dashboard's Connect panel, and note that the username becomes postgres.<project-ref>.
Tests
uv run pytest336 tests covering the safety layer, serialization, read-only enforcement and timeouts, HTTP auth and token groups, every tool, and error reporting. Each uses a temporary SQLite database, so the suite needs no credentials and no running server.
tests/test_live_databases.py covers what SQLite cannot — the read-only transaction, the statement timeout, NUMERIC/TIMESTAMP values, catalog reflection — against real PostgreSQL and MySQL. Set TEST_POSTGRES_URL and/or TEST_MYSQL_URL to a throwaway database to run it (it creates and drops dbx_live_* tables). CI runs both.
Agent evals
The tests check each tool; the evals check whether a model uses the tools well. evals/run.py runs each task in evals/scenarios.py through Claude Code in headless mode (claude -p), with this server as its only tool source, against a seeded evals/eval.db. Each run is scored on tool choice, call budget and a correct answer read from the database, and a second claude -p call grades the whole run from 1 to 5.
uv run python evals/seed_eval_db.py # build evals/eval.db (the runner does this if it is missing)
uv run python evals/run.py # every scenario, on Sonnet
uv run python evals/run.py --only top_customers --model opusIt needs the claude CLI on PATH, logged in; runs count toward that login's usage limits, and no API key is involved. Results vary between runs, so the evals are not part of CI. Reports are written to evals/results/.
Latest results
Claude Sonnet 5.5 as agent and judge, 2026-10-14. Full report, including every tool call and its result: evals/reports/2026-10-14-sonnet.json.
14/14 passed · 0 tool errors · 0 repeated calls · 28 tool calls in total · mean judge score 4.64 / 5
Task | Tools the agent chose | Calls / budget | Judge |
List the tables and views |
| 1/3 | 5/5 |
Orders per status |
| 2/4 | 5/5 |
Top 3 customers by delivered spend |
| 3/5 | 4/5 |
Category with the most revenue |
| 2/5 | 5/5 |
Category with the best average rating |
| 2/5 | 5/5 |
Users who never ordered |
| 2/4 | 5/5 |
5 most recent orders with customer names |
| 2/4 | 5/5 |
Row count of "the user table" (real name: |
| 2/4 | 5/5 |
Why a date-filtered query is slow |
| 2/4 | 5/5 |
Audit the schema for structural problems |
| 2/4 | 4/5 |
Which foreign keys on |
| 2/3 | 4/5 |
Migration adding a |
| 2/4 | 5/5 |
"Delete all cancelled orders" (must refuse: read-only) |
| 2/5 | 4/5 |
Users on the pro plan |
| 2/3 | 4/5 |
Every answer that has a single right value was checked against the database, not just judged. Scores of 4 were for one call more than needed, or extra commentary beyond the question.
Known limitation: explore_schema does not yet report UNIQUE or CHECK constraints, so in a schema audit the model can claim one is missing when it exists.
Project layout
server.py FastMCP instance, engine, and the 7 tool definitions
safety.py query validation and row-limited execution
db.py engine construction, statement timeouts, read-only transactions
serialization.py driver values to JSON-safe primitives
auth.py bearer-token verification for HTTP transports
inspector.py schema reflection (columns, PK, FKs, indexes, samples)
explain.py dialect-aware EXPLAIN
index_suggest.py index recommendations from plans or FK metadata
schema_health.py objective schema issue reporting
indexes.py index coverage shared by schema_health and index_suggest
migration.py migration context and non-executing script validation
errors.py coded errors with hints, and driver-error classification
config.py environment configuration with fail-fast checks
seed_demo_db.py builds the demo sample.db used in Quickstart
skill/ optional agent skill for clients that load SKILL.md files
tests/ pytest suite over temporary SQLite databases
evals/ agent evals: a Claude model works scripted tasks through the toolsDesign notes and limits
Query-mode
suggest_indexflags full table scans, not missing indexes in general. It names the scan rather than writing aCREATE INDEX, because which column to index depends on the query. A scan on a tiny table is expected, not a problem.Only the default schema is inspected: the first schema on PostgreSQL's
search_path(usuallypublic), or the database named inDATABASE_URLon MySQL.execute_querystill readsbilling.invoiceswhen the SQL names the schema. To inspect another PostgreSQL schema, put it first:ALTER ROLE explorer SET search_path = billing, public;.A few read-only shapes are refused.
sqlparsecannot type a parenthesized(SELECT 1),VALUES (1)orTABLE users, so they are rejected with a hint to write a plainSELECT.SQL is capped at 50,000 characters, and past
sqlparse's token or nesting limits the call fails withquery_too_complex.The row cap guards context, not cost. A heavy aggregate still runs in full;
QUERY_TIMEOUT_SECONDSbounds that.Long text and large results are cut. A text value over 2000 characters arrives truncated with its full length noted. A query result stops at 500,000 bytes with
truncatedset, and a schema page stops there too and continues atnext_offset.MariaDB is not tested. It shares the MySQL code path, but may drop an
ORDER BYinside the row-cap subquery; add your ownLIMITto an ordered query there.Binary values up to 256 bytes arrive hex-encoded; larger ones are reported as a size only.
Wide
NUMERICvalues arrive as strings, so they keep their exact digits rather than being rounded.
License
MIT. See LICENSE.
Available Tools
7 toolsexecute_queryB
Execute one validated, read-only SQL SELECT query.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | Yes | ||
| row_limit | 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 must carry the full transparency burden. It does disclose that the operation is read-only and limited to one SELECT query, which is valuable. However, it does not mention row_limit behavior, validation semantics, permissions, or effect on resources.
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 concise sentence with no fluff or redundancy. Every word contributes meaning, making it an efficient, front-loaded description.
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 completed enough for a very simple tool, especially given an output schema exists. However, it omits the row_limit behavior and does not explain what validates the query or how the tool relates to validation, leaving the user without a fully complete picture.
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 parameter semantics. The description only references the SQL query itself but does not mention row_limit or clarify how the two parameters interact. This leaves an important parameter undocumented.
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 clearly states the tool's action ('Execute'), the resource ('SQL SELECT query'), and a key constraint ('read-only'). It is specific enough to differentiate from siblings like explain_query and validate_schema, though it does not explicitly name sibling alternatives.
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?
No explicit guidance is given about when to use this tool vs alternatives such as explain_query or validate_schema. The read-only SELECT wording implies the tool is for running queries, but it does not state exclusions or provide decision-making context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explain_queryA
Return the database execution plan for one safe SELECT query.
| Name | Required | Description | Default |
|---|---|---|---|
| sql | 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 must disclose behavioral traits itself. It mentions 'safe SELECT' but does not clarify what 'safe' means (e.g., read-only, no side effects) or describe any permissions, limitations, or output format. The description is too terse to adequately inform an agent about behavior beyond the basic function.
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 sentence, front-loaded with the primary action and scope. Every word contributes meaning, with no redundancy or fluff.
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?
The description is minimal and does not explain what 'safe' means or what the execution plan looks like claiming to rely on the output schema. Since the tool has an output schemaache, return values are implicitly covered, but the description lacks details on limitations (e.g., only SELECT, read-only behavior, privileges) that are not captured elsewhere. Given the simple nature of the tool and existing output schema, it is adequate but not thorough.
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 has 0% description coverageaine, so the description must compensate. The phrase 'one safe SELECT query' adds constraint that the sql parameter must be a SELECT statementable, but it does not specify allowed syntax, single-statement requirement, or other constraints. This is minimal compensation; a more detailed description of the sql parameter would be needed for a higher score.
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 clearly states the tool returns a database execution plan for a SELECT query. This is a specific verb-resource pairing that distinguishes it from siblings like execute_query (which actually runs queries) or suggest_index (which recommends indexes).
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 description implies usage for SELECT queries only ('one safe SELECT query'), but does not explicitly state when to use this tool instead of alternatives like execute_query or suggest_index. No exclusions or alternative comparisons are provided, so the guidance is implied rather than explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explore_schemaA
Explore database tables, columns, keys, indexes, and sample rows.
| Name | Required | Description | Default |
|---|---|---|---|
| table_name | No | ||
| include_sample_data | No |
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 burden of behavioral disclosure. It mentions the tool explores tables, columns, keys, indexes, and sample rows—implying a read-only nature. However, it does not disclose specific behaviors like sample row limits, whether all tables are listed when no table_name is given, or any performance implications for large schemas. Some transparency is present, but important details are missing.
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, concise sentence that front-loads the verb and resource list. No wasted words, no redundant information. It is efficient and immediately understandable.
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?
With no annotations, no output schema, and only a single parameter, the description must compensate for context. It lists what the tool explores but does not specify behavior when table_name is null (list all tables?) or provided (details for one table?), nor what 'sample rows' means (count? random? first N?). For a tool with moderate complexity and no other metadata, this is adequate but incomplete—more detail on parameters and behaviors would be expected.
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?
The input schema defines table_name as optional (string or null with default null), but the description does not explain its semantics—e.g., 'omit to list all tables' or 'specify to get details for a particular table.' Since schema coverage is limited (no descriptions in the schema), the description could have clarified how the parameter affects behavior. There is a general connection (the tool explores tables, so table_name likely filters by table), but this is only implied, not explicit.
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 ('explore') with a clear resource list ('database tables, columns, keys, indexes, and sample rows'). This clearly communicates the tool's function and distinguishes it from sibling tools like query execution or explanation tools. It loses one point because it doesn't explicitly differentiate itself by naming any sibling tool or stating what it does NOT do (e.g., modifications, query execution).
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 description conveys that this tool is for exploring schema structure, which implies usage for inspection rather than data manipulation. However, it lacks explicit guidance on when to use this tool versus alternatives (e.g., 'use get_schema for just columns' or 'don't use this for querying data'). No exclusions or alternative tool references are given, leaving the agent to infer context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
migration_contextB
Return schema context for client-side migration generation.
| 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 provided, the description carries the full burden of behavioral disclosure. It only mentions 'Return schema context' without stating whether it is read-only, what side effects (if any) exist, or performance characteristics. This is minimal and does not add meaningful transparency beyond the tool name itself.
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 that directly states the tool's purpose. There is no fluff or redundancy, and every word adds value.
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?
The tool has an output schema, so the description need not explain return values. It is a simple no-parameter tool, and the purpose is clear. However, it could briefly mention what 'schema context' encompasses, but given the output schema and simplicity, it is adequately 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?
The tool has zero parameters, so the baseline is 4 per the rubric. The description adds no param-related info because there are none, and the schema already confirms no parameters, so there is no deficiency to compensate.
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 clearly states the verb 'return' and the resource 'schema context' with a specific purpose 'for client-side migration generation.' This distinguishes it from siblings like 'validate_schema' or 'explore_schema' by its intended use, though it doesn't explicitly contrast with them.
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?
There is no guidance on when to use this tool versus the sibling tools, such as 'explore_schema' or 'validate_migration.' The description only states what it does, leaving the agent to infer usage context without helpful exclusions or alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
suggest_indexB
Suggest indexes from a query plan or table foreign-key metadata.
| Name | Required | Description | Default |
|---|---|---|---|
| query | No | ||
| table_name | No |
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 present, and the description does not mention side effects, permissions, or behavior when both parameters are provided.
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 sentence, clear and free of unnecessary wording.
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?
The description lacks information about the output format, parameter relationships, and expected usage scenarios, making it incomplete for a tool with two optional parameters.
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?
The description loosely references the parameters (query plan and table foreign-key metadata) but does not clarify their formats, constraints, or how they are used.
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 clearly states the tool suggests indexes, using either a query plan or table foreign-key metadata, which distinguishes it from sibling tools like explain_query.
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 description does not provide explicit guidance on when to use this tool versus alternatives, such as when query optimization is needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_migrationA
Validate migration scripts without executing them.
| Name | Required | Description | Default |
|---|---|---|---|
| up_sql | Yes | ||
| down_sql | 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 burden of revealing behavior. It does disclose a key trait—non-execution—which is valuable for a validation tool. Nonetheless, it lacks other behavioral details (e.g., does it require a DB connection? what is the failure mode?), leaving some gaps. It adds value but not comprehensively.
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, well-constructed sentence delivers the core message with zero wasted words. It is front-loaded and 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?
For a simple tool with two string parameters, an output schema (though not shown in the prompt), and a concise description, the context is largely adequate. The description covers the essential safety aspect. Minor gaps exist: potential side-effects or prerequisites are unmentioned, but for a validation utility, the description meets most expectations.
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 coverage is 0%, yet the parameter names 'up_sql' and 'down_sql' are self-explanatory as migration scripts. The description refers to them collectively as 'migration scripts' but adds no detail about format or constraints. The baseline is acceptable due to intuitive parameter names, but the description does not explicitly compensate for the lack of schema documentation.
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 clearly states a specific verb ('Validate') and resource ('migration scripts'), and adds a crucial qualifier ('without executing them') that distinguishes it from siblings like execute_query and validate_schema. This is a strong, purpose-driven statement that leaves no ambiguity about the tool's role.
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 'without executing them' phrase implies a safe-to-run interpretation, which implicitly guides usage. However, it does not explicitly name alternatives or provide when/when-not conditions. The usage context is implied rather than stated, so it earns a mid-range score.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
validate_schemaC
Check tables for missing primary keys and unindexed foreign keys.
| Name | Required | Description | Default |
|---|---|---|---|
| table_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 burden of behavioral disclosure. It does not state whether the tool is read-only or if it has side effects, nor does it describe the output format or any side effects, making its behavior opaque.
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, concise sentence that directly states the tool's function with no redundant words or unnecessary detail.
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?
Given the presence of an output schema, the description should explain what the tool returns, but it omits that. It also lacks context about parameter semantics and usage scenarios, leaving the description incomplete for a tool with even a single parameter and output schema.
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?
The parameter 'table_name' is not explained. The description does not clarify whether it targets a specific table or all tables, nor what happens when it is null. Since the schema provides no parameter description (0% coverage), the description fails entirely to add meaning.
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 clearly states the tool's purpose: to check for missing primary keys and unindexed foreign keys. This specific action distinguishes it from sibling tools like explore_schema or suggest_index, making its function unambiguous.
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?
No guidance is provided on when to use this tool versus alternatives. It does not mention scenarios, prerequisites, or how it differs from other validation-related tools, leaving the agent without context for selection.
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.
7 tool updates
v0.1.0- First observed
execute_query - First observed
explain_query - First observed
explore_schema - First observed
migration_context - First observed
suggest_index - First observed
validate_migration - First observed
validate_schema
TDQS
Scored across 7 tools
Most tools have distinct purposes: exploring, executing, explaining, validating, and suggesting indexes are clearly separate. The main overlap risk is between explore_schema and migration_context, since both provide schema context, but their intended use cases differ enough to avoid serious confusion.
Six of seven tools follow a clean verb_noun pattern (explore_schema, execute_query, explain_query, validate_schema, suggest_index, validate_migration). migration_context breaks the pattern by being a noun phrase, but the overall naming style remains predictable and readable.
With 7 tools, the server is well-scoped for the stated purpose of database exploration, query execution, and migration validation. Each tool addresses a meaningful workflow step without unnecessary bloat.
The toolset covers the core read-only database workflows: schema inspection, query validation/execution/explanation, structural tenant-focused checks, index suggestions, and migration validation. No obvious dead-end exists for the server's stated purpose.
Maintenance
Related MCP Connectors
Text-to-SQL MCP server: read-only queries on PostgreSQL, MySQL and SQL Server with your real schema
- mcpOAuthcom.gibsonai
GibsonAI MCP server: manage your databases with natural language
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Query your warehouse or a CSV with Claude/ChatGPT over MCP, governed by table-level ACL + audit.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP server that provides safe, read-only access to SQLite databases through MCP. This server is built with the FastMCP framework, which enables LLMs to explore and query SQLite databases with built-in safety features and query validation.108-
- FlicenseNot gradedqualityFmaintenanceA production-ready MCP server that transforms natural language into safe, executable SQL queries with multi-database support and intelligent schema analysis.1-
- AlicenseNot gradedqualityDmaintenanceA read-only MCP server for PostgreSQL that enables safe database introspection and querying via natural language.1,043 npmMIT
- AlicenseNot gradedqualityCmaintenanceRead-only Text-to-SQL MCP server for PostgreSQL and MySQL that lets users query databases using natural language, with robust multi-layer safety guarantees against writes.17 npmMIT