@seungje.jun/redash-mcp
v0.2.1
Published
Redash MCP server with schema caching and column exploration
Maintainers
Readme
redash-mcp
Expose the Redash API as an MCP (Model Context Protocol) server. Schema, column values, and code-to-label mappings are cached — in memory for schema and persistently on disk for metadata — so repeated lookups skip redundant Redash calls and the model can compose queries without re-exploring the database every time.
Installation & Setup
npx (no installation required)
Add the following to ~/.mcp.json.
{
"mcpServers": {
"redash": {
"command": "npx",
"args": ["@seungje.jun/redash-mcp"],
"env": {
"REDASH_URL": "https://redash.example.com",
"REDASH_API_KEY": "your-key"
}
}
}
}Build from source
git clone https://github.com/ninanung/redash-mcp.git
cd redash-mcp
npm install
npm run buildAdd the following to ~/.mcp.json.
{
"mcpServers": {
"redash": {
"command": "node",
"args": ["/path/to/redash-mcp/dist/cli.js"],
"env": {
"REDASH_URL": "https://redash.example.com",
"REDASH_API_KEY": "your-key"
}
}
}
}Restart Claude Code to activate the MCP tools.
Environment Variables
| Variable | Description |
|----------|-------------|
| REDASH_URL | Redash server URL (single-instance mode, required when REDASH_INSTANCES is unset) |
| REDASH_API_KEY | Redash API Key (single-instance mode) |
| REDASH_INSTANCES | (optional) JSON array for multi-instance mode. Example: [{"name":"prod","url":"...","api_key":"...","allowed_data_sources":[1,2]},{"name":"dev","url":"...","api_key":"..."}]. When set, pass instance: "prod" on any tool call to pick the target. Falls back to the first entry if instance is omitted. |
| REDASH_ALLOWED_DS | (optional) Comma-separated list of allowed data source IDs (e.g. 1,3,7). When set, all other IDs are blocked and list_data_sources only returns allowed ones. |
| REDASH_MCP_LOG | (optional) Log level: debug, info (default), warn, error, silent. Logs go to stderr to avoid corrupting the MCP stdio channel. |
| REDASH_MCP_AUDIT_LOG | (optional) Audit log file path. Defaults to ~/.redash-mcp/audit.log. Set to off to disable. Each line is a JSON record with tool name, args, duration, status. |
| REDASH_QUERY_TIMEOUT_MS | (optional) Default query timeout in ms (default 120000). Override per-call via execute_query's timeout_ms argument. |
| REDASH_SUMMARIZE_THRESHOLD | (optional) Row count above which execute_query auto-summarizes results (per-column min/max/distinct/null + 10 sample rows). Default 500. save_csv disables auto mode. Override per call with summarize:"never". |
| REDASH_MASK_COLUMNS | (optional) Column-name patterns to mask in result rows (comma-separated, * wildcard supported). Include builtin to also mask common PII columns (email, phone, SSN/RRN, password, token, card). Example: builtin,user_name,addr* |
| REDASH_DEFAULT_FORMAT | (optional) Default result encoding for execute_query / execute_saved_query: json (default) or compact. A per-call format argument overrides it. |
| REDASH_DEFAULT_MAX_ROWS | (optional) Default row cap for execute_query / execute_saved_query when the call passes no max_rows (default 1000). |
| REDASH_RESULT_HINTS | (optional) Set to off to drop the trailing LLM guidance from query results ("IMPORTANT: include the executed SQL verbatim…", "To save this query, use save_query…"). Default: on. Useful when your own prompt governs presentation or save_query is not exposed. |
| REDASH_METADATA_TTL_DAYS | (optional) Metadata cache TTL in days. When set, entries older than this are treated as cache misses by explore_column / find_mapping / get_schema and are re-fetched; get_cache output tags them [stale]. Unset = keep forever. |
Tools
| Tool | Description |
|------|-------------|
| list_data_sources | List available data sources |
| self_test | Diagnostic check — verifies env vars, Redash connectivity, and schema access |
| get_schema | Fetch table/column schema (keyword filter, cached) |
| execute_query | Run SQL (only SELECT/WITH allowed; job polling handled automatically; auto-injects LIMIT max_rows — default 1000 — when the query has none; format: "compact" returns rows as arrays at about a quarter of the size) |
| explain_query | Run EXPLAIN for a query without executing it — inspect cost / scan plan before a heavy run (engine-specific support) |
| explore_column | Inspect unique values/counts and infer column types (supports multiple columns at once) |
| sample_rows | Return a few raw rows from a table (default 5) to inspect real column values at a glance |
| describe_table | Combined schema + sample rows for a single table |
| find_table | Find tables by column name and/or table-name keyword (useful when locating join targets) |
| join_hints | List other tables that share column names with the given table — candidate join keys |
| find_mapping | Automatically find mapping tables for numeric code columns |
| save_query | Save a SQL query to Redash (supports description and tags) |
| update_query | Update name/query/description/tags of an existing saved query |
| list_saved_queries | List queries already saved in Redash (supports search + data source filter) |
| get_saved_query | Fetch SQL and metadata of a saved query by ID, including declared parameters (name, type, stored default, enum choices) and who last saved it |
| execute_saved_query | Run a saved query by ID with optional parameters; omitted parameters fall back to the stored defaults, a p_ URL-style prefix is stripped, values are converted to the declared parameter type (text / number), unknown names are rejected with the accepted list, and the result echoes the parameter values actually used |
| list_dashboards | List Redash dashboards (search supported) |
| get_dashboard | Fetch widgets of a dashboard and the query IDs they reference |
| get_cache | Read the metadata cache (column types/values, mapping tables, recommended tables) |
| export_metadata_cache | Export the metadata cache to a JSON file (share across a team or back up) |
| import_metadata_cache | Import a metadata cache from a JSON file (merge/replace modes) |
Usage Example
A typical natural-language flow, as orchestrated by the MCP client:
- User: "Find me the orders table and show yesterday's revenue."
list_data_sources→ pick the targetdata_source_id.get_schemawith keywordorder→ locate candidate tables/columns.explore_columnon status/type columns → understand enum values and infer types.find_mappingon code columns (e.g.status_cd) → resolve numeric codes to labels.execute_query→ run the finalSELECTand return rows.- (Optional)
save_query→ persist the SQL back to Redash.
Subsequent runs reuse the metadata cache, so step 4–5 often short-circuits via get_cache.
Safety & Constraints
- Read-only SQL: only statements starting with
SELECTorWITHare allowed. DML/DDL keywords (INSERT,UPDATE,DELETE,DROP,ALTER,CREATE,TRUNCATE,MERGE,GRANT,REVOKE,CALL,EXEC, ...) are rejected even if buried inside a CTE. Multiple statements separated by semicolons are also blocked. Comments and string literals are stripped before the scan to prevent keyword-smuggling. - Row-limit guardrail:
execute_queryauto-injectsLIMIT max_rows(default 1000) when the query has no LIMIT, preventing accidental full-table scans from blowing up the model context. Override with themax_rowsargument or include an explicitLIMITin the SQL. - CSV export: pass
save_csv: "/path/to/out.csv"toexecute_queryto write the full result to disk instead of (or in addition to) returning it through the model context. - Automatic schema recovery: if a query fails with a "table/column not found" style error, the schema cache for that data source is invalidated and refreshed. For a missing column, the actual columns of the tables referenced in FROM/JOIN are returned; for a missing table, up to 50 similarly named tables are suggested. Either way the model can fix the query in one retry.
- Compact output:
format: "compact"(orREDASH_DEFAULT_FORMAT=compact) encodescolumns/column_typesas arrays androwsas arrays of values with no indentation. Row caps are announced in the notes ("Returned the first M rows" / "Returned the first M of N rows"). - Job polling: Redash async jobs are polled until completion; only the final result is returned to the client.
- No write API: the server does not expose any endpoint that mutates Redash state other than
save_query(creating a new saved query).
Data Source Selection
data_source_idis a required argument on every query-related tool (execute_query,get_schema,explore_column,find_mapping,save_query).- Call
list_data_sourcesfirst to discover available IDs; the MCP client is expected to pass the chosen ID explicitly. - There is no implicit "default data source" — this is intentional, to avoid accidentally querying the wrong database when multiple sources are configured.
- Set
REDASH_ALLOWED_DS=1,3,7to restrict the server to specific data source IDs. Any other ID is rejected before hitting Redash, andlist_data_sources/list_saved_queriesreturn only the allowed subset.
Cache
- Schema cache: in-memory, kept alive while the server runs. Refreshed automatically when a query execution hits a table/column error. Manual refresh is available via
refresh: trueonget_schema, which also asks Redash to rebuild its own server-side schema cache (this is what picks up newly created tables). Everyget_schemaresponse starts with a status line: cached/fresh, table count, and fetch time. - Metadata cache: persisted to
~/.redash-mcp/metadata-cache.json. Results fromexplore_columnandfind_mappingare stored automatically and reused on subsequent lookups. Keys are prefixed withds<id>:so the same table name in different data sources never collides.
Cache Location & Reset
| Cache | Location | Reset |
|-------|----------|-------|
| Schema | in-memory (per server process) | restart the MCP server, or call get_schema with refresh: true |
| Metadata | ~/.redash-mcp/metadata-cache.json | delete the file (rm ~/.redash-mcp/metadata-cache.json) — it will be recreated on the next write |
The metadata cache file is a plain JSON document — safe to inspect, edit, or back up manually.
