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
6 changes: 6 additions & 0 deletions .changeset/warm-tools-negotiate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@atrib/attest': patch
'@atrib/summarize': patch
---

Install Zod v4 with the standalone MCP servers so fresh npm installs can register their tools and negotiate MCP 2026-07-28.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:

- run: pnpm -r build

- run: pnpm mcp-v2:packed-runtime

- run: pnpm -r typecheck

- run: pnpm lint
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ atrib/
refresh-global-emit-cli.mjs # D082/D128-class updater and guard for the global atrib-emit-cli hook signer. Reinstalls @atrib/emit from the registry, fails on npm-link drift into a checkout, and gates on a signed smoke envelope.
collect-npm-downloads.mjs # METRICS.md Tier 2 collector. Writes metrics/npm-downloads-<date>.json with last-week npm downloads per designed-public package (current/deprecated from package.json descriptions) plus the PyPI atrib distribution.
check-release-publish-readiness.mjs # Release preflight. Verifies every Changesets-publishable workspace package already exists on npm, while first-publish-pending packages stay in `.changeset/config.json` ignore.
check-mcp-v2-owned-surfaces.mjs # MCP v2 source and publish-archive inventory gate. Uses pnpm pack so workspace dependencies match the publish-ready manifest.
check-mcp-v2-packed-runtime.mjs # Fresh-installs every published MCP v2 surface and its atrib dependency closure from local pnpm archives, then requires modern 2026-07-28 negotiation and tools/list from each stdio binary.
metrics/ # Dated JSON snapshots from `pnpm --filter @atrib/log-node metrics` and `pnpm metrics:npm-downloads` (npm + PyPI download counts per package, current/deprecated tagged)
packages/
mcp/ # @atrib/mcp: MCP server middleware (public)
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
"typos": "typos .",
"clean": "turbo clean",
"version-packages": "changeset version",
"release": "node scripts/check-release-publish-readiness.mjs && pnpm -r build && node scripts/check-mcp-v2-owned-surfaces.mjs --require-built --packed && changeset publish"
"mcp-v2:packed-runtime": "node scripts/check-mcp-v2-packed-runtime.mjs",
"release": "node scripts/check-release-publish-readiness.mjs && pnpm -r build && node scripts/check-mcp-v2-owned-surfaces.mjs --require-built --packed && pnpm mcp-v2:packed-runtime && changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.31.1",
"@eslint/js": "^10.0.1",
"@modelcontextprotocol/client": "^2.0.0",
"eslint": "^10.8.0",
"eslint-config-prettier": "^10.1.8",
"globals": "^17.8.0",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 5 additions & 6 deletions scripts/check-mcp-v2-owned-surfaces.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ function packPublishedPackages(inventory) {

try {
for (const [packageName, entry] of packages) {
const packed = spawnSync('npm', ['pack', '--silent', '--pack-destination', tempDir], {
const packed = spawnSync('pnpm', ['pack', '--pack-destination', tempDir], {
cwd: join(ROOT, entry.workspace),
encoding: 'utf8',
env: process.env,
Expand All @@ -147,13 +147,12 @@ function packPublishedPackages(inventory) {
packed.stderr.trim() ||
packed.stdout.trim() ||
`exit status ${packed.status}`
errors.push(`${packageName}: npm pack failed: ${diagnostic}`)
errors.push(`${packageName}: pnpm pack failed: ${diagnostic}`)
continue
}
const archiveName = packed.stdout.trim().split('\n').at(-1)
const archivePath = join(tempDir, archiveName)
if (!archiveName || !existsSync(archivePath)) {
errors.push(`${packageName}: npm pack did not produce an archive`)
const archivePath = packed.stdout.trim().split('\n').at(-1)
if (!archivePath || !existsSync(archivePath)) {
errors.push(`${packageName}: pnpm pack did not produce an archive`)
continue
}

Expand Down
282 changes: 282 additions & 0 deletions scripts/check-mcp-v2-packed-runtime.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,282 @@
#!/usr/bin/env node
// SPDX-License-Identifier: Apache-2.0

import { spawnSync } from 'node:child_process'
import {
mkdtempSync,
mkdirSync,
readdirSync,
readFileSync,
realpathSync,
rmSync,
writeFileSync,
} from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, isAbsolute, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { randomBytes } from 'node:crypto'
import { Client } from '@modelcontextprotocol/client'
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'

const ROOT = fileURLToPath(new URL('..', import.meta.url))
const INVENTORY_PATH = join(ROOT, 'scripts', 'mcp-v2-owned-surfaces.json')
const EXPECTED_PROTOCOL_VERSION = '2026-07-28'
const PROBE_TIMEOUT_MS = 30_000

function readJson(path) {
return JSON.parse(readFileSync(path, 'utf8'))
}

function workspaceManifests() {
const manifests = new Map()
const pending = [join(ROOT, 'packages'), join(ROOT, 'services')]

while (pending.length > 0) {
const directory = pending.pop()
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.name === 'node_modules' || entry.name === 'dist') continue
const path = join(directory, entry.name)
if (entry.isDirectory()) {
pending.push(path)
} else if (entry.isFile() && entry.name === 'package.json') {
const manifest = readJson(path)
if (typeof manifest.name === 'string') {
manifests.set(manifest.name, { directory: dirname(path), manifest })
}
}
}
}

return manifests
}

function requiredWorkspacePackages(inventory, manifests) {
const required = new Set(
inventory.surfaces.filter((surface) => surface.published).map((surface) => surface.package),
)
const pending = [...required]

while (pending.length > 0) {
const packageName = pending.pop()
const entry = manifests.get(packageName)
if (!entry) throw new Error(`Missing workspace manifest for ${packageName}`)

for (const dependencyName of Object.keys(entry.manifest.dependencies ?? {})) {
if (!manifests.has(dependencyName) || required.has(dependencyName)) continue
required.add(dependencyName)
pending.push(dependencyName)
}
}

return [...required].sort()
}

function run(command, args, options = {}) {
const result = spawnSync(command, args, {
encoding: 'utf8',
env: process.env,
...options,
})
if (result.status !== 0) {
const diagnostic =
result.error?.message ||
result.stderr.trim() ||
result.stdout.trim() ||
`exit status ${result.status}`
throw new Error(`${command} ${args.join(' ')} failed: ${diagnostic}`)
}
return result.stdout.trim()
}

function packPackages(packageNames, manifests, packDirectory) {
const archives = []
for (const packageName of packageNames) {
const output = run('pnpm', ['pack', '--pack-destination', packDirectory], {
cwd: manifests.get(packageName).directory,
})
const archivePath = output.split('\n').at(-1)
if (!archivePath) throw new Error(`${packageName}: pnpm pack returned no archive`)
archives.push(isAbsolute(archivePath) ? archivePath : join(packDirectory, archivePath))
}
return archives
}

function installPackedPackages(archives, installDirectory) {
writeFileSync(
join(installDirectory, 'package.json'),
JSON.stringify({ name: 'atrib-mcp-v2-packed-runtime-proof', private: true }),
)
run(
'npm',
[
'install',
'--ignore-scripts',
'--no-audit',
'--no-fund',
'--no-package-lock',
'--save=false',
...archives,
],
{ cwd: installDirectory },
)
}

function installedEntrypoint(installDirectory, surface) {
const packageDirectory = join(installDirectory, 'node_modules', ...surface.package.split('/'))
const manifest = readJson(join(packageDirectory, 'package.json'))
const relativeEntrypoint = manifest.bin?.[surface.bin]
if (typeof relativeEntrypoint !== 'string') {
throw new Error(`${surface.id}: installed package is missing bin ${surface.bin}`)
}
return realpathSync(join(packageDirectory, relativeEntrypoint))
}

function writeWrapperFixture(installDirectory) {
const fixturePath = join(installDirectory, 'echo-server.mjs')
writeFileSync(
fixturePath,
`import { McpServer } from '@modelcontextprotocol/server'
import { serveStdio } from '@modelcontextprotocol/server/stdio'

const server = new McpServer({ name: 'packed-wrapper-fixture', version: '0.0.0' })
server.registerTool('echo', { inputSchema: {} }, async () => ({
content: [{ type: 'text', text: 'echo' }],
}))
serveStdio(() => server)
`,
)
return fixturePath
}

function writeWrapperConfig(installDirectory, fixturePath) {
const configPath = join(installDirectory, 'wrap-config.json')
writeFileSync(
configPath,
JSON.stringify({
name: 'packed-v2-proof',
agent: 'release-check',
upstream: {
command: process.execPath,
args: [fixturePath],
},
serverUrl: 'mcp://packed-v2-proof.local',
logEndpoint: 'http://127.0.0.1:1/v1/entries',
recordFile: join(installDirectory, 'wrapper-records.jsonl'),
logFile: join(installDirectory, 'wrapper.log'),
}),
)
return configPath
}

async function withTimeout(promise, label) {
let timer
try {
return await Promise.race([
promise,
new Promise((_, reject) => {
timer = setTimeout(() => reject(new Error(`${label} timed out`)), PROBE_TIMEOUT_MS)
}),
])
} finally {
clearTimeout(timer)
}
}

async function probeSurface(surface, installDirectory, wrapperConfigPath) {
const args = [installedEntrypoint(installDirectory, surface)]
if (surface.id === 'mcp-wrap-stdio') args.push(wrapperConfigPath)

const transport = new StdioClientTransport({
command: process.execPath,
args,
env: {
...process.env,
ATRIB_PRIVATE_KEY: randomBytes(32).toString('base64url'),
ATRIB_RECORD_FILE: join(installDirectory, `${surface.id}-records.jsonl`),
},
stderr: 'pipe',
})
let stderr = ''
transport.stderr?.on('data', (chunk) => {
stderr += String(chunk)
})
const client = new Client(
{ name: 'atrib-packed-runtime-proof', version: '0.0.0' },
{ versionNegotiation: { mode: { pin: EXPECTED_PROTOCOL_VERSION } } },
)

try {
await withTimeout(client.connect(transport), `${surface.id} connect`)
const listed = await withTimeout(client.listTools(), `${surface.id} tools/list`)
if (client.getProtocolEra() !== 'modern') {
throw new Error(`${surface.id}: expected modern protocol era`)
}
if (client.getNegotiatedProtocolVersion() !== EXPECTED_PROTOCOL_VERSION) {
throw new Error(
`${surface.id}: negotiated ${client.getNegotiatedProtocolVersion() ?? 'nothing'}`,
)
}
if (listed.tools.length === 0) {
throw new Error(`${surface.id}: tools/list returned no tools`)
}
if (surface.id === 'mcp-wrap-stdio' && !listed.tools.some((tool) => tool.name === 'echo')) {
throw new Error('mcp-wrap-stdio: tools/list did not include the upstream echo tool')
}
return { id: surface.id, package: surface.package, tools: listed.tools.length }
} catch (error) {
const diagnostic = stderr.trim()
const message = `${surface.id}: ${error instanceof Error ? error.message : String(error)}`
throw new Error(diagnostic ? `${message}\nstderr:\n${diagnostic}` : message, {
cause: error,
})
} finally {
await client.close().catch(() => {})
}
}

async function main() {
const inventory = readJson(INVENTORY_PATH)
if (inventory.protocol_version !== EXPECTED_PROTOCOL_VERSION) {
throw new Error(`Inventory protocol version is ${inventory.protocol_version}`)
}

const tempDirectory = mkdtempSync(join(tmpdir(), 'atrib-mcp-v2-packed-runtime-'))
const packDirectory = join(tempDirectory, 'packs')
const installDirectory = join(tempDirectory, 'install')
mkdirSync(packDirectory)
mkdirSync(installDirectory)

try {
const manifests = workspaceManifests()
const packageNames = requiredWorkspacePackages(inventory, manifests)
const archives = packPackages(packageNames, manifests, packDirectory)
installPackedPackages(archives, installDirectory)

const wrapperFixturePath = writeWrapperFixture(installDirectory)
const wrapperConfigPath = writeWrapperConfig(installDirectory, wrapperFixturePath)
const surfaces = inventory.surfaces.filter(
(surface) => surface.published && surface.transport === 'stdio',
)
const results = []
for (const surface of surfaces) {
results.push(await probeSurface(surface, installDirectory, wrapperConfigPath))
}

process.stdout.write(
`MCP v2 packed-runtime proof passed: ${results.length} stdio surfaces from ${packageNames.length} fresh package archives negotiated ${EXPECTED_PROTOCOL_VERSION} and listed tools.\n`,
)
} finally {
if (process.env.ATRIB_MCP_V2_KEEP_TEMP === '1') {
process.stderr.write(`MCP v2 packed-runtime temp preserved at ${tempDirectory}\n`)
} else {
rmSync(tempDirectory, { recursive: true, force: true })
}
}
}

main().catch((error) => {
process.stderr.write(
`MCP v2 packed-runtime proof failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`,
)
process.exit(1)
})
2 changes: 1 addition & 1 deletion services/atrib-attest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@noble/ed25519": "^3.1.0",
"@noble/hashes": "^2.2.0",
"canonicalize": "^3.0.0",
"zod": "^3.25.76"
"zod": "^4.4.3"
},
"devDependencies": {
"@types/node": "^26.1.1",
Expand Down
2 changes: 1 addition & 1 deletion services/atrib-summarize/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"dependencies": {
"@atrib/mcp": "workspace:*",
"@modelcontextprotocol/server": "^2.0.0",
"zod": "^3.25.76"
"zod": "^4.4.3"
},
"devDependencies": {
"@types/node": "^26.1.1",
Expand Down
Loading