A repository containing multiple MCP servers: data_visualizer (SQLite data querying and chart generation) and sqlite_explorer (SQLite database schema and query exploration)
This repository contains two separate MCP servers with significant quality gaps. The `data_visualizer` server has 3 tools (read_table, run_query, generate_chart) and the `sqlite_explorer` server has 2 tools (read_table, run_query), with one resource (read_schema). Tool definitions are present but suffer from: (1) SQL injection vulnerabilities with no input sanitization; (2) minimal, generic descriptions lacking LLM-optimization and WHEN-to-use guidance; (3) complete absence of error recovery guidance (raw Exception messages only); (4) no parameter constraints (enums, formats, ranges); (5) no output schema documentation; (6) duplicate tool names across servers (read_table, run_query) causing ambiguity and LLM confusion. The generate_chart tool makes unsafe assumptions about DataFrame structure (uses iloc[:, 0] and iloc[:, 1]) without validation. Naming is acceptable (verb-noun pattern), but lack of schema formality and error handling prevents production readiness.
Generate a chart (bar, line, pie) from a SQL query and return the image path.
Read all rows from a specific table.
reads all rows from a given table
Run a raw SQL query on the database.
executes a given SQL query and returns the results.
SQL injection vulnerability: run_query tool accepts raw SQL strings and executes them via cursor.execute(query) with no sanitization. LLMs can be prompted to construct malicious payloads (e.g., 'DROP TABLE users'). Both servers affected.
Duplicate tool names (read_table, run_query) across two servers cause LLM ambiguity. When both servers are registered, the LLM cannot reliably distinguish which read_table to call.
Descriptions are under 20 characters and provide minimal context. 'Read all rows from a specific table.' and 'executes a given SQL query and returns the results.' lack WHEN-to-use guidance, prerequisites, and expected output format. LLMs struggle to select tools without rich descriptions.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | F | 40 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 32 | - | v1 |
No output schema documentation. Tools return free-form strings (e.g., '\n'.join(map(str, rows))) without structure. LLMs cannot parse or chain results predictably. For example, run_query returns 'Query Results:\n(row1)\n(row2)\n...' as unstructured text, forcing LLMs to parse brittle output.
run_query has no input schema documented. The query parameter accepts any string with no validation, format constraints, or length limits. LLMs can pass multi-gigabyte queries or statements that hang the server.
Error handling provides no recovery guidance. Raw exception messages like 'Query failed: {str(e)}' or 'Error reading table: {str(e)}' are returned but do not guide the LLM on next steps. Per pattern:recovery-guide, errors must tell the agent what to do next (e.g., 'Invalid table name. Call list_tables() to see available tables.').
generate_chart makes unsafe assumptions about DataFrame structure: labels = df.iloc[:, 0] and values = df.iloc[:, 1] assume exactly 2 columns in the first two positions. If query returns 1 column, 3+ columns, or non-numeric second column, the tool silently fails or crashes. No validation of data types or shape.
Parameters lack enum constraints and format specifications. The chart_type parameter in generate_chart accepts 'bar', 'line', 'pie' but is defined as a free-form string. Without an enum in the schema, LLMs can hallucinate invalid types like 'scatter', 'histogram', or 'box_plot'.
No pagination or result limits. read_table and run_query can return thousands of rows as a single string, exhausting context windows and wasting tokens. Per pattern:paginated-result, these tools should accept limit and offset/cursor parameters and return result counts.
Parameter descriptions are missing or trivial. table_name is described as 'The name of the table to read from', no guidance on valid values, case sensitivity, or how to discover table names. query parameter has no description at all in sqlite_explorer/main.py (inline docstring only).