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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions docs/user-guide-ID.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
7 changes: 7 additions & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@
"@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": {
"@agentpair/relay": "workspace:*",
"@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"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/mcp-server/src/e2e/dual-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
}
Expand All @@ -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}`);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/mcp-server/src/runners/codegen-compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
};
}
}
Expand Down
3 changes: 2 additions & 1 deletion packages/mcp-server/src/runners/payload-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
};
}

Expand Down
3 changes: 2 additions & 1 deletion packages/mcp-server/src/runners/spectral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
};
}
}
Expand Down
6 changes: 3 additions & 3 deletions pnpm-lock.yaml

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

138 changes: 138 additions & 0 deletions scripts/verify-npm-consumer.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading