#1212 shipped the client half. Lumen consumes MCP servers through MCPTool (lumen/ai/tools/mcp.py:90, PR #1686). The server half was set aside on purpose:
I have a prototype at ghostiee-11/lumen-mcp driving Lumen from Claude Code and Cursor, and two of the upstream changes it needed are already merged (#1909, #1910). #1212 itself looks closeable now that #1686 is in.
Why a frontier model should call this
If the answer is that Lumen makes charts, this is a wrapper and should not exist. A frontier model writes matplotlib perfectly well.
The real answer is that Lumen checks its work against real engines, and a model working alone cannot. Everything below already exists in Lumen. The MCP surface only has to expose it.
| What the caller wants |
Doing it alone |
Through Lumen |
Gain |
| SQL that runs |
One shot, and the traceback lands on the user |
Executed against the real backend, the exception goes back to the model (ai/agents/sql.py:440) |
More precise |
| A read on a large table |
head(), then a guess at cardinality and range |
SQLCount, SQLDistinct and SQLMinMax over the whole table (sources/base.py:1134) |
Cheaper, and correct |
| A chart that renders |
A malformed Vega-Lite spec renders blank and reports nothing |
vl_convert.vegalite_to_vega returns a compiler error to act on (ai/editors.py:435) |
More precise |
| Snowflake, BigQuery, Zarr |
No engine to reach them, and too large to load anyway |
Dialect aware sources, including xarray and Zarr (sources/xarray_sql.py:21) |
Possible at all |
| A follow-up question |
Re-reads the file on every call |
The derived query becomes a source, so pushdown continues (ai/agents/sql.py:483) |
Faster |
| Something to hand a human |
A PNG in the chat log |
A spec that round trips and re-runs (base.py:626), with parents recorded (ai/agents/sql.py:512) |
Reusable |
Given one small DuckDB file and a generic SQL tool, a frontier model covers the first row by itself. The rest is the durable gap: dialect breadth, profiling that never moves a row, a chart loop that fails loudly instead of silently, and a spec instead of a screenshot.
What I am asking for
Two things. The first is much smaller and stands on its own.
A headless path through Coordinator. Today Coordinator.__init__ builds a Panel ChatInterface when interface is None (lumen/ai/coordinator/base.py:354), and respond streams into it (:542, :562), so the planner cannot run without a UI attached. vega_lite.py:478 raises UserCancelledError waiting on a human.
That seam is worth opening whether or not the MCP server lands. It is what a REST API around Lumen would sit on. It makes the planner testable in CI. It makes a stalled deployment legible instead of silent, which may be the class of problem behind #1034. I would like a read on this part first, since everything else depends on it and nothing else does.
Tool surface
Fewer tools than my prototype has today. list_tables and describe_table return subsets of what connecting a source already gives you, and get_chart is subsumed by view. Shipping those is what makes a wrapper look like a wrapper.
What is worth exposing is the table above: connect a source, profile it by pushdown, query it with validation, chart it with compilation, serve it live, and export a spec or a report.
#1212 shipped the client half. Lumen consumes MCP servers through
MCPTool(lumen/ai/tools/mcp.py:90, PR #1686). The server half was set aside on purpose:I have a prototype at ghostiee-11/lumen-mcp driving Lumen from Claude Code and Cursor, and two of the upstream changes it needed are already merged (#1909, #1910). #1212 itself looks closeable now that #1686 is in.
Why a frontier model should call this
If the answer is that Lumen makes charts, this is a wrapper and should not exist. A frontier model writes matplotlib perfectly well.
The real answer is that Lumen checks its work against real engines, and a model working alone cannot. Everything below already exists in Lumen. The MCP surface only has to expose it.
ai/agents/sql.py:440)head(), then a guess at cardinality and rangeSQLCount,SQLDistinctandSQLMinMaxover the whole table (sources/base.py:1134)vl_convert.vegalite_to_vegareturns a compiler error to act on (ai/editors.py:435)sources/xarray_sql.py:21)ai/agents/sql.py:483)base.py:626), with parents recorded (ai/agents/sql.py:512)Given one small DuckDB file and a generic SQL tool, a frontier model covers the first row by itself. The rest is the durable gap: dialect breadth, profiling that never moves a row, a chart loop that fails loudly instead of silently, and a spec instead of a screenshot.
What I am asking for
Two things. The first is much smaller and stands on its own.
A headless path through
Coordinator. TodayCoordinator.__init__builds a PanelChatInterfacewheninterface is None(lumen/ai/coordinator/base.py:354), andrespondstreams into it (:542,:562), so the planner cannot run without a UI attached.vega_lite.py:478raisesUserCancelledErrorwaiting on a human.That seam is worth opening whether or not the MCP server lands. It is what a REST API around Lumen would sit on. It makes the planner testable in CI. It makes a stalled deployment legible instead of silent, which may be the class of problem behind #1034. I would like a read on this part first, since everything else depends on it and nothing else does.
Tool surface
Fewer tools than my prototype has today.
list_tablesanddescribe_tablereturn subsets of what connecting a source already gives you, andget_chartis subsumed byview. Shipping those is what makes a wrapper look like a wrapper.What is worth exposing is the table above: connect a source, profile it by pushdown, query it with validation, chart it with compilation, serve it live, and export a spec or a report.