diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04aef49..bdfe1ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,3 +57,6 @@ jobs: - name: Test run: pnpm test + + - name: Verify npm consumer install + run: SKIP_BUILD=1 bash scripts/verify-npm-consumer.sh diff --git a/docs/user-guide-ID.md b/docs/user-guide-ID.md index 2e1c18f..711a10d 100644 --- a/docs/user-guide-ID.md +++ b/docs/user-guide-ID.md @@ -238,6 +238,13 @@ Dengan profil `atest/1`, `atest_run` dapat menjalankan runner terdaftar: Hanya dua ini yang terdaftar di MCP referensi saat ini. +**Dependensi runner:** `npx -y agentpair` sudah menyertakan runner +`payload-size` (`json-schema-faker` adalah production dependency). Runner +`spectral` opt-in: pasang `@stoplight/spectral-cli` di project Node yang sama +dengan `agentpair` (install `npx` terisolasi tidak melihat paket yang Anda +tambah di tempat lain). Packaging runner lengkap (`@agentpair/runners`, +codegen-compile, resolusi npx) direncanakan v1.1 — lihat issue tracker. + ## Troubleshooting **Tool MCP tidak muncul di klien** diff --git a/docs/user-guide.md b/docs/user-guide.md index 93614ed..c0bac31 100644 --- a/docs/user-guide.md +++ b/docs/user-guide.md @@ -239,6 +239,13 @@ With profile `atest/1`, `atest_run` can execute registered runners: Only these two are registered in the reference MCP today. +**Runner dependencies:** `npx -y agentpair` ships the `payload-size` runner +(`json-schema-faker` is a production dependency). The `spectral` runner is +opt-in: install `@stoplight/spectral-cli` in the same Node project as +`agentpair` (a bare `npx` cache install cannot see packages you add elsewhere). +Full runner packaging (`@agentpair/runners`, codegen-compile, npx resolution) +is planned for v1.1 — see issue tracker. + ## Troubleshooting **MCP tools missing in the client** diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 5302119..b4453e5 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -27,6 +27,7 @@ "@agentpair/protocol": "workspace:^0.5.0", "@modelcontextprotocol/sdk": "^1.12.1", "@noble/ciphers": "^2.2.0", + "json-schema-faker": "^0.5.9", "zod": "^3.25.67" }, "devDependencies": { @@ -34,7 +35,6 @@ "@hono/node-server": "^1.14.4", "@stoplight/spectral-cli": "^6.15.0", "@types/node": "^22.15.0", - "json-schema-faker": "^0.5.9", "quicktype": "^23.3.21", "typescript": "^5.8.3" } diff --git a/packages/mcp-server/src/e2e/dual-server.ts b/packages/mcp-server/src/e2e/dual-server.ts index f3c16ff..cb5ce43 100644 --- a/packages/mcp-server/src/e2e/dual-server.ts +++ b/packages/mcp-server/src/e2e/dual-server.ts @@ -308,7 +308,7 @@ export async function runSessionHappyPath( if (!initiatorAtest.ok) { const detail = "error" in initiatorAtest && /unavailable/i.test(initiatorAtest.error) - ? " (install json-schema-faker dev dependency for payload-size runner)" + ? " (payload-size runner unavailable — reinstall agentpair (json-schema-faker should ship with the package))" : ""; throw new Error(`initiator atest_run failed: ${JSON.stringify(initiatorAtest)}${detail}`); } @@ -324,7 +324,7 @@ export async function runSessionHappyPath( if (!joinerAtest.ok) { const detail = "error" in joinerAtest && /unavailable/i.test(joinerAtest.error) - ? " (install json-schema-faker dev dependency for payload-size runner)" + ? " (payload-size runner unavailable — reinstall agentpair (json-schema-faker should ship with the package))" : ""; throw new Error(`joiner atest_run failed: ${JSON.stringify(joinerAtest)}${detail}`); } diff --git a/packages/mcp-server/src/runners/codegen-compile.ts b/packages/mcp-server/src/runners/codegen-compile.ts index 1d92937..515780b 100644 --- a/packages/mcp-server/src/runners/codegen-compile.ts +++ b/packages/mcp-server/src/runners/codegen-compile.ts @@ -82,7 +82,8 @@ export async function runCodegenCompile( } catch { return { ok: false, - error: "codegen-compile runner unavailable: install quicktype (dev dependency)", + error: + "codegen-compile runner unavailable: npm install quicktype in this project (not registered in agentpair; planned for v1.1 runner packaging)", }; } } diff --git a/packages/mcp-server/src/runners/payload-size.ts b/packages/mcp-server/src/runners/payload-size.ts index 537072e..ef709ab 100644 --- a/packages/mcp-server/src/runners/payload-size.ts +++ b/packages/mcp-server/src/runners/payload-size.ts @@ -32,7 +32,8 @@ export function runPayloadSize( if (!faker) { return { ok: false, - error: "payload-size runner unavailable: install json-schema-faker (dev dependency)", + error: + "payload-size runner unavailable: reinstall or upgrade agentpair (json-schema-faker should ship with the package)", }; } diff --git a/packages/mcp-server/src/runners/spectral.ts b/packages/mcp-server/src/runners/spectral.ts index 42136ec..adfa0e1 100644 --- a/packages/mcp-server/src/runners/spectral.ts +++ b/packages/mcp-server/src/runners/spectral.ts @@ -37,7 +37,8 @@ export async function runSpectral( } catch { return { ok: false, - error: "spectral runner unavailable: install @stoplight/spectral-cli (dev dependency)", + error: + "spectral runner unavailable: npm install @stoplight/spectral-cli in this project (not included with agentpair; bare npx agentpair cannot run spectral until v1.1 runner packaging)", }; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9049102..96a9991 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: '@noble/ciphers': specifier: ^2.2.0 version: 2.2.0 + json-schema-faker: + specifier: ^0.5.9 + version: 0.5.9 zod: specifier: ^3.25.67 version: 3.25.76 @@ -51,9 +54,6 @@ importers: '@types/node': specifier: ^22.15.0 version: 22.20.0 - json-schema-faker: - specifier: ^0.5.9 - version: 0.5.9 quicktype: specifier: ^23.3.21 version: 23.3.21 diff --git a/scripts/verify-npm-consumer.sh b/scripts/verify-npm-consumer.sh new file mode 100755 index 0000000..348169c --- /dev/null +++ b/scripts/verify-npm-consumer.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# Smoke-test agentpair as an npm consumer would install it (not monorepo devDeps). +# +# Uses `pnpm pack` on the real packages (same as publish) so the verified tarball +# matches bin/files/exports and workspace: protocol rewrites — not a hand-built subset. +# +# By default rebuilds protocol + agentpair (standalone-safe). In CI, set +# SKIP_BUILD=1 after Build/Test so only pack + install + smoke runs. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +MCP_DIR="${ROOT}/packages/mcp-server" +PROTO_DIR="${ROOT}/packages/protocol" +TMP_DIR="$(mktemp -d)" +trap 'rm -rf "${TMP_DIR}"' EXIT + +tgz_name_for() { + node --input-type=module -e " +import { readFileSync } from 'node:fs'; +const pkg = JSON.parse(readFileSync('${1}/package.json', 'utf8')); +const base = pkg.name.startsWith('@') ? pkg.name.slice(1).replace('/', '-') : pkg.name; +console.log(\`\${base}-\${pkg.version}.tgz\`); +" +} + +tar_contains() { + local archive="$1" + local member="$2" + local listing + listing="$(tar -tzf "${archive}")" + grep -Fq "${member}" <<<"${listing}" +} + +require_dist() { + local missing=0 + for path in \ + "${PROTO_DIR}/dist/index.js" \ + "${PROTO_DIR}/dist/wasm/pkg/spake2_pake.js" \ + "${MCP_DIR}/dist/runners/payload-size.js" \ + "${MCP_DIR}/dist/cli.js"; do + if [[ ! -f "${path}" ]]; then + echo "ERROR: missing build artifact: ${path}" >&2 + missing=1 + fi + done + if [[ "${missing}" -ne 0 ]]; then + echo "ERROR: dist artifacts missing; run without SKIP_BUILD=1 or pnpm build first" >&2 + exit 1 + fi +} + +if [[ "${SKIP_BUILD:-}" == "1" ]]; then + echo "==> SKIP_BUILD=1 — using existing dist artifacts" + require_dist +else + echo "==> Building protocol and agentpair" + pnpm --filter @agentpair/protocol build + pnpm --filter agentpair build +fi + +echo "==> Packing @agentpair/protocol (pnpm pack)" +(cd "${PROTO_DIR}" && pnpm pack --pack-destination "${TMP_DIR}" >/dev/null) +PROTO_TGZ="$(tgz_name_for "${PROTO_DIR}")" +PROTO_PACKED="${TMP_DIR}/${PROTO_TGZ}" + +echo "==> Packing agentpair (pnpm pack — publish-shaped manifest + files allowlist)" +(cd "${MCP_DIR}" && pnpm pack --pack-destination "${TMP_DIR}" >/dev/null) +AGENT_TGZ="$(tgz_name_for "${MCP_DIR}")" +AGENT_PACKED="${TMP_DIR}/${AGENT_TGZ}" + +for packed in "${PROTO_PACKED}" "${AGENT_PACKED}"; do + if [[ ! -f "${packed}" ]]; then + echo "ERROR: expected pack output missing: ${packed}" >&2 + exit 1 + fi +done + +echo "==> Asserting packed agentpair manifest" +MANIFEST_JSON="${TMP_DIR}/pack-manifest.json" +tar -xzf "${AGENT_PACKED}" -O package/package.json > "${MANIFEST_JSON}" +node --input-type=module -e " +import { readFileSync } from 'node:fs'; +const pkg = JSON.parse(readFileSync('${MANIFEST_JSON}', 'utf8')); +const deps = pkg.dependencies ?? {}; +const devDeps = pkg.devDependencies ?? {}; + +if (!deps['json-schema-faker']) { + console.error('FAIL: json-schema-faker missing from packed dependencies'); + process.exit(1); +} +if (devDeps['json-schema-faker']) { + console.error('FAIL: json-schema-faker still listed in packed devDependencies'); + process.exit(1); +} +if (String(deps['@agentpair/protocol'] ?? '').includes('workspace:')) { + console.error('FAIL: packed manifest still contains workspace: protocol dependency'); + process.exit(1); +} +if (!pkg.bin?.agentpair) { + console.error('FAIL: packed manifest missing bin.agentpair'); + process.exit(1); +} +if (!Array.isArray(pkg.files) || !pkg.files.includes('dist')) { + console.error('FAIL: packed manifest files allowlist drifted from package.json'); + process.exit(1); +} +console.log('OK: pnpm pack manifest rewrites workspace protocol dep and ships json-schema-faker'); +" + +echo "==> Asserting packed tarball honors files allowlist (bin + runner assets)" +if ! tar_contains "${AGENT_PACKED}" 'package/dist/cli.js'; then + echo "FAIL: packed tarball missing package/dist/cli.js" >&2 + exit 1 +fi +if ! tar_contains "${AGENT_PACKED}" 'package/dist/runners/spectral-ruleset.yaml'; then + echo "FAIL: packed tarball missing spectral-ruleset.yaml from dist/runners" >&2 + exit 1 +fi + +echo "==> Installing packed tarballs in isolated consumer dir" +CONSUMER_DIR="${TMP_DIR}/consumer" +mkdir -p "${CONSUMER_DIR}" +cd "${CONSUMER_DIR}" +npm init -y >/dev/null +npm install "${PROTO_PACKED}" "${AGENT_PACKED}" >/dev/null + +echo "==> Smoke-testing payload-size runner from consumer install" +node --input-type=module -e " +import { runPayloadSize } from 'agentpair/dist/runners/payload-size.js'; +const result = runPayloadSize({ type: 'object', properties: { id: { type: 'string' } } }); +if (!result.ok) { + console.error('FAIL: runPayloadSize returned', result); + process.exit(1); +} +console.log('OK: payload-size runner works from consumer install'); +" + +echo "==> npm consumer verify passed"