Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/lit-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"@modelcontextprotocol/client": "2.4.0",
"@modelcontextprotocol/core": "2.4.0",
"@modelcontextprotocol/express": "2.0.2",
"@modelcontextprotocol/ext-apps": "2.0.3",
"@modelcontextprotocol/ext-apps": "2.1.0",
"@modelcontextprotocol/fastify": "2.0.1",
"@modelcontextprotocol/hono": "2.0.2",
"@modelcontextprotocol/node": "2.1.1",
Expand Down
2 changes: 1 addition & 1 deletion docs/authorized-playback.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ apps/lit-host/src/mcp/listener.ts # HTTP server outside the iframe

The reusable action runtime (`@saga-a2ui/core/actions`) supplies direct bindings, asynchronous local actions, resource scopes and server actions. The original [v0.1.0 snapshot](https://github.com/MachineWisdomAI/saga-a2ui/tree/v0.1.0) (`dda384de`, 2026-10-18) did not use it for Play; the current source does (CORE2, [PR #133](https://github.com/MachineWisdomAI/saga-a2ui/pull/133)). The control opens its own instance on an action host and the application binds `play`, `pause` and `resume` to it; Play is an `Action.server` and Pause and Resume are local actions. The sections below describe the migrated path. The request bounds, the session-label rule and the player's lease ownership are application decisions that stay in `apps/lit-host`, as they did before the migration.

The MCP integration uses `ext-apps` 2.0.3 and the split MCP SDK 2 packages (`client`, `core`, `server` 2.4.0; `node` 2.1.1). The listener uses `NodeStreamableHTTPServerTransport`; a handler reads verified identity from `ctx.http.authInfo` and request metadata from `ctx.mcpReq._meta`. Browser views use `App` and `PostMessageTransport`. These replace the old monolithic SDK imports without changing the capability/proof protocol below.
The MCP integration uses `ext-apps` 2.1.0 and the split MCP SDK 2 packages (`client`, `core`, `server` 2.4.0; `node` 2.1.1). The listener uses `NodeStreamableHTTPServerTransport`; a handler reads verified identity from `ctx.http.authInfo` and request metadata from `ctx.mcpReq._meta`. Browser views use `App` and `PostMessageTransport`. These replace the old monolithic SDK imports without changing the capability/proof protocol below.

## One Play press

Expand Down
2 changes: 1 addition & 1 deletion docs/candidate-update-and-rollback.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ The tarball digests, the built views and servers, and the consumer installs are

**Catalogs.** Protocol A2UI v0.9 on upstream `@a2ui/web_core` 0.13.0. The `own` combination lists the application's own catalog (`https://pack1.example/catalogs/notes/v1`: `Line`, `Column`, `Row`, `List`, `ActionButton`); `card` adds the optional `FaithfulCard` at content contract version 1. A version other than 1 is refused by the card without reading its Markdown (existing reader tests).

**Recipe.** Node: CI uses the version in `.nvmrc` (22.23.3, also the minimum in `package.json`); `candidate.json` records the version the run actually used, and local runs may use a newer one. npm: recorded per run. Upstream: `@a2ui/web_core`/`lit`/`react` 0.13.0; `@modelcontextprotocol/client`, `core`, `server` 2.4.0 and `ext-apps` 2.0.3; Zod 4.6.7 (3.25.76 only as core's `zod3` alias); React and React DOM 19.3.1 exactly, for the React renderer and stack. Optional peers: `@saga-a2ui/components` for `lit` and `react`.
**Recipe.** Node: CI uses the version in `.nvmrc` (22.23.3, also the minimum in `package.json`); `candidate.json` records the version the run actually used, and local runs may use a newer one. npm: recorded per run. Upstream: `@a2ui/web_core`/`lit`/`react` 0.13.0; `@modelcontextprotocol/client`, `core`, `server` 2.4.0 and `ext-apps` 2.1.0; Zod 4.6.7 (3.25.76 only as core's `zod3` alias); React and React DOM 19.3.1 exactly, for the React renderer and stack. Optional peers: `@saga-a2ui/components` for `lit` and `react`.

**Application overrides.** An application must carry `"overrides": { "@a2ui/markdown-it": "0.2.1", "dompurify": "3.4.17", "@hono/node-server": "2.1.5" }` in its own `package.json`. Without it a fresh install resolves `@a2ui/markdown-it` 0.2.2, whose peer `@a2ui/web_core ^0.14.0` conflicts with the pinned 0.13.0, and npm stops with ERESOLVE. npm honors overrides of the root project only, so a package cannot carry this for its consumers. `candidate:pack` installs with the overrides taken from the workspace root, and `candidate.json` records them. For pnpm, place the equivalent override in the consumer workspace root `pnpm-workspace.yaml`. See [React consumer dependencies](react-consumers.md) for the supported imports and Zod ownership; merely declaring another direct Markdown dependency does not constrain every transitive resolution.

Expand Down
12 changes: 6 additions & 6 deletions docs/development-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ Invalid messages, unknown catalogs/components or wrongly typed properties produc

`apps/lit-host` also hosts an ordinary MCP server and renders one of its tool results through the same A2UI session. It serves public synthetic data.

Pinned additions (exact; the MCP packages are the MCPV2 recipe below): `@modelcontextprotocol/ext-apps` 2.0.3 (MIT; its optional `react` peers are not installed) with `client`, `core` and `server` 2.4.0 and `node` 2.1.1 (Apache-2.0), dev-only `vite-plugin-singlefile` 2.3.3, `tsx` 4.23.15, `@types/node` 22.20.5. No MCP-UI, CopilotKit, AG-UI or Ora packages are used; the only overlap is shared standards (MCP, MCP Apps, A2UI).
Pinned additions (exact; the MCP packages are the MCPV2 recipe below): `@modelcontextprotocol/ext-apps` 2.1.0 (MIT; its optional `react` peers are not installed) with `client`, `core` and `server` 2.4.0 and `node` 2.1.1 (Apache-2.0), dev-only `vite-plugin-singlefile` 2.3.3, `tsx` 4.23.15, `@types/node` 22.20.5. No MCP-UI, CopilotKit, AG-UI or Ora packages are used; the only overlap is shared standards (MCP, MCP Apps, A2UI).

npm run build # app, the single-file view (dist/view/view.html) and the test-only host fixture
npm run serve:mcp # MCP server at http://127.0.0.1:3001/mcp (PORT overrides); needs the build first
Expand Down Expand Up @@ -386,7 +386,7 @@ Both starts return a running view synchronously (`{ ready, dispose }`; the A2UI
- Capabilities (`updateModelContext`, `message`) are read at call time. With `context`, `ui/update-model-context` is sent first (`text` as one text content block, `data` as `structuredContent`), then `ui/message` with role `user` and one text content block, never in parallel. A context that is unsupported, rejected or answered with `isError` stops the message at `stage: 'context'` unless `bestEffort` is set; then the message is still attempted and an accepted one reports `{outcome: 'appended', context: <why>}`.
- Payloads are admitted before anything is sent, against `CONVERSATION_LIMITS` in `packages/mcp/src/view/conversation.ts` (16 KiB of UTF-8 for the message, 16 KiB for the context text, 64 KiB of exact `JSON.stringify` size for the structured data). `snapshotData` copies the data while it measures it, with escapes, braces, colons and commas counted, and the request carries that copy, so the original object is never read again; the context object is read once. The data must be plain objects, arrays, strings, finite numbers, booleans and `null`, at most 32 levels deep without cycles: `undefined`, `bigint`, symbols, functions, non-finite numbers, array holes, accessor properties and non-plain objects (`Map`, `Date`, class instances) are refused, not converted. An over-budget message resolves `denied` at the message stage with nothing sent, context included; an over-budget context resolves `denied` at the context stage and follows the `bestEffort` rules. Nothing is truncated or rewritten, and the walk stops once it passes the budget (an array longer than the budget is refused unread). The limits are the SDK's own.
- Once the lifetime signal has aborted, including while a request is outstanding, the call resolves `unavailable`, sends nothing further and ignores the late answer. Nothing is retried.
- The pinned bridge (`@modelcontextprotocol/ext-apps` 2.0.3) gives `ui/update-model-context` an empty result and rejects any other key, so a host cannot answer a context update with `isError`: its refusal can only be a rejected request, which reads `failed`. `denied` at the context stage is kept for a bridge that allows it. `ui/message` can answer `isError`, which reads `denied`.
- The pinned bridge (`@modelcontextprotocol/ext-apps` 2.1.0) gives `ui/update-model-context` an empty result and rejects any other key, so a host cannot answer a context update with `isError`: its refusal can only be a rejected request, which reads `failed`. `denied` at the context stage is kept for a bridge that allows it. `ui/message` can answer `isError`, which reads `denied`.

Tests: `packages/core/src/conversation.test.ts` (the default instance), `packages/mcp/src/view/conversation.test.ts` (a real `App` and `AppBridge`: message only, the order, a held context, withheld, failed and best-effort contexts, refused, failing and unavailable messages, payloads at and over each budget, exact JSON sizing with escapes, non-JSON values, accessors and large objects, an ended view before and during each request), the `ViewHost.conversation` case in each stack's `start-a2ui-view.test.ts`, and `apps/lit-host/e2e/view-conversation.spec.ts`, which runs the same scenarios through both stacks in Chromium against the local host fixture (probes in `e2e/view-conversation/`, built by `vite.view-conversation.config.ts`; like the view-stack probes, not part of the installed-package set). The fixture (`host-fixture/host.ts`) takes `conversation: {context, message}` in `mountView`, each `accept`, `refuse`, `fail`, `withhold` (the capability is not advertised) or `hold` (recorded, unanswered until `releaseConversation`), and records every request in order (`conversationRequests`).

Expand Down Expand Up @@ -512,7 +512,7 @@ Mechanisms chosen for the Blueprint's open decisions (the evidence is the tests
- **Playing means the audio clock advanced.** The view reports `playing` only after that signal; `denied`, `expired`, `unsupported`, `unavailable`, `media-failed` and `gesture-needed` are distinct states, and pending work is retired so a late success cannot restore cleared media. PLAY1 did this with a generation number and an `attemptLimitMs` bound inside the controller; CORE2 replaced both with the shared action runtime: a changed `subject` and the action's work deadline (30,000 ms by default) retire the press, and one deadline covers every client stage until audio advances. See "Bounded requests and playback (STATE2)" below.
- **Pause, Resume and a short episode tell the truth.** `pause()` and `resume()` return promises, so the view shows `paused` or `playing` only once the audio did that. A Resume the browser withholds (`NotAllowedError`, or a clock that does not advance) shows `resume-blocked`, keeps the position, and the next explicit press retries it without a new authorization. A Pause or Resume the player cannot perform is `media-failed`. Audio that ends before the start is confirmed shows `ended`: the end is remembered for the request that was waiting, and the late `started` result cannot replace it, while a retired request still cannot restore anything.

Wire mapping, verified against `@modelcontextprotocol/server` 2.4.0 and `ext-apps` 2.0.3 (see the MCPV2 recipe below):
Wire mapping, verified against `@modelcontextprotocol/server` 2.4.0 and `ext-apps` 2.1.0 (see the MCPV2 recipe below):

| Condition | Result |
| --- | --- |
Expand Down Expand Up @@ -1275,7 +1275,7 @@ The complete example is `apps/lit-host/e2e/archive-fixture/archive.ts` (test-onl

**The cumulative contract.** The shared runtime keeps the behavior recorded for ACT1 to ACT5, and the server action takes it unchanged: synchronous and asynchronous local work, ordered reusable prerequisites, both busy policies, per-callback `current()`, immutable outcomes, bounded hooks and finalization, retirement by disposal, replacement, subject change and instance loss, and the ACT5 lease acquisition, release and transfer. `server-actions.test.ts` checks the join for a server action (a lease taken in the event turn and transferred or released across success, a timeout and a cancellation; every hook, the release and `onFinally` bounded by `hookMs`). `authoring-qualification.test.ts` runs the Blueprint's positive and negative authoring cases that the earlier slices did not yet check (P1 to P8, L1, N4, N9 to N11, N17, N18, N23 to N25) against the package's real exports and its own tsconfig rather than the evidence companion's stand-in declarations, and names the suites that check the others (N1 to N3, N5 to N8, N12 to N16, N19 to N22). The renderer-facing cases are the Lit, React and Lynx fixtures themselves, compiled by `npm run typecheck` against the published renderer packages.

**The pinned transport types.** `archive-integration.test.ts` declares `(app: App) => ServerTransport<CallToolResult>` and builds `Action.server<Event, CallToolResult>('archive-note', app)`. They compile against `@modelcontextprotocol/ext-apps` 2.0.3 and `@modelcontextprotocol/client` 2.4.0, the pinned `App.callServerTool(params: CallToolRequest['params'], options?: RequestOptions): Promise<CallToolResult>`, so the Blueprint's stand-ins are replaced by the real types. A transport whose reply has no `content` is a compile error.
**The pinned transport types.** `archive-integration.test.ts` declares `(app: App) => ServerTransport<CallToolResult>` and builds `Action.server<Event, CallToolResult>('archive-note', app)`. They compile against `@modelcontextprotocol/ext-apps` 2.1.0 and `@modelcontextprotocol/client` 2.4.0, the pinned `App.callServerTool(params: CallToolRequest['params'], options?: RequestOptions): Promise<CallToolResult>`, so the Blueprint's stand-ins are replaced by the real types. A transport whose reply has no `content` is a compile error.

**Dependency separation.** Core's own export test bundles `@saga-a2ui/core/actions` and finds `server.ts` in the graph and no MCP, renderer, DOM or other third-party module. The built Lit consumer holds the archive definition and its fixture, which import MCP types only; `view-bundle.spec.ts` (every file under `dist/`, including `dist/action-consumer`) finds no server transport, server registration or credential in it.

Expand Down Expand Up @@ -1391,7 +1391,7 @@ Both pages and both servers come from the same application source. The two bundl
| --- | --- |
| Upstream A2UI | `@a2ui/web_core` 0.13.0 in `core`, `lit`, `components` and `react`; `@a2ui/lit` 0.13.0 in `lit`; `@a2ui/react` 0.13.0 in `react` |
| Zod | Zod 3 only as core's `zod3` alias (`npm:zod@3.25.76`, for A2UI catalog schemas); Zod 4.6.7 in `components` and `mcp` |
| MCP set | `@modelcontextprotocol/client`, `core`, `server` 2.4.0 and `ext-apps` 2.0.3, all in `mcp` and nowhere else |
| MCP set | `@modelcontextprotocol/client`, `core`, `server` 2.4.0 and `ext-apps` 2.1.0, all in `mcp` and nowhere else |
| Between SDK packages | range `*` in the repository, resolved by installing the packed copy; any other form (`workspace:`, `file:`, `link:`, a path, a URL) fails. Since PACK2 the packed copy pins the exact candidate version instead of `*` |
| Optional components | `lit` and `react` name `@saga-a2ui/components` only as an optional peer (`*`); `core`, `mcp` and `components` name no renderer; `markdown-it` 15.0.3 belongs to `components` alone |
| React | peers `react` and `react-dom` `^19.3.1`; `./faithful-card.css` is a named export, not a file path |
Expand Down Expand Up @@ -1724,7 +1724,7 @@ The browse and Play paths run on one peer-complete MCP Apps 2 recipe. It replace

| Package | Exact version | License |
| --- | --- | --- |
| `@modelcontextprotocol/ext-apps` | 2.0.3 | MIT |
| `@modelcontextprotocol/ext-apps` | 2.1.0 | MIT |
| `@modelcontextprotocol/client`, `core`, `server` | 2.4.0 | Apache-2.0 |
| `@modelcontextprotocol/node` | 2.1.1 | Apache-2.0 |
| `zod` (MCP seam, hoisted to the workspace root) | 4.6.7 | MIT |
Expand Down
2 changes: 1 addition & 1 deletion docs/release-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ qualification directly, without a public RC.
Verify the seven tarballs that would be published, not a candidate or an earlier
build of different source. Evidence from another source revision or version
identity is context, not qualification of these bytes. Use React and React DOM
**19.3.1**, MCP Apps **2.0.3**, and Zod **4.6.7** for MCP schemas. Keep the
**19.3.1**, MCP Apps **2.1.0**, and Zod **4.6.7** for MCP schemas. Keep the
consumer-root overrides from [public installation](public-install.md#install-one-stack-plus-framework-peers). SAGA owns A2UI **0.13.0** and its
Zod **3.25.76** context; do not add direct application A2UI dependencies or force
a single Zod version. Follow [React consumer dependencies](react-consumers.md)
Expand Down
2 changes: 1 addition & 1 deletion examples/pack1-consumer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "module",
"dependencies": {
"@modelcontextprotocol/client": "2.4.0",
"@modelcontextprotocol/ext-apps": "2.0.3",
"@modelcontextprotocol/ext-apps": "2.1.0",
"@modelcontextprotocol/server": "2.4.0",
"@saga-a2ui/components": "*",
"@saga-a2ui/core": "*",
Expand Down
Loading
Loading