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: 0 additions & 2 deletions apps/mcp/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ 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
Expand Down
13 changes: 6 additions & 7 deletions apps/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,14 @@ User-provided capture of the Dev answer card in Claude, showing Arabic text, RTL

## Dependencies

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/`.
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,
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, 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.
`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.

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,
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
20 changes: 10 additions & 10 deletions apps/mcp/docs/show-me-ansari-mcp.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions apps/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ansari-mcp",
"version": "0.17.0",
"version": "0.17.1",
"private": true,
"type": "module",
"description": "Ansari's MCP application",
Expand Down Expand Up @@ -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": "0.0.0-candidate.g8b4d987dd233",
"@saga-a2ui/react-mcp-stack": "1.0.0-rc.1",
"hono": "4.13.16",
"react": "19.3.1",
"react-dom": "19.3.1",
Expand Down
21 changes: 4 additions & 17 deletions apps/mcp/scripts/sdk-identity.mjs
Original file line number Diff line number Diff line change
@@ -1,26 +1,13 @@
// Prints the SDK identity the build stamps into the card document (`data-sdk-*` on the body), as JSON.
// A public release is identified by its pinned stack version; the registry integrity of every SAGA package is recorded
// in the lockfile and is not repeated here. A private candidate (`0.0.0-candidate.g<commit>`, installed from
// candidates/ until the release is on the registry) is identified by its source commit and the bytes of its tarballs.
import {createHash} from 'node:crypto';
import {readFileSync, readdirSync} from 'node:fs';
// The application installs one exact public SAGA release from the npm registry, so the identity is that pinned stack
// version; the registry integrity of every SAGA package is recorded in the lockfile and is not repeated here.
import {readFileSync} from 'node:fs';
import {fileURLToPath} from 'node:url';

const STACK = '@saga-a2ui/react-mcp-stack';
const CANDIDATE = /^0\.0\.0-candidate\.g([0-9a-f]{12})$/;
const manifest = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');

export function sdkIdentity() {
const version = manifest.dependencies[STACK];
const candidate = CANDIDATE.exec(version);
if (!candidate) return {kind: 'registry', version, source: 'none', stackSha256: 'none', setSha256: 'none'};
const directory = new URL(`../candidates/saga-${version}/`, import.meta.url);
const tarballs = readdirSync(directory).filter(name => name.endsWith('.tgz')).sort();
const digests = tarballs.map(name => `${name} ${sha256(readFileSync(new URL(name, directory)))}\n`);
const stack = `saga-a2ui-react-mcp-stack-${version}.tgz`;
if (!tarballs.includes(stack)) throw new Error(`candidates/saga-${version}/ has no ${stack}.`);
return {kind: 'candidate', version, source: candidate[1], stackSha256: sha256(readFileSync(new URL(stack, directory))), setSha256: sha256(digests.join(''))};
return {kind: 'registry', version: manifest.dependencies['@saga-a2ui/react-mcp-stack'], source: 'none', stackSha256: 'none', setSha256: 'none'};
}

if (process.argv[1] === fileURLToPath(import.meta.url)) console.log(JSON.stringify(sdkIdentity()));
2 changes: 1 addition & 1 deletion apps/mcp/src/version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// One application identity, shared by the server and built view.
export const APP_VERSION = '0.17.0';
export const APP_VERSION = '0.17.1';
88 changes: 32 additions & 56 deletions apps/mcp/test/packaging.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import assert from 'node:assert/strict';
import {test} from 'node:test';
import {execFileSync} from 'node:child_process';
import {createHash} from 'node:crypto';
import {existsSync, readFileSync, readdirSync, realpathSync} from 'node:fs';
import {dirname, join} from 'node:path';
import {fileURLToPath} from 'node:url';
Expand All @@ -26,45 +25,33 @@ test('packaged HTML is self-contained and assets contain bytes',()=>{
}
});

