Owns the product
Question and answer contract, API client, status wording, fonts, branding, attribution treatment, HTTP configuration and platform entry points.
diff --git a/apps/mcp/Dockerfile b/apps/mcp/Dockerfile index e5227bee..a6ff0511 100644 --- a/apps/mcp/Dockerfile +++ b/apps/mcp/Dockerfile @@ -5,6 +5,8 @@ RUN corepack enable && corepack prepare pnpm@10.33.0 --activate COPY package.json pnpm-lock.yaml pnpm-workspace.yaml LICENSE ./ COPY packages packages COPY apps/mcp/package.json apps/mcp/ +# Until SAGA 1.0.0-rc.1 is on the registry, the install resolves it from these private tarballs. +COPY apps/mcp/candidates apps/mcp/candidates RUN pnpm install --strict-peer-dependencies --frozen-lockfile --filter ansari-mcp... COPY apps/mcp apps/mcp RUN pnpm mcp build diff --git a/apps/mcp/README.md b/apps/mcp/README.md index 6f027629..e1c4e5df 100644 --- a/apps/mcp/README.md +++ b/apps/mcp/README.md @@ -1,6 +1,6 @@ # Ansari MCP -A standalone MCP application. `answer_islamic_question` calls the Ansari API and returns its complete answer and attribution unchanged. SAGA links each result to one content-addressed, self-contained React card. The application owns Ansari branding, floral artwork, theme mapping and Hono HTTP hosting; SAGA owns content interpretation, FaithfulCard rendering, content policy, consent and MCP view linkage. +A standalone MCP application. `answer_islamic_question` calls the Ansari API and returns its complete answer and attribution unchanged. SAGA links each result to one self-contained React card at a stable, versioned address. The application owns Ansari branding, floral artwork, theme mapping and Hono HTTP hosting; SAGA owns content interpretation, FaithfulCard rendering, content policy, consent and MCP view linkage. ## How it works @@ -32,11 +32,15 @@ User-provided capture of the Dev answer card in Claude, showing Arabic text, RTL ## Dependencies -SAGA is installed from the public npm registry at one exact release, `0.7.6` (stable), from the [0.7.6 release branch](https://github.com/MachineWisdomAI/saga-a2ui/tree/codex/release-0.7.6): the stack `@saga-a2ui/react-mcp-stack` -plus its exact-version siblings `@saga-a2ui/core`, `@saga-a2ui/components`, `@saga-a2ui/mcp` and `@saga-a2ui/react`. No private registry, -credential, SDK tarball, SDK workspace link or SDK-package override is involved, and the manifest pins an exact version rather than a tag or range. -`pnpm check:release --target beta` checks the pinned release against the registry and the lockfile; `--target production` additionally refuses a -prerelease. The Beta and Production deploy scripts run the corresponding gate before any build or deployment. +SAGA is the 1.0 release candidate, `0.0.0-candidate.g8b4d987dd233`: the stack `@saga-a2ui/react-mcp-stack` plus its exact-version +siblings `@saga-a2ui/core`, `@saga-a2ui/components`, `@saga-a2ui/mcp` and `@saga-a2ui/react`, packed from the SDK's source at commit +`8b4d987` and committed under `candidates/` until `1.0.0-rc.1` is on the npm registry. The manifest pins that exact version; the root +`pnpm-workspace.yaml` overrides the five siblings to those tarballs, and the lockfile records their integrity. No private registry or +credential is involved. Moving to the registry release replaces the pin and the overrides and deletes `candidates/`. +The card's address names its compatibility line, `ui://views/answer-card.v1.html` (`VIEW_MAJOR` in `src/server/view-artifact.ts`), not its +bytes: every compatible release is served at the same address, and only a change an already-loaded card cannot handle increases it. +`pnpm check:release --target beta` checks the pinned release against the registry and the lockfile, and refuses the candidate; `--target production` additionally refuses a +prerelease. The Beta and Production deploy scripts run the corresponding gate before any build or deployment, so neither deploys until the release is published. Dev accepts the candidate. The application depends on one SAGA package, the stack `@saga-a2ui/react-mcp-stack`, plus React and React DOM, and imports only its subpaths: `/faithful-card` (`FaithfulCardApp`, `startFaithfulCardApp`, and the definition's one `.result(...)` reader that reads the whole delivered result, @@ -100,7 +104,7 @@ The Worker takes the variables in the table below as Wrangler `vars` or secrets, | `MCP_VIEW_DIAGNOSTICS_ORIGIN` | Set only on a deployment being diagnosed: its own public origin, such as `https://ansari-mcp-development.up.railway.app`. The card may then connect to that one origin and reports its start-up milestones to `POST /diag/view`; this changes the card's bytes, address and permissions. Unset, the card, its address and its closed permissions are unchanged and the route does not exist. A value that is not a bare `https` origin (`http` only for localhost) stops startup. Implies `MCP_LOG_LEVEL=debug`. See [First-load diagnosis](#first-load-diagnosis). | | `MCP_LOG_LEVEL` | `info` (default) or `debug`. `debug` also writes the first-load measurement records; set it only on a deployment being measured. Any other value, or `info` together with `MCP_VIEW_DIAGNOSTICS_ORIGIN`, stops startup. | -`POST /mcp` serves stateless MCP requests through Hono and the official MCP request handler. Modern requests need no initialization; older hosts can use the handler’s stateless compatibility path. No MCP session IDs, session map, expiry timer or replay store are retained. `GET /mcp` and `DELETE /mcp` return 405. `/health` exposes the release. The HTML is supplied by the hosting target (read from `dist/` at Node start, bundled into the Worker), revision-stamped and fingerprinted once before the first request is served: the release is stamped first, so equal final bytes have equal identities and a release change legitimately changes the hash. Each request creates its own lightweight MCP server using that prepared view; resource bytes and identity stay fixed until restart. No demo, font server, alternative renderer or historical resource is served. Tool calls have a fresh 60-second budget; timeout yields a safe error and leaves subsequent calls usable. The same lifetime signal aborts the upstream fetch and response-body read when the request ends or the budget expires. +`POST /mcp` serves stateless MCP requests through SAGA's fetch app (`createMcpFetchApp`), which admits by Host and Origin, bounds the body and the request budget, ends each request's lifetime, and runs the official MCP request handler. The application adds only `/health`, the Dev-only diagnostics beacon and its logs around it. Modern requests need no initialization; older hosts can use the handler’s stateless compatibility path. No MCP session IDs, session map, expiry timer or replay store are retained. `GET /mcp` and `DELETE /mcp` return 405. `/health` exposes the release. The HTML is supplied by the hosting target (read from `dist/` at Node start, bundled into the Worker), revision-stamped once before the first request is served. The release stamp changes the card's bytes, not its address, which is `ui://views/answer-card.v1.html` for every compatible release. Each request creates its own lightweight MCP server using that prepared view; resource bytes and identity stay fixed until restart. No demo, font server, alternative renderer or historical resource is served. Tool calls have a fresh 60-second budget; timeout yields a safe error and leaves subsequent calls usable. The same lifetime signal aborts the upstream fetch and response-body read when the request ends or the budget expires. Closing a request server releases its budget even if it was never connected to a transport. On SIGINT or SIGTERM the listener stops accepting requests, closes MCP handlers and HTTP connections, and exits; unfinished calls are interrupted, including older-host SSE calls. Fetch cancellation does not guarantee that the remote backend has stopped processing. Caller-disconnect propagation depends on the host transport; the local Workers runtime does not establish immediate upstream cancellation from a client disconnect. Actual fetch cancellation at the request deadline is covered on both hosting targets. @@ -112,7 +116,7 @@ the host gave up before the card showed anything. The aim is to locate the first the card's start, the host handshake, tool-result arrival and the first rendered answer, where that step can be observed at all. After the trials, this is removed or narrowed to what proved useful. -**Log levels.** Every record carries a `level`. `error`: `mcp.error` and `source.error`, by error name and code only. +**Log levels.** Every record carries a `level`. `error`: `mcp.error` (SAGA's fixed failure `kind` for `/mcp`: `server-factory`, `handler` or `route`; or, for a failure outside it, the error name and code) and `source.error`. `warn`: rejected or rate-limited card reports. `info`: the operational records (`startup`, `mcp.request` with the HTTP method, `source.start`, `source.complete`). `debug`: the measurement records below, written only with `MCP_LOG_LEVEL=debug`. At `info` the server neither inspects nor wraps an exchange, and it writes the records of a diff --git a/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-components-0.0.0-candidate.g8b4d987dd233.tgz b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-components-0.0.0-candidate.g8b4d987dd233.tgz new file mode 100644 index 00000000..0a01a17a Binary files /dev/null and b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-components-0.0.0-candidate.g8b4d987dd233.tgz differ diff --git a/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-core-0.0.0-candidate.g8b4d987dd233.tgz b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-core-0.0.0-candidate.g8b4d987dd233.tgz new file mode 100644 index 00000000..33512d44 Binary files /dev/null and b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-core-0.0.0-candidate.g8b4d987dd233.tgz differ diff --git a/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-mcp-0.0.0-candidate.g8b4d987dd233.tgz b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-mcp-0.0.0-candidate.g8b4d987dd233.tgz new file mode 100644 index 00000000..66bcad02 Binary files /dev/null and b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-mcp-0.0.0-candidate.g8b4d987dd233.tgz differ diff --git a/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-react-0.0.0-candidate.g8b4d987dd233.tgz b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-react-0.0.0-candidate.g8b4d987dd233.tgz new file mode 100644 index 00000000..038cb5a1 Binary files /dev/null and b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-react-0.0.0-candidate.g8b4d987dd233.tgz differ diff --git a/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-react-mcp-stack-0.0.0-candidate.g8b4d987dd233.tgz b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-react-mcp-stack-0.0.0-candidate.g8b4d987dd233.tgz new file mode 100644 index 00000000..36b64d67 Binary files /dev/null and b/apps/mcp/candidates/saga-0.0.0-candidate.g8b4d987dd233/saga-a2ui-react-mcp-stack-0.0.0-candidate.g8b4d987dd233.tgz differ diff --git a/apps/mcp/docs/show-me-ansari-mcp.html b/apps/mcp/docs/show-me-ansari-mcp.html index 3799d81b..13b7e95d 100644 --- a/apps/mcp/docs/show-me-ansari-mcp.html +++ b/apps/mcp/docs/show-me-ansari-mcp.html @@ -42,19 +42,21 @@ @media(max-width:900px){.tabs{grid-template-columns:repeat(3,1fr)}.responsibility,.lifecards{grid-template-columns:1fr 1fr}.journey{grid-template-columns:220px 1fr}} @media(max-width:600px){.tabs{grid-template-columns:repeat(2,1fr);gap:7px}.tabs button{min-height:46px}.top{flex-wrap:wrap}.theme{margin-left:auto}.top .titleblock{order:2;width:100%}.top>.mark{width:46px}.journey{grid-template-columns:1fr}.minirow,.scopegrid,.responsibility,.lifecards,.sourceindex{grid-template-columns:1fr}.questionbar img{width:48px;height:48px}.steps{grid-template-columns:1fr 1fr}.step{font-size:12px;padding:8px;min-height:44px}.detailgrid{grid-template-columns:1fr}.groupbuttons{grid-template-columns:1fr 1fr}.map{grid-template-columns:1fr}.map>.arrow{display:none}.map>.mapbox:nth-child(5){grid-column:auto}.mapbox{margin-bottom:7px}.runtime-counter{min-width:0}.runtime-counter strong{font-size:23px}.source-exact{line-height:1.6}header,main,footer{padding-left:18px;padding-right:18px}pre{font-size:11px}.detail{padding:18px}} .whycards{grid-template-columns:repeat(3,1fr)}@media(max-width:850px){.whycards{grid-template-columns:1fr}}@media(min-width:951px){.tabs{grid-template-columns:repeat(7,minmax(0,1fr))}}
-From a question in a conversation to a complete, attributed answer in a branded card. Explore the whole application: its data flow, rendering contract, hosting boundaries and release lifecycle.
From a question in a conversation to a complete, attributed answer in a branded card. Explore the whole application: its data flow, rendering contract, hosting boundaries and release lifecycle.
The answer service owns the answer. Ansari owns the application. The SAGA stack connects that meaning to an MCP card.
The application supplies the presentation and every user-facing status sentence. The stack supplies the shared rendering and interaction mechanics.
Kitab Regular and Bold are embedded as bytes and loaded through FontFace. Font readiness has its own failure sentence; no device font is silently substituted for an answer.
readingProfile comes from the stack. Ansari maps host colors, reading tokens and direction without changing answer data or applying a second root scale.
The card renders the full answer once. Ansari watches the rendered closing block to style the attribution; the optional framework credit is separate.
The browser imports one public stack. Hosting adapters inject their platform-specific inputs into one fetch application. Explore a module to see its real source and its boundary.
Question and answer contract, API client, status wording, fonts, branding, attribution treatment, HTTP configuration and platform entry points.
Host bridge, held/latest results, Markdown content, document policy, image consent, host links, own sizing and teardown. Server exports own view linkage and request-lifetime attachment.
Tool selection, permissions, discovery/cache behavior, card sandbox and external-navigation experience. The app does not dictate host refresh timing.
The reusable object is the immutable card document. Each MCP request gets a fresh server and budget. Platform shutdown and iframe teardown are separate lifecycles.
This small model illustrates ownership; it does not send MCP traffic.
Ready. No active request.
The declared tool bound and each request budget agree. Closing its server releases the lifetime even if no transport was connected. The same lifetime signal aborts the upstream fetch and response-body read; remote backend processing may still continue.
SSE can carry client-requested progress. The operation still returns one complete answer. JSON response mode sends the final result; older-host compatibility can use SSE.
The card and server are built together. Node supplies a listener and filesystem access at startup; Workers supplies a fetch entry and bindings. Neither changes the answer contract.
@saga-a2ui/react-mcp-stack 0.7.6 ++ Application meaning, shared mechanics
The browser imports one SAGA stack. Hosting adapters inject their platform-specific inputs into one fetch application. Explore a module to see its real source and its boundary.
Ansari Owns the product
Question and answer contract, API client, status wording, fonts, branding, attribution treatment, HTTP configuration and platform entry points.
SAGA stack Owns the card connection
Host bridge, held/latest results, Markdown content, document policy, image consent, host links, own sizing and teardown. Server exports own view linkage; its fetch app owns
/mcpadmission, request budgets and lifetimes.Host Owns the conversation
Tool selection, permissions, discovery/cache behavior, card sandbox and external-navigation experience. The app does not dictate host refresh timing.
+ Prepare once. Give each request its own lifetime.
The reusable object is the immutable card document. Each MCP request gets a fresh server and budget. Platform shutdown and iframe teardown are separate lifecycles.
One preparationIndependent requestsTry the lifetime model
This small model illustrates ownership; it does not send MCP traffic.
1prepared views0active calls0retired callsReady. No active request.60-second application bound
The declared tool bound and each request budget agree. Closing its server releases the lifetime even if no transport was connected. The same lifetime signal aborts the upstream fetch and response-body read; remote backend processing may still continue.
Progress is not answer streaming
SSE can carry client-requested progress. The operation still returns one complete answer. JSON response mode sends the final result; older-host compatibility can use SSE.
· ·- One card. Two hosting artifacts.
The card and server are built together. Node supplies a listener and filesystem access at startup; Workers supplies a fetch entry and bindings. Neither changes the answer contract.
Source + dependenciesInstall the SAGA 1.0 candidate
@saga-a2ui/react-mcp-stack + 0.0.0-candidate.g8b4d987dd233 react 19.3.1 react-dom 19.3.1 -All five SDK siblings: 0.7.6 -@a2ui/markdown-it override: 0.2.1The app imports only stack subpaths. The application pins these exact public npm packages in the repository root’s one committed lockfile; no SDK source checkout, local tarball or package-manager override for SAGA is involved.
→Build-time content policyProduce self-contained HTML
Vite + single-file plugin +All five SDK siblings: same version + apps/mcp/candidates/ (SDK 8b4d987) +@a2ui/markdown-it override: 0.2.1The app imports only stack subpaths. The manifest pins the SAGA 1.0 release candidate, packed from the SDK’s source at commit
8b4d987and committed underapps/mcp/candidates/until1.0.0-rc.1is on the npm registry. The rootpnpm-workspace.yamloverrides the five siblings to those tarballs, and the root’s one committed lockfile records their integrity. No private registry or credential is involved.→Build-time content policyProduce self-contained HTML
Vite + single-file plugin React + card code + CSS embedded Kitab + floral assets application + SDK identity @@ -67,21 +69,22 @@ Worker: pnpm build:worker Wrangler deploy --dry-run - dist-worker/ bundle + HTMLA dry-run build does not publish or deploy a Worker.
Identity follows the final bytes
Build stamps app + SDK identity + dist-worker/ bundle + HTMLA dry-run build does not publish or deploy a Worker.
The address names the compatibility line
Build stamps app + SDK identity ↓ Runtime stamps release / revision ↓ -operationViewFromHtml(final HTML) +operationViewFromHtml(final HTML, + major: VIEW_MAJOR = 1) ↓ -content-addressed MCP resourceA changed release marker can legitimately change the resource URI. The same URI must link discovery, tool results and resource reads.
Validate the installation and artifacts
pnpm install --frozen-lockfile \ +ui://views/answer-card.v1.htmlA release stamp or a compatible card change keeps the address and changes only the bytes. Only a change an already-loaded card cannot handle increases
VIEW_MAJOR. The same URI links discovery, tool results and resource reads.Validate the installation and artifacts
pnpm install --frozen-lockfile \ --strict-peer-dependencies pnpm --filter ansari-mcp verify # Uncached root-context container qualification -bash apps/mcp/scripts/verify-container.shThe root lockfile is authoritative. Frozen workspace installation and a root-context Docker smoke check protect packaging; they do not establish an actual host connection.
Setting Where it belongs Why it matters Upstream URL, client label, optional auth Node environment / Worker bindings Same client contract; secrets stay out of source. Allowed Host and Origin Named deployment environment Validation matches the selected public endpoint. Dev / Beta / Production Distinct native Wrangler environment configuration Select an explicit target and keep release/rollback decisions separate. Application, SDK and resource identity Built and served card metadata Inspect the card actually loaded by the host, rather than assuming its cache is current. - Four lifecycles meet at one card
A server release, a host catalog, a request and an embedded view each have a different owner and lifetime. Keeping them distinct explains refreshes, retries and teardown.
01 · Release Build & serve
Install exact packages, build artifacts, configure a named target, start the application and prepare immutable view bytes.
02 · Host catalog Discover & refresh
Register an MCP endpoint and discover its tool and UI association. After a release, verify the host sees the current resource identity.
03 · Request Invoke & finish
Send a question, call the API, return one full result and release the per-request server lifetime. Calls remain independent.
04 · View Mount & retire
Connect the bridge, wait for fonts, present the latest result and apply host context. Teardown releases owned browser resources.
What this walkthrough establishes
It explains checked application source, using real names and exact excerpts. The interactive diagrams and card examples are synthetic and make no network requests.
- Runtime and view composition are grounded in the complete application source of app
0.16.6as it is committed in this folder.- SAGA
0.7.6is installed from the public npm registry as one exact release; the root lockfile records its registry integrity.- This document is a fresh explanation of the full architecture, not a summary of changes from an earlier version.
What must be verified in a host
- A real tool invocation and an actual embedded answer card.
- Matching application revision, SDK identity and served resource bytes.
- Fonts, language direction, attribution, sizing and the relevant consent interactions.
- First-load success distinguished from recovery after Retry.
Rendering inside ChatGPT, Claude and Alexa+ is a separate host acceptance. Neither this document nor this repository’s automated checks establish it.
Read the source offline
Each path links to its exact source commit on GitHub. The embedded source remains available offline through “Read embedded source”.
- Why build on SAGA
Ansari could build this card directly on the MCP Apps SDK. Building on SAGA instead means Ansari starts from A2UI’s published protocol and a complete answer component, so its own code deals with answers and branding rather than MCP Apps wiring.
01 · Protocol A published binding contract
A2UI specifies how component properties bind to data-model paths and how model updates converge. SAGA uses the upstream A2UI processor and React surface rather than a second binding engine. Ansari’s card binds two fields: the contract version and the answer Markdown.
02 · Component A complete answer card
FaithfulCard renders the whole answer: nested lists, quotes, code, mixed-direction text, labelled scrollable tables, and links that open through the host with their hostname shown. It also handles answer replacement and view readiness. Ansari styles its documented surface with its own
theme.css.03 · Shielding MCP Apps stays behind the stack
An MCP App has two ends of one iframe channel: the view’s
Appand the host’sAppBridge, with mirrored methods. A view can ask withrequestTeardown(), but only the host’sAppBridge.teardownResource()tears the view down. The specification describes wire messages, with host and view rules side by side, and names neither class.AppBridge’s host role appears only in the overview diagram and its API reference, so a view author has to work out which half applies. Ansari’s source imports nothing from@modelcontextprotocol/ext-apps.What a direct build would add
Rough estimate for the same card built on
ext-apps, React and a maintained Markdown renderer:
- View: about 700–1,500 TS/TSX lines plus 100–200 CSS lines for the rendering rules, host links, readiness, sizing and lifecycle.
- Server: about 50–150 lines for the content-addressed card resource, output validation, failure mapping, the 60-second deadline and per-request cleanup.
- Together, about 750–1,850 lines, next to the app’s current 1,394 lines of source.
Tests are not counted. No direct version has been built, so these figures are order-of-magnitude estimates, not measured savings.
What Ansari still owns
- What an answer means and every user-facing status sentence.
- The theme, host-color mapping, Kitab fonts, floral artwork and attribution treatment.
- Hono hosting, the Node and Worker entry points, and deployment.
- SAGA upgrades and host acceptance.
In exchange, fixes to parsing, the card and delivery arrive from upstream with their regression tests (saga-a2ui, Apache-2.0).
| Setting | Where it belongs | Why it matters |
|---|---|---|
| Upstream URL, client label, optional auth | Node environment / Worker bindings | Same client contract; secrets stay out of source. |
| Allowed Host and Origin | Named deployment environment | Validation matches the selected public endpoint. |
| Dev / Beta / Production | Distinct native Wrangler environment configuration | Select an explicit target and keep release/rollback decisions separate. |
| Application, SDK and resource identity | Built and served card metadata; a candidate SDK is data-sdk-kind="candidate" with its source commit and tarball sha256 | Inspect the card actually loaded by the host, rather than assuming its cache is current. |
A server release, a host catalog, a request and an embedded view each have a different owner and lifetime. Keeping them distinct explains refreshes, retries and teardown.
Install exact packages, build artifacts, configure a named target, start the application and prepare immutable view bytes.
Register an MCP endpoint and discover its tool and UI association. After a release, verify the card the host loaded carries the current release.
Send a question, call the API, return one full result and release the per-request server lifetime. Calls remain independent.
Connect the bridge, wait for fonts, present the latest result and apply host context. Teardown releases owned browser resources.
It explains checked application source, using real names and exact excerpts. The interactive diagrams and card examples are synthetic and make no network requests.
0.17.0 as it is committed in this folder.0.0.0-candidate.g8b4d987dd233, packed from SDK commit 8b4d987 and committed under apps/mcp/candidates/; the root lockfile records the tarballs’ integrity.Rendering inside ChatGPT, Claude and Alexa+ is a separate host acceptance. Neither this document nor this repository’s automated checks establish it.
Each path links to its exact source commit on GitHub. The embedded source remains available offline through “Read embedded source”.
Ansari could build this card directly on the MCP Apps SDK. Building on SAGA instead means Ansari starts from A2UI’s published protocol and a complete answer component, so its own code deals with answers and branding rather than MCP Apps wiring.
A2UI specifies how component properties bind to data-model paths and how model updates converge. SAGA uses the upstream A2UI processor and React surface rather than a second binding engine. Ansari’s card binds two fields: the contract version and the answer Markdown.
FaithfulCard renders the whole answer: nested lists, quotes, code, mixed-direction text, labelled scrollable tables, and links that open through the host with their hostname shown. It also handles answer replacement and view readiness. Ansari styles its documented surface with its own theme.css.
An MCP App has two ends of one iframe channel: the view’s App and the host’s AppBridge, with mirrored methods. A view can ask with requestTeardown(), but only the host’s AppBridge.teardownResource() tears the view down. The specification describes wire messages, with host and view rules side by side, and names neither class. AppBridge’s host role appears only in the overview diagram and its API reference, so a view author has to work out which half applies. Ansari’s source imports nothing from @modelcontextprotocol/ext-apps.
Rough estimate for the same card built on ext-apps, React and a maintained Markdown renderer:
Tests are not counted. No direct version has been built, so these figures are order-of-magnitude estimates, not measured savings.
In exchange, fixes to parsing, the card and delivery arrive from upstream with their regression tests (saga-a2ui, Apache-2.0).