Coupler.io live artifact
A live artifact is a self-contained HTML page registered with Cowork that persists across sessions and re-fetches data from MCP connectors every time it opens. This skill covers the specific pitfalls of wiring one to a Coupler.io dataflow. Most generic widget guidance (CDN allowlist, sandboxing) lives in Cowork's `create_artifact` tool description — read that first. This skill captures the Coupler-specific traps that cost real time the first time someone hits them.
Skill instructions
When to use
The user wants a re-openable view over Coupler.io data: KPIs, funnel, leaderboard, time series, comparison. They have an existing dataflow or are pointing at a data set in their Coupler workspace. The output is a .html file registered via mcp__cowork__create_artifact.
Process
Step 1 — Probe in chat first
Before writing a single line of HTML, run these chat-side calls and read the output. All Coupler operations go through the single dispatcher tool coupler: coupler({verb: "call", name: "<operation>", args: {...}}). Args are snake_case.
search-datasets({query: "<dataflow name>"})to find thedataflow_id,last_dataset_snapshot_id, and the datasetid.get-schema({dataset_snapshot_id})and readai_contextcarefully. It documents column meanings, the funnel order, the SQL conventions, and known caveats. Most pitfalls are flagged here.get-data({dataset_snapshot_id, query: "SELECT * FROM data LIMIT 3"})to see the actual row shape. Confirm what the schema claims.get-data({dataset_snapshot_id, query: "SELECT DISTINCT col_X FROM data ORDER BY col_X"})for any column you intend to expose as a filter dropdown — pre-compute the full enum.- Verify the stated grain. The schema’s
ai_contextoften claims one row per(entity × time), but real data sometimes has finer grain. Run:SELECT col_<id>, col_<time>, COUNT(*) cnt FROM data GROUP BY col_<id>, col_<time> HAVING cnt > 1 LIMIT 5If this returns rows, the grain is finer than advertised — the leaderboard query must
GROUP BY <id>andSUM(...)everything, otherwise duplicates show up.
Read references/coupler-conventions.md once before writing SQL. It encodes Coupler’s column/value conventions you will hit immediately.
Step 2 — Decide the refresh strategy
Two modes, pick by how stale the user can tolerate:
| Mode | Behavior | Latency | When to use |
|---|---|---|---|
| Live snapshot (default) | Each artifact open calls list-datasets({dataflow_id}), takes last_dataset_snapshot_id, queries with get-data. |
Fast (~1 RTT). | Daily checks, dashboards, any case where the user is fine with the last scheduled run. |
| Forced refresh (button) | Manual button calls run-dataflow({dataflow_id}), polls list-datasets every 5 s for up to 5 min until snapshot ID changes, then re-queries. |
30 s – several min. | “I want fresh data right now” buttons. Never on every page load — too slow and wasteful. |
Default to live-snapshot for load. Add a “Refresh data” button for forced refresh. Show last_success_run_at (“Last run: 5h ago”) in the header so the user knows how stale things are.
Step 3 — Pick widget MCP tools
Only list MCP tools you actually call from the widget in the mcp_tools parameter of create_artifact. There is a single Coupler tool — the dispatcher:
mcp__b2221b32-5723-40e6-b17d-a36abcd4c057__coupler— list it once inmcp_tools; it covers all operations.
Widget-side calls take the form:
window.cowork.callMcpTool("mcp__b2221b32-5723-40e6-b17d-a36abcd4c057__coupler",
{ verb: "call", name: "list-datasets", args: { dataflow_id: DATAFLOW_ID } })
Operations the widget uses: list-datasets (snapshot lookup — accepts dataflow_id alone), get-data (runs SQL), run-dataflow (only if a refresh button exists).
Do not use:
search-datasetswith onlydataflow_id— its validator rejects this and returns “At least one of query, source, or name must be provided”.get-dataflowfor snapshot lookup — it returns config (sources/destinations) only, no snapshot ID.
Step 4 — Write the artifact HTML
Start from references/widget-template.html — it’s a minimal working scaffold with the right boilerplate (Chart.js CDN tag, snapshot lookup, localStorage filter persistence, light-mode styling).
Required helpers (in references/snippets.js, copy-paste into the artifact):
- Tolerant MCP response parser. Widget-side
callMcpToolwraps results inconsistently — sometimes direct JSON, sometimes{content:[{type:"text",text:"..."}]}, sometimes prose-prefixed text like"Total datasets: 1\n\n[...]". Use the snippet’sparseToolResult+tryJsonFromText— they handle all variants. - Timezone-safe month strings. Never use
new Date(y, m, 1).toISOString().slice(0, 10)— for users east of UTC it returns the previous day, silently producing zero-row queries. Use the snippet’smonthFirst/prevMonthFirst. - Quote stripping. Coupler stores string columns with embedded double quotes (e.g.
col_1 = '"Looker Studio"'). SQLWHEREmatches must include the quotes; display layer must strip them. Use the snippet’sstripQuotesfor display, and quote values in SQL likecol_1 = '"${value}"'. - Run-and-poll. If the widget has a refresh button, copy the snippet’s
refreshDataflow. - Chart.js canvas wrapping. With
responsive: true, maintainAspectRatio: false, every<canvas>must sit inside a parent with explicit height (e.g.<div style="position:relative;height:260px;width:100%"><canvas/></div>). Without it, Chart.js silently collapses the canvas and renders a broken-image icon. - Allowed CDN libs only. Chart.js, Grid.js, Mermaid — exact tags from Cowork’s
create_artifactdescription (withintegrityandcrossorigin). Anything else must be inlined. localStoragefor filter/sort state. Persist the user’s dropdown selections and sort order between opens — that’s what makes a daily-check view feel right.
Step 5 — Verify in the widget runtime
Probing in chat verifies data shape but not widget-runtime behavior. The two failure modes that are invisible to chat-side testing:
- MCP-wrapper shape differences — same tool returns one shape in chat, another shape (often prose-prefixed text) in widgets.
- Timezone bugs —
new Date(...).toISOString()behaves differently per user, and the symptom is silent zeros, not an error.
After registering the artifact, verify the rendered widget shows non-zero values for a known-populated month. If KPIs all read 0, suspect the timezone bug or the quote convention before suspecting the data.
Step 6 — Register the artifact
mcp__cowork__create_artifact({
id: "<kebab-slug>",
html_path: "<absolute path to the .html in outputs>",
description: "<one-line summary of what it shows>",
mcp_tools: ["mcp__b2221b32-5723-40e6-b17d-a36abcd4c057__coupler"]
})
For updates: mcp__cowork__update_artifact({id, html_path, update_summary}). Include a real update_summary — it’s shown to the user in the approval prompt.
Rules & Edge Cases
- Always read
ai_contextfromget-schemabefore writing SQL. It documents column quote conventions, the funnel order, and known caveats. Skipping this step is the most common time-waster. - Verify dataset grain with a
GROUP BY ... HAVING COUNT(*) > 1probe. Don’t trust the schema’s stated grain. If the real grain is finer than advertised, your leaderboard will show duplicates. - Never hardcode
dataset_snapshot_id. It rotates on every dataflow run. Always resolve vialist-datasets({dataflow_id})(through the dispatcher) at load time. - Default to live-snapshot loads, gate
run-dataflowbehind an explicit button. Triggering a fresh run on every artifact open turns a 1-second load into a 1-minute load. Users will hate it. - Coupler’s string-quoting convention is not stable. Some dataflows/snapshots store strings with embedded double quotes (
col_X = '"Looker Studio"'), others do not (col_X = 'Looker Studio'). The convention can flip between snapshots of the same dataflow. Always probeSELECT * FROM data LIMIT 1and look at the actual values before writing SQL filters. Defense in depth: write filters that match both conventions, e.g.col_X IN ('value', '"value"'). KeepstripQuoteson the display layer as a no-op safety net. - Build YYYY-MM-01 strings manually. Never via
toISOString(). Use${y}-${String(m+1).padStart(2,"0")}-01. The timezone bug is silent — it produces zero-row queries, not errors. - Chart.js canvases need a fixed-height parent. Wrap every
<canvas>in<div style="position:relative;height:Npx;width:100%">. Otherwise canvases collapse to 0 and you get a broken-image fallback. - Only the CDN libs listed in the
create_artifacttool description load. Chart.js, Grid.js, Mermaid. Use the exact<script>tags includingintegrityandcrossorigin. Other CDNs are blocked. - The widget runs in light mode. Set
:root { color-scheme: light }and use a light background with dark text. - All Coupler operations go through the
couplerdispatcher.mcp_toolslists only the dispatcher tool; widget calls pass{verb: "call", name, args}with snake_case args. The operation set can change — discover via{verb: "tools"}rather than trusting memory. The dispatcher’s widget-runtime response wrapping has not been end-to-end verified; the tolerant parser inreferences/snippets.jsis designed to absorb extra wrapping, but the first artifact built against the dispatcher must complete the Step 5 verification. mcp_toolsarray must list every tool the widget actually calls (for Coupler, that is just the dispatcher). Tools not listed will fail at runtime.- Filter dropdown values: pre-compute, don’t query in the widget. Pulling distinct values is slow and pointless when you already know the enum at build time.
- End-to-end widget verification is not optional. Probe-in-chat catches data-shape bugs but misses widget-wrapper and timezone bugs. Always reload the rendered widget once and confirm non-zero values for a known-populated period.
table-layout: fixedcollapsesauto-width columns when other columns sum near the table width. If a column needs to be flexible but visible, give it an explicit width (e.g.280px), notauto. Wrap the table inoverflow-x:autoas a fallback.
Reference files
references/coupler-conventions.md— Coupler quote convention, grain caveats, ai_context tips, MCP tool quirks. Read before writing SQL.references/snippets.js— Drop-in helpers:parseToolResult,tryJsonFromText,monthFirst,prevMonthFirst,stripQuotes,runQuery,fetchSnapshotInfo,refreshData. Copy what you need into the artifact.references/widget-template.html— Minimal working scaffold (snapshot lookup, KPI cards, Chart.js, localStorage state, refresh button). Start here for new artifacts.
Self-improvement
When the user flags a new Coupler quirk or widget-runtime gotcha, append it to Rules & Edge Cases. When the user approves a finished artifact that demonstrates a useful pattern (e.g. a clean way to render a stacked-bar funnel with destination breakdown), copy it to references/examples/<descriptive-name>.html for future runs to anchor on.
Three Steps to Your First Answer
Connect your data
Add skill to your workflow
Ask in any language
Other skills you might like
Browse all skillsCreate dataflow
Tells your AI which sources you want to connect and sets up the data flow for you. No manual configuration required or coding agent required.
Start Running AI Analytics Today
Add the skill, connect a source, and ask your first question in minutes.
Looking for an AI agent skill tuned to your exact workflow?