Skip to main content
Glama
AYUSH-148

DB-Explorer-MCP

by AYUSH-148

DB Explorer MCP

M8ven Score

A Model Context Protocol server 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.

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 + OAuth 2.0 (remote)
┌───────────────────────────▼──────────────────────────────────┐
│ server.py  —  FastMCP instance + one shared SQLAlchemy engine│
│                                                              │
│   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 (text() + inspect())
┌──────────────────▼───────────────────────────────────────────┐
│  Target database   ·   PostgreSQL  /  MySQL  /  SQLite       │
└──────────────────────────────────────────────────────────────┘

The LLM lives in the client, not the server. Most NL-to-SQL designs put a model call inside the server; this one does not. The client already has a capable model, so the server ships zero LLM dependencies, zero API keys, and zero per-call inference cost — and stays usable from any MCP client, not just Claude.

That split defines the trust boundary: the SQL arriving at safety.py is model-authored and therefore untrusted, so it is parsed rather than pattern-matched, and a rejected query never reaches the driver.

Request lifecycle

A typical execute_query call:

  1. Client turns the user's question into SQL, using schema it fetched earlier via explore_schema.

  2. FastMCP deserializes the tool call and validates arguments against the tool's type hints.

  3. safety.py parses the SQL with sqlparse — one statement, type SELECT, no comments, no blocked keywords. Failure raises before any connection is opened.

  4. Row cap applied: if the query has no LIMIT, it is wrapped in SELECT * FROM (…) AS limited_query LIMIT row_limit.

  5. SQLAlchemy executes it on a pooled connection and the rows are serialized to plain dicts.

  6. Client receives {columns, rows, count} as structured JSON and explains it in natural language.

Errors travel the same path in reverse: a raised ValueError becomes an MCP tool error, which the client surfaces to the user while the server keeps serving.

Module responsibilities

Module

Role

server.py

Tool surface only — thin @mcp.tool wrappers over plain functions, plus transport selection

safety.py

The trust boundary: AST validation and row-limited execution

inspector.py

Reflection via SQLAlchemy inspect() — columns, PK, FKs, indexes, row counts, samples

explain.py

Dialect-aware plans (EXPLAIN QUERY PLAN on SQLite, EXPLAIN elsewhere)

index_suggest.py

Recommendations from a live plan or from FK metadata

schema_health.py

Objective schema audit, no heuristics about naming or style

migration.py

Schema context out, script validation in — never executes DDL

errors.py

Coded, hinted errors and driver-error classification

config.py

Environment resolution with fail-fast checks

Each tool body delegates to a module-level function that takes an Engine argument, so the whole system is testable against a temporary SQLite database with no MCP client and no network involved.

Transports

Mode

Transport

Auth

Use

Local

stdio

process-level

development; client spawns the server

Remote

streamable HTTP

OAuth 2.0 (DCR + PKCE) at the platform edge

shared deployment; many clients, one database

Both modes run identical tool code — only MCP_TRANSPORT changes.

Tools

Tool

Arguments

Returns

explore_schema

table_name?, include_sample_data=false, name_pattern?, detail=false, limit=200, offset=0

A table listing with column counts, or one table's columns, PK, FKs, indexes, row count, and up to 3 sample rows

execute_query

sql, row_limit=100 (max 1000)

columns, rows, count for one validated SELECT

explain_query

sql

Native execution plan plus the resolved dialect

validate_schema

table_name?

Schema issues with severity, code, message, suggestion

suggest_index

query? xor table_name?

CREATE INDEX recommendations with reasons

migration_context

Dialect and full schema, for client-side migration drafting

validate_migration

up_sql, down_sql

Parsed statement types per script; never executed

validate_schema reports four codes: missing_primary_key, unindexed_foreign_key, wide_table (50+ columns), and no_indexes.

