Skip to content

docs(api-canvas): require the emitting response to name the dataframe tools #347

Description

@cyanheads

The api-canvas skill requires that a server emitting a canvas_id also register dataframe_query / dataframe_describe. It says nothing about the emitting response naming them. A server can satisfy the checklist in full and still hand the agent an opaque 10-character token with no reachable next step.

That is the gap this proposes closing, in the skill that teaches the pattern.

Why registration alone is not enough

spillover() auto-names the staged table spilled_<8-hex>. An agent holding a canvas_id therefore needs dataframe_describe before it can write a single valid FROM clause — and nothing in the current guidance puts describe in front of query, or in the response at all.

The failure is quiet. An agent that gets truncated: true plus a token, with no pointer, either reports the truncation and stops, or re-calls the producing tool with a narrower filter — re-fetching rows already staged one query away. Neither path errors, so nothing surfaces it.

There is a second, sharper reason describe is load-bearing rather than polish: staged column names routinely differ from the inline preview's field names, because the rows registered to the canvas are a projection built for SQL (snake_case, flattened, joined arrays) while the preview reflects the output schema. SQL inferred from the preview references columns that do not exist.

What the skill currently teaches

The "minimum viable spillover server" example in skills/api-canvas/SKILL.md sets a reasonable canvas_id output .describe(), then returns from the spill path with no enrichment call. Copying it faithfully produces exactly the gap above — the pointer exists in tools/list prose, never in the payload.

The surface distinction is the part worth making explicit, because it is easy to get wrong while believing it is handled: a pointer written into a tool's format() reaches content[] only. A client forwarding structuredContent sees the token and nothing else. ctx.enrich.notice(...) reaches both surfaces in one call. The skill documents both mechanisms but never states which one a canvas pointer belongs in.

Proposal

Amend skills/api-canvas/SKILL.md:

  1. Add a checklist line under the existing registration item:

    - [ ] Every response that returns a `canvas_id` names the dataframe tools in the
          response payload — `<prefix>_dataframe_describe` first (table and column
          names), then `<prefix>_dataframe_query`. Schema `.describe()` prose is not
          sufficient; use `ctx.enrich.notice(...)` so the pointer reaches
          `structuredContent` and `content[]` alike.
    
  2. Extend the spillover example's spill branch so the canonical snippet models it:

    if (result.spilled) {
      ctx.enrich.notice(
        `Staged ${result.handle.rowCount} rows to table "${result.handle.tableName}" — ` +
        `use <prefix>_dataframe_describe to inspect columns, then ` +
        `<prefix>_dataframe_query to analyze the full set with SQL.`,
      );
    }
  3. Note the last-wins constraint on notice where the example introduces it. A handler that already calls ctx.enrich.notice(...) on another branch (a degraded-fetch warning, an unparseable-label warning) will silently drop that warning if the spill path issues a second call — the two must be composed into one string.

Scope

  • skills/api-canvas/SKILL.md: checklist item, spillover example, and the notice last-wins caveat.
  • The Tools row of the simple-shape defaults table, which currently frames dataframe_describe as "required alongside" — it should also say the emitting response names it.

Out of scope

  • Any runtime change to spillover(), CanvasInstance, or the canvas providers. A framework-emitted notice would have to guess the consumer's tool prefix, and prefixes are server-owned — this belongs in guidance, not in the primitive.
  • The lint:mcp definition linter. Detecting "the response names the right tools" needs cross-referencing a handler's enrichment strings against the server's registered tool names; worth considering separately if the guidance alone does not hold.

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions