From 68dba3e8ce291be7c3c4c35d995fe9a7c8a1e84b Mon Sep 17 00:00:00 2001 From: Younes Abouelnagah Date: Thu, 5 Nov 2026 13:16:05 -0500 Subject: [PATCH 1/2] build(mcp): install SAGA 1.0.1 from npm, with independently versioned siblings Ansari moves to @saga-a2ui/react-mcp-stack@1.0.1. The stack pins its siblings at their own exact versions: core, components and react at 1.0.0, and mcp at 1.0.2. SAGA now versions its packages independently, so the "one SAGA version" rule is gone. Instead, every SAGA package resolves to exactly the version its dependents pin, and the stack resolves to the direct pin: - check-release.mjs reads the pins from the lockfile's snapshots. - packaging.test.ts checks each sibling's npm version and integrity, the exact pins in the installed tree, and per-package licence notices. A new release test accepts independently versioned siblings. It fails on the previous rule. The Dev, Beta and Production gates now pass, the last two with every package verified against npm. App version 0.17.2. Co-Authored-By: Claude Opus 5.5 --- apps/mcp/README.md | 10 +++--- apps/mcp/package.json | 4 +-- apps/mcp/scripts/check-release.mjs | 32 +++++++++++++++--- apps/mcp/src/version.ts | 2 +- apps/mcp/test/packaging.test.ts | 39 +++++++++++----------- apps/mcp/test/release.test.ts | 8 ++++- pnpm-lock.yaml | 52 +++++++++++++++--------------- 7 files changed, 90 insertions(+), 57 deletions(-) diff --git a/apps/mcp/README.md b/apps/mcp/README.md index 1de7f68e..0e15ee73 100644 --- a/apps/mcp/README.md +++ b/apps/mcp/README.md @@ -32,14 +32,16 @@ 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, `1.0.0-rc.1` (the `next` tag): 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, +SAGA is installed from the public npm registry: the stack `@saga-a2ui/react-mcp-stack@1.0.1` plus the siblings it pins exactly, +`@saga-a2ui/core@1.0.0`, `@saga-a2ui/components@1.0.0`, `@saga-a2ui/mcp@1.0.2` and `@saga-a2ui/react@1.0.0`. SAGA versions its packages +independently, so the siblings need not share the stack's version; each resolves to exactly the version the stack pins. 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. The root `pnpm-workspace.yaml` keeps the SDK's documented `dompurify` and `@hono/node-server` overrides. 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; `--target production` additionally refuses a -prerelease, so Production waits for the stable SAGA release. The Beta and Production deploy scripts run the corresponding gate before any build or deployment. +`pnpm check:release --target beta` checks the pinned release against the registry and the lockfile, including that every SAGA package resolves to +the exact version its dependents pin; `--target production` additionally refuses a prerelease. The Beta and Production deploy scripts run the +corresponding gate before any build or deployment. 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, diff --git a/apps/mcp/package.json b/apps/mcp/package.json index 9a854bb2..1be03e24 100644 --- a/apps/mcp/package.json +++ b/apps/mcp/package.json @@ -1,6 +1,6 @@ { "name": "ansari-mcp", - "version": "0.17.1", + "version": "0.17.2", "private": true, "type": "module", "description": "Ansari's MCP application", @@ -31,7 +31,7 @@ "@hono/node-server": "2.1.5", "@modelcontextprotocol/hono": "2.0.2", "@modelcontextprotocol/server": "2.4.0", - "@saga-a2ui/react-mcp-stack": "1.0.0-rc.1", + "@saga-a2ui/react-mcp-stack": "1.0.1", "hono": "4.13.16", "react": "19.3.1", "react-dom": "19.3.1", diff --git a/apps/mcp/scripts/check-release.mjs b/apps/mcp/scripts/check-release.mjs index 8af0b630..5966d842 100644 --- a/apps/mcp/scripts/check-release.mjs +++ b/apps/mcp/scripts/check-release.mjs @@ -103,6 +103,23 @@ function lockedPackages(lock) { return result; } +/** + * Every exact `@saga-a2ui/*` version a resolved SAGA package pins, from the lockfile's snapshots: who pins what, at which + * version. SAGA versions its packages independently, so the siblings need not share one version; each pin must name the + * version the lockfile resolves. + */ +function sagaPins(lock) { + const pins = []; + for (const [, key, block] of section(lock, 'snapshots').matchAll(/^ {2}'?([^'\n]+?)'?:\n((?: {4}.*\n?)*)/gm)) { + const owner = /^(@saga-a2ui\/[^@]+)@/.exec(key)?.[1]; + if (!owner) continue; + for (const [, name, value] of block.matchAll(/^ {6}'?(@saga-a2ui\/[^':]+)'?: '?([^'(\s]+)/gm)) { + pins.push({owner, name, version: value.startsWith('file:') ? localVersion(value) ?? value : value}); + } + } + return pins; +} + /** pnpm-workspace.yaml `overrides:` entries for SAGA packages. */ function sagaOverrides(workspace) { return [...(workspace ?? '').matchAll(/^ {2}'?(@saga-a2ui\/[^':]+)'?: '?([^'\n]+)'?$/gm)].map(([, name, value]) => ({name, value})); @@ -141,7 +158,8 @@ export function validateRelease({appRoot = defaultRoot, target} = {}) { for (const name of manifestOverrides) problems.push(`package.json overrides ${name}: remove every SAGA override for ${target}.`); } - // 3. The resolved graph: one version across every SAGA sibling, equal to the direct pin, from the registry. + // 3. The resolved graph: one version of each SAGA package, each pinned sibling at exactly its resolved version, the stack + // at the direct pin, from the registry. let packages = []; if (lock === undefined) problems.push('pnpm-lock.yaml is missing: commit the resolved graph.'); else { @@ -156,9 +174,15 @@ export function validateRelease({appRoot = defaultRoot, target} = {}) { for (const entry of locked.filter(item => item.name !== STACK)) problems.push(`pnpm-lock.yaml records a redundant direct dependency ${entry.name}.`); packages = lockedPackages(lock); if (!packages.some(entry => entry.name === STACK)) problems.push(`pnpm-lock.yaml resolves no ${STACK}.`); - const versions = [...new Set(packages.map(entry => entry.version ?? `unrecognised:${entry.ref}`))]; - if (versions.length > 1) problems.push(`The resolved SAGA siblings are inconsistent: ${packages.map(entry => `${entry.name}@${entry.version ?? entry.ref}`).join(', ')}. They must share one version.`); - if (versions.length === 1 && stack !== undefined && versions[0] !== stack) problems.push(`The resolved SAGA siblings are ${versions[0]}, but the direct pin is ${stack}.`); + const resolved = new Map(); + for (const entry of packages) resolved.set(entry.name, [...(resolved.get(entry.name) ?? []), entry.version ?? `unrecognised:${entry.ref}`]); + for (const [name, versions] of resolved) if (new Set(versions).size > 1) problems.push(`The resolved SAGA siblings are inconsistent: ${name} resolves to ${[...new Set(versions)].join(' and ')}. Each SAGA package must resolve to one version.`); + for (const pin of sagaPins(lock)) { + const actual = resolved.get(pin.name); + if (actual !== undefined && !actual.includes(pin.version)) problems.push(`The resolved SAGA siblings are inconsistent: ${pin.owner} pins ${pin.name}@${pin.version}, but the lockfile resolves ${pin.name}@${actual.join(', ')}.`); + } + const resolvedStack = resolved.get(STACK); + if (resolvedStack !== undefined && stack !== undefined && !resolvedStack.includes(stack)) problems.push(`The resolved ${STACK} is ${resolvedStack.join(', ')}, but the direct pin is ${stack}.`); const notRegistry = packages.filter(entry => entry.source !== 'registry'); if (release && notRegistry.length > 0) problems.push(`${notRegistry.length} resolved SAGA package(s) are not public registry releases with an integrity hash (${[...new Set(notRegistry.map(entry => entry.source))].join(', ')}); ${target} accepts only registry releases: ${notRegistry.map(entry => entry.name).join(', ')}.`); const localRelease = packages.filter(entry => entry.source === 'file' && entry.version !== undefined && !CANDIDATE.test(entry.version)); diff --git a/apps/mcp/src/version.ts b/apps/mcp/src/version.ts index 590ac4d5..02adf4c0 100644 --- a/apps/mcp/src/version.ts +++ b/apps/mcp/src/version.ts @@ -1,2 +1,2 @@ // One application identity, shared by the server and built view. -export const APP_VERSION = '0.17.1'; +export const APP_VERSION = '0.17.2'; diff --git a/apps/mcp/test/packaging.test.ts b/apps/mcp/test/packaging.test.ts index a1a79d08..8c75a8e4 100644 --- a/apps/mcp/test/packaging.test.ts +++ b/apps/mcp/test/packaging.test.ts @@ -25,19 +25,20 @@ test('packaged HTML is self-contained and assets contain bytes',()=>{ } }); -// ---- The SAGA packages: one exact public npm release, never a tag, range, tarball or local file. -// The expected values below were read from the public npm registry (`npm view @saga-a2ui/@1.0.0-rc.1 dist.integrity`), -// not from the files they are compared with. -const SDK_VERSION='1.0.0-rc.1'; -const SDK_INTEGRITY:Record={ - core:'sha512-0a0MAMSlPTslHhG4Id62UEsmLDZw55e5H/rl9zOMTJseeWRfQUWLieuCt5c11nYHy0FqSNXij8BfhyAq60im9Q==', - components:'sha512-djlrxMR8NXz9sN5IjIC1LIlKEBt4VgqhoAc7b4ZepOpmAywZ6YuyrmHJUZSP/FJviigA0IP7Z2iO0OWIwz3WNA==', - mcp:'sha512-7h6vCTfLvLoHDS6QPD+/waewynSZwf6uX+X+WsvvSLaVJzf9t0emqyT0jxw4qd2EeWbT5nOrWyuxEgvC77p7/A==', - react:'sha512-ZFTuDE1IgEzvMewgqzD+niFE9URfLUwWT77tzhB3EyVcDfHTcT8bIdX5FWqEOsftBpwIk3j+mk7n9gcD1aciSg==', - 'react-mcp-stack':'sha512-YSeb7xFRgy5oWYqkGlkXilfTVrGFobkG00FmRRwxurjbmvbCvNeJHNRwetnVXwB9WxGoLGNWnm2JYurjIvM0lg==', +// ---- The SAGA packages: exact public npm releases, never a tag, range, tarball or local file. +// SAGA versions its packages independently: the stack pins each sibling to one exact version, which need not be its own. +// The versions and integrity below were read from the public npm registry (`npm view @saga-a2ui/react-mcp-stack@1.0.1 +// dependencies`, then `npm view @saga-a2ui/@ dist.integrity`), not from the files they are compared with. +const SDK_VERSION='1.0.1'; +const SDK_PACKAGES:Record={ + core:{version:'1.0.0',integrity:'sha512-vrL4nuxLzFWs98vJKp1P5v3eVFxPAgyu7b17TkHaYqIIAwWDjAN7ZRdyABk5a8MKG7NIWyPFiqLEK04mfn62rA=='}, + components:{version:'1.0.0',integrity:'sha512-SLNlcsw6IrRSd087fTp2uGgBHoqdpUnE+o3G3OZvmRqdRCPZhfjO45mofSGnH2VsrpUQjHTtWSUv0X1NPPHq5g=='}, + mcp:{version:'1.0.2',integrity:'sha512-gbskrVWOXLnrkS6tSl5FUQMSvx/lankpzQGJFMg/9nHMBup+BoV5kkdjdKyzrHMlrzD5WmpEXIZtAJ403r+hPQ=='}, + react:{version:'1.0.0',integrity:'sha512-Pay/Mz2yNcnsoDLkJo8twFEYUoIx3te6n/w/FOo2jfuSrVh7A0HSS7UVEFuo/oT6pkouwJoEZPO5Sqf6NUojOw=='}, + 'react-mcp-stack':{version:SDK_VERSION,integrity:'sha512-5uu8h9NAZGs9sfvjmFKfxQzWMTpm/0DjKrb5x1TtlyDRkiRCUSuD2qmxQVFI7f5/TIMymc4XYxGAROwacx/XNg=='}, }; -const SIBLINGS=Object.keys(SDK_INTEGRITY); -const pinned:string=JSON.parse(readFileSync(join(root,'package.json'),'utf8')).dependencies['@saga-a2ui/react-mcp-stack']; +const SIBLINGS=Object.keys(SDK_PACKAGES); +const ref=(sibling:string)=>`@saga-a2ui/${sibling}@${SDK_PACKAGES[sibling].version}`; const node=(...args:string[])=>execFileSync(process.execPath,args,{cwd:root,encoding:'utf8'}); test('the manifest pins one exact public version for every SAGA package it imports',()=>{ @@ -65,12 +66,12 @@ test('every lockfile resolves the five SAGA packages to the public registry with assert.ok(lockfiles.length>0,'at least one lockfile installs this application'); for(const {label,text} of lockfiles) { for(const sibling of SIBLINGS) { - const key=`'@saga-a2ui/${sibling}@${SDK_VERSION}':\n resolution: {integrity: ${SDK_INTEGRITY[sibling]}}`; + const key=`'${ref(sibling)}':\n resolution: {integrity: ${SDK_PACKAGES[sibling].integrity}}`; assert.ok(text.includes(key),`${label} lockfile locks ${sibling} to the registry release with its integrity`); } // Nothing else from @saga-a2ui, no second SAGA version, and no local source of any kind for it. const resolved=[...new Set([...text.matchAll(/^ {2}'(@saga-a2ui\/[a-z-]+)@([^'(]+)':$/gm)].map(match=>`${match[1]}@${match[2]}`))].sort(); - assert.deepEqual(resolved,SIBLINGS.map(sibling=>`@saga-a2ui/${sibling}@${SDK_VERSION}`).sort(),`${label} lockfile: exactly the five packages at one version`); + assert.deepEqual(resolved,SIBLINGS.map(ref).sort(),`${label} lockfile: exactly the five packages, each at the version the stack pins`); assert.doesNotMatch(text,/@saga-a2ui\/[a-z-]+@file:|tarball: file:|@saga-a2ui\/lit/,`${label} lockfile: no local file or Lit package`); assert.doesNotMatch(text,/(?:\/tmp\/|\/opt\/|\/Users\/|\/home\/|[A-Za-z]:\\)/,`${label} lockfile records no machine path`); } @@ -88,15 +89,15 @@ test('no workspace file overrides a SAGA package',()=>{ assert.equal(existsSync(join(root,'candidates')),false,'no local SAGA inputs are committed'); }); -test('the installed tree has strict same-version siblings and one framework runtime',()=>{ +test('the installed siblings are the versions the stack pins, with one framework runtime',()=>{ const modules=join(root,'node_modules'); const scope=dirname(realpathSync(join(modules,'@saga-a2ui/react-mcp-stack'))); for(const sibling of SIBLINGS) { const installed=JSON.parse(readFileSync(join(scope,sibling,'package.json'),'utf8')); assert.equal(installed.name,`@saga-a2ui/${sibling}`); - assert.equal(installed.version,pinned,`${sibling} is the pinned SAGA version`); - // Every SDK sibling it depends on or needs as a peer is the same version. - for(const field of ['dependencies','peerDependencies']) for(const [dependency,range] of Object.entries(installed[field]??{})) if(dependency.startsWith('@saga-a2ui/')) assert.equal(range,pinned,`${sibling} ${field} ${dependency}`); + assert.equal(installed.version,SDK_PACKAGES[sibling].version,`${sibling} is the version the stack pins`); + // Every SDK sibling it depends on or needs as a peer is pinned to exactly the installed version of that sibling. + for(const field of ['dependencies','peerDependencies']) for(const [dependency,range] of Object.entries(installed[field]??{})) if(dependency.startsWith('@saga-a2ui/')) assert.equal(range,SDK_PACKAGES[dependency.slice('@saga-a2ui/'.length)]?.version,`${sibling} ${field} ${dependency}`); } // Only those five are installed. assert.deepEqual(readdirSync(scope).sort(),SIBLINGS.slice().sort()); @@ -145,5 +146,5 @@ test('distributed HTML retains complete installed SAGA and font licence notices' assert.ok(notices.includes(readFileSync(join(dirname(stack), 'components/THIRD_PARTY_NOTICES.md'), 'utf8').trim())); assert.ok(notices.includes(readFileSync(join(root, 'assets/fonts/OFL.txt'), 'utf8').trim())); assert.ok(notices.includes(readFileSync(join(root, 'assets/NOTICE.md'), 'utf8').trim())); - for (const name of SIBLINGS) assert.ok(notices.includes(`@saga-a2ui/${name}@${SDK_VERSION}`)); + for (const name of SIBLINGS) assert.ok(notices.includes(ref(name)), ref(name)); }); diff --git a/apps/mcp/test/release.test.ts b/apps/mcp/test/release.test.ts index 627b9be0..e803e5b4 100644 --- a/apps/mcp/test/release.test.ts +++ b/apps/mcp/test/release.test.ts @@ -140,7 +140,13 @@ describe('release input validation (fixture inputs, no registry)', () => { const skewed = {...STABLE, lockTransform: (lock: string) => lock.replace("'@saga-a2ui/core@0.7.0'", "'@saga-a2ui/core@0.6.9'").replace("'@saga-a2ui/core': 0.7.0", "'@saga-a2ui/core': 0.6.9")}; for (const target of ['beta', 'production']) assert.match(validate(skewed, target).join('\n'), /siblings are inconsistent/); const wrongPin = {...STABLE, lockTransform: (lock: string) => lock.replaceAll('0.7.0', '0.7.1')}; - assert.match(validate({...wrongPin, stackSpec: '0.7.0'}, 'beta').join('\n'), /siblings are 0\.7\.1, but the direct pin is 0\.7\.0|is stale/); + assert.match(validate({...wrongPin, stackSpec: '0.7.0'}, 'beta').join('\n'), /react-mcp-stack is 0\.7\.1, but the direct pin is 0\.7\.0|is stale/); + }); + + it('accepts independently versioned siblings when every pin names the resolved version', () => { + // SAGA 1.0.x: the stack (here 0.7.0) pins core at its own version; core resolves there and every sibling pins it there. + const independent = {...STABLE, lockTransform: (lock: string) => lock.replaceAll("'@saga-a2ui/core@0.7.0'", "'@saga-a2ui/core@0.6.9'").replaceAll("'@saga-a2ui/core': 0.7.0", "'@saga-a2ui/core': 0.6.9")}; + for (const target of ['beta', 'production']) assert.doesNotMatch(validate(independent, target).join('\n'), /siblings are inconsistent|direct pin/); }); it('rejects a lockfile that was not resolved for the committed pin', () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 251735f5..f71b4835 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -390,8 +390,8 @@ importers: specifier: 2.4.0 version: 2.4.0 '@saga-a2ui/react-mcp-stack': - specifier: 1.0.0-rc.1 - version: 1.0.0-rc.1(@modelcontextprotocol/client@2.4.0)(@modelcontextprotocol/core@2.4.0)(@modelcontextprotocol/server@2.4.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + specifier: 1.0.1 + version: 1.0.1(@modelcontextprotocol/client@2.4.0)(@modelcontextprotocol/core@2.4.0)(@modelcontextprotocol/server@2.4.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) hono: specifier: 4.13.16 version: 4.13.16 @@ -2936,30 +2936,30 @@ packages: '@rushstack/eslint-patch@1.16.1': resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==} - '@saga-a2ui/components@1.0.0-rc.1': - resolution: {integrity: sha512-djlrxMR8NXz9sN5IjIC1LIlKEBt4VgqhoAc7b4ZepOpmAywZ6YuyrmHJUZSP/FJviigA0IP7Z2iO0OWIwz3WNA==} + '@saga-a2ui/components@1.0.0': + resolution: {integrity: sha512-SLNlcsw6IrRSd087fTp2uGgBHoqdpUnE+o3G3OZvmRqdRCPZhfjO45mofSGnH2VsrpUQjHTtWSUv0X1NPPHq5g==} engines: {node: '>=22.12.0'} - '@saga-a2ui/core@1.0.0-rc.1': - resolution: {integrity: sha512-0a0MAMSlPTslHhG4Id62UEsmLDZw55e5H/rl9zOMTJseeWRfQUWLieuCt5c11nYHy0FqSNXij8BfhyAq60im9Q==} + '@saga-a2ui/core@1.0.0': + resolution: {integrity: sha512-vrL4nuxLzFWs98vJKp1P5v3eVFxPAgyu7b17TkHaYqIIAwWDjAN7ZRdyABk5a8MKG7NIWyPFiqLEK04mfn62rA==} engines: {node: '>=22.12.0'} - '@saga-a2ui/mcp@1.0.0-rc.1': - resolution: {integrity: sha512-7h6vCTfLvLoHDS6QPD+/waewynSZwf6uX+X+WsvvSLaVJzf9t0emqyT0jxw4qd2EeWbT5nOrWyuxEgvC77p7/A==} + '@saga-a2ui/mcp@1.0.2': + resolution: {integrity: sha512-gbskrVWOXLnrkS6tSl5FUQMSvx/lankpzQGJFMg/9nHMBup+BoV5kkdjdKyzrHMlrzD5WmpEXIZtAJ403r+hPQ==} engines: {node: '>=22.12.0'} - '@saga-a2ui/react-mcp-stack@1.0.0-rc.1': - resolution: {integrity: sha512-YSeb7xFRgy5oWYqkGlkXilfTVrGFobkG00FmRRwxurjbmvbCvNeJHNRwetnVXwB9WxGoLGNWnm2JYurjIvM0lg==} + '@saga-a2ui/react-mcp-stack@1.0.1': + resolution: {integrity: sha512-5uu8h9NAZGs9sfvjmFKfxQzWMTpm/0DjKrb5x1TtlyDRkiRCUSuD2qmxQVFI7f5/TIMymc4XYxGAROwacx/XNg==} engines: {node: '>=22.12.0'} peerDependencies: react: ^19.3.1 react-dom: ^19.3.1 - '@saga-a2ui/react@1.0.0-rc.1': - resolution: {integrity: sha512-ZFTuDE1IgEzvMewgqzD+niFE9URfLUwWT77tzhB3EyVcDfHTcT8bIdX5FWqEOsftBpwIk3j+mk7n9gcD1aciSg==} + '@saga-a2ui/react@1.0.0': + resolution: {integrity: sha512-Pay/Mz2yNcnsoDLkJo8twFEYUoIx3te6n/w/FOo2jfuSrVh7A0HSS7UVEFuo/oT6pkouwJoEZPO5Sqf6NUojOw==} engines: {node: '>=22.12.0'} peerDependencies: - '@saga-a2ui/components': 1.0.0-rc.1 + '@saga-a2ui/components': 1.0.0 react: ^19.3.1 react-dom: ^19.3.1 peerDependenciesMeta: @@ -11251,19 +11251,19 @@ snapshots: '@rushstack/eslint-patch@1.16.1': {} - '@saga-a2ui/components@1.0.0-rc.1': + '@saga-a2ui/components@1.0.0': dependencies: '@a2ui/web_core': 0.13.0 - '@saga-a2ui/core': 1.0.0-rc.1 + '@saga-a2ui/core': 1.0.0 markdown-it: 15.0.3 zod: 4.6.7 - '@saga-a2ui/core@1.0.0-rc.1': + '@saga-a2ui/core@1.0.0': dependencies: '@a2ui/web_core': 0.13.0 zod3: zod@3.25.76 - '@saga-a2ui/mcp@1.0.0-rc.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + '@saga-a2ui/mcp@1.0.2(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@hono/node-server': 2.1.5(hono@4.13.16) '@modelcontextprotocol/client': 2.4.0 @@ -11271,20 +11271,20 @@ snapshots: '@modelcontextprotocol/ext-apps': 2.0.3(@modelcontextprotocol/client@2.4.0)(@modelcontextprotocol/core@2.4.0)(@modelcontextprotocol/server@2.4.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(zod@4.6.7) '@modelcontextprotocol/hono': 2.0.2(@modelcontextprotocol/server@2.4.0)(hono@4.13.16) '@modelcontextprotocol/server': 2.4.0 - '@saga-a2ui/core': 1.0.0-rc.1 + '@saga-a2ui/core': 1.0.0 hono: 4.13.16 zod: 4.6.7 transitivePeerDependencies: - react - react-dom - '@saga-a2ui/react-mcp-stack@1.0.0-rc.1(@modelcontextprotocol/client@2.4.0)(@modelcontextprotocol/core@2.4.0)(@modelcontextprotocol/server@2.4.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + '@saga-a2ui/react-mcp-stack@1.0.1(@modelcontextprotocol/client@2.4.0)(@modelcontextprotocol/core@2.4.0)(@modelcontextprotocol/server@2.4.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@modelcontextprotocol/ext-apps': 2.0.3(@modelcontextprotocol/client@2.4.0)(@modelcontextprotocol/core@2.4.0)(@modelcontextprotocol/server@2.4.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(zod@4.6.7) - '@saga-a2ui/components': 1.0.0-rc.1 - '@saga-a2ui/core': 1.0.0-rc.1 - '@saga-a2ui/mcp': 1.0.0-rc.1(react-dom@19.3.1(react@19.3.1))(react@19.3.1) - '@saga-a2ui/react': 1.0.0-rc.1(@saga-a2ui/components@1.0.0-rc.1)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@saga-a2ui/components': 1.0.0 + '@saga-a2ui/core': 1.0.0 + '@saga-a2ui/mcp': 1.0.2(react-dom@19.3.1(react@19.3.1))(react@19.3.1) + '@saga-a2ui/react': 1.0.0(@saga-a2ui/components@1.0.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1) react: 19.3.1 react-dom: 19.3.1(react@19.3.1) zod: 4.6.7 @@ -11293,16 +11293,16 @@ snapshots: - '@modelcontextprotocol/core' - '@modelcontextprotocol/server' - '@saga-a2ui/react@1.0.0-rc.1(@saga-a2ui/components@1.0.0-rc.1)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': + '@saga-a2ui/react@1.0.0(@saga-a2ui/components@1.0.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)': dependencies: '@a2ui/react': 0.13.0(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(zod@3.25.76) '@a2ui/web_core': 0.13.0 - '@saga-a2ui/core': 1.0.0-rc.1 + '@saga-a2ui/core': 1.0.0 react: 19.3.1 react-dom: 19.3.1(react@19.3.1) zod: 3.25.76 optionalDependencies: - '@saga-a2ui/components': 1.0.0-rc.1 + '@saga-a2ui/components': 1.0.0 '@selderee/plugin-htmlparser2@0.11.0': dependencies: From f34b552e29280795c384e19adde9bca3c4151d96 Mon Sep 17 00:00:00 2001 From: Younes Abouelnagah Date: Thu, 5 Nov 2026 13:18:00 -0500 Subject: [PATCH 2/2] docs(mcp): the walkthrough installs SAGA 1.0.1 and its pinned siblings from npm Co-Authored-By: Claude Opus 5.5 --- apps/mcp/docs/show-me-ansari-mcp.html | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/mcp/docs/show-me-ansari-mcp.html b/apps/mcp/docs/show-me-ansari-mcp.html index b88f22b3..a19c9bdf 100644 --- a/apps/mcp/docs/show-me-ansari-mcp.html +++ b/apps/mcp/docs/show-me-ansari-mcp.html @@ -42,21 +42,23 @@ @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 1.0.0-rc.1Hono · 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.1Hono · 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
- + -