explore_schema is cheap by default and expensive only on request. With no arguments it returns table names and column counts — a handful of queries however wide the database is, and small enough to read before picking a table. Row counts cost a COUNT(*) scan, so they arrive only with table_name. Narrow a large schema with name_pattern (order matches any name containing it, order_* is a glob), page with limit/offset (capped at 1000), and use detail=true to expand a whole page into columns, keys, and indexes.

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 usersExactly one SQL statement is required

  • SELECT only, determined from the parsed statement type rather than a string prefix → Only SELECT queries are allowed. Got: DELETE

  • No SQL comments. --, /*, */ are refused outright, closing the classic comment-smuggling route

  • No blocked keywords anywhere in the token stream: ALTER, CREATE, DELETE, DROP, EXEC, EXECUTE, GRANT, INSERT, INTO, REVOKE, TRUNCATE, UPDATE

  • No locking clause. FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE, FOR KEY SHARE and MySQL's LOCK IN SHARE MODE are refused, because a locking read is not a read: it blocks other transactions from writing those rows. This one is defense in depth rather than a hole being closed — PostgreSQL 16 refuses both forms itself inside a read-only transaction (cannot execute SELECT FOR SHARE in a read-only transaction, verified). What the check adds is a rejection before a connection is opened, an error naming the clause and the fix instead of a generic driver message, and consistency: FOR UPDATE used to be refused only incidentally, because UPDATE is on the denylist for data-modifying CTEs.

    This is matched as a clause, not as a keyword, and the distinction is the point. SHARE alone is a legal column name — sqlparse types the share in SELECT share FROM cap_table as a Keyword — so adding SHARE to the denylist above would reject a real query. A flat set of words is the wrong shape for a rule about multi-word clauses, so safety.py collects the keyword sequence and matches FOR [NO] [KEY] UPDATE|SHARE against it. A FOR belonging to something else (FOR XML, FOR JSON, FOR SYSTEM_TIME) falls through, because its target is not a lock strength.

Every query that passes is wrapped as SELECT * FROM (<your query>) AS limited_query LIMIT <row_limit>, so an unbounded scan cannot flood the client's context. The wrap is unconditional: a LIMIT in your own query narrows the inner result, but row_limit still caps what comes back, so LIMIT 500 with the default row_limit returns 100 rows. row_limit is itself clamped to 1000, so raising it cannot defeat the guard.

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_timeout on PostgreSQL, max_execution_time on MySQL, and a progress-handler deadline on SQLite. Configured by QUERY_TIMEOUT_SECONDS, applied in db.py.

  • A read-only transaction. Reads run through BEGIN READ ONLY on PostgreSQL, SET SESSION TRANSACTION READ ONLY on MySQL, and PRAGMA query_only on 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 like pg_read_file() that no keyword check reliably catches.

validate_migration is deliberately the inverse: it rejects SELECT statements, and it never runs either script. You get the parsed statement types back and run the DDL yourself.

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

table_not_found

No such table. Carries the nearest matching names the database does have

sql_error

The database rejected the query — a missing column, a type mismatch, bad syntax

query_timeout

The statement hit QUERY_TIMEOUT_SECONDS and was cancelled

unsafe_query

Blocked by safety.py. The hint names the specific rule that fired

invalid_argument

An argument out of range, such as row_limit below 1

missing_argument / conflicting_arguments

suggest_index needs exactly one of query or table_name

comments_not_allowed / unparsable_sql / select_in_migration

validate_migration rejected a script

Two details worth knowing. Errors are raised as FastMCP ToolError, which is the only error type that survives a server configured with mask_error_details=True — reasonable hardening for an HTTP deployment, and it would otherwise reduce every message above to Error calling tool. And sql_error reports the query you sent, not the row-limit wrapper safety.py builds around it, so the SQL in the message is SQL you can act on.

Internally these stay Python exceptions. errors.py defines ToolInputError, a ValueError subclass whose str() is the plain message, so composition between modules and direct Python use both keep working; conversion happens only at the tool boundary in server.py.

Quickstart

Requires Python 3.11+ and uv.

uv sync
uv run python tests/seed_test_db.py   # creates sample.db
uv run pytest                         # 151 tests, no external database needed
uv run server.py                      # stdio transport

If uv is not on PATH, prefix with py -m (py -m uv sync).