// ---- The SAGA packages: one exact version from one source, never a tag, range or machine path.
// A public release resolves from the npm registry with the integrity below, read from the registry
// (`npm view @saga-a2ui/<name>@<version> dist.integrity`), not from the files compared with it. Until a release is on the
// registry, a private candidate (`0.0.0-candidate.g<commit>`) resolves from the tarballs committed under candidates/,
// and the lockfile must record exactly their bytes.
const REGISTRY_INTEGRITY:Record<string,Record<string,string>>={
'0.7.6':{
core:'sha512-m+ynUS2+f36exYwqimAnnjxtVKJ1+Xm2Zscja+kWI9iFoDxVtT6C2lYxO6exSSXyi4LreJMgFhQsKh0hsaMVDg==',
components:'sha512-Gpm2uc2VbXNrxfWNy2uHfdJYdzNf5ewZKTpsaO5nsLSF0ymDjwzJ45h0BiVD77zyO4KXmDb2QipuCraTbqFApQ==',
mcp:'sha512-LBBOUhhagKZszb+Y6ooZSNDvJs3GZ6EE+Ysk8gsSVkxWrGRdcg5j3GgY/jcYF8K1NzEFQw75rm1KhM+C210ZAA==',
react:'sha512-6oz4GAWZzLjsysHSPKw6zOqYFF5D+bAcp9CtGJ5Exc/9Z+x2OM4Sn7oHMdul47iDMEJi9WNbrI5+DSGvii1a3g==',
'react-mcp-stack':'sha512-ZYv9uL9FAjVRrfboabT6YRu6zHO8JOQKXc9D2pYskpacCRpezsVrQpNtjbh2TUDOdaY+SYEDIAl8+Js1U0oJqQ==',
},
// ---- 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/<name>@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<string,string>={
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==',
};
const SIBLINGS=['core','components','mcp','react','react-mcp-stack'];
const SIBLINGS=Object.keys(SDK_INTEGRITY);
const pinned:string=JSON.parse(readFileSync(join(root,'package.json'),'utf8')).dependencies['@saga-a2ui/react-mcp-stack'];
const SDK_VERSION=pinned;
const CANDIDATE=/^0\.0\.0-candidate\.g([0-9a-f]{12})$/.exec(pinned);
const CANDIDATE_DIR=`candidates/saga-${pinned}`;
const candidateFile=(sibling:string)=>`${CANDIDATE_DIR}/saga-a2ui-${sibling}-${pinned}.tgz`;
const digest=(algorithm:string,file:string,encoding:'hex'|'base64')=>createHash(algorithm).update(readFileSync(join(root,file))).digest(encoding);
/** The integrity each sibling must resolve with: the registry's for a release, the committed tarball's for a candidate. */
function expectedIntegrity(sibling:string):string{
if(CANDIDATE) return `sha512-${digest('sha512',candidateFile(sibling),'base64')}`;
const known=REGISTRY_INTEGRITY[SDK_VERSION];
assert.ok(known,`record the registry integrity of every SAGA package at ${SDK_VERSION} (npm view @saga-a2ui/<name>@${SDK_VERSION} dist.integrity)`);
return known[sibling];
}
const node=(...args:string[])=>execFileSync(process.execPath,args,{cwd:root,encoding:'utf8'});

