The api-canvas skill makes a dataframe_query tool mandatory once any tool emits a canvas_id (#185), and the linter flags its absence (#209) — but the framework ships none of that surface. Every canvas consumer hand-builds it; across ~15 consumer servers two divergent dialects have emerged:
| Dialect |
Shape |
Duplicated per server |
| canvas-token |
canvas_id in tool I/O, spillover() staging |
query/describe/drop tool definitions + canvas accessor |
| shared-canvas |
hidden per-tenant canvas, minted df_<id> handles |
all of the above plus a ~400-line bridge: handle minting, provenance + TTL bookkeeping in ctx.state, catalog denial, structured missing-table errors (cyanheads/secedgar-mcp-server src/services/canvas-bridge/) |
Fixes do not propagate: the structured-error work in one bridge (cyanheads/secedgar-mcp-server#47) never reached the sibling copies, and the two dialects give agents inconsistent UX across servers backed by the same primitive.
Proposal
Philosophy: the skill mandates the surface, so the framework should ship it.
A /canvas/toolkit subpath export:
const { tools, registerDataframe } = createDataframeTools({
toolPrefix: 'secedgar', // → secedgar_dataframe_query / _describe / _drop
scope: 'shared-canvas', // or 'canvas-token'
ttlMs: 3_600_000, // optional per-table TTL, forwarded to #140's sliding ttlMs
});
Scope
- New
src/services/canvas/toolkit/ + subpath export
- api-canvas skill: recipe section becomes "import the toolkit"; template example updated
Out of scope
- New engine/provider work; Workers support
- Migrating existing consumers (per-server adoption follows separately)
The api-canvas skill makes a
dataframe_querytool mandatory once any tool emits acanvas_id(#185), and the linter flags its absence (#209) — but the framework ships none of that surface. Every canvas consumer hand-builds it; across ~15 consumer servers two divergent dialects have emerged:canvas_idin tool I/O,spillover()stagingdf_<id>handlesctx.state, catalog denial, structured missing-table errors (cyanheads/secedgar-mcp-serversrc/services/canvas-bridge/)Fixes do not propagate: the structured-error work in one bridge (cyanheads/secedgar-mcp-server#47) never reached the sibling copies, and the two dialects give agents inconsistent UX across servers backed by the same primitive.
Proposal
Philosophy: the skill mandates the surface, so the framework should ship it.
A
/canvas/toolkitsubpath export:ToolDefinitions — Zod schemas with.describe(), error contracts, format parity — plus aregisterDataframe/spill helper owning handle minting and provenance (ctx.state).shared-canvasscope setsdenySystemCatalogs(feat(canvas): opt-in system-catalog denial in the SQL gate #224) and relies on gate-level missing-table NotFound (feat(canvas): surface missing-table NotFound from the SQL gate instead of generic "must be SELECT" #223) instead of bridge pre-checks.Scope
src/services/canvas/toolkit/+ subpath exportOut of scope