You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Make hal-mcp callable from inside a Claude artifact. Today's Edifice tools assume a Claude-Code-side consumer that can follow a signed URL and tolerate permission prompts. An artifact can do neither. Three changes, all in supabase/functions/hal-mcp/index.ts.
Downstream consumer: bluegreen-marketplace#50 (Edifice front as an artifact). Phases 2–3 there are blocked on this issue.
1. get_mission_context — return the mission inline
The problem.get_mission_with_assets (index.ts:240) ends by writing an export to storage and returning a signed download_url, 300 s (index.ts:~359–367), and each photo carries a signed_url valid 3600 s. Empirically validated in a real Cowork session on 2026-07-27: an artifact's CSP blocks all outbound network — fetch and <img> against a fresh, non-expired Supabase signed URL both fail with TypeError. Connector calls are the artifact's only outbound channel. A signed URL is therefore dead weight there.
Ask. A tool returning the full mission context in the MCP result body, no URL indirection — same payload shape build_context.py consumes (flat snake_case header + observations[] + notes[] + photos[]), so the artifact adapter and the existing skill agree on one shape.
Either extend get_mission_with_assets with an inline: true mode, or add get_mission_context(mission_id). Prefer the new tool: get_mission_with_assets has a working contract with the /edifice skill and the report pipeline, and this is a different consumer.
Photo metadata inline; photo bytes are tool 2's job.
Keep the existing rule: mission_context is never surfaced for display.
Sizing constraint to respect: a rendered artifact page is capped at 16 MiB. At ~300 KB/photo the ceiling is roughly 50 photos — beyond that the artifact must page or fetch on demand. Full-resolution (~24 MB for the 12-photo reference mission) is over the limit and is not an option.
3. readOnlyHint annotations — the load-time permission cascade
The problem.anthropics/claude-code#57398 (closed not planned, stale): the artifact permission gate classifies a tool as "can modify data" at the tool level, not the call level, and fires on any call not triggered by a click or keypress. Result: N distinct load-time tool calls → N Allow/Deny dialogs, with no "Always allow", on every fresh session. The connector-level "Always Allow" setting is ignored in artifact context.
A front that reads its mission on load is exactly the shape that triggers this. The fix is upstream-proof and cheap: declare the reads as read-only.
Add annotations: { readOnlyHint: true } to the tool config object (2nd argument of registerTool, alongside title/description/inputSchema) for at minimum: list_edifice_missions, read_edifice_mission, get_mission_context, get_mission_photo.
Consider the same pass over the other obvious reads — list_tasks, list_sprints, list_documents, get_document, list_projects, list_contacts, list_companies, whoami — which would also benefit the command-center-quotidien artifact.
Explicitly annotate push_mission_context as a write (readOnlyHint: false), so no cache layer can ever replay it.
Second reason this matters: on the claude.ai artifact runtime, readOnlyHint: true is what enables result caching (staleTime/gcTime), and watchTool — the API arm for data that stays current — rejects tools annotated readOnlyHint: false. Without the annotation the artifact cannot use the live-refresh path at all.
4. Write-path robustness (push_mission_context)
The artifact contract states that server_unavailable and upstream_error are ambiguous outcomes for writes — a rejection is not proof the tool didn't run. The client is told to re-read state before re-issuing. That argues for making the write safely repeatable rather than asking the client to guess.
Verify push_mission_context (index.ts:385) is idempotent for a granular edit — one note, one requalification, one assessment — without clobbering fields the caller didn't send. Its observations[] entries already carry optional fields, so partial updates look supported; confirm with a test rather than by reading.
Consider returning the updated rows so a client can reconcile after an ambiguous failure without a second round trip.
Acceptance criteria
get_mission_context('3f2bd270-fd5f-4d09-a64e-063a66348dc8') returns the 10 notes and 12 photos of the reference mission with zero URL fields required for rendering.
get_mission_photo returns a photo under 400 KB base64, decodable to a valid JPEG.
tools/list shows readOnlyHint: true on every read tool listed above.
A test proves push_mission_context with a single-field observation leaves the other fields of that row untouched.
Edge Function deployed; plugins/hal/.mcp.json version bumped in bluegreen-marketplace in the same window.
Cross-repo hygiene
Log in docs/cross-repo-log.md; update .claude/STATUS.md.
No edifice_* schema change expected. If any: docs/schema-contract.json + plugin_version bumped in the same commit (contract rule).
Goal
Make
hal-mcpcallable from inside a Claude artifact. Today's Edifice tools assume a Claude-Code-side consumer that can follow a signed URL and tolerate permission prompts. An artifact can do neither. Three changes, all insupabase/functions/hal-mcp/index.ts.Downstream consumer: bluegreen-marketplace#50 (Edifice front as an artifact). Phases 2–3 there are blocked on this issue.
1.
get_mission_context— return the mission inlineThe problem.
get_mission_with_assets(index.ts:240) ends by writing an export to storage and returning a signeddownload_url, 300 s (index.ts:~359–367), and each photo carries asigned_urlvalid 3600 s. Empirically validated in a real Cowork session on 2026-07-27: an artifact's CSP blocks all outbound network —fetchand<img>against a fresh, non-expired Supabase signed URL both fail withTypeError. Connector calls are the artifact's only outbound channel. A signed URL is therefore dead weight there.Ask. A tool returning the full mission context in the MCP result body, no URL indirection — same payload shape
build_context.pyconsumes (flat snake_case header +observations[]+notes[]+photos[]), so the artifact adapter and the existing skill agree on one shape.get_mission_with_assetswith aninline: truemode, or addget_mission_context(mission_id). Prefer the new tool:get_mission_with_assetshas a working contract with the/edificeskill and the report pipeline, and this is a different consumer.mission_contextis never surfaced for display.2.
get_mission_photo(photo_id, max_width?)— base64 bytes3.
readOnlyHintannotations — the load-time permission cascadeThe problem. anthropics/claude-code#57398 (closed not planned, stale): the artifact permission gate classifies a tool as "can modify data" at the tool level, not the call level, and fires on any call not triggered by a click or keypress. Result: N distinct load-time tool calls → N Allow/Deny dialogs, with no "Always allow", on every fresh session. The connector-level "Always Allow" setting is ignored in artifact context.
A front that reads its mission on load is exactly the shape that triggers this. The fix is upstream-proof and cheap: declare the reads as read-only.
annotations: { readOnlyHint: true }to the tool config object (2nd argument ofregisterTool, alongsidetitle/description/inputSchema) for at minimum:list_edifice_missions,read_edifice_mission,get_mission_context,get_mission_photo.list_tasks,list_sprints,list_documents,get_document,list_projects,list_contacts,list_companies,whoami— which would also benefit thecommand-center-quotidienartifact.push_mission_contextas a write (readOnlyHint: false), so no cache layer can ever replay it.Second reason this matters: on the claude.ai artifact runtime,
readOnlyHint: trueis what enables result caching (staleTime/gcTime), andwatchTool— the API arm for data that stays current — rejects tools annotatedreadOnlyHint: false. Without the annotation the artifact cannot use the live-refresh path at all.4. Write-path robustness (
push_mission_context)The artifact contract states that
server_unavailableandupstream_errorare ambiguous outcomes for writes — a rejection is not proof the tool didn't run. The client is told to re-read state before re-issuing. That argues for making the write safely repeatable rather than asking the client to guess.push_mission_context(index.ts:385) is idempotent for a granular edit — one note, one requalification, one assessment — without clobbering fields the caller didn't send. Itsobservations[]entries already carry optional fields, so partial updates look supported; confirm with a test rather than by reading.Acceptance criteria
get_mission_context('3f2bd270-fd5f-4d09-a64e-063a66348dc8')returns the 10 notes and 12 photos of the reference mission with zero URL fields required for rendering.get_mission_photoreturns a photo under 400 KB base64, decodable to a valid JPEG.tools/listshowsreadOnlyHint: trueon every read tool listed above.push_mission_contextwith a single-field observation leaves the other fields of that row untouched.plugins/hal/.mcp.jsonversion bumped in bluegreen-marketplace in the same window.Cross-repo hygiene
docs/cross-repo-log.md; update.claude/STATUS.md.edifice_*schema change expected. If any:docs/schema-contract.json+plugin_versionbumped in the same commit (contract rule).References