The default database is sqlite:///sample.db. Point at your own with DATABASE_URL:

$env:DATABASE_URL = "postgresql+psycopg2://user:password@localhost:5432/example"
$env:DATABASE_URL = "mysql+pymysql://user:password@localhost:3306/example"
$env:DATABASE_URL = "sqlite:///C:/data/example.db"

Percent-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.py

Then 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/mcp

Run /mcpAuthenticate for the OAuth flow; tokens are cached and refreshed automatically.

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 — no extra setup for anyone who clones the repo.

MCP Inspector

npx @modelcontextprotocol/inspector

Use 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, prompts like these work directly:

  • "What tables exist, and which ones are missing primary keys?"

  • "Show me 5 rows from orders with the highest total."

  • "Why is this query slow? SELECT * FROM orders WHERE customer_id = 42"

  • "Which foreign keys in this database lack indexes? Give me the CREATE INDEX statements."

  • "Draft a migration adding a status column to orders, 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.

Configuration

Variable

Default

Notes

DATABASE_URL

sqlite:///sample.db (stdio only)

Required when MCP_TRANSPORT is not stdio; startup fails loudly otherwise

MCP_TRANSPORT

stdio

stdio, streamable-http, or sse

MCP_HOST

127.0.0.1

HTTP transports only

MCP_PORT

8000

HTTP transports only

QUERY_TIMEOUT_SECONDS

15

Upper bound on any single statement; must be a positive integer

MCP_AUTH_TOKEN

Required when MCP_TRANSPORT is not stdio. Minimum 32 characters

MCP_ALLOW_UNAUTHENTICATED

false

Explicit opt-out of the token requirement, for trusted networks only

The sqlite fallback exists for local development only. config.py raises RuntimeError: DATABASE_URL must be set when serving over HTTP rather than silently serving an empty local file from a deployment — a failure mode that otherwise surfaces much later as a confusing unable to open database file.

.env is loaded at startup by config.py, so copying .env.example to .env works as that file instructs. The file next to config.py is read first and a .env in the working directory second, because an MCP client launches this server with a working directory you do not control. Real environment variables always win over both, so a host's secret store overrides the file without editing it. .env stays gitignored.

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.py

An 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 rather than coming up unprotected. Clients send it 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.

For real user identity rather than one shared secret, swap SharedSecretVerifier for one of FastMCP's OAuth providers. See DEPLOYMENT.md for FastMCP Cloud / Prefect Horizon deployment, where OAuth 2.0 with dynamic client registration and PKCE is handled by the platform.

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 pytest

151 tests covering the safety layer, value serialization, read-only enforcement and timeouts, HTTP authentication, inspector, explain, index suggestions, schema health, migration validation, error reporting, and the tool wrappers. Each uses a temporary SQLite database, so the suite needs no credentials and no running server.

SQLite cannot produce the types that break a real driver -- it has no NUMERIC and returns str/int for nearly everything -- so tests/test_serialization.py exercises Decimal, datetime, UUID, and binary values directly rather than through a query. A PostgreSQL and MySQL test path is the next gap worth closing.

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
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
tests/            pytest suite over temporary SQLite databases

