---
summary: "DataCanvas primitive lands as a Tier 3 SQL/analytical workspace backed by DuckDB ([#97](https://github.com/cyanheads/mcp-ts-core/issues/97)) — opt-in via CANVAS_PROVIDER_TYPE, fails closed on Cloudflare Workers."
breaking: false
---

# 0.8.9 — 2026-05-02

First landing of the DataCanvas primitive ([#97](https://github.com/cyanheads/mcp-ts-core/issues/97)) — register tabular data from upstream APIs, run SQL across multiple registered tables, export results. Tier 3, optional peer dep `@duckdb/node-api`, disabled by default.

## Added

- **`@cyanheads/mcp-ts-core/canvas` subpath** (`src/canvas/`) — new public export. Surfaces `DataCanvas`, `CanvasInstance`, `CanvasRegistry`, `IDataCanvasProvider`, `DuckdbProvider`, `assertReadOnlyQuery`, `quoteIdentifier`, plus types (`ColumnSchema`, `RegisterTableOptions`, `QueryOptions`, `ExportOptions`, etc.).
- **`CoreServices.canvas?: DataCanvas`** (`src/core/app.ts`) — wired in `composeServices()` when `CANVAS_PROVIDER_TYPE !== 'none'`. `undefined` when disabled or running on Workers. `ServerHandle.shutdown()` automatically tears down active DuckDB instances and stops the sweeper.
- **Token-sharing model** — opaque 10-char URL-safe `canvasId` (~10¹⁸ keyspace). Tools accept an optional `canvas_id` input; framework mints on omit, resolves on match, throws `NotFound` on cross-tenant or unknown id (uniform to avoid existence leaks). Composite scope is `(tenantId, canvasId)`.
- **Lifecycle controls** — sliding TTL (default 24 h, extended on every op), absolute cap from creation (default 7 d), per-tenant active cap (default 100), background `unref`'d sweeper (default 60 s, set 0 to disable). In-memory only in v1; restart drops all canvases.
- **DuckDB provider** (`src/canvas/providers/duckdb/`) — one DuckDB instance per canvasId with `memory_limit` PRAGMA. SQL gate (`assertReadOnlyQuery`) blocks writes via AST inspection, not regex. Path-sandbox for file exports (CSV/Parquet/JSON) rejects absolute paths and `..` traversal; stream-based exports bypass. Schema sniffer materializes a sample (default 100 rows) when `schema` is omitted on `registerTable`.
- **Canvas env vars** (`src/config/index.ts`) — `CANVAS_PROVIDER_TYPE` (`none` | `duckdb`), `CANVAS_DEFAULT_MEMORY_LIMIT_MB` (1024), `CANVAS_EXPORT_PATH` (`./.canvas-exports`), `CANVAS_MAX_CANVASES_PER_TENANT` (100), `CANVAS_TTL_MS` (86400000), `CANVAS_ABSOLUTE_CAP_MS` (604800000), `CANVAS_SWEEPER_INTERVAL_MS` (60000), `CANVAS_DEFAULT_ROW_LIMIT` (10000), `CANVAS_SCHEMA_SNIFF_ROWS` (100). Worker bindings include `CANVAS_PROVIDER_TYPE` so explicit `duckdb` triggers the fail-closed factory path instead of silently no-opping.
- **Cloudflare Workers fail-closed** — `createCanvasService(config)` throws `ConfigurationError` when `CANVAS_PROVIDER_TYPE=duckdb` is set on a Worker (DuckDB has no V8-isolate build). Leave unset or `none` for Worker deployments.
- **Test coverage** — 7 unit suites (`tests/unit/canvas/`: `canvasFactory`, `CanvasRegistry`, `classifyDuckdbError`, `DataCanvas`, `exportWriter`, `schemaSniffer`, `sqlGate`) and a real-DuckDB smoke test (`tests/smoke/canvas-duckdb.test.ts`).
- **`surplus-token-idea` issue label** — new option in `.github/ISSUE_TEMPLATE/{bug_report,feature_request}.yml`, mirrored to `templates/.github/ISSUE_TEMPLATE/`. Added to the label tables in `skills/report-issue-framework/SKILL.md` and `skills/report-issue-local/SKILL.md`, plus a `gh label create` line in the latter (color `FF10F0`).

## Docs

- **`skills/api-canvas/SKILL.md` v1.0** — new skill. Covers acquire → register → query → export flow, the token-sharing pattern for multi-agent collaboration, env config, lifecycle, SQL gate, and Cloudflare Workers fail-closed behavior.
- **`skills/api-config/SKILL.md`** — new Canvas env var table; platform support note (Linux/macOS/Windows × x64; Linux/macOS arm64; Windows arm64 unsupported per DuckDB upstream).
- **`skills/api-workers/SKILL.md`** — DataCanvas-unavailable callout with the exact `ConfigurationError` message and a `if (!ctx.core.canvas) { ... }` guard pattern.
- **`AGENTS.md` / `CLAUDE.md`** — `/canvas` row added to the exports table; `api-canvas` row added to the skills table.

## Deps

- `@biomejs/biome` 2.4.13 → 2.4.14 (schema URL bumped in `biome.json`).
- `@cloudflare/workers-types` 4.20260501.1 → 4.20260502.1 (daily roll).
- `zod` 4.4.1 → 4.4.2 (resolutions + runtime dep + peer range).
- **Added** `@duckdb/node-api` ^1.5.2-r.1 as devDependency and optional peerDependency (Tier 3 — only installed when canvas is enabled).
