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))}} -
Ansari floral artwork
A source-guided, interactive walkthrough

Ansari MCP

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.

React FaithfulCardSAGA 0.7.6Hono · stateless MCPNode + Cloudflare WorkersOffline walkthrough · no live API calls
+
Ansari floral artwork
A source-guided, interactive walkthrough

Ansari MCP

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.

React FaithfulCardSAGA 1.0 candidateHono · stateless MCPNode + Cloudflare WorkersOffline walkthrough · no live API calls
Choose a chapter. Each is a view of the complete application.

One question, end to end

The answer service owns the answer. Ansari owns the application. The SAGA stack connects that meaning to an MCP card.

HostAnsari appSAGA stackAnsari API
HostConversationDiscovers, invokes and displays
→
Ansari appHono + MCP operationDeclares the input, answer envelope and view
↔
Ansari APIComplete answerReturns text and attribution unchanged
→
SAGA stackFaithfulCardRenders inside the host’s card surface
- - -