test('the manifest pins one exact version for every SAGA package it imports',()=>{
test('the manifest pins one exact public version for every SAGA package it imports',()=>{
const manifest=JSON.parse(readFileSync(join(root,'package.json'),'utf8'));
const saga=Object.entries<string>({...manifest.dependencies,...manifest.devDependencies}).filter(([name])=>name.startsWith('@saga-a2ui/'));
assert.deepEqual(saga.map(([name])=>name).sort(),['@saga-a2ui/react-mcp-stack']);
for(const [name,version] of saga) assert.match(version,/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/,`${name} is an exact version, never a tag or range`);
for(const [name,version] of saga) {
assert.match(version,/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/,`${name} is an exact version, never a tag or range`);
assert.equal(version,SDK_VERSION,name);
}
assert.equal(manifest.devDependencies.typescript, 'catalog:');
assert.equal(manifest.devDependencies.eslint, 'catalog:');
assert.equal(manifest.devDependencies['@ansari/eslint-config'], 'workspace:*');
assert.ok(!/"(?:file|link|workspace|git\+?[a-z]*|https?):/.test(JSON.stringify({...manifest.dependencies,overrides:manifest.overrides,pnpm:manifest.pnpm})),'no local, tarball or workspace dependency source in the manifest');
assert.ok(!/"(?:file|link|workspace|git\+?[a-z]*|https?):/.test(JSON.stringify({...manifest.dependencies,overrides:manifest.overrides,pnpm:manifest.pnpm})),'no local, tarball or workspace dependency source');
});

// The lockfile that installs this application: the workspace root's. The application has none of its own (asserted below).
Expand All @@ -74,37 +61,31 @@ assert.equal(existsSync(join(root,'pnpm-workspace.yaml')), false, 'only the root
const workspaceLock=join(root,'../../pnpm-lock.yaml');
if(existsSync(workspaceLock)&&existsSync(join(root,'../../pnpm-workspace.yaml'))) lockfiles.push({label:'workspace root',text:readFileSync(workspaceLock,'utf8'),prefix:'apps/mcp'});

test('every lockfile resolves the five SAGA packages from one source with that source\'s integrity',()=>{
test('every lockfile resolves the five SAGA packages to the public registry with the registry integrity',()=>{
assert.ok(lockfiles.length>0,'at least one lockfile installs this application');
for(const {label,text,prefix} of lockfiles) {
const ref=(sibling:string)=>CANDIDATE?`file:${prefix}/${candidateFile(sibling)}`:SDK_VERSION;
for(const {label,text} of lockfiles) {
for(const sibling of SIBLINGS) {
const resolution=CANDIDATE?`{integrity: ${expectedIntegrity(sibling)}, tarball: ${ref(sibling)}}`:`{integrity: ${expectedIntegrity(sibling)}}`;
assert.ok(text.includes(`'@saga-a2ui/${sibling}@${ref(sibling)}':\n resolution: ${resolution}`),`${label} lockfile locks ${sibling} to ${CANDIDATE?'the committed candidate tarball':'the registry release'} with its integrity`);
const key=`'@saga-a2ui/${sibling}@${SDK_VERSION}':\n resolution: {integrity: ${SDK_INTEGRITY[sibling]}}`;
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 other local source for it.
// 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}@${ref(sibling)}`).sort(),`${label} lockfile: exactly the five packages from one source`);
if(!CANDIDATE) assert.doesNotMatch(text,/@saga-a2ui\/[a-z-]+@file:|tarball: file:/,`${label} lockfile: no local file`);
assert.doesNotMatch(text,/@saga-a2ui\/lit/,`${label} lockfile: no Lit package`);
assert.deepEqual(resolved,SIBLINGS.map(sibling=>`@saga-a2ui/${sibling}@${SDK_VERSION}`).sort(),`${label} lockfile: exactly the five packages at one version`);
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`);
}
});

test('workspace overrides name a SAGA package only to install a committed candidate',()=>{
test('no workspace file overrides a SAGA package',()=>{
const files=[join(root,'pnpm-workspace.yaml'),join(root,'../../pnpm-workspace.yaml')].filter(file=>existsSync(file));
assert.ok(files.length>0);
for(const file of files) {
const workspace=readFileSync(file,'utf8');
const overrides=[...workspace.matchAll(/^ {2}['"]?(@saga-a2ui\/[^'":]+)['"]?\s*:\s*['"]?([^'"\n]+)['"]?$/gm)].map(([,name,value])=>`${name}=${value}`).sort();
assert.deepEqual(overrides,CANDIDATE?SIBLINGS.map(sibling=>`@saga-a2ui/${sibling}=file:apps/mcp/${candidateFile(sibling)}`).sort():[],`${file}: ${CANDIDATE?'every sibling, and only the siblings, from the candidate set':'no SAGA override'}`);
if(!CANDIDATE) assert.doesNotMatch(workspace,/file:|link:/,`${file} has no local-file dependency source`);
assert.doesNotMatch(workspace,/link:/,`${file} links no local package`);
assert.doesNotMatch(workspace,/@saga-a2ui/,`${file} does not override or mention a SAGA package`);
assert.doesNotMatch(workspace,/file:|link:/,`${file} has no local-file dependency source`);
assert.match(workspace,/['"]@a2ui\/react@0\.13\.0>@a2ui\/markdown-it['"]\s*:\s*['"]0\.2\.1['"]/,`${file} pins the A2UI Markdown adapter`);
}
if(!CANDIDATE) assert.equal(existsSync(join(root,'candidates')),false,'no local SAGA inputs are committed');
else assert.deepEqual(readdirSync(join(root,'candidates')),[`saga-${pinned}`],'one candidate set, the pinned one');
if(CANDIDATE) assert.deepEqual(readdirSync(join(root,CANDIDATE_DIR)).sort(),SIBLINGS.map(sibling=>`saga-a2ui-${sibling}-${pinned}.tgz`).sort(),'the candidate set holds exactly the five tarballs');
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',()=>{
Expand Down Expand Up @@ -143,20 +124,15 @@ test('production code imports SAGA only through the one stack, and the browser b
assert.ok(!html.includes('The MCP server already ends a different request lifetime.'),'the view bundle holds no server lifetime module');
});

// ---- The card document names the SDK it was built from, beside the application release.
test('the emitted card document carries the SDK identity beside the application release',()=>{
// ---- The card document names the SDK release it was built from, beside the application release.
test('the emitted card document carries the registry SDK identity beside the application release',()=>{
const html=readFileSync(join(root,'dist/view.html'),'utf8');
// A candidate is named by its source commit and the bytes of its tarballs, recomputed here from the committed files.
const set=CANDIDATE?createHash('sha256').update(SIBLINGS.map(sibling=>`saga-a2ui-${sibling}-${pinned}.tgz`).sort().map(name=>`${name} ${digest('sha256',`${CANDIDATE_DIR}/${name}`,'hex')}\n`).join('')).digest('hex'):'none';
const identity=CANDIDATE
?{kind:'candidate',version:SDK_VERSION,source:CANDIDATE[1],stackSha256:digest('sha256',candidateFile('react-mcp-stack'),'hex'),setSha256:set}
:{kind:'registry',version:SDK_VERSION,source:'none',stackSha256:'none',setSha256:'none'};
const expected={kind:identity.kind,version:identity.version,source:identity.source,'stack-sha256':identity.stackSha256,'set-sha256':identity.setSha256};
const expected={kind:'registry',version:SDK_VERSION,source:'none','stack-sha256':'none','set-sha256':'none'};
const body=/<body\b[^>]*>/.exec(html)?.[0]??'';
for(const [name,value] of Object.entries(expected)) assert.equal(body.match(new RegExp(`\\sdata-sdk-${name}="([^"]*)"`))?.[1],value,`data-sdk-${name}`);
assert.equal(body.match(/\sdata-release="([^"]*)"/)?.[1],JSON.parse(readFileSync(join(root,'package.json'),'utf8')).version,'the application release stays beside the SDK identity');
assert.ok(html.includes(`<meta name="ansari-sdk" content="${SDK_VERSION}"`));
assert.deepEqual(JSON.parse(node('scripts/sdk-identity.mjs')),identity);
assert.deepEqual(JSON.parse(node('scripts/sdk-identity.mjs')),{kind:'registry',version:SDK_VERSION,source:'none',stackSha256:'none',setSha256:'none'});
});

test('distributed HTML retains complete installed SAGA and font licence notices', () => {
Expand Down
Loading
Loading