sql-explorer
Provides read-only exploration and querying of SQLite databases, including listing tables, describing schemas, running SELECT queries with row caps and timeouts, and enforcing blocked columns that cannot be read or used in filters.
Click on "Install 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., "@sql-explorerWhich city has spent the most in total?"
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.
sql-explorer
An MCP server that lets an AI assistant answer questions about a SQLite database in plain language — without being able to damage it or read the parts you have marked off limits.
Ask "which city spends the most?" and the model discovers the tables, reads the schema, writes its own SQL, and answers. It never gets a chance to write, delete, or read a blocked column.
You: Which city has spent the most in total?
Claude: Lyon, with 14 orders totalling 2,840.03.
You: Give me the email and phone of every customer.
Claude: I can't — the server refuses access to customers.email.Why it exists
Handing a language model a database connection is a genuinely risky idea. Three things can go wrong:
Risk | How it is handled |
It issues | Only statements beginning with |
It reads personal data | A SQLite authorizer denies configured columns inside the engine |
It returns millions of rows | Results are capped at 500 rows and queries are aborted after 5 seconds |
The second one is the interesting one. The blocked columns are not filtered
out of the SQL text — SQLite asks permission before reading any column and
the server answers. That means a query which never mentions email, but
filters on it to leak addresses one guess at a time, is refused too:
SELECT name FROM customers WHERE email LIKE '%ana%'
-- Query refused: access to customers.email is prohibitedThere is no phrasing that gets around it, because the check does not look at the phrasing.
Related MCP server: safe-sql-mcp
Quick start
Requires Python 3.12+ and uv.
git clone <your-repo-url>
cd mcp_server
uv sync
uv run python scripts/make_sample_db.py # builds the practice database
uv run pytest # 28 testsTo poke at the tools by hand in a browser (needs Node.js):
uv run mcp dev src/mcp_server/__init__.pyUsing it with Claude Desktop
Settings → Developer → Edit config, then add:
{
"mcpServers": {
"sql-explorer": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/mcp_server", "mcp-server"],
"env": {
"SQL_EXPLORER_DB": "/absolute/path/to/your.db",
"SQL_EXPLORER_BLOCKED_COLUMNS": "users.password_hash, users.ssn"
}
}
}
}Restart the app afterwards. Editing the file while it is running does not work — the app overwrites it on exit.
Configuration
Variable | Default | Meaning |
|
| Which SQLite file to serve |
|
| Columns to deny, as |
|
|
|
|
| Port to listen on, HTTP transport only |
| none | Bearer token required by the HTTP transport. No default, and no server without it |
A value that is not shaped like table.column makes the server refuse to
start. A typo in a security setting should be loud, not silently ignored.
Tools
Tool | Purpose |
| Names of every table |
| Columns of one table: name, type, whether required |
| Runs a |
| Liveness check |
run_query reports truncated: true when the result hit the row cap, so a
partial answer is never mistaken for a complete one.
Resources
URI | Content |
| Every table with its columns, one line each |
| One table in detail: column name, type, whether required |
Columns the server refuses to read are marked [blocked]:
customers(id, name, email [blocked], phone [blocked], city, signup_date)That is deliberate. The protection does not depend on secrecy — the
authorizer refuses regardless of what the caller knows — so naming the
blocked columns costs nothing and saves a wasted SELECT * that would only
be rejected.
schema://{table} is a template: one definition serves one address per
table, whatever tables the database turns out to have.
Prompts
Prompt | What it does |
| Walks one table: size, distributions, gaps, outliers |
| Audits for duplicates, orphans, impossible values, suspicious uniformity |
Prompts return instructions, not data. They describe how to drive this server well — read the schema first, aggregate rather than list rows, do not reach for blocked columns — so a user who does not know the database can still ask a good question.
Running it over HTTP
By default the server runs on stdio: a client launches it as a child process and they talk over pipes. Nothing needs authenticating, because the operating system already decided who may start the process.
Set SQL_EXPLORER_TRANSPORT=streamable-http and it becomes a web service
instead — and then anyone who can reach the port can talk to it. So a token
is mandatory:
SQL_EXPLORER_TRANSPORT=streamable-http \
SQL_EXPLORER_TOKEN=$(python -c "import secrets; print(secrets.token_urlsafe(32))") \
uv run mcp-serverEvery request must carry it:
curl -X POST http://127.0.0.1:8000/mcp \
-H "Authorization: Bearer $SQL_EXPLORER_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'Anything else gets 401 and never reaches a tool, a resource, or the
database.
With no SQL_EXPLORER_TOKEN set, the server refuses to start. It does
not fall back to running open with a warning printed somewhere. A missed
warning leaves the database published while everything looks healthy, which
is the worst kind of failure: silent, and indistinguishable from success.
The listener binds 127.0.0.1. Read the security note below before
changing that.
Before exposing this to a network
TLS is not optional. A bearer token over plain HTTP travels in clear text; anyone between the client and the server can read it and reuse it. Put this behind a reverse proxy that terminates HTTPS.
A shared token is not OAuth. The MCP specification calls for OAuth 2.1 for remote servers, which gives per-user identity, scopes and revocation. One shared secret gives none of those: every caller is the same caller, and rotating it locks everyone out at once. That is a reasonable trade for a single-user or small-team service, and the wrong one for a public deployment.
Rate limiting is absent. Nothing here slows down a caller hammering expensive queries.
Design notes
Why the schema is both a tool and a resource. describe_table returns
structured rows for a model to compute with; schema://customers returns a
readable page a person can attach to a conversation. The same information in
two shapes, because tools and resources are consumed differently. The tool is
also the reliable path, since resource support still varies between clients.
Why SELECT * is refused. The expansion includes the blocked columns,
so the authorizer denies it. The model has to name the columns it wants.
Slightly more work for it; no accidental leaks.
Why describe_table interpolates its argument. PRAGMA table_info
cannot take a bound parameter, so the table name goes into the statement
directly — after being checked against the real table list. An allowlist,
not an escape.
Limitations
SQLite only. Postgres or MySQL would need a different authorization approach, since the authorizer callback is a SQLite feature.
Blocking is per column, not per row. There is no way to say "only this user's rows".
The 5 second timeout is wall clock, not CPU time.
Running the tests
uv run pytest -vTwenty-eight tests in three files.
tests/test_guards.py covers every safety guard: refused statements,
refused columns including the filter-only leak, truncation, unknown table
names, and the query timeout.
tests/test_resources_and_prompts.py covers what the resources render and
what the prompts say, including that blocked columns keep their [blocked]
marker and that the prompts still name the tools and URIs they rely on.
tests/test_http_auth.py covers the HTTP door: a correct token passes, a
missing header, a wrong token, a bare token without the Bearer prefix and
a truncated token are all refused, and the server refuses to start in HTTP
mode with no token set. Each refusal asserts the request never reached the
endpoint, not merely that the status was 401.
tests/conftest.py builds the sample database if it is missing, so the
suite runs on a fresh clone.
The guard and resource tests call the server's functions directly rather than through an MCP session, so they would not catch a decorator being removed.
Available Tools
4 toolsdescribe_tableA
Describe the columns of one table: name, type, and whether it is required.
| Name | Required | Description | Default |
|---|---|---|---|
| table | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description must bear the entire burden of disclosing behavioral traits. It does not mention whether the operation is read-only, whether it can fail on non-existent tables, or any side effects. While the tool name and wording imply a harmless read, the description offers no explicit transparency about error handling or assumptions, leaving an agent potentially uninformed.
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, direct sentence that immediately communicates the core function and output fields. There is no filler or redundancy, and it is appropriately sized for a one-parameter utility tool.
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 and the simplicity of the tool, the description covers the essential information: it states the input (table) and the output (column details). It lacks explicit notes on error cases or prerequisites, but for a straightforward metadata query, this is largely sufficient. The existence of an output schema reduces the need to describe return values in detail.
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 schema provides only a parameter name 'table' with type string, and schema description coverage is 0%. The description adds the semantic that the parameter refers to the table whose columns are to be described, which is valuable. However, it does not specify format constraints (e.g., whether it must match an existing table exactly, case sensitivity, or quoting rules). It partially compensates for the schema gap but could be more 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 states a specific verb (describe) and resource (table columns), and clearly specifies the returned attributes: name, type, and required status. This distinguishes it from sibling tools like list_tables (which lists table names) and run_query (which executes queries). An agent can accurately select this tool for schema inspection without ambiguity.
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 the tool should be used when needing column-level metadata for a specific table, but it does not explicitly state when to prefer it over alternatives or mention exclusions. For example, it does not say 'use run_query for filtering data' or 'use list_tables to see available tables.' The context is clear from the purpose, but explicit routing guidance is missing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_tablesA
List the names of every table in the database.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
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 states the core function but adds no extra context such as whether the operation is read-only, requires any permissions, or has side effects. While the purpose is clear and it is evidently a passive listing operation, the description does not explicitly guarantee that nothing is mutated or that it is safe to call repeatedly.
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 states exactly what the tool does with no superfluous content. Every word earns its place, and it is immediately scannable. It achieves maximum conciseness while preserving clarity.
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 that the tool has no parameters and an output schema exists (providing the return format), the description is complete for its purpose. It tells the agent what the tool does (lists table names) and nothing more is required for a successful invocation. There are no configuration details, prerequisites, or edge cases that need to be disclosed.
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 takes zero parameters, and the schema shows an empty properties object, so there is no parameter information to add. The baseline for 0 parameters is 4, and the description correctly reflects that no arguments are needed. It does not add anything about parameters because there are none, but it also does not create any confusion.
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 ('list') and a precise resource ('names of every table in the database'). It clearly distinguishes itself from siblings: 'ping' (health check), 'describe_table' (specific table schema), and 'run_query' (executes queries). An agent can immediately understand what this tool does and why it differs from the others.
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 gives no explicit guidance on when to use this tool versus alternatives. It does not mention that this should be used to discover tables before querying or describing them, nor does it note any limitations or exclusions. The usage context is only implied by the sibling names, not stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
pingA
Check that the server is alive.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations are absent, so the description must carry the burden of behavioral disclosure. 'Check that the server is alive' conveys the intent but does not mention whether it performs a network call, what it returns (though output schema exists), or that it is a read-only operation. The description is not misleading, but it adds minimal behavioral detail 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 concise clause, front-loading the core purpose with no extraneous words. Every word earns its place, making it highly efficient for an agent to parse.
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 zero-parameter health check with an output schema present, the description is complete. An agent knows that calling the tool requires no inputs and will return a result defined by the schema. No additional context is needed to invoke it correctly.
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 schema carries no parameter information to describe. The baseline is 4, and the description need not add any parameter documentation. It correctly implies no arguments are required.
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 'check' and the resource 'server is alive', making the tool's purpose unambiguous. It naturally distinguishes itself from sibling tools (list_tables, describe_table, run_query) which handle database operations, though it does not explicitly name them as 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?
The description provides no explicit guidance on when to use this tool versus the siblings. While it is evident that it is a health check and the siblings are for table/query operations, the description does not state 'use this to verify connectivity before running queries' or any other usage context. It relies on the agent to infer from the tool name and description.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
run_queryA
Run a read-only SELECT query and return the rows.
At most 500 rows come back; if the query matched more, "truncated" is true and you should add a LIMIT or aggregate instead. Queries running longer than 5 seconds are aborted.
| 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 provided, the description carries the full burden of behavioral disclosure. It discloses that the operation is read-only, caps results at 500 rows, sets the 'truncated' flag when exceeded, and aborts queries longer than 5 seconds. This covers safety, limits, and timeout behavior, which is substantial. It does not describe the exact response structure, but an output schema is present to handle that.
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 concise and front-loaded: the core action is stated first, followed by two constraint sentences that are directly actionable. Every sentence adds value (payload limit, truncation handling, timeout), with no fluff or repetition. It is well-structured and easy to scan.
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 a single parameter, and the description covers key operational aspects: read-only nature, row limit, truncation flag, and timeout. Given an output schema exists (for return format) and the tools is simple, the description is nearly complete. It lacks error-handling details, but those are often covered by the runtime rather than the description. Overall, it adequately equips an agent to invoke it correctly.
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. It implies that the 'sql' parameter is the SELECT query to run, but does not explicitly state 'sql contains the query'. For a single parameter named 'sql', this is reasonably inferred. The description adds minimal further semantics beyond the parameter name, but the connection is clear.
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 'Run' and the resource ('read-only SELECT query'), specifying exactly what the tool does. It differentiates itself from siblings like list_tables and describe_table by focusing on arbitrary SELECT queries, though it doesn't explicitly contrast with them. The action and scope are 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?
The description does not explicitly mention when to use this tool versus alternatives. It does provide practical guidance on handling truncation ('add a LIMIT or aggregate instead') and timeout, which helps the agent use the tool correctly. However, it assumes the agent understands the difference from ping/list_tables/describe_table, providing no explicit selection criteria.
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.
4 tool updates
v0.1.0- First observed
describe_table - First observed
list_tables - First observed
ping - First observed
run_query
TDQS
Each tool serves a unique purpose: health check, table enumeration, schema inspection, and query execution. No two tools overlap in functionality, making misselection nearly impossible.
All tool names follow a consistent verb_noun pattern (ping, list_tables, describe_table, run_query), using lowercase with underscores throughout. The naming clearly indicates the action and target.
With only 4 tools, the server is tightly scoped for its read-only SQL exploration purpose. Each tool is essential and covers the core workflow without unnecessary bloat.
The tool set covers the full exploration lifecycle: check health, list tables, describe schema, and run queries. Minor gaps like database-level metadata or caching are not needed for the stated purpose, so it is nearly complete.
Maintenance
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
Safe, read-only Postgres and MySQL access for AI agents. Audit log + column-level controls.
Query 40 databases from Claude, ChatGPT, or Cursor — on any device. Read-only, encrypted, audited.
Query PostgreSQL databases in plain English — LLM-generated, safety-validated SQL.
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables safe, read-only SQL access to SQLite databases for AI agents, allowing schema exploration and SELECT queries with defense-in-depth protections.3MIT
- FlicenseNot gradedqualityCmaintenanceEnables read-only SQL database access for AI assistants, allowing schema exploration and safe query execution without risk of data modification.-
- FlicenseNot gradedqualityCmaintenanceEnables AI assistants to query SQL databases safely with read-only access, allowing schema discovery and SELECT queries while blocking writes and DDL operations.-
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to explore and query SQLite databases through read-only tools, with defense-in-depth sandboxing preventing any data modifications.MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/ccervantes369/mcp-sql-explorer'
If you have feedback or need assistance with the MCP directory API, please join our Discord server