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
1 change: 1 addition & 0 deletions .agents/skills
23 changes: 16 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@ on:
pull_request:
branches: [main]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
Comment thread
coderabbitai[bot] marked this conversation as resolved.
with:
persist-credentials: false
- run: corepack enable
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run check
- run: npm run build
- run: npm run test
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run check
- run: pnpm run typecheck
- run: pnpm run skills:check
- run: pnpm test
- run: pnpm run build
- run: pnpm run package:smoke
28 changes: 19 additions & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,31 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v6
with:
persist-credentials: false
- run: corepack enable
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
cache: pnpm
registry-url: https://registry.npmjs.org
- run: npm ci
- run: pnpm install --frozen-lockfile
- name: Verify version matches release tag
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
PKG_VERSION="v$(node -p 'require("./package.json").version')"
if [ "$PKG_VERSION" != "${{ github.event.release.tag_name }}" ]; then
echo "::error::package.json version ($PKG_VERSION) does not match release tag (${{ github.event.release.tag_name }})"
if [ "$PKG_VERSION" != "$RELEASE_TAG" ]; then
echo "::error::package.json version ($PKG_VERSION) does not match release tag ($RELEASE_TAG)"
exit 1
fi
- run: npm run check
- run: npm run build
- run: npm run test
- run: pnpm run check
- run: pnpm run typecheck
- run: pnpm run skills:check
- run: pnpm test
- run: pnpm run build
- run: pnpm run package:smoke
- name: Install trusted-publishing npm client
run: npm install --global npm@12.0.2
- run: npm publish --access public
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ build/
*.tsbuildinfo
.env
.claude/
.pi/
.mcp.json
CLAUDE.md
docs/plans/
115 changes: 56 additions & 59 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,74 @@
# zaparoo-mcp
# zaparoo-cli

MCP server that bridges AI assistants to Zaparoo Core devices (NFC-based game launchers) over WebSocket using JSON-RPC 2.0.
Remote developer CLI and Agent Skills for exploring Zaparoo APIs, building integrations, and diagnosing Zaparoo Core devices.

## Project map

- `src/cli/` — parser, output/errors, thin command handlers.
- `src/client/` — bounded one-shot client, endpoint/config/resolution, redacted trace JSONL.
- `src/crypto/` — PAKE pairing, credential storage, encrypted sessions, Core-derived fixtures.
- `src/api/` — versioned Core method/notification snapshot and baseline.
- `src/discovery/` — bounded mDNS discovery for `_zaparoo._tcp`.
- `src/online/` — public Online User API contract, bounded HTTPS client, pagination, and errors.
- `skills/` — canonical packaged Agent Skills; `.agents/skills` links here for project discovery.
- `docs/` — public CLI contracts and developer guidance.
- `scripts/` — Core API audit plus package/skill release checks.

