Skip to main content
Glama
README.md
# gavel-mcp

The gavel acceptance oracle as an MCP server: one tool that turns an
agent's "done" into a receipt. `gavel_acceptance` cold-runs a command
and reports the exit code. Exit code 0 is the only passing verdict.

## Setup

### 1. Build

Requires Node ≥ 20 and git.

```
cd gavel-mcp
npm install
npm run build        # → dist/index.js
```

`dist/` is gitignored — every fresh clone needs this step before the
server can start.

### 2. Wire it into ZCode

Two scopes; both auto-connect at session start.

**Workspace scope** — versioned with the repo, shared with the team.
Create `<repo>/.zcode/config.json`:

```json
{
  "mcp": {
    "servers": {
      "gavel": {
        "command": "node",
        "args": ["/ABS/PATH/TO/gavel-mcp/dist/index.js"]
      }
    }
  }
}
```

**User scope** — applies to every workspace. Put the same
`mcp.servers` object in `~/.zcode/cli/config.json`, and pair it with
the acceptance rule (section 5) in `~/.zcode/AGENTS.md` so every
session knows when to call the tool, not just how.

A user-scope install pins every workspace to this machine's build:

- After changing `src/`, run `npm run build` — other sessions keep
  loading the old `dist/` until you do.
- Moving or deleting the repo directory breaks every session at once.

Works from a git remote today — no registry needed. The `prepare`
script builds `dist/` on install, so `npx` handles the rest:

```json
{
  "command": "npx",
  "args": ["-y", "github:newlix/gavel-mcp#v0.5.0"]
}
```

Pin a tag (`#v0.5.0`) to make the npx cache stable; without one you
track the default branch and cache refresh is at npx's discretion.
First start on a machine pays a one-time clone + install + build.
Once published to npm, `["-y", "gavel-mcp"]` is equivalent and skips
the git requirement. Any other MCP host works too; only the config
shape differs.

### 3. Restart the session

MCP servers connect at session start. An already-running session will
not pick the server up.

### 4. Verify

- ZCode: **Settings → MCP** shows `gavel` connected.
- Or simply ask the agent to call `gavel_acceptance` with
  `cmd: "test -d ."` — expect `verdict=pass exit=0`.

### 5. The rule (AGENTS.md)

The tool is the structure; the rule tells the agent when to use it.
Drop this into `<repo>/AGENTS.md`:

```markdown
## Acceptance

- Done = `gavel_acceptance` returned exit 0. One self-contained
  command, cold from the repo root; report the verdict and the
  command itself — never a paraphrase of test results.
- The command asserts intent (what should happen), not the
  implementation.
- `refused` means it never ran. Report it verbatim.
```

For user-scope installs, the same block goes in `~/.zcode/AGENTS.md`
instead — user instructions load first, so a repo's own AGENTS.md can
still narrow the rule per project.

## The contract

The oracle never trusts a paraphrased result — it runs the command
itself, so a red acceptance cannot be narrated green. Two structural
layers, cheapest first:

1. **Lint** (`src/lint.ts`): a command that cannot fail (`true`,
   `exit 0`, bare echo/printf, `x && true` with no real check in it)
   is refused before execution — `passed: false`, `refused: <reason>`,
   no receipt minted. The Go linter's syntax and destructive-pattern
   checks are deliberately dropped: syntax fails identically when
   executed, and policing dangerous commands is the host permission
   layer's job, not the verdict layer's.
2. **Cold run** (`src/runner.ts`): the command runs via the platform
   shell from the project root; exit code 0 is the only pass. Signal
   deaths report 128+signal, spawn failure -1, command-not-found 127.

Receipt semantics: `refused` = never executed. Report it verbatim.

## Tools

### `gavel_acceptance(cmd, cwd?, timeout_sec?)`

→ `{ passed, exit_code, duration_ms, refused, output }`

- `output`: merged stdout+stderr, raw; head+tail with a marker when
  longer than ~20 KB.
- A timeout kills the whole process tree and fails the run.

## Troubleshooting

- **Server not connected** (Settings → MCP shows an error): the dist
  path is wrong or `npm run build` was skipped. The path must be
  absolute and point at `dist/index.js`.
- **`exit_code: 127`**: the acceptance command itself was not found.

## Dev

```
npm install
npm test       # node:test via tsx (24 tests)
npm run build  # tsc → dist/
```

Layout: `src/index.ts` is the thin stdio bootstrap; the MCP surface
(`buildServer`) lives in `src/server.ts` so tests can drive it
in-process over `InMemoryTransport` plus one cold stdio smoke via
tsx. The manual smoke below is the same exchange the stdio test runs.

Manual smoke (MCP stdio is newline-delimited JSON):

```
printf '%s\n' \
  '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
  '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"gavel_acceptance","arguments":{"cmd":"test -d ."}}}' \
  | node dist/index.js
```

TDQS

A4.5/5.0

Scored across 1 tool

Disambiguation5/5

There is only one tool, so there is no possibility of an agent confusing it with another. Its purpose is clearly distinct by default.

Naming Consistency4/5

With a single tool there are no conflicting conventions to penalize. The name gavel_acceptance is clear and follows snake_case, though it is noun-oriented rather than a verb_noun pattern.

Tool Count4/5

One tool is minimal, but the server appears purpose-built for a single acceptance action, so the count is slightly under the typical range yet reasonable for the stated scope.

Completeness5/5

The tool fully covers the server's apparent purpose: running an acceptance command and returning a mechanical verdict with well-specified exit-code and output handling. No obvious operations are missing for this narrow domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues