An MCP (Model Context Protocol) server that gives AI assistants (Claude, Cursor, etc.) the ability to analyze and optimize SQL queries against live Oracle and MySQL databases — without executing the query.
The AI connects to your database, collects execution plans and metadata, detects performance anti-patterns, and produces a structured optimization report with concrete CREATE INDEX / DROP INDEX recommendations and an optimized query variant.
You ask: "Why is <query> slow on <db_name>?" -- need this db accessiable
↓
AI calls analyze_oracle_query / analyze_mysql_query
↓
MCP validates SQL (security check — no DML/DDL allowed)
↓
Connects to your database (read-only)
↓
Runs EXPLAIN PLAN — never executes the query
↓
Collects metadata: table stats, index stats, column stats, optimizer params
↓
Detects anti-patterns: full table scans, Cartesian products, unused indexes
↓
Returns structured facts{} + analysis prompt to the AI
↓
AI writes optimization report with specific SQL recommendations with option to compare orgin query to optimize one
| Database | Versions | Tool |
|---|---|---|
| Oracle | 11g, 12c, 18c, 19c, 21c | analyze_oracle_query |
| MySQL | 5.7, 8.0+ | analyze_mysql_query |
- Read-only — never executes the query being analyzed, only
EXPLAIN PLAN/EXPLAIN FORMAT=JSON - Safe — two-phase SQL validation blocks all DML, DDL, DCL, and PL/SQL before any DB interaction
- Token-efficient — three output presets control how much data is returned to the AI (minimal ~5K, compact ~20K, standard ~40K tokens)
- No external dependencies — no data warehouse, no telemetry, no cloud services required
| Tool | Database | Purpose |
|---|---|---|
analyze_oracle_query |
Oracle | Full query analysis: plan + table/index/column stats + anti-pattern detection |
analyze_mysql_query |
MySQL | Full query analysis: plan + table/index stats + index usage from performance_schema |
compare_oracle_query_plans |
Oracle | Side-by-side cost comparison of original vs optimized query |
compare_mysql_query_plans |
MySQL | Side-by-side cost comparison of original vs optimized query |
get_slow_queries |
Oracle | List slowest queries from V$SQL ranked by elapsed time, CPU, or I/O |
analyze_query_by_sql_id |
Oracle | Fetch a query from V$SQL by SQL_ID and run full analysis |
get_slow_mysql_queries |
MySQL | List slowest queries from performance_schema |
analyze_mysql_query_by_digest |
MySQL | Fetch a query from performance_schema by digest and analyze it |
list_available_databases |
Both | List configured database presets and test connectivity |
get_knowledge_base_content |
— | Fetch embedded documentation for any topic |
Ask: "Why is this query slow?" or "Optimize this SQL" or "Compare 2 queries"
The AI will:
- Run
list_available_databases()to confirm the DB type - Call
analyze_oracle_query()oranalyze_mysql_query()with your SQL - Review the execution plan for full table scans, bad join methods, missing indexes
- Produce a report with specific
CREATE INDEXstatements and a rewritten query - Offer to verify the improvement with
compare_*_query_plans()
Example conversation:
You: "This query takes 45 seconds on our transformer_master DB. Can you optimize it?
SELECT o.id, c.name, SUM(p.amount)
FROM orders o
JOIN customers c ON o.customer_id = c.id
JOIN payments p ON p.order_id = o.id
WHERE o.status = 'OPEN' AND o.created_at > '2024-01-01'
GROUP BY o.id, c.name"
AI: [calls analyze_oracle_query("prod_oracle", sql)]
Query Performance Analysis
──────────────────────────
Issues Found:
• Full table scan on ORDERS (12.4M rows) — no index on (status, created_at)
• Full table scan on PAYMENTS (38M rows) — no index on order_id
• Hash join on CUSTOMERS is fine (indexed PK lookup)
Recommendations:
CREATE INDEX idx_orders_status_date ON orders(status, created_at);
CREATE INDEX idx_payments_order_id ON payments(order_id);
Estimated improvement: ~95% cost reduction (cost 84,200 → ~4,100)
Optimized query: [same query — indexes do the work, no rewrite needed]
Confidence: High
Growth Impact (2x/5x/10x rows):
| Growth | Est. Cost | Risk |
|--------|-----------|-----------------------------|
| 1x | 84,200 | baseline (current) |
| 2x | ~168,000 | critical — add indexes now |
| 5x | ~420,000 | system impact |
| 10x | ~840,000 | query will time out |
Shall I run compare queries plans to verify the improvement?
Ask: "What are the slowest queries running on prod right now?"
You: "Show me the top 5 slowest queries from the last 2 hours"
AI: [calls get_slow_queries("prod_oracle", limit=5, order_by="elapsed", last_hours=2)]
Rank 1 — sql_id: 6pvvuu6m6z9x4
Avg elapsed: 47.3s | Executions: 142 | Avg buffer gets: 2,847,000
Preview: SELECT o.id, c.name FROM orders o JOIN customers...
Rank 2 — sql_id: 9abcd1234xyz
Avg elapsed: 12.1s | Executions: 891 | Avg buffer gets: 445,000
...
You: "Analyze rank 1"
AI: [calls analyze_query_by_sql_id("prod_oracle", "6pvvuu6m6z9x4")]
→ Full optimization report
Ask: "Did adding that index actually help?"
You: "Compare the original query vs the version with the new index hint"
AI: [calls compare_oracle_query_plans("prod_oracle", original_sql, optimized_sql)]
Comparison Result:
┌─────────────┬──────────┬──────────┐
│ │ Original │ Optimized│
├─────────────┼──────────┼──────────┤
│ Cost │ 84,200 │ 4,100 │
│ Plan steps │ 12 │ 7 │
│ FTS count │ 2 │ 0 │
└─────────────┴──────────┴──────────┘
Improvement: 95.1% cost reduction ✅
Optimizer switched: TABLE ACCESS FULL → INDEX RANGE SCAN
Ask: "Do we have any unused indexes on the orders table?"
You: "Check for unused or duplicate indexes on our MySQL prod DB"
AI: [calls analyze_mysql_query("mysql_prod", "SELECT * FROM orders WHERE status='OPEN'")]
Index Audit:
• idx_orders_legacy — UNUSED (0 reads in performance_schema)
→ Safe to DROP — saves ~2.3GB disk + reduces write overhead
• idx_orders_status and idx_orders_status_date — DUPLICATE
→ idx_orders_status is redundant (covered by idx_orders_status_date)
→ DROP INDEX idx_orders_status ON orders;
These are two independent controls:
| Value | Speed | What's Collected |
|---|---|---|
plan_only |
~0.3s | Execution plan only — no metadata |
standard |
1–3s | Plan + all table/index/column stats (default) |
| Value | Tokens | Best For |
|---|---|---|
minimal |
~5K | Large/complex queries, tight token budget |
compact |
~20K | Routine analysis — recommended default |
standard |
~40K | Deep analysis, storage investigation |
Combining them:
depth="standard", output_preset="compact" → full collection, compact output (recommended)
depth="plan_only", output_preset="minimal" → fastest possible, plan only
depth="standard", output_preset="standard" → everything, for deep investigations
All SQL is validated in two phases before any database interaction:
- Security check — blocks
INSERT,UPDATE,DELETE,MERGE,CREATE,DROP,ALTER,GRANT,REVOKE,BEGIN,EXECUTE,CALL,SELECT INTO, and all other non-SELECT operations - Syntax check — validates the SELECT query parses correctly and references valid objects
Dangerous queries are rejected immediately. The MCP never executes the query being analyzed.
Copy the example config and add your database connections:
cp server/config/settings.yaml server/config/settings.yamldatabase_presets:
prod_oracle:
type: oracle
user: myuser
password: mypassword
dsn: hostname:1521/service_name
prod_mysql:
type: mysql
host: hostname
port: 3306
user: myuser
password: mypassword
database: myschemacp .env.example .env
# Edit .env and set MCP_PORT and any other valuesdocker compose upOr directly:
pip install -r server/requirements.txt
python server/server.pyThe MCP endpoint depends on the FastMCP version. Check the server startup logs for the exact path. Typically one of:
http://localhost:8101/mcp # FastMCP 2.x streamable-http
http://localhost:8101 # if mounted directly at root
Authentication is optional and controlled by a single flag in settings.yaml.
server:
authentication:
enabled: falseAll requests are accepted with no credentials required. Useful for local development or trusted internal networks.
server:
authentication:
enabled: true
api_keys:
- name: "dba-team"
key: "your-secure-key-here"
description: "DBA team access"
- name: "claude-desktop"
key: "another-key-here"
description: "Claude Desktop client"When enabled, every request must include a Bearer token:
Authorization: Bearer your-secure-key-here
The following endpoints are always public regardless of auth setting:
GET /healthGET /healthzGET /health/deepGET /version
python -c "import secrets; print(secrets.token_urlsafe(32))"{
"mcpServers": {
"query-optimizer": {
"url": "http://localhost:8101/mcp",
"headers": { // adjust path if needed — check server startup logs
"Authorization": "Bearer your-secure-key-here"
}
}
}
}{
"mcpServers": {
"query-optimizer": {
"url": "http://localhost:8101/mcp" // adjust path if needed — check server startup logs
}
}
}GRANT SELECT ON all_tables TO <user>;
GRANT SELECT ON all_indexes TO <user>;
GRANT SELECT ON all_tab_col_statistics TO <user>;
GRANT SELECT ON all_ind_columns TO <user>;
GRANT SELECT ON all_constraints TO <user>;
GRANT SELECT ON v$sql TO <user>; -- for get_slow_queries
GRANT SELECT ON v$parameter TO <user>; -- for optimizer params
-- plan_table must exist (created automatically or via GRANT)GRANT SELECT ON information_schema.* TO '<user>'@'%';
GRANT SELECT ON performance_schema.* TO '<user>'@'%'; -- for index usage stats
GRANT SELECT ON <your_schema>.* TO '<user>'@'%';mcp_query_optimizer/
├── server/
│ ├── tools/
│ │ ├── oracle_analysis.py # Oracle MCP tools (analyze, compare, slow queries)
│ │ ├── mysql_analysis.py # MySQL MCP tools (analyze, compare, slow queries)
│ │ ├── oracle_collector_impl.py # Oracle metadata collection logic
│ │ ├── mysql_collector_impl.py # MySQL metadata collection logic
│ │ ├── plan_visualizer.py # Execution plan ASCII tree builder
│ │ ├── database_tools.py # list_available_databases tool
│ │ └── help_tools.py # get_knowledge_base_content tool
│ ├── monitoring/
│ │ └── oracle_monitor.py # Real-time Oracle V$ view monitoring
│ ├── knowledge_base/ # Embedded docs served to the AI
│ │ ├── overview.md
│ │ ├── architecture.md
│ │ ├── workflows.md
│ │ ├── depth_modes.md
│ │ ├── troubleshooting.md
│ │ └── tools/
│ │ ├── analyze_oracle_query.md
│ │ └── analyze_mysql_query.md
│ ├── config/
│ │ └── settings.yaml # Database presets + server config
│ ├── server.py # FastAPI/MCP server entrypoint
│ ├── mcp_app.py # MCP app instance
│ ├── db_connector.py # Oracle connection manager
│ ├── mysql_connector.py # MySQL connection manager
│ ├── auth_middleware.py # API key authentication
│ ├── config.py # Config loader
│ ├── Dockerfile
│ └── requirements.txt
├── docker-compose.yaml
├── .env.example
└── README.md