Skip to content

docs: add MCP Apps contributor skill - #81

Merged
chicoxyzzy merged 2 commits into
feat/mcp-apps-ui-extensionfrom
docs/mcp-apps-skill
Jul 9, 2026
Merged

docs: add MCP Apps contributor skill#81
chicoxyzzy merged 2 commits into
feat/mcp-apps-ui-extensionfrom
docs/mcp-apps-skill

Conversation

@chicoxyzzy

@chicoxyzzy chicoxyzzy commented Jul 9, 2026

Copy link
Copy Markdown
Member

Before / After

Before. MCP Apps guidance for contributors was spread across the host-facing
docs/agent-host-integration.md, the view build notes in
internal/app/views/README.md, and the code comments. There was no single,
task-shaped "how do I add or change a ui:// view without breaking the wire
contract or the CSP" reference, and AGENTS.md had no skills index.

After. Adds a contributor skill at docs/skills/mcp-apps/SKILL.md and a
Skills pointer table in AGENTS.md. The skill is verified against the actual
implementation and covers:

  • Spec essentials — the io.modelcontextprotocol/ui extension
    (UIExtensionID), ui:// resources served as text/html;profile=mcp-app
    (UIAppMimeType, no space after ;), _meta.ui.resourceUri tool tagging via
    uiAppMeta, the capability declared only when an app is registered (reactive
    RegisterApps + capabilities()), and the view↔host handshake
    (ui/initialize, ui/notifications/initialized, ui/notifications/tool-result
    carrying structuredContent) as driven by the official ext-apps SDK.
  • Recipe — add a new app end to end — write the stateless view, wire the
    bridge with the official @modelcontextprotocol/ext-apps App, add it to
    build.ts (ESM bundle inlined into a <script type="module">), keep the strict
    default-deny CSP, bun install + build/commit the embedded bundle, register +
    tag in Go, and add the required tests.
  • Gotchas — bundle the SDK as ESM, never IIFE (the __require is not defined
    crash is a Bun --format=iife interop bug, not an SDK defect — fixed by ESM
    output + a module script; the SDK runs clean under the strict, no-unsafe-eval
    CSP because the App constructor defaults allowUnsafeEval:false so zod runs
    jitless), never innerHTML raw structuredContent, keep the tagged tool result
    byte-identical, keep the extension reactive, the exact mime-type string, and
    uneven host support.
  • Pointers to the normative tool-error-code catalog and the
    agent-host-integration.md host contract.
  • A verification ladder with the exact commands (go build/vet/test,
    go test -race, bun install + bun run build, and the headless Chromium
    render check via verify/verify.mjs, which renders the view inside a sandboxed
    iframe host).

Docs-only; no production code changes.

Stacking

This PR is stacked on #80 (feat/mcp-apps-ui-extension), which introduces
the MCP Apps foundation and the Project Status app this skill documents. It is
based on that branch, not main, because #80 is not yet merged. Rebase onto
main once #80 lands.

Verification

Draft — not for merge yet.

