Workflow Artifacts as MCP Apps
OpenWork exposes Workflow results as portable, standards-based MCP Apps. A supporting MCP host can render an artifact inline; every other MCP client still receives a useful Markdown result.
Product flow
- Code Mode produces data and a deterministic Markdown rendering.
- The result can be saved as an immutable Workflow snapshot with a receipt, schema validation, result digest, and exact Workflow version.
- A person or an Automation may run the Workflow again. Automations only refresh the data snapshot; they do not create or execute UI code.
- The read-only
render_workflow_artifacttool loads the latest successful snapshot, or an exact receipt when requested.render_dynamic_artifactremains as a deprecated alias for one release. - MCP Apps hosts resolve the linked
ui://resource and inject the tool's structured result. Other hosts show the Markdown fallback.
This keeps execution, scheduling, data, and presentation separate:
Workflow ──run──> immutable snapshot ──read──> MCP tool result
▲ ▲ ├─ Markdown fallback
│ │ └─ ui:// MCP App
explicit run Automation refresh
The MCP App never runs a Workflow, mutates an artifact, or introduces another scheduler. It only presents an already-authorized snapshot.
MCP Apps conformance
The agent MCP server implements the io.modelcontextprotocol/ui extension and
the 2026-01-26 MCP Apps protocol:
- server capability:
extensions.io.modelcontextprotocol/ui.mimeTypescontainstext/html;profile=mcp-app; - tool metadata:
_meta.ui.resourceUripoints toui://openwork/workflow-artifact/v1/view.html(with the compatibility metadata emitted by the official MCP Apps server helpers); - resource delivery:
resources/readreturns one self-contained HTML5 document with MIME typetext/html;profile=mcp-app; - view lifecycle: the document performs
ui/initialize, sendsui/notifications/initialized, receives tool input/result/cancellation and host-context notifications, reports size changes, and acknowledges teardown; - data delivery: the tool returns versioned
structuredContent, lightweight lineage in result_meta, and a complete text fallback; - security: the resource declares an empty network/frame/base-URI CSP, loads no external code, performs no network requests, and inserts artifact values with DOM text APIs rather than HTML interpolation.
The shared payload schema lives in @openwork/types/workflows so a host
can validate the data contract independently of this presentation resource.
UI behavior
The self-contained view is intentionally data-first and framework-neutral. It adapts the Preview tab to common result shapes:
- arrays of records become a bounded table;
- flat objects become metric cards;
- nested or irregular values fall back to formatted JSON;
- Data always exposes the full structured result, with a rendering-size guard;
- Lineage shows the immutable receipt, Workflow/version IDs, source, digest, renderer, and Automation run when present.
The view follows host theme/style context, uses responsive HTML, and caps large tables and rendered JSON without changing the underlying tool result.
Authorization and failure behavior
The renderer uses the same organization membership, team grants, and saved Workflow authorization path as the existing artifact APIs. It does not accept a plugin or result payload from the caller. The caller supplies only a saved Workflow ID, an optional exact receipt, and an optional freshness threshold.
Only readable, successful, non-deleted snapshots can be rendered. Missing, failed, deleted, or unauthorized results fail closed with a non-sensitive text error. A stale snapshot remains renderable and is labeled stale; a failed refresh leaves the last successful artifact available with a needs-attention state.
Next interoperable slice
The provider side is deliberately usable without an OpenWork-specific host.
The next slice is a generic MCP Apps host in the desktop conversation surface:
negotiate the UI extension with upstream MCP servers, preserve tool/resource
metadata and structured results through the runtime, sandbox ui:// resources,
and bridge the standard JSON-RPC lifecycle. That host should consume the same
standard contract rather than adding a custom React artifact format.