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.
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"
}
}
}
}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.
| 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. |
| 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) |
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.
- 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_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.
- 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 |
|---|---|---|
| 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.