Design notes and limits

  • Migrations are never executed. The server returns schema context and validates scripts; you run the DDL. That keeps the connection read-only in practice, not just by policy.

  • Query-mode suggest_index is tuned to SQLite plan output, which exposes a detail column containing SCAN. On PostgreSQL and MySQL the plan is still returned in full, but automatic recommendations will usually be empty — use table_name mode there, which works from foreign-key metadata on every dialect.

  • The keyword denylist matches whole tokens, not substrings, so a keyword that merely contains a blocked word is unaffected: GROUPING SETS and SELECT grant_date FROM permissions both pass, where a naive "SET" in sql check would reject the first and "GRANT" in sql the second.

  • Each denylist entry has to earn its place. INTO does: SELECT * INTO archive FROM users has statement type SELECT but creates a table, so only the keyword scan catches it. SET did not, and was removed — every statement that changes session state (SET ROLE, SET search_path, even SET x = (SELECT 1)) parses as type UNKNOWN and is refused by the type check, while UPDATE ... SET inside a data-modifying CTE is caught by UPDATE. All it added was rejecting SELECT set FROM config, since sqlparse types a bare set as a keyword rather than a column name.

  • Four of the twelve entries are load-bearingINSERT, UPDATE, DELETE and INTO are reachable in a statement whose type is SELECT, the first three through Postgres data-modifying CTEs. The rest are redundant, because a CTE accepts only INSERT, UPDATE, DELETE and MERGE, never DDL: no legal SELECT-typed statement can contain DROP. They stay as a second line if sqlparse type detection ever regresses.

  • A few read-only statements are rejected for lack of a statement type. sqlparse reports UNKNOWN for a parenthesized (SELECT 1), for VALUES (1) and for TABLE users, and the type check refuses anything that is not SELECT. All are harmless; none is currently accepted.

    This is left as a rejection rather than fixed by unwrapping, because the incidence is near zero — a caller writes the plain SELECT — and widening what the parser accepts to serve a query nobody sends is a poor trade against the risk. What was fixed is the explanation: these reasons and a genuine write both contain Only SELECT, so they used to collapse to the same hint, and a caller that sent a read was told the server is read-only and pointed at validate_migration. Got: UNKNOWN now carries its own hint naming the shapes that cause it. A rejection the caller can recover from in one turn is an acceptable cost; a rejection that misdiagnoses itself is not.

  • The row cap is a context guard, not a performance guard. A heavy aggregate still runs in full on the database before its output is limited. QUERY_TIMEOUT_SECONDS is what bounds the cost of that work.

  • Binary columns are summarised, not returned. Values up to 256 bytes arrive hex-encoded, which suits BINARY(16) UUIDs and digests; anything larger is reported as a size only. Inlining a multi-megabyte blob would consume the context window it was sent to.

  • Wide NUMERIC values arrive as strings. A decimal that fits a float is a JSON number so it sorts and compares correctly; one that does not keeps its exact digits rather than being silently rounded.

Available Tools

7 tools
execute_queryB

Execute one validated, read-only SQL SELECT query.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes
row_limitNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.1/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters2/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
sqlYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.5/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
table_nameNo
include_sample_dataNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.5/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness3/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines3/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.3/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters4/5

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.

Purpose4/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryNo
table_nameNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.1/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness2/5

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.

Parameters2/5

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.

Purpose5/5

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.

Usage Guidelines2/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
up_sqlYes
down_sqlYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.8/5.0
Behavior3/5

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.

Conciseness5/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose5/5

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.

Usage Guidelines3/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
table_nameNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.9/5.0
Behavior2/5

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.

Conciseness5/5

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.

Completeness2/5

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.

Parameters1/5

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.

Purpose5/5

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.

Usage Guidelines2/5

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. Dates show when Glama detected each change.

  1. 7 tool updatesv0.1.0
    • First observedexecute_query
    • First observedexplain_query
    • First observedexplore_schema
    • First observedmigration_context
    • First observedsuggest_index
    • First observedvalidate_migration
    • First observedvalidate_schema

TDQS

A3.5/5.0
Disambiguation4/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness5/5

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

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Connectors

  • 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.

  • The Instant MCP server is a wrapper around the Instant Platform SDK that enables creating, managing, and updating InstantDB applications directly within an editor. It provides tools for fetching rules files for LLMs, retrieving and pushing app schemas, managing permission rules, and executing database queries. Key capabilities include schema management (get-schema, push-schema), permission management (get-perms, push-perms), query execution, and listing recent query history.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    An 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.
    107
    -
  • F
    license
    Not graded
    quality
    F
    maintenance
    A production-ready MCP server that transforms natural language into safe, executable SQL queries with multi-database support and intelligent schema analysis.
    1
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A read-only MCP server for PostgreSQL that enables safe database introspection and querying via natural language.
    751
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Read-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.
    37
    MIT

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/AYUSH-148/DB-Explorer-MCP'

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