For first-party Zaparoo application work, reference the latest development version of [Zaparoo Core](https://github.com/ZaparooProject/zaparoo-core). For third-party integration work, reference the latest stable [public Core API documentation](https://zaparoo.org/docs/core/api/). Use `https://developers.zaparoo.com/openapi-user.yaml` as sole Online User API authority. Treat behavior missing from public documentation as a documentation gap; do not infer a third-party contract from unreleased implementation details.

## Commands

```bash
npm run build # Build to build/index.js
npm run dev # Build in watch mode
npm run lint:fix # Auto-fix lint issues (Biome)
npm run format # Format code (Biome)
npm run check # CI lint+format check
npm test # Run tests (Vitest)
npm run test:watch # Tests in watch mode
pnpm run api:audit -- --core ../zaparoo-core
pnpm run api:user:audit
pnpm run build
pnpm run check
pnpm run lint:fix
pnpm run format
pnpm run skills:check
pnpm test
pnpm run typecheck
pnpm run package:smoke
```

## Architecture
Before finishing broad changes, run API audit, check, typecheck, full tests, build, and pack dry-run.

### Connection layer (`src/connection/`)
- `DeviceConnection` — WebSocket client for a single Zaparoo device. Handles JSON-RPC request/response correlation, automatic reconnection with exponential backoff, and heartbeat pings.
- `DeviceManager` — orchestrates multiple connections. `getDevice(id?)` returns a specific device or the first READY one. Supports dynamic `addDevice()`/`removeDevice()` for mDNS discovery.
## CLI rules

### Discovery (`src/discovery/`)
- `MdnsDiscovery` — browses for `_zaparoo._tcp` services via mDNS using `bonjour-service`. Emits `discovered`/`removed` events. Runs automatically when no devices are manually configured.
- Package identity is `@zaparoo/cli`; packaged installs expose `zaparoo-cli`.
- In source checkout, build then use `node build/index.js ...`; do not assume global CLI exists.
- One-shot machine output uses `--json`; watch streams use `--jsonl`.
- Errors go to stderr with non-zero exit codes.
- Core commands open bounded WebSocket sessions, perform calls, then close. Backup operations use unbounded method policy.
- Online commands use fixed official HTTPS origin, reject redirects, and send bearer keys only to `/v1` requests.
- Credentials default to `~/.config/zaparoo-cli/credentials.json`, remain versioned/atomic/mode `0600`, and never appear in output or traces.
- `src/api/methods.ts` mirrors registered Core methods. Keep `rpc` as debug escape hatch; do not promote unregistered `run.script`.
- Public integration guidance must use public API docs. Treat missing public behavior as a documentation gap, not a reason to inspect private implementation.

### Tools (`src/tools/`)
Each file registers one MCP tool via `registerXxxTool(server, manager)`. All tools are wired up in `src/tools/index.ts` through `registerAllTools()`.
## Adding commands

### Resources (`src/resources/`)
- `zaparoo://devices` — all device states
- `zaparoo://{deviceId}/state` — per-device state (readers, media, tokens)
- `zaparoo://reference/zapscript` — ZapScript language reference
1. Add thin handler under `src/cli/commands/`.
2. Use `withClient()` from `src/cli/commands/common.ts`.
3. Return plain data; let `src/cli/output.ts` render it.
4. Validate required params and add colocated tests for non-trivial mapping.
5. Decode binary API payloads through atomic owner-only output files.

### Notifications (`src/notifications/`)
`NotificationHandler` listens for device events, updates `DeviceStateStore` (in-memory cache), and pushes MCP resource change notifications.
## Skills and artifacts

### Config (`src/config.ts`)
CLI args take precedence over env vars. Optional: `--devices`/`ZAPAROO_DEVICES`, `--keys`/`ZAPAROO_KEYS`. Default port is 7497. When no devices are configured, mDNS discovery is enabled automatically. Use `--no-discovery` or `ZAPAROO_NO_DISCOVERY=1` to disable. Tool filtering: `--allowed-tools`/`ZAPAROO_ALLOWED_TOOLS` (comma-separated whitelist) or `--blocked-tools`/`ZAPAROO_BLOCKED_TOOLS` (comma-separated blacklist). Cannot use both simultaneously.
- Skills must work from npm/Pi package and standalone `npx skills` install; no hardcoded checkout paths in `skills/`.
- Standalone skills may not include `build/`; check before using package-relative fallback.
- Offline log/database retrieval is guidance in `skills/zaparoo-artifacts/`, not product code.
- Do not add CLI SSH, PowerShell, filesystem scouting, process probing, Core stop/restart, or artifact collector logic.
- Agents choose user-authorized transport/commands. Live SQLite copies are always potentially inconsistent; include present WAL/SHM/journal sidecars.

## Adding a new tool
## Live-device safety

1. Create `src/tools/mytool.ts` with a `registerMyTool(server, manager)` function
2. Define input schema with Zod, use `toolRequest()` from `src/tools/helpers.ts` to call the device
3. Add an entry to the `registry` array in `src/tools/index.ts` inside `registerAllTools()`
Ask before launching/stopping media, input, NFC writes, mapping/settings/profile changes, update apply, inbox clear, or downtime. Never automatically stop or restart Core. Pairing initiation is Core-side/localhost-only; completion uses client PAKE PIN flow.

## Testing

Tests use Vitest and live alongside source files as `*.test.ts`. Test files are excluded from `tsconfig.json` compilation so they don't end up in build output.

**What to test:** Focus on modules with real logic — parsing, state management, error handling, branching. Don't write tests that just assert tool registration boilerplate or static content.

**Mocking patterns:**
- Mock `ws` module with a `MockWebSocket` class extending `EventEmitter` for `DeviceConnection` tests. Use `vi.useFakeTimers()` for timeout/backoff tests.
- Mock `DeviceConnection` import via `vi.mock()` for `DeviceManager` tests, tracking created instances in an array.
- Mock `node:util` `parseArgs` for config tests to control CLI arg values.
- Mock `bonjour-service` with a mock class returning `EventEmitter`-based browsers for `MdnsDiscovery` tests.
- Use `as unknown as <Type>` double-cast for partial mocks of complex interfaces (DeviceManager, MCP Server).
- Reset module-level mock state (e.g., `lastMockWs`, `createdDevices`) in `beforeEach`.

**Test guidelines:**
- Test files MUST be colocated with source: `src/foo.ts` → `src/foo.test.ts`
- Prefer testing through public interfaces over reaching into private methods
- Every test that uses fake timers MUST call `vi.useRealTimers()` in `afterEach`
- Don't write tests that give false confidence — if an assertion can't actually fail when the code is broken, remove it

## Conventions

- Tool names MUST be prefixed with `zaparoo_`
- Resource URIs MUST use the `zaparoo://` scheme
- All tool inputs MUST be validated with Zod schemas
- Use `import type` for type-only imports — Biome enforces this (`useImportType`)
- Zod is imported from `zod/v3` (Zod v4 package, v3-compatible API)
- Error responses from tools MUST use the `{ isError: true }` pattern (see `src/tools/helpers.ts`)
- Device IDs use `host:port` format
- NEVER use CommonJS (`require`/`module.exports`) — this is an ESM-only project
- PR descriptions MUST NOT include test plans — keep them to a summary only
- NEVER amend commits — always create new commits
- Tests use Vitest beside source as `*.test.ts`.
- Mock WebSocket with EventEmitter-based `ws` doubles where practical.
- Every fake-timer test restores real timers in `afterEach`.
- Online tests mock fetch and never use real API keys.
- Live pairing, Online account, SSH, and stopped-database acceptance require designated targets and explicit approval; report skipped checks.
Loading