The Project Status app now uses the official @modelcontextprotocol/ext-apps
SDK under the same strict CSP. The earlier "SDK breaks under strict CSP, use a
hand-rolled bridge" guidance was wrong: the __require crash was a Bun
--format=iife interop bug, fixed by bundling ESM and loading a module script.
Update the wire-handshake description, the recipe (SDK App wiring, ESM bundle,
bun install before build), the gotchas, and the verification ladder
(frozen-lockfile install, iframe-hosted verify.mjs).
@chicoxyzzy
chicoxyzzy force-pushed the docs/mcp-apps-skill branch from 3ed3fb2 to 745b452 Compare July 9, 2026 17:11
@chicoxyzzy
chicoxyzzy marked this pull request as ready for review July 9, 2026 17:25
@chicoxyzzy
chicoxyzzy merged commit a2e66d7 into feat/mcp-apps-ui-extension Jul 9, 2026
2 checks passed
chicoxyzzy added a commit that referenced this pull request Jul 9, 2026
…s app (#80)

* feat(mcp): add MCP Apps (ui://) extension foundation

Add an MCP Apps (SEP-1865, io.modelcontextprotocol/ui) foundation so hosts
can render an interactive HTML view for a tool result.

- RegisterApps wires a ui:// resource provider/reader that serves app HTML
  with the text/html;profile=mcp-app mime type, and reactively declares the
  io.modelcontextprotocol/ui extension only when at least one app is
  registered.
- uiAppMeta tags a tool descriptor with _meta.ui.resourceUri.
- Add the embedded, self-contained Project Status view plus a bun build
  pipeline under internal/app/views; the view is committed to dist/ and
  embedded via go:embed, so runtime and go test need no JS toolchain.

The view implements the MCP Apps postMessage bridge directly instead of the
ext-apps SDK, which does not bundle to a self-contained browser IIFE and
needs eval that the view's default-deny CSP forbids.

* feat(mcp): add read-only Project Status app

Attach the Project Status MCP Apps view to the projection tools so hosts that
render apps can show project health, the operations brief, and activity.

- Tag projects.health, projects.operations_brief, and projects.activity
  descriptors with _meta.ui.resourceUri pointing at the Project Status app.
  Adding the tag is descriptor-only: a regression test compares each tool's
  call result byte-for-byte (timestamps normalized) against an untagged
  reference server and asserts the results are unchanged.
- The single stateless view detects which of the three structuredContent
  shapes it received and renders that section; action_kind/action_label hints
  render as inert labels (no tool calls in this batch).
- Document MCP Apps authoring and the host-support caveat in the README and
  the agent host integration guide.

* fix(mcp): render Project Status app per project to prevent view bleed

The view accumulated tool results in unkeyed module state, so results for
two different projects merged into one view. Key accumulated sections on
project_id and reset every section when a result for a new project arrives,
so one project's health/operations/activity can never bleed into another's.

Complete the MCP Apps handshake in spec order: post
ui/notifications/initialized only after the host answers ui/initialize.

Extend the headless verify harness to answer ui/initialize, assert the
readiness notification follows the response, and assert no cross-project
bleed after switching project_id. Rebuild the embedded dist bundle.

* ci(mcp): guard embedded views bundle freshness and pin bun

A source edit under views/src that skipped `bun run build` would ship a
stale embedded dist/ bundle undetected. Add a CI job that installs the
pinned Bun, rebuilds the bundle, and fails via git diff --exit-code if the
committed dist/ drifted from source.

Pin the Bun version in .bun-version and package.json (packageManager /
engines) for reproducible builds. Stop git-ignoring the lockfile so it is
committed if a dependency is ever added; the build currently pulls in no
third-party packages, so Bun writes no lockfile and CI needs no network
beyond installing Bun itself.

* test(mcp): assert projection tools against pre-app golden results

The regression test compared each projection tool's call result against a
freshly built untagged server, which is near-tautological: it only proves
the app _meta tag does not change the result, not that the result body is
stable. Replace it with committed golden fixtures capturing the exact
text + structuredContent for a fresh project (timestamps and project id
normalized). The tool bodies predate the app tag (PR only added descriptor
_meta), so any future drift in a result body is now caught, not just the
tag. Regenerate with -update.

* docs(mcp): correct stale extension-negotiation comments

The initialize and ClientCapabilities comments claimed Cairnline declares no
extensions of its own; it now declares io.modelcontextprotocol/ui via
DeclareExtension when an MCP Apps view is registered. Correct both.

* feat(mcp): drive Project Status app with ext-apps SDK

Replace the hand-rolled MCP Apps postMessage bridge with the official
@modelcontextprotocol/ext-apps App. The earlier "__require is not defined"
failure was a Bun IIFE-interop bug, not an SDK defect: bundling the SDK's
mixed ESM/CJS graph to an IIFE emits an undefined __require reference. Building
to ESM and loading it as an inline <script type="module"> removes that
reference and runs under the unchanged strict CSP
(default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline') with
no unsafe-eval: the App constructor puts zod in jitless mode, so no
eval/new Function path is taken at runtime.

- add @modelcontextprotocol/ext-apps@1.7.4 as a pinned dependency with a
  committed bun.lock; the minified bundle is byte-reproducible under the pinned
  Bun + frozen lockfile
- switch build.ts to --format=esm and guard against a stray second chunk
- rebuild the embedded dist bundle; view rendering, per-project keying, and
  inert action labels are unchanged
- run verify against a sandboxed iframe host, since the SDK App is a full
  JSON-RPC peer and cannot answer its own ui/initialize at top level
- install views deps in the freshness CI job; update README and the host
  integration guide

* docs: add MCP Apps contributor skill (#81)

* docs: add MCP Apps contributor skill

* docs: correct MCP Apps skill for ext-apps SDK adoption

The Project Status app now uses the official @modelcontextprotocol/ext-apps
SDK under the same strict CSP. The earlier "SDK breaks under strict CSP, use a
hand-rolled bridge" guidance was wrong: the __require crash was a Bun
--format=iife interop bug, fixed by bundling ESM and loading a module script.
Update the wire-handshake description, the recipe (SDK App wiring, ESM bundle,
bun install before build), the gotchas, and the verification ladder
(frozen-lockfile install, iframe-hosted verify.mjs).

* chore(mcp): ignore generated views verify screenshot

* refactor(mcp): convert views verify script to typescript

Rename verify/verify.mjs to verify/verify.ts and convert it to proper
TypeScript: Playwright Browser/Page/Frame types, typed fixture payloads,
and typed page-injected Window helpers. Behavior is unchanged — same CSP
injection, handshake, tool-result delivery, section assertions, and the
same default screenshot path (verify/project-status.png).

Pin playwright, typescript, and @types/node as devDependencies (updating
bun.lock), add a tsconfig.json that type-checks both src/ and verify/, and
add a typecheck script. The verify script now runs via bun (bun verify/verify.ts).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant