sql_tool
Provides a read-only SQL tool server backed by DuckDB: fetches dataset schemas and executes generated SQL queries against the data, with server-side guardrails enforcing read-only access, blocked keywords, and single-statement execution.
Provides a Python analysis tool server backed by pandas for statistics SQL expresses poorly, exposing a fixed set of safe operations such as compute_correlation and compute_stat on the dataset's columns.
Click on "Deploy 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_toolget the schema for sales.csv"
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.
DataPilot — AI Data Analyst Agent
Multi-agent system: ask a question about a dataset in plain language, agents plan, generate SQL, execute it (read-only, via MCP), validate the result, and explain it back in plain language — with guardrails, a Python analysis path, charts, memory, and observability layered in as the build progresses.
Builds on the same orchestration/MCP/guardrails skeleton as the Job/Resume Intel Agent project — domain swapped, agent pattern reused.
Status: Step 9 — Eval harness ✅
tests/eval_harness.py runs a benchmark of queries against the full
graph and checks results two ways:
Ground truth checks — numeric results (total revenue, grouped sums, correlation) are compared against values computed independently with pandas directly from the CSV, not against anything the agent itself produced. A must-fail case (the nonsense question) and a must-chart case are also checked.
Row-count consistency — a systematic version of the bug caught by hand in testing (Run 7, Q4: SQL correctly returned 20 rows, but the Interpreter's final answer said "13 unique orders"). This check scans every answer for stated counts ("13 orders", "4 regions", etc.) and flags it if none of them match the actual row count. Run on every case except the expected-failure one.
python -m tests.eval_harnessThe Planner's own LLM-based NEEDS_PYTHON judgment proved unreliable
(flagged a nonsense shoe-size question as needing Python, missed both
real correlation questions). Fixed with a deterministic keyword override
in planner.py: any question mentioning "correlation" routes straight
to the new Python Agent instead of SQL — no more fighting DuckDB's
GROUP BY rules for something pandas does in one line.
app/mcp_servers/python_tool_server.py exposes compute_correlation
and compute_stat — deliberately not arbitrary code execution. The
original project plan called for a sandboxed run_python(code, df)
tool with Docker isolation; that's real future work, but exec()-ing
LLM-generated code without a sandbox in place is exactly the kind of
shortcut worth avoiding rather than "temporarily" allowing. This is a
small, fixed set of safe pandas operations instead — solves the actual
problem (Q6/Q3 correlation questions) without that risk.
Python Agent has no retry loop — a failure means the deterministic
column-lookup couldn't confidently map the question to two known
columns, and retrying the same lookup wouldn't help. The Validator
branches on a new used_python flag: Python-path results skip the
SQL-specific checks (column-match, NULL detection) since the tool
itself already validates its inputs.
Related MCP server: mcp-sql
Architecture (full plan)
Planner — decides if the question needs SQL, Python analysis, or both
SQL Agent — fetches schema + generates + executes SQL, all via MCP
Python Agent (step 6) — for stats SQL can't express (correlation, etc.)
Validator — checks results are non-empty/error-free; will drive a retry loop back to SQL Agent (step 5)
Interpreter — compiles plain-language answer, or explains failure honestly instead of hallucinating from bad data
MCP layer: sql_tool server now (get_schema, execute_sql). python_tool
and chart_tool servers added in steps 6-7, same MCP pattern.
Guardrails: SQL safety filter lives server-side in sql_tool_server.py
(read-only enforcement, blocked keywords, single-statement only) —
deliberately at the tool boundary, not trusted to the LLM's output alone.
Step 4 adds a broader guardrail layer (prompt-injection-aware, output
validation, loop/budget caps).
Memory: session state now; ChromaDB later for business-term RAG (e.g. "revenue = gross sales - returns") — same idea as Job Intel Agent's long-term memory step.
Observability: Langfuse tracing (step 8, shared plan with Job Intel Agent).
Build order
✅ Domain + sample dataset (
data/sales.csv) + fixed test queries✅ State schema + baseline graph
✅ MCP
sql_toolserver (DuckDB, read-only enforced)✅ Guardrail proven (
tests/test_sql_guardrail.py, LLM-bypassing)✅ Self-correction retry loop (bad SQL → re-plan → re-execute)
✅ Python analysis tool (MCP-wrapped) for non-SQL-expressible questions
✅ Chart tool (MCP-wrapped) + visualization logic
⬜ Memory (ChromaDB for business-term RAG) + observability (Langfuse)
✅ Eval harness (this step)
⬜ UI (Streamlit)
Fixed test queries
Simple aggregation — total revenue
Group + filter — sales by region, Electronics only
Python-needed — correlation between discount and quantity (tests Planner routing
needs_python, even though the Python tool doesn't exist yet)Adversarial — SQL-injection-style instruction embedded in the question (tests the
sql_tool_server.pyread-only enforcement)Nonsense / no-match — question the dataset can't answer (tests Validator catching a zero-row or malformed result honestly)
Setup
Two terminals — Ollama needs to stay running while the script runs.
Terminal 1:
ollama serveTerminal 2:
pip install -r requirements.txt
cp .env.example .env
ollama pull llama3.2:3b # only needed once
python -m app.mainSame
mcp<2.0.0pin as Job Intel Agent — MCP 2.x renamedFastMCPtoMCPServer. If you seeModuleNotFoundError: No module named 'mcp.server.fastmcp', runpip install "mcp<2.0.0" --force-reinstall.
How to tell if the SQL tool is working
sql_errorisNoneandsql_resulthas rows → real DuckDB execution worked[BLOCKED_SQL]in the trace → the read-only guardrail caught something (expected and correct for Q4, the adversarial query)[MCP_CLIENT_ERROR]→ the MCP server process itself failed to start — checkduckdbis installed
Project structure
data/
sales.csv # sample dataset used by all test queries
app/
state.py # shared GraphState
llm.py # centralized LLM client (Ollama default)
mcp_client.py # generalized MCP client (works with any server script)
graph.py # LangGraph wiring
main.py # entrypoint, runs 5 fixed test queries
agents/
planner.py
sql_agent.py # calls MCP sql_tool
python_agent.py # calls MCP python_tool (correlation questions)
validator.py
interpreter.py
mcp_servers/
sql_tool_server.py # FastMCP server, DuckDB-backed, read-only enforced
python_tool_server.py # FastMCP server, pandas-backed, fixed safe stats only
chart_tool_server.py # FastMCP server, matplotlib-backed, fixed bar chart only
tests/
test_sql_guardrail.py # bypasses the LLM, proves the read-only guardrail directly
eval_harness.py # benchmark: ground-truth checks + row-count consistency checkGenerated charts are written to outputs/ (gitignored — regenerate by
running python -m app.main, don't expect them in the repo).
Known limitations (honest, as of this commit)
Small-model self-correction is limited. With
llama3.2:3b, a genuinely malformed query (missingFROMclause) sometimes gets retried with the identical broken SQL rather than a fix — the retry cap still kicks in and the system fails honestly rather than hallucinating, but it doesn't always self-correct. A larger model would likely do better here; this is a model-capability limit, not a guardrail or architecture gap.Column extraction for the Python Agent is a hardcoded candidate list (
_CANDIDATE_COLUMNSinpython_agent.py), not fetched dynamically from the schema. Fine for this fixed dataset; would need generalizing for arbitrary uploaded datasets.No arbitrary Python code execution. By design (see step 6 above) — a real sandboxed
run_python(code, df)tool is future work, not something this project currently does.Routing between SQL and Python is keyword-based, not learned. It's deliberately conservative (SQL is the default; only a correlation keyword match routes to Python) rather than trusting the small LLM's own judgment, after that judgment proved unreliable twice in testing (once flagging a nonsense question as Python-only with no SQL fallback). A broader set of "SQL can't express this well" cases (regression, forecasting) isn't covered yet.
The Interpreter's narration isn't fact-checked against the data. Seen once in testing: a correct 20-row SQL result got summarized as "13 unique orders." The retry loop and guardrail both worked correctly — this is a separate LLM counting/narration error in the final answer step. Not currently caught by anything in the pipeline; a real fix belongs in an eval harness (step 9) that checks stated facts against the actual result data, not a one-off patch.
This server cannot be deployed
Maintenance
Related MCP Connectors
Query, join, profile, clean and convert CSV/JSON/Parquet with server-side DuckDB over MCP.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
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.
Related MCP Servers
- AlicenseAqualityCmaintenanceEnables DuckDB database interaction through MCP, supporting SQL queries, table creation, and schema inspection with optional read-only mode.1MIT
- AlicenseNot gradedqualityCmaintenanceAn extensible read-only MCP server for SQL databases, enabling schema exploration and safe SELECT queries via tools like list_schemas, list_tables, describe_table, and execute_query.MIT
- AlicenseNot gradedqualityCmaintenanceA read-only MCP server that enables LLMs to safely explore and query any SQLite database via natural language. It exposes tools for listing tables, describing schemas, and executing SELECT/WITH queries with built-in safety guards like write prevention and row limits.MIT
- AlicenseNot gradedqualityCmaintenanceRead-only MCP server for SQL databases (SQLite/PostgreSQL) that enables listing tables, describing schemas, and executing SELECT queries with safety guardrails.MIT