An MCP server that puts a database behind five validated tools, with a propose/confirm gate on every write
Safe Database demonstrates strong naming conventions (verb-noun pattern: list_tables, describe_table, run_query, propose_change, confirm_change, list_pending_changes) and comprehensive descriptions (100-250 chars each, well above the 34-char minimum). All tools have clear, actionable descriptions explaining WHAT they do, WHEN to use them, and consequences. Input schemas are present with type definitions. However, parameter descriptions are minimal or absent in the schema definitions visible in server.py, the 'table' parameter in describe_table has a description, but 'sql' parameters lack detail about constraints (e.g., no mention of single-statement-only requirement in the schema itself, only in the docstring). Output schemas are not formally documented in the code; responses are JSON-serialized but structure is inferred from docstrings. Error handling is present (SqlRejected, BackendError, ProposalError exceptions) but error messages lack recovery guidance. The propose/confirm pattern is excellent for safety but not annotated with destructiveHint or idempotentHint metadata.
Commit a change that propose_change previewed. The statement is re-validated and re-executed, then committed. The change_id must be one propose_change returned, still unused and not yet expired. There is no way to commit a write without one.
Show the columns, types, constraints and foreign keys of one table. Read-only. Runs immediately, no confirmation needed.
List the proposed changes that are still awaiting confirmation. Read-only. Shows each pending change_id, its SQL and how long it has left before it expires.
List every table in the database with its row count and columns. Read-only. Runs immediately, no confirmation needed.
Preview a write without committing it, and get a change_id back. The statement is validated, executed inside a transaction to compute a real preview, then rolled back. Nothing is written. Show the returned preview to the human and call confirm_change with the change_id to commit it. The id is single use and expires. Accepts one INSERT, UPDATE or DELETE against an existing table. UPDATE and DELETE must have a WHERE clause.
Parameter descriptions missing from JSON Schema definitions. The 'sql' parameter in run_query, propose_change lacks constraint documentation (single SELECT only, no DDL/PRAGMA/ATTACH) in the schema itself, only in docstrings. LLMs cannot read docstrings; they parse schema descriptions.
Output schemas not formally documented. Tools return JSON via _json() helper, but the structure (fields, types, nested objects) is not declared in tool definitions. Agents cannot plan downstream calls without knowing response shape.
Error messages lack recovery guidance. SqlRejected and BackendError exceptions are caught and converted to ToolError strings, but error text does not suggest next steps (e.g., 'Invalid SQL: only SELECT allowed. Try run_query() instead.').
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 60 | 2026-07-28+ | v2 |
Run one read-only SELECT and return the rows. Only a single SELECT is accepted. Anything else - a write, DDL, PRAGMA, ATTACH, a stacked statement, a comment - is rejected before it reaches the database, and the connection used here is opened read-only anyway.
Tool annotations missing. The propose_change and confirm_change tools implement a confirmation pattern but lack destructiveHint and idempotentHint metadata to signal to clients that these are write operations with side effects.
No pagination or result limits documented. list_tables and list_pending_changes do not declare max result counts or pagination parameters, risking context window exhaustion on large databases.