Live data artifacts
A published page can call the viewer's claude.ai connectors and render current data. Powerful, and narrower than it first appears — read the constraints before designing around it.
Load the host's artifact-capabilities skill when it is available before writing a connector
call. It carries the live capability roster and authoritative type definitions. If it is absent,
do not guess: build the baked snapshot path and state that live mode is unavailable.
Decide whether you should
Four questions. A "no" on any of the first three means build a snapshot instead
(artifact-architecture, Tier 1).
- Is the source a connector on the organization's claude.ai account? A locally-configured MCP server is not reachable from a published page. Local setup is irrelevant here — this is about what the org exposes.
- Does this page need to avoid being shared publicly? A page declaring connector access cannot be shared publicly. Each viewer authenticates as themselves. For financial data that is the right posture; it is also a distribution constraint. A board pack that only opens for people with warehouse credentials is not a board pack.
- Does every intended viewer have access to the connector?
If half the audience gets
not_granted, you have built a page that works for you and is empty for them. - Would a changing number be a defect?
For anything reported, sent, or presented — yes. Liveness suits an operational monitor, not a
deliverable. See the as-of discussion in
artifact-architecture.
Good fits: cash position, collections status, pipeline, daily bookings, job/queue monitors. Bad fits: board packs, investor updates, month-end reporting, anything with an as-of in its title.
Getting the namespace
const mcp = await claude.use('mcp');
if (!mcp) {
renderSnapshot(); // ALWAYS have this path
showBanner('Showing the built-in snapshot — live data is unavailable here.');
return;
}
claude.use() resolves null when the capability cannot run in this view — not served, not granted,
or failed to load, indistinguishable by design. Branch on null and design for absence. Never
probe window.claude members to guess which case you are in.
Permission lives on the calls, not on use(). A consent prompt, rate limit, or policy refusal
arrives on the first call.
Two arms: display versus act
Displaying data → watchTool. Replays cache immediately, refreshes when stale, polls only if you
ask. Returns a synchronous unsubscribe.
const stop = mcp.watchTool(server, tool, input, (result, error) => {
if (error) { showStale(error); return; }
render(result.payload);
showFreshness(result.cache?.storedAt);
}, { refetchInterval: 60_000 });
Taking an action → callTool. Fires once; read result.payload. Tool failures reject with a
tool_error, so wrap it.
The distinction matters: watchTool is idempotent-by-assumption and may re-run. Never put a
side-effecting tool behind it.
The rule that prevents shipping broken pages
Observe one real request/response pair per tool, in this session, before publishing.
The type definitions cover the call envelope. They do not tell you a connector tool's argument names or how its results are encoded. Guessing produces a page that looks right and returns nothing.
If you cannot safely observe a pair — the connector is unauthenticated here, or calling it would have side effects — say so explicitly at publish time, in your reply to the user, not as a note buried in the page. Do not ship a guessed shape.
And when you do observe: learn the shape, discard the values. Observed data is the user's real data. It never becomes sample or placeholder content in the published page.
Error handling
Branch on the error code; do not treat all failures alike.
| Class | Behaviour |
|---|---|
| Retryable (transient, rate limit) | Retry with backoff, cap the attempts, show a retrying state |
| Authorization denied | Drop the data. Show an access-required state. Never fall back to stale data the viewer may no longer be entitled to see |
| Not granted / unavailable | Degrade to the snapshot, say so plainly |
| Tool error (bad args, upstream failure) | Not retryable. Surface it; retrying identical bad input just burns quota |
Retry only what is marked retryable. A retry loop on a permission error is indistinguishable from an attack and will get the page throttled.
Freshness UI
If a page claims to be live, it must be able to say how live. Drive this from the result's cache timestamp:
● Live · updated 14:22:07 (under a minute)
◐ Updated 6 min ago (stale but usable)
◌ Reconnecting… (retrying)
▲ Snapshot · as of 2026-08-19 (degraded — say it plainly)
Rules:
- Never show a bare number with no freshness indicator. A stale figure presented as current is worse than no figure.
- Never silently swap live for cached. State the degradation.
- Timestamps in the viewer's locale, with a timezone.
Degradation is a first-class path, not an afterthought
Build the snapshot first, then layer live on top. This ordering is deliberate: it guarantees the page works for every viewer, and it means "live unavailable" is a state you designed rather than a blank screen you discover.
render(SNAPSHOT); // always renders something real
const mcp = await claude.use('mcp');
if (mcp) upgradeToLive(mcp); // enhancement, not the foundation
The page footer should always name which tier is actually in use.
Manifest hygiene
Declare the minimum:
capabilities: { mcp: { servers: [{ server: 'connector_name', tools: ['search', 'query'] }] } }
serveris the connector segment from the tool name (mcp__<connector>__<tool>), copied exactly, case included.toolstakes the connector's upstream tool names, which can differ from the normalized segment when the upstream name contains a dot or a space.- It is a viewer-consented grant. A large manifest asks for more trust and buys nothing.
On redeploy, omitting capabilities carries the stored declaration forward; {} clears it; a
non-empty object is a full-set declaration and revokes anything not restated.
Related skills
- Host
artifact-capabilities— conditional contract; use the baked snapshot when absent artifact-architecture— the tier decision this sits insidestateful-artifacts— when the page needs to write, not just readapp-interaction-patterns— loading, stale, empty, and error statesartifact-testing— verifying the degraded path actually works