A read-only MCP server that lets an LLM explore a SQL database safely — scoped tool surface, redacted columns, and responses shaped so the model can't guess.
Four well-named, verb-prefixed tools with clear, actionable descriptions (avg 156 chars, within 10 - 1024 baseline). All tools have complete Zod schemas with typed parameters and descriptions. Strong security posture: read-only operations, input validation (assertSelectOnly), and redaction of sensitive columns. Descriptions explain WHAT, WHEN, and WHY. However, output schemas are not formally documented, responses are rendered as TSV text rather than structured objects, forcing LLMs to parse unstructured output. Error messages are helpful but inconsistent in format. No pagination support for large result sets (MAX_ROWS cap exists but not exposed as a parameter).
Show the columns of one table: name, declared type, nullability, and primary key. Columns whose values are withheld are marked, so you know they exist even though you cannot read them.
List every table in the database with its row count. Start here — it is the cheapest way to learn the shape of the data before querying.
Run a read-only SELECT against the database and return the rows. Only SELECT (or WITH ... SELECT) is accepted — this server has no capability to modify data, so do not attempt writes. Results are capped, and truncation is always reported.
Return a few rows from a table so you can see real values before writing a query. Prefer this over guessing at the data format.
Output schemas not formally documented. Responses are rendered as TSV text strings rather than structured JSON objects. LLMs must parse unstructured output, increasing error likelihood and wasting tokens on parsing logic.
No pagination support. query() and sample_rows() cap results at MAX_ROWS (appears to be 100) but do not expose this as a parameter or return a next_cursor/total_count. Large result sets are silently truncated without clear guidance on how to fetch remaining rows.
Error handling is text-based and inconsistent. Some errors suggest recovery steps (e.g., 'Try search_users()'), but others are raw SQLite messages. No structured error classification (retryable vs. user-fixable vs. fatal) to guide agent behavior.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 60 | 2026-07-28+ | v2 |
query() accepts arbitrary SELECT statements with minimal validation. assertSelectOnly() guards against writes, but no protection against expensive queries (Cartesian joins, full table scans) that could hang the server or exhaust resources.
Redaction of sensitive columns is pattern-based (environment variable) but not discoverable via tool output. LLMs cannot know which columns are redacted until they call sample_rows() or query(). describe_table() marks redacted columns, but this requires an extra call.