clembot-doorman 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +17 -0
- package/LICENSE +21 -0
- package/README.md +951 -0
- package/WALKTHROUGH.md +224 -0
- package/doorman/.claude/hooks/mcp-gate.sh +205 -0
- package/doorman/.claude/settings.json +16 -0
- package/doorman/.claude-plugin/plugin.json +22 -0
- package/doorman/.mcp.json +24 -0
- package/doorman/README.md +259 -0
- package/doorman/agents/doorman.md +104 -0
- package/doorman/cli/agents.mjs +128 -0
- package/doorman/cli/allow.mjs +128 -0
- package/doorman/cli/cost.mjs +119 -0
- package/doorman/cli/discover.mjs +265 -0
- package/doorman/cli/doctor.mjs +282 -0
- package/doorman/cli/doorman.mjs +345 -0
- package/doorman/cli/eval.mjs +320 -0
- package/doorman/cli/harness.mjs +179 -0
- package/doorman/cli/install.mjs +175 -0
- package/doorman/cli/needs.mjs +116 -0
- package/doorman/cli/report.mjs +89 -0
- package/doorman/cli/sandbox.mjs +177 -0
- package/doorman/cli/task.mjs +239 -0
- package/doorman/cli/verdict.mjs +199 -0
- package/doorman/cli/watch.mjs +218 -0
- package/doorman/commands/doorman.md +116 -0
- package/doorman/commands/vet.md +69 -0
- package/doorman/hooks/hooks.json +30 -0
- package/doorman/install.sh +186 -0
- package/doorman/package.json +38 -0
- package/doorman/recipes/README.md +36 -0
- package/doorman/recipes/deepwiki.md +10 -0
- package/doorman/recipes/planted-bad.md +27 -0
- package/doorman/recipes/scorecard.md +10 -0
- package/doorman/registry/allowlist.json +37 -0
- package/doorman/registry/denylist.json +23 -0
- package/doorman/registry/ledger.jsonl +1 -0
- package/doorman/scripts/poller.mjs +292 -0
- package/doorman/scripts/resolve-cli.sh +58 -0
- package/doorman/scripts/vet.mjs +190 -0
- package/doorman/skills/doorman-guide/SKILL.md +69 -0
- package/doorman/src/budget.mjs +236 -0
- package/doorman/src/candidate.mjs +132 -0
- package/doorman/src/fit-review.mjs +255 -0
- package/doorman/src/injection.mjs +189 -0
- package/doorman/src/instructions.mjs +134 -0
- package/doorman/src/inventory.mjs +411 -0
- package/doorman/src/llm.mjs +87 -0
- package/doorman/src/needs.mjs +491 -0
- package/doorman/src/note.mjs +213 -0
- package/doorman/src/reviews.mjs +120 -0
- package/doorman/src/scorecard.mjs +123 -0
- package/doorman/src/vet.mjs +174 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
<img src="logo.png" alt="Clembot Doorman" width="150" align="right">
|
|
2
|
+
|
|
3
|
+
# Clembot Doorman
|
|
4
|
+
|
|
5
|
+
> **The Package Manager & Security Doorman for Clembot.**
|
|
6
|
+
> Inspect your build. Recommend vetted MCPs from prompt history. Block rogue tools before they reach context.
|
|
7
|
+
|
|
8
|
+
[](https://clembot-doorman.wanessalabs.com)
|
|
9
|
+
[](https://clembot-doorman.bazgateway.com)
|
|
10
|
+
[](RUNBOOK.md)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## TL;DR · What is Clembot Doorman?
|
|
16
|
+
|
|
17
|
+
**Clembot is built in many versions, with the latest utilizing Claude Code and a custom combination of agent harnesses.** While AI agents need tools to do real work, loading raw MCP servers blindly introduces **in-context steering ads**, prompt injection risks, and token-draining schema hallucination.
|
|
18
|
+
|
|
19
|
+
1. **Inspect & Recommend (`doorman doctor` & `doorman needs`)**
|
|
20
|
+
Inspects your active build (`doorman doctor`). Reads local prompt history (`~/.claude/projects/`), identifies capability gaps across 12 taxonomies, and recommends safe, pre-graded MCP tools from `scorecard.wanessalabs.com/feed`.
|
|
21
|
+
2. **Zero-Dependency Offline Security Gate (`PreToolUse` hook)**
|
|
22
|
+
A 180-line offline hook blocks hostile or unapproved tools locally (`exit 2`). Deny beats allow; rogue servers never reach your agent's context window.
|
|
23
|
+
3. **Arm with Bazantic Recipes (`recipe.md`)**
|
|
24
|
+
Turns raw, unpredictable APIs into bounded, high-performing tools via structured `recipe.md` guidance. Built and verified against the Bazantic gateway (`clembot-doorman.bazgateway.com`).
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
*A real run against a real server. The grade, the hard fail, and the arithmetic are live values, not a mockup: `webzum.com/api/mcp` scores **89.9% on configuration** and still fails, because one tool description injects **6,290 characters of unprompted upsell and competitor steering** directly into your agent's context window. One cent through the Bazantic gateway answered a question that would have cost $54.47 to measure in an unguided LLM loop. [Run it live in the simulator](https://clembot-doorman.wanessalabs.com/#flow).*
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## What this is for
|
|
33
|
+
|
|
34
|
+
Doorman measures whether a candidate tool actually helps **your** agent, and gives you a report about **your** build.
|
|
35
|
+
|
|
36
|
+
That emphasis is the whole design. A benchmark someone else ran tells you whether a tool helped *their* agent. Whether it helps yours depends on your harness, your model, your existing servers, and what your agents actually do. Those differ enough that a central verdict is close to meaningless.
|
|
37
|
+
|
|
38
|
+
So this is a thing you install, not a service you ask:
|
|
39
|
+
|
|
40
|
+
- **It runs on your machine.** Local static checks, prompt history parsing, and throwaway Docker sandboxes.
|
|
41
|
+
- **It drives the harness you already run.** Native integration with Claude Code and generic agent harnesses.
|
|
42
|
+
- **It spends from your own account**, bounded by a ceiling you set (`--max-cost`), while the inspection and recommendation layers spend nothing at all.
|
|
43
|
+
- **Nothing is sent to us.** There is no account here to create, no telemetry, and no tracking server in the path. We never see your prompt history or results.
|
|
44
|
+
|
|
45
|
+
The verdict is yours, produced on your machine, from numbers we never receive.
|
|
46
|
+
|
|
47
|
+
## The Toolchain: Five Layers, Cheapest First
|
|
48
|
+
|
|
49
|
+
| Layer | Command | Needs | Answers |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| **L0** | `doorman doctor` | nothing | **What is in my build?** Which harness, which MCP servers are reachable, how many subagents hold tools, and is the gate wired? |
|
|
52
|
+
| **L0.5** | `doorman needs [path]` | nothing (local history) | **What does my build keep asking for?** Analyzes prompt history (`~/.claude/projects/`), identifies unmet capabilities, and recommends safe, pre-graded tools from the feed. |
|
|
53
|
+
| **L1** | `doorman report <link>` | nothing (read-only) | **Is this candidate safe?** Audits protocol compliance, schema bloat, and scans tool descriptions for in-context steering ads and prompt injection. |
|
|
54
|
+
| **L2** | `PreToolUse hook` (`gate`) | local registry | **Block rogue tools offline.** 180-line zero-dependency hook that intercepts tool calls before LLM execution (`exit 2`). Deny beats allow. |
|
|
55
|
+
| **L3** | `doorman eval <link> --task <f>` | Docker + agent key | **Does this tool actually improve output?** A/B comparative benchmark across two isolated containers, bounded by a strict cost ceiling (`--max-cost`). |
|
|
56
|
+
|
|
57
|
+
**L0, L0.5, and L1 need no model key, no Docker, and no network spend.** Finding out whether a tool is safe, or what your agents are missing, costs nothing.
|
|
58
|
+
|
|
59
|
+
## What is in this repo
|
|
60
|
+
|
|
61
|
+
| Path | What it is |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `doorman/cli/` | The CLI: `doctor`, `needs`, `report`, `watch`, `eval`. Zero runtime dependencies. |
|
|
64
|
+
| `doorman/` | The gate you install: a 180-line `PreToolUse` hook that blocks unapproved MCP tools, a subagent that vets them, and a registry you own. |
|
|
65
|
+
| `mcp-scorecard/` | The grading service behind L1. Cloudflare Worker + D1 + a local probe runner. |
|
|
66
|
+
| `site/` | The live product & explainer at [clembot-doorman.wanessalabs.com](https://clembot-doorman.wanessalabs.com). |
|
|
67
|
+
| `fixtures/planted-bad-mcp/` | A deliberately hostile MCP server, deployed, so the demo denies something real instead of a line in a JSON file. |
|
|
68
|
+
|
|
69
|
+
## Quickstart & Installation
|
|
70
|
+
|
|
71
|
+
**The answer depends on your stack, so run it on yours.**
|
|
72
|
+
|
|
73
|
+
There are two halves and you want both. The **plugin** is the gate that blocks
|
|
74
|
+
untrusted servers at the point of use. The **CLI** is the measurement that
|
|
75
|
+
decides what belongs on your trust list. Installing one does not install the
|
|
76
|
+
other.
|
|
77
|
+
|
|
78
|
+
### 1. The plugin: the gate, `/doorman`, `/vet`, and the subagent
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
claude plugin marketplace add clemenswan/clembot-doorman
|
|
82
|
+
claude plugin install clembot-doorman
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Then, in Claude Code:
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
/doorman
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
That is the front door. With no arguments it tells you what is gating this
|
|
92
|
+
build, which trust list is actually in force, and how many of the servers it
|
|
93
|
+
trusts were **graded** versus simply allowed by you. Everything else is a branch
|
|
94
|
+
off it: `/doorman allow <server>`, `/doorman check <url>`, `/doorman needs`.
|
|
95
|
+
|
|
96
|
+
> **The gate starts strict, and you should expect to be blocked.** It ships
|
|
97
|
+
> trusting two servers. Anything else, including connectors you already use, is
|
|
98
|
+
> UNKNOWN and fails closed. That is the design: an ungraded server is not a
|
|
99
|
+
> trusted one. When it blocks something, it prints the exact command to allow
|
|
100
|
+
> it.
|
|
101
|
+
|
|
102
|
+
Verify what actually loaded, because a manifest that validates can still ship
|
|
103
|
+
components that never register:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
claude plugin details clembot-doorman
|
|
107
|
+
# Skills (3) doorman, doorman-guide, vet
|
|
108
|
+
# Agents (1) doorman
|
|
109
|
+
# Hooks (1) PreToolUse
|
|
110
|
+
# MCP servers (1) scorecard
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2. The CLI: doctor, needs, report, watch, eval
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
git clone https://github.com/clemenswan/clembot-doorman
|
|
117
|
+
npm i -g ./clembot-doorman
|
|
118
|
+
doorman --version
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Node 20+. Zero runtime dependencies, deliberately: every dependency is one more thing that can fail to install on your machine.
|
|
122
|
+
|
|
123
|
+
**New here?** [`WALKTHROUGH.md`](WALKTHROUGH.md) runs the first ten minutes
|
|
124
|
+
against three real builds: one with a long history, one brand new, and one in
|
|
125
|
+
between. The output in it is captured from real runs, not written by hand.
|
|
126
|
+
|
|
127
|
+
### Core Workflow
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# 1. What is in YOUR build. Free, local, read-only.
|
|
131
|
+
# No model, no container, no network.
|
|
132
|
+
doorman doctor
|
|
133
|
+
|
|
134
|
+
# 2. What do your prompts keep reaching for?
|
|
135
|
+
# Reads prompt history, identifies capability gaps, recommends vetted tools.
|
|
136
|
+
doorman needs
|
|
137
|
+
|
|
138
|
+
# 3. Grade a candidate server by its implementation before installing.
|
|
139
|
+
# Scans for hidden steering ads, injection patterns, and protocol violations.
|
|
140
|
+
doorman report https://webzum.com/api/mcp
|
|
141
|
+
|
|
142
|
+
# 4. Stream newly graded servers from the feed and flag blocked/unreviewed tools.
|
|
143
|
+
doorman watch --all
|
|
144
|
+
|
|
145
|
+
# 5. Does a candidate actually help YOUR agent? Two sandboxes, identical
|
|
146
|
+
# except one install layer, driving the harness you already run.
|
|
147
|
+
doorman eval npm:some-candidate --task evals/tasks/url-to-note.yaml --agent claude-code --max-cost 2
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### What `doorman doctor` tells you
|
|
151
|
+
|
|
152
|
+
Which harness the project is set up for, every MCP server your agents can reach
|
|
153
|
+
and where each was declared, how many subagents hold MCP tools, and whether the
|
|
154
|
+
gate is installed **and wired**. Those last two are different states, and the
|
|
155
|
+
dangerous one is the middle: a gate that is present but not wired is not
|
|
156
|
+
running, and looks exactly like one that is. Both are quiet.
|
|
157
|
+
|
|
158
|
+
### Your key, your machine
|
|
159
|
+
|
|
160
|
+
`--agent claude-code` drives the agent you already run. The credential your
|
|
161
|
+
harness already uses is passed straight into a local container. It is never
|
|
162
|
+
written to a file, never logged, and never leaves your machine except to the
|
|
163
|
+
provider you already pay.
|
|
164
|
+
|
|
165
|
+
An adapter that cannot measure something reports it as **not measured** rather
|
|
166
|
+
than estimating it. `--agent exec "<command>"` will drive any harness at all,
|
|
167
|
+
and reports success rate and wall time only, because a command doorman knows
|
|
168
|
+
nothing about cannot be asked how many turns it took.
|
|
169
|
+
|
|
170
|
+
### The bill is bounded before it starts
|
|
171
|
+
|
|
172
|
+
An agent loop resends the whole conversation every turn, so cost grows with the
|
|
173
|
+
**square** of the turn count. A 24-turn cap authorises far more than it looks
|
|
174
|
+
like: on Sonnet, three runs per arm is **$54 at worst**.
|
|
175
|
+
|
|
176
|
+
So `--max-cost` is the input and the turn cap is **derived from it**. A ceiling
|
|
177
|
+
of $2 is a ceiling of $2. Add `--estimate` to print the worst case and spend
|
|
178
|
+
nothing:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
doorman eval npm:some-candidate --task <file> --max-cost 2 --estimate
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
It refuses rather than shaving: a ceiling too small for even a three-turn run
|
|
185
|
+
stops and shows the arithmetic. The permit ledger is the same one the doorman
|
|
186
|
+
uses on its own outbound spend.
|
|
187
|
+
|
|
188
|
+
Fewer than three runs per arm cannot reach ADOPT, because one sample cannot be
|
|
189
|
+
told apart from luck. DECLINE stays reachable at any run count, so a cheap run
|
|
190
|
+
is still worth doing: it can tell you a candidate is bad, just not that one is
|
|
191
|
+
good.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Step-by-Step Developer Walkthrough & Skill Management
|
|
196
|
+
|
|
197
|
+
A complete guide for net-new Claude Code / Clembot builds, prompt history analysis, and managing existing skill rosters.
|
|
198
|
+
|
|
199
|
+
### 1. Fresh Init Walkthrough (`claude init` → `doorman doctor` → `doorman install`)
|
|
200
|
+
|
|
201
|
+
When you run `claude init` in an empty repository, Claude Code sets up baseline configuration (`CLAUDE.md`, `.claude/settings.json`). Before installing tools, run `doorman doctor`:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
doorman doctor
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Verbatim output on a fresh build:**
|
|
208
|
+
```markdown
|
|
209
|
+
# Your build
|
|
210
|
+
|
|
211
|
+
`C:\Users\username\my-new-project`
|
|
212
|
+
|
|
213
|
+
Read-only. Nothing here was executed, sent anywhere, or billed.
|
|
214
|
+
|
|
215
|
+
## Harness
|
|
216
|
+
|
|
217
|
+
- **Claude Code** (CLAUDE.md)
|
|
218
|
+
|
|
219
|
+
## MCP servers this project can reach
|
|
220
|
+
|
|
221
|
+
None declared. Nothing to grade yet, and nothing to gate.
|
|
222
|
+
|
|
223
|
+
## Agents
|
|
224
|
+
|
|
225
|
+
No .claude/agents/ directory.
|
|
226
|
+
|
|
227
|
+
## The gate
|
|
228
|
+
|
|
229
|
+
**not installed**
|
|
230
|
+
|
|
231
|
+
- hook present: no
|
|
232
|
+
- wired in settings.json: no
|
|
233
|
+
- registry present: no
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
_doorman doctor. Static, local, free. It reports what is here; it does not
|
|
237
|
+
say whether any of it works. That is doorman report and doorman eval._
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
To wire the local deterministic gate before adding any external MCP servers:
|
|
241
|
+
```bash
|
|
242
|
+
doorman install
|
|
243
|
+
```
|
|
244
|
+
This registers `doorman/.claude/hooks/mcp-gate.sh` into `.claude/settings.json` and creates `registry/allowlist.json`. Unapproved tools or payload injections are stopped at **exit 2** before reaching context.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
### 2. Elevate Your Build with Prompt Recommendations (`doorman needs`)
|
|
249
|
+
|
|
250
|
+
After working in your project for a few sessions, run `doorman needs`:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
doorman needs
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**How it works:**
|
|
257
|
+
1. **Local Transcript Ingestion**: Reads `~/.claude/projects/<slug>/*.jsonl`. Filters out tool results, compacted summaries, and slash command templates to isolate the sentences *you actually typed*.
|
|
258
|
+
2. **12 Capability Taxonomies**: Categorizes asks into `docs-lookup`, `web-search`, `database`, `browser-automation`, `cloud-deploy`, `observability`, `payments`, `comms`, `design-assets`, `knowledge-base`, `code-host`, `data-files`.
|
|
259
|
+
3. **Public Feed Matching**: Compares unmet capabilities against the free [Scorecard Feed](https://scorecard.wanessalabs.com/feed).
|
|
260
|
+
|
|
261
|
+
**Verbatim output:**
|
|
262
|
+
```text
|
|
263
|
+
doorman needs — 42 prompts read from this build’s own history
|
|
264
|
+
history: ~/.claude/projects/C--my-new-project
|
|
265
|
+
feed: 24 graded rows
|
|
266
|
+
|
|
267
|
+
UNMET Current documentation for a library it does not know
|
|
268
|
+
14 prompts across 3 sessions · matched "latest docs", "deepwiki"
|
|
269
|
+
> …can you look up the latest docs for drizzle orm…
|
|
270
|
+
> …check the documentation for cloudflare workers assets…
|
|
271
|
+
worth-measuring DeepWiki MCP [A (85.7)] matched "docs"
|
|
272
|
+
https://mcp.deepwiki.com/mcp
|
|
273
|
+
worth-measuring Cloudflare Docs MCP [A (94.2)] matched "documentation"
|
|
274
|
+
https://docs.mcp.cloudflare.com/mcp
|
|
275
|
+
|
|
276
|
+
UNMET Driving a real browser
|
|
277
|
+
6 prompts across 2 sessions · matched "screenshot the page", "playwright"
|
|
278
|
+
> …take a screenshot of the landing page at 390px…
|
|
279
|
+
worth-measuring Peek Browser MCP [C (66.7)] matched "screenshot"
|
|
280
|
+
https://mcp.peek.com
|
|
281
|
+
|
|
282
|
+
UNMET Reading the live web
|
|
283
|
+
5 prompts across 1 sessions · matched "search online"
|
|
284
|
+
> …search online for the error code…
|
|
285
|
+
blocked WebZum Search [F (49.0)] matched "search"
|
|
286
|
+
hard fail: injection-shaped content in tool:host_site.description
|
|
287
|
+
https://webzum.com/api/mcp
|
|
288
|
+
|
|
289
|
+
GAP Production errors and logs
|
|
290
|
+
3 prompts across 1 sessions · matched "tail the logs"
|
|
291
|
+
> …tail the logs from production…
|
|
292
|
+
nothing graded covers this. The feed has the gap, not your build.
|
|
293
|
+
|
|
294
|
+
3 unmet, 1 of them with nothing graded to offer.
|
|
295
|
+
|
|
296
|
+
What this is: your own prompts, counted, against capability text those
|
|
297
|
+
candidates published about themselves. Nothing here was driven, so
|
|
298
|
+
nothing here is a claim that a server works. `worth-measuring` means
|
|
299
|
+
exactly that — run `doorman eval` with your key and find out.
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
- **`worth-measuring`**: Verified Grade A/B servers matching your exact needs.
|
|
303
|
+
- **`blocked`**: Identifies hostile or compromised servers (e.g. WebZum prompt injection).
|
|
304
|
+
- **`GAP`**: Honestly states when the ecosystem has no graded server for that need yet.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### 3. What If Your Build Already Has Skills or Tools?
|
|
309
|
+
|
|
310
|
+
Doorman is specifically designed to stop "skill sprawl" and prevent duplicate tools:
|
|
311
|
+
|
|
312
|
+
1. **Automatic Suppression (`coveredBy(inv)`)**:
|
|
313
|
+
In `doorman needs`, if a capability term matches a tool already declared in `.mcp.json` or `.claude/settings.json`, it labels the need:
|
|
314
|
+
```text
|
|
315
|
+
COVERED Current documentation for a library it does not know
|
|
316
|
+
already covered by: DeepWiki MCP (mcp__deepwiki_lookup)
|
|
317
|
+
```
|
|
318
|
+
Candidate recommendations for that need are **suppressed** so your output stays focused on real gaps.
|
|
319
|
+
|
|
320
|
+
2. **Adverse Drift Detection (`doorman watch`)**:
|
|
321
|
+
When you run `doorman watch`, any candidate server already in your inventory is tagged `already-installed`. If an installed server is downgraded or caught with prompt injection on the feed, `watch` raises an immediate security alert.
|
|
322
|
+
|
|
323
|
+
3. **Frontmatter Arithmetic (30 KB vs 640 KB)**:
|
|
324
|
+
Doorman reads only YAML frontmatter (`name`, `description`) from `.claude/skills/*/SKILL.md` and `.claude/agents/*.md`. In our production vault, reading full markdown bodies was **642 KB**; reading frontmatter was **30 KB**. This allows the complete roster to be reviewed by a model in a single prompt without bloating context.
|
|
325
|
+
|
|
326
|
+
4. **The Two-Phase Fit Review (`node scripts/vet.mjs <candidate> --dry-run`)**:
|
|
327
|
+
Before spending any money or tokens on external audits, the Fit Review compares the candidate against your existing skills. If an existing skill already covers it, it returns `REDUNDANT` and halts at Phase 1 ($0.00 spent):
|
|
328
|
+
```text
|
|
329
|
+
candidate https://github.com/example/git-mcp
|
|
330
|
+
inventory C:\Users\username\my-project — 3 agents, 22 skills, 1 mcp servers
|
|
331
|
+
|
|
332
|
+
FIT REDUNDANT
|
|
333
|
+
Already covered by existing skill: git-pr covers reviewing and merging pull requests.
|
|
334
|
+
|
|
335
|
+
Already covered by:
|
|
336
|
+
- skill git-pr: handles GitHub pull requests and diff review locally
|
|
337
|
+
|
|
338
|
+
STOPPED before the paid grade. $0.00 spent.
|
|
339
|
+
Nothing was sent to the scorecard, and no client was built.
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### 4. Running Doorman as an Agent Skill or Slash Command
|
|
345
|
+
|
|
346
|
+
- **As a Slash Command (`/vet <url>`)**: Create `.claude/commands/vet.md` calling `node scripts/vet.mjs $ARG --dry-run`. Type `/vet <url>` directly in your Claude Code chat to run the two-phase check.
|
|
347
|
+
- **As a Dedicated Subagent (`.claude/agents/doorman.md`)**: Sandbox tool evaluation by assigning a dedicated `doorman` agent holding only read tools and the Bazantic Scorecard gateway.
|
|
348
|
+
- **As a Harness Skill (`.claude/skills/doorman/SKILL.md`)**: Equip your agents to run `doorman doctor`, `doorman needs`, or `doorman report` during planning turns before proposing new tool installs.
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
### 5. Implementation Status: How Built Out Is This?
|
|
353
|
+
|
|
354
|
+
| Component | Status | Verification & Evidence |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| **Static Scanner (`doorman report <url>`)** | **Production Ready** | Live SSE handshake, tool schema linting, 6-pattern injection detection. [Caught WebZum injection](evidence/needs-demo/watch-blocked.txt) on live internet. |
|
|
357
|
+
| **Needs Engine (`doorman needs`)** | **Production Ready** | Parses real `~/.claude/projects/` JSONL prompts, deduplicates resumes, maps to 12 capability taxonomies, matches against [Scorecard Feed](https://scorecard.wanessalabs.com/feed). |
|
|
358
|
+
| **Harness Doctor (`doorman doctor`)** | **Production Ready** | Zero-dependency local scan. Detects Claude Code, Cursor, Windsurf, Copilot, Gemini; audits MCP configs and agent exposure. |
|
|
359
|
+
| **Security Gate (`mcp-gate.sh`)** | **Production Ready** | 180-line offline Bash hook. Passed 29/29 test suites in `test-gate.sh` (blocks unallowlisted tools, prevents shell escapes, enforces 5s timeout). |
|
|
360
|
+
| **Fit Review Engine (`fitReview`)** | **Production Ready** | Compares candidates against `.claude/skills/*/SKILL.md` frontmatter. Enforces temperature 0, strict JSON schema, and hallucination rejection. |
|
|
361
|
+
| **Budget & Spend Ledger** | **Production Ready** | Enforces per-run ($1) and per-day ($5) caps in USDC on Base; auto-releases unspent reserves on error. |
|
|
362
|
+
| **Bazantic x402 Gateway** | **Live in Production** | `clembot-doorman.bazgateway.com` live x402 challenge ($0.01 USDC on Base) + MCP SSE tool stream. |
|
|
363
|
+
| **Multi-Directory Skill Reading** | **Configuration Detail** | Currently looks in `.claude/skills/*/SKILL.md` by default. Set `DOORMAN_INVENTORY_ROOT` for alternate paths like `.agents/skills/`. |
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Live
|
|
368
|
+
|
|
369
|
+
| Surface | URL |
|
|
370
|
+
|---|---|
|
|
371
|
+
| Explainer site | https://clembot-doorman.wanessalabs.com |
|
|
372
|
+
| Developer Walkthrough | https://clembot-doorman.wanessalabs.com/guide.html |
|
|
373
|
+
| Scorecard API | https://scorecard.wanessalabs.com |
|
|
374
|
+
| OpenAPI spec | https://scorecard.wanessalabs.com/openapi.json (3.1.0) |
|
|
375
|
+
| Same spec as 3.0.3 | https://scorecard.wanessalabs.com/openapi-3.0.json |
|
|
376
|
+
| End-to-end runbook | [`RUNBOOK.md`](RUNBOOK.md) |
|
|
377
|
+
| Embeddable demo | https://clembot-doorman.wanessalabs.com/embed/flow.html |
|
|
378
|
+
| Where this is headed | https://clembot-doorman.wanessalabs.com/direction.html |
|
|
379
|
+
| Example badge | https://scorecard.wanessalabs.com/badge/https%3A%2F%2Fmcp.deepwiki.com%2Fmcp.svg |
|
|
380
|
+
|
|
381
|
+
Two real production audits, both queued through the API, claimed by a laptop
|
|
382
|
+
runner, graded, posted back:
|
|
383
|
+
|
|
384
|
+
| Server | Grade | Static layer | Audit | Tape |
|
|
385
|
+
|---|---|---|---|---|
|
|
386
|
+
| `mcp.deepwiki.com/mcp` | **A 85.71** | 85.71% | `9fbb3558` | [replay](https://scorecard.wanessalabs.com/grade/9fbb3558-8b6e-475b-9b6f-161b32bbb7a1/transcripts) |
|
|
387
|
+
| this service, graded by itself | **A 98.63** | 98.63% | `d4bc490c` | [replay](https://scorecard.wanessalabs.com/grade/d4bc490c-5e51-4fb7-be67-6ef6f5a0a0ea/transcripts) |
|
|
388
|
+
| our planted fixture | **F 49** | **91.78%** | `f468e5b8` | [replay](https://scorecard.wanessalabs.com/grade/f468e5b8-232a-43bb-9cb4-2ad680bab1ae/transcripts) |
|
|
389
|
+
|
|
390
|
+
**Read the static column twice.** The hostile server scores 91.78% on
|
|
391
|
+
configuration, higher than the A-graded one. It negotiates the protocol
|
|
392
|
+
correctly and would survive a config review. The F is entirely in what it tells
|
|
393
|
+
the agent reading it.
|
|
394
|
+
|
|
395
|
+
That is the whole argument, and it is why the planted server is a real deployed
|
|
396
|
+
server rather than a row in a denylist. It is also why the middle row is there:
|
|
397
|
+
the scorecard is itself an MCP server, it was graded by itself, and it had to
|
|
398
|
+
pass its own gate to be callable. Nothing here is exempt.
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
### Embed the demo
|
|
403
|
+
|
|
404
|
+
One file, no build step, no dependency on this repo at runtime. It runs the same
|
|
405
|
+
live call the site runs.
|
|
406
|
+
|
|
407
|
+
```html
|
|
408
|
+
<iframe src="https://clembot-doorman.wanessalabs.com/embed/flow.html"
|
|
409
|
+
width="100%" height="1900" style="border:0" loading="lazy"
|
|
410
|
+
title="Doorman: should I adopt this server?"></iframe>
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Add `?api=` to point it at your own scorecard deployment.
|
|
414
|
+
|
|
415
|
+
It is **generated** by `node scripts/build-widget.mjs`, never hand-maintained: a
|
|
416
|
+
second hand-copied copy of a 12KB driver and a 45KB stylesheet drifts the first
|
|
417
|
+
time anyone edits either, and drifts silently, because both still run. The
|
|
418
|
+
builder carries a drift guard that refuses to write a widget whose Run button
|
|
419
|
+
would throw.
|
|
420
|
+
|
|
421
|
+
## Where this is headed
|
|
422
|
+
|
|
423
|
+
**A subscription that keeps an agent stack current.** New tools appear every
|
|
424
|
+
week. The useful question is not whether one is good, it is whether one is good
|
|
425
|
+
for the build you already have, and answering that repeatedly is a different
|
|
426
|
+
product from answering it once.
|
|
427
|
+
|
|
428
|
+
Full version, with the line between built and specified drawn where it actually
|
|
429
|
+
falls: **<https://clembot-doorman.wanessalabs.com/direction.html>**
|
|
430
|
+
|
|
431
|
+
### The split, and why it is the whole design
|
|
432
|
+
|
|
433
|
+
| Half | Runs | Who pays | Cost of the next subscriber |
|
|
434
|
+
|---|---|---|---|
|
|
435
|
+
| The grade | ours, cached, public tape | whoever asked first, once | **$0.00** |
|
|
436
|
+
| The fit | **your machine** | you, in tokens | their own |
|
|
437
|
+
|
|
438
|
+
A candidate is graded **once** and every subscriber reads that grade for
|
|
439
|
+
nothing, so the marginal cost of the thousandth subscriber is not another audit.
|
|
440
|
+
The half that is actually about you, your agent roster, your installed servers,
|
|
441
|
+
your allowlist, is read locally and never leaves. `doorman watch` makes exactly
|
|
442
|
+
one request, a `GET` for the feed, and that request says nothing about who is
|
|
443
|
+
asking. The privacy is not a policy, it is the shape of the thing.
|
|
444
|
+
|
|
445
|
+
### Working today
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
# the shared half: newly graded candidates, one row per server, free
|
|
449
|
+
curl https://scorecard.wanessalabs.com/feed
|
|
450
|
+
|
|
451
|
+
# the private half: which of those are new to THIS build
|
|
452
|
+
node doorman/cli/doorman.mjs watch . --all
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
`watch` sorts candidates into `already-installed`, `blocked`, `unreviewed` and
|
|
456
|
+
`skipped`. It will not tell you a candidate **fits**: that word belongs to the
|
|
457
|
+
fit review, which reads the candidate against your build with a model, and a
|
|
458
|
+
string match cannot earn it. Two tests exist for the sole purpose of stopping it
|
|
459
|
+
ever saying so.
|
|
460
|
+
|
|
461
|
+
### Why this needs a payment rail, in two lines
|
|
462
|
+
|
|
463
|
+
The useful price for "is this new tool worth your attention" is a fraction of a
|
|
464
|
+
cent, and card fees exceed the value of the thing being sold. The product is not
|
|
465
|
+
*nicer* on a micropayment rail, it is impossible without one.
|
|
466
|
+
|
|
467
|
+
And a grade is cached with its full transcript free forever, so one agent's cent
|
|
468
|
+
does not buy one answer. It funds a commons nobody could bill for directly.
|
|
469
|
+
|
|
470
|
+
### Finding candidates in the first place
|
|
471
|
+
|
|
472
|
+
`doorman discover` sweeps a public MCP registry and writes a candidate file, then
|
|
473
|
+
stops. It never enqueues and never spends.
|
|
474
|
+
|
|
475
|
+
```bash
|
|
476
|
+
node doorman/cli/doorman.mjs discover --pages 1
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
The registry returns only its own proxy, which needs its token, so the origin an
|
|
480
|
+
audit would need is not in the record. But the detail record ships the full tool
|
|
481
|
+
descriptions, so the static scan reads the exact surface an agent reads without
|
|
482
|
+
calling a single server.
|
|
483
|
+
|
|
484
|
+
**The first sweep is why the scan is now measured.** It flagged 15 of 100, and
|
|
485
|
+
two survived a hand check. The rest were ordinary documentation: a Slack
|
|
486
|
+
parameter that posts a reply to a conversation, an LLM testing tool whose job is
|
|
487
|
+
to accept a system prompt, `system:` as a docstring parameter name, and five
|
|
488
|
+
vendors saying "use this instead of" about another tool in their own server.
|
|
489
|
+
Five patterns were tightened and the same sweep now flags two.
|
|
490
|
+
|
|
491
|
+
All fifteen strings live in `doorman/test/discover-precision.test.mjs`, verbatim
|
|
492
|
+
and named, next to the strings that must keep tripping. The baseline is a
|
|
493
|
+
ratchet: it fails if precision gets worse, and demands the number be lowered in
|
|
494
|
+
the commit that improves it.
|
|
495
|
+
|
|
496
|
+
### Not built, and said so
|
|
497
|
+
|
|
498
|
+
The released Bazantic CLI has **no marketplace discovery command**, so ingest is
|
|
499
|
+
still whatever gets pointed at the feed. Nothing has been settled through the
|
|
500
|
+
gateway even once. And 70 of every 100 points on every grade in the feed are
|
|
501
|
+
unmeasured until an `ANTHROPIC_API_KEY` exists.
|
|
502
|
+
|
|
503
|
+
## The grade
|
|
504
|
+
|
|
505
|
+
Three layers, weighted 30 / 50 / 20, banded A at 85, B at 70, C at 50, F below.
|
|
506
|
+
|
|
507
|
+
**Static (30).** Wraps the `mcpscore` CLI. Protocol version, TLS, schema validity,
|
|
508
|
+
annotations, pagination. Its raw output has a *moving denominator*, because rules
|
|
509
|
+
get skipped per server, so it is normalised to a percentage before anything is
|
|
510
|
+
compared. A server scoring 78/91 is worse than one scoring 64/73, and only the
|
|
511
|
+
normalised number shows it.
|
|
512
|
+
|
|
513
|
+
**Behavioural (50).** A real agent, a pinned model, temperature 0, three runs per
|
|
514
|
+
probe. This is the half that reading cannot produce.
|
|
515
|
+
|
|
516
|
+
**Guidance delta (20).** The same cold task re-run with the drafted recipe in the
|
|
517
|
+
agent's system prompt. Scored as *recovered headroom*, not raw delta, so a server
|
|
518
|
+
that was already strong is not punished for having little room to improve.
|
|
519
|
+
|
|
520
|
+
The recipe is derived from the very runs it is then measured against, so this is
|
|
521
|
+
deliberately **not** a generalisation claim. It answers a narrower question, and
|
|
522
|
+
the narrow question is the useful one:
|
|
523
|
+
|
|
524
|
+
> We told the agent, in plain language, exactly what went wrong last time.
|
|
525
|
+
> Did that fix it?
|
|
526
|
+
|
|
527
|
+
A server that recovers can be put safely behind a recipe. A server that still
|
|
528
|
+
fails with the correction sitting in front of it is one where no amount of
|
|
529
|
+
documentation saves you, and that is the finding worth having. A **low** guidance
|
|
530
|
+
score is the interesting result; a high one is expected.
|
|
531
|
+
|
|
532
|
+
Four gates stop it reporting a number nobody can defend, and each has a mutation
|
|
533
|
+
check. It is `not measured` when the recipe derived no rules, when `cold_open`
|
|
534
|
+
never produced a baseline, when the cold run already scored 100 (zero headroom
|
|
535
|
+
would otherwise pay a perfect server twenty free points), and whenever the model
|
|
536
|
+
probes did not run. The guided run is scored **separately and kept out of the
|
|
537
|
+
behavioural mean**, or a server would be paid twice for one recovery. The guided
|
|
538
|
+
agent never sees the grade, the band, or the hard-fail banner: feed it
|
|
539
|
+
"Do not use this server" and the delta measures our own warning.
|
|
540
|
+
|
|
541
|
+
Two hard fails cap a grade at F regardless of everything else: injection-shaped
|
|
542
|
+
content in the advertised strings, and a transport that is not TLS.
|
|
543
|
+
|
|
544
|
+
### Three properties worth stating plainly
|
|
545
|
+
|
|
546
|
+
1. **An unmeasured layer is not a zero.** If the guidance delta did not run, its
|
|
547
|
+
weight is removed and the other two renormalise to 37.5 / 62.5. Scoring it zero
|
|
548
|
+
against a 20-point weight would drag every honest partial audit into a failing
|
|
549
|
+
band. That is lying with arithmetic, and there is a test for it.
|
|
550
|
+
|
|
551
|
+
2. **A grade is relative to the model that produced it.** The model id is on the
|
|
552
|
+
audit, in the report, and on the badge. Grades from different models are not
|
|
553
|
+
comparable.
|
|
554
|
+
|
|
555
|
+
3. **A skipped probe is excluded, not failed.** Ambiguity only fires when tool
|
|
556
|
+
descriptions overlap; Chain is skipped under three tools. A two-tool server is
|
|
557
|
+
not worse for having nothing to chain.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## The six probes
|
|
562
|
+
|
|
563
|
+
| # | Probe | The question it answers |
|
|
564
|
+
|---|---|---|
|
|
565
|
+
| 1 | Handshake and inventory | Is there anything here at all? Dead servers exit free. |
|
|
566
|
+
| 2 | Cold open | Can an agent that has never seen this succeed on the first try? |
|
|
567
|
+
| 3 | Ambiguity gauntlet | Do two overlapping descriptions actually distinguish themselves? |
|
|
568
|
+
| 4 | Bad input recovery | Can an agent self-correct from this error message in two turns? |
|
|
569
|
+
| 5 | Chain test | Do these outputs compose, or only look like they should? |
|
|
570
|
+
| 6 | Injection sniff | Is this documentation, or is it giving my agent orders? |
|
|
571
|
+
|
|
572
|
+
Probe 4 grades the **error message**, not the agent. "Invalid input" and
|
|
573
|
+
"missing required `repoName` (string, e.g. `facebook/react`)" are the same failure
|
|
574
|
+
and completely different products.
|
|
575
|
+
|
|
576
|
+
Probe 6 is scan-only and never calls a tool. We do not execute a server to find
|
|
577
|
+
out whether it is hostile. Because a hit caps the grade at F, which is a public
|
|
578
|
+
accusation about somebody else's software, it is deliberately conservative and
|
|
579
|
+
every hit records the pattern plus the offending text verbatim.
|
|
580
|
+
|
|
581
|
+
It also needs no model, so it is the one probe that **runs without an API key**.
|
|
582
|
+
Dropping it alongside the model-driven probes under `--static-only` would have
|
|
583
|
+
made the cheapest audits the ones that stayed quiet about hostile tool
|
|
584
|
+
descriptions. The planted fixture grades C without it and F with it.
|
|
585
|
+
|
|
586
|
+
---
|
|
587
|
+
|
|
588
|
+
## Why the Worker does not grade anything
|
|
589
|
+
|
|
590
|
+
`mcpscore` is Python and pulls in `cryptography`, `pydantic-core` and `cffi`,
|
|
591
|
+
which are native compiled extensions. A Cloudflare Worker cannot spawn a process
|
|
592
|
+
and Python Workers only load a curated package set. **The Worker physically
|
|
593
|
+
cannot run the static layer.**
|
|
594
|
+
|
|
595
|
+
So `POST /grade` returns **202 and an audit id**, a probe runner on a real machine
|
|
596
|
+
claims the work, and posts the result back. The API says this in its own
|
|
597
|
+
description rather than pretending to be synchronous and timing out.
|
|
598
|
+
|
|
599
|
+
The grade math lives in one isomorphic module that both halves import. `src/` is
|
|
600
|
+
typechecked against Cloudflare Workers types only, so a probe that reaches for a
|
|
601
|
+
Node API fails the build. The runner imports the *built* version of that module
|
|
602
|
+
rather than reimplementing it, because two implementations would drift and a
|
|
603
|
+
laptop-produced grade would stop meaning the same thing as a Worker-produced one.
|
|
604
|
+
|
|
605
|
+
---
|
|
606
|
+
|
|
607
|
+
## The gate
|
|
608
|
+
|
|
609
|
+
`doorman/.claude/hooks/mcp-gate.sh` runs before every MCP tool call.
|
|
610
|
+
|
|
611
|
+
- **No network.** Not a curl, not a DNS lookup. A gate that asks a server for
|
|
612
|
+
permission is offline the moment the network is, and offline would mean allow.
|
|
613
|
+
- **No dependencies.** Bash builtins and coreutils. No jq, no node, no python.
|
|
614
|
+
A gate that fails to start is a gate that fails open.
|
|
615
|
+
- **Fails closed.** Unparseable input, missing registry, unknown server,
|
|
616
|
+
unreadable file: all block.
|
|
617
|
+
- **Exit 2, never exit 1.** Only exit 2 blocks a call. Exit 1 is treated as a
|
|
618
|
+
script error and the call proceeds. A test asserts the file contains no exit 1.
|
|
619
|
+
- **Humans own the list.** Nothing writes the allowlist automatically. The poller
|
|
620
|
+
reports changes and refuses to allowlist a hard fail even when the service says
|
|
621
|
+
to.
|
|
622
|
+
|
|
623
|
+
The `doorman` subagent holds `Read` and exactly one MCP tool. The agent that
|
|
624
|
+
decides what to trust does not also carry capabilities an untrusted server could
|
|
625
|
+
talk it into using.
|
|
626
|
+
|
|
627
|
+
---
|
|
628
|
+
|
|
629
|
+
## Running it
|
|
630
|
+
|
|
631
|
+
> Proving the whole chain works, rather than one piece of it, is
|
|
632
|
+
> **`RUNBOOK.md`**: four ordered tests, three of them free, with the
|
|
633
|
+
> observed output of each.
|
|
634
|
+
|
|
635
|
+
```bash
|
|
636
|
+
# The service
|
|
637
|
+
cd mcp-scorecard
|
|
638
|
+
npm install
|
|
639
|
+
npm run migrate:local
|
|
640
|
+
npx wrangler dev --port 8799 --local
|
|
641
|
+
|
|
642
|
+
# Grade a real server, static layer only, no API key needed
|
|
643
|
+
pip install mcpscore
|
|
644
|
+
node runner/run.mjs --once --server https://mcp.deepwiki.com/mcp \
|
|
645
|
+
--needed-for "look up how a public repository works" \
|
|
646
|
+
--static-only --out ../evidence/deepwiki
|
|
647
|
+
|
|
648
|
+
# Full behavioural run (needs a key)
|
|
649
|
+
export ANTHROPIC_API_KEY=...
|
|
650
|
+
node runner/run.mjs --once --server https://mcp.deepwiki.com/mcp \
|
|
651
|
+
--needed-for "look up how a public repository works"
|
|
652
|
+
|
|
653
|
+
# Poll the queue
|
|
654
|
+
export RUNNER_TOKEN=... SCORECARD_API=http://127.0.0.1:8799
|
|
655
|
+
node runner/run.mjs --poll
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
```bash
|
|
659
|
+
# Tests
|
|
660
|
+
cd mcp-scorecard && npm test # 228 unit tests, 12 files
|
|
661
|
+
node test/smoke-grade.mjs # grades a live public server
|
|
662
|
+
node test/smoke-api.mjs # 75 assertions over the HTTP surface
|
|
663
|
+
cd ../doorman && bash test-gate.sh # 29 adversarial gate tests
|
|
664
|
+
node test-poller.mjs # registry key derivation
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
---
|
|
668
|
+
|
|
669
|
+
## Status
|
|
670
|
+
|
|
671
|
+
Working and verified end to end **except the behavioural probes and the guidance
|
|
672
|
+
pass**, which have never been executed against a live model because no
|
|
673
|
+
`ANTHROPIC_API_KEY` has been supplied. Every guidance number in this repo comes
|
|
674
|
+
from a scripted stub in the test suite; none is a measurement of a real server. They are written, typechecked and unit-tested behind a mock-free
|
|
675
|
+
interface, and the runner refuses to fabricate results without a key: it marks
|
|
676
|
+
the audit failed and says why.
|
|
677
|
+
|
|
678
|
+
The chain those probes sit in was verified end to end against the deployed stack
|
|
679
|
+
on **3 September 2026**: a hostile server published, graded F through the live
|
|
680
|
+
queue, written into a registry that cites the audit id and evidence hash, and
|
|
681
|
+
blocked by the gate at exit 2 while the A-graded server passes at exit 0.
|
|
682
|
+
|
|
683
|
+
**No probe runner has been polling since that date.** The queue accepts work and
|
|
684
|
+
nothing claims it, so an audit requested today stays `queued` until someone
|
|
685
|
+
starts a runner. That is a second gap, separate from the missing key and more
|
|
686
|
+
immediate: `RUNBOOK.md` Test 2 closes it, costs nothing, and needs no key.
|
|
687
|
+
|
|
688
|
+
## Replay the tape
|
|
689
|
+
|
|
690
|
+
Every turn behind every grade is public and needs no token:
|
|
691
|
+
|
|
692
|
+
```
|
|
693
|
+
GET /grade/{audit_id}/transcripts # JSONL, one object per turn
|
|
694
|
+
GET /grade/{audit_id}/transcripts?format=json # grouped by probe run
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
Never paginated, never sampled. A grade is an accusation, and the evidence for
|
|
698
|
+
one cannot sit behind the token held by the party making it. It does not sit
|
|
699
|
+
behind the paywall either: `POST /grade` is the only chargeable route, and a
|
|
700
|
+
test asserts the transcripts stay free with payment fully switched on.
|
|
701
|
+
|
|
702
|
+
## Money
|
|
703
|
+
|
|
704
|
+
Two halves, and only one of them is finished.
|
|
705
|
+
|
|
706
|
+
**The spend cap is complete.** The doorman refuses to spend twice: once on fit
|
|
707
|
+
(does this system need it at all) and once on budget (can it afford to find
|
|
708
|
+
out). The second refusal is a **permit**, not a check. `scorecardClient` will
|
|
709
|
+
not be constructed without a budget and `enqueue()` will not run without an open
|
|
710
|
+
permit from it, because a required argument cannot be forgotten by a code path
|
|
711
|
+
that does not know the rule exists.
|
|
712
|
+
|
|
713
|
+
```
|
|
714
|
+
budget 0 spent today, 5 of 5 left. Per-run cap 1.
|
|
715
|
+
FIT FITS -> scheduler
|
|
716
|
+
REFUSED the spend cap said no. $0.00 spent.
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
An **unknown price is not a free one**. `GET /price` is free and
|
|
720
|
+
unauthenticated, `/vet` reads it rather than assuming, and a price that cannot
|
|
721
|
+
be read stops the run. A client that defaults an unknown price to zero passes
|
|
722
|
+
every cap it has, forever. A `price_usdc: 0` from the service is a *discovered*
|
|
723
|
+
zero and spends cleanly; a missing field is not.
|
|
724
|
+
|
|
725
|
+
**Settlement is refused rather than faked.** The x402 **v2** challenge is real
|
|
726
|
+
and was implemented from the published spec rather than from memory, which
|
|
727
|
+
caught three errors that would otherwise have shipped: the header is
|
|
728
|
+
`PAYMENT-SIGNATURE` not `X-PAYMENT`, the field is `amount` not
|
|
729
|
+
`maxAmountRequired`, and `network` is CAIP-2 rather than a name. But there is no
|
|
730
|
+
wallet and no facilitator wired to this Worker, so a request arriving with a
|
|
731
|
+
`PAYMENT-SIGNATURE` is **refused**, using the protocol's own failure channel. A
|
|
732
|
+
paywall that opens for any string is worse than no paywall, because it looks
|
|
733
|
+
like protection.
|
|
734
|
+
|
|
735
|
+
Switching payment on without a configured recipient fails **closed** with 503
|
|
736
|
+
and publishes no placeholder address. An agent that paid a made-up recipient
|
|
737
|
+
would lose real money.
|
|
738
|
+
|
|
739
|
+
---
|
|
740
|
+
|
|
741
|
+
## Bazantic Integration & Prizes
|
|
742
|
+
|
|
743
|
+
Bazantic simplifies AI development by allowing developers to turn APIs into services agents can understand, use, and pay for via x402 micropayments.
|
|
744
|
+
|
|
745
|
+
Clembot Doorman uses Bazantic to:
|
|
746
|
+
1. **Agentify the Scorecard API** into a paid MCP service at `clembot-doorman.bazgateway.com`.
|
|
747
|
+
2. **Draft & Enforce Bazantic Recipes (`recipe.md`)** that turn raw, hallucination-prone MCP tools into safe, bounded, high-reliability agent tasks.
|
|
748
|
+
|
|
749
|
+
### ETHOnline 2026 Bazantic Prize Tracks
|
|
750
|
+
|
|
751
|
+
| Prize Track | Amount | How Clembot Doorman Qualifies |
|
|
752
|
+
|---|---|---|
|
|
753
|
+
| 🤖 **Help an Agent Use Your Hackathon Project** | $1,000 (Continuity) | **Autonomous agent usage without human guidance:** An agent can inspect its own Clembot build (`doorman doctor`), detect missing tools from its prompt history (`doorman needs`), query the live Bazantic Scorecard gateway, and install the local offline `PreToolUse` security hook. |
|
|
754
|
+
| 🍳 **Best Recipe Using EthGlobal Sponsor APIs** | $1,000 | **Raw MCP servers fail; Recipes succeed:** Our A/B evals demonstrate that raw MCP servers (like WebZum) inject thousands of characters of steering ads and cause token loops. Our drafted Bazantic Recipes (`recipe.md`) constrain schemas, enforce deterministic parameters, and prevent context window pollution. |
|
|
755
|
+
| 👨🍳 **Agentify a New API** | $1,000 | **Live x402 Micropayments Gateway:** Deployed OpenAPI 3.1 scorecard endpoints through the Bazantic gateway at `clembot-doorman.bazgateway.com` with per-grade pricing ($0.01/grade, free cached reads), key masking, and machine-readable tool generation. |
|
|
756
|
+
|
|
757
|
+
### Live Gateway Status
|
|
758
|
+
|
|
759
|
+
The gateway **Doorman** is active at `clembot-doorman.bazgateway.com`:
|
|
760
|
+
- **Endpoint**: `https://scorecard.wanessalabs.com`
|
|
761
|
+
- **Spec**: `https://scorecard.wanessalabs.com/openapi.json` (OpenAPI 3.1.0)
|
|
762
|
+
- **Auth**: `api-key` (forwards `GRADE_TOKEN` upstream, calling agents never see raw credentials)
|
|
763
|
+
- **Pricing**: $0.01 per behavioural grade, $0.00 for cached reads and feed streaming
|
|
764
|
+
|
|
765
|
+
### What a Recipe actually is
|
|
766
|
+
|
|
767
|
+
Not a flow, not a pipeline, not a DAG. **One task: typed inputs, a prompt, a
|
|
768
|
+
model, and a bound set of MCP tools**, published as a single MCP tool that any
|
|
769
|
+
agent can call. Sequencing happens inside one prompt, so a Recipe is a model
|
|
770
|
+
given tools rather than a declared sequence of steps.
|
|
771
|
+
|
|
772
|
+
Ingredients have to already exist on Bazantic as gateways. A Recipe cannot
|
|
773
|
+
invent a tool. So the gateway comes first and the Recipe second, always.
|
|
774
|
+
|
|
775
|
+
### Install and use it
|
|
776
|
+
|
|
777
|
+
```bash
|
|
778
|
+
# 1. the CLI, once
|
|
779
|
+
npm i -g @bazantic/cli
|
|
780
|
+
baz login
|
|
781
|
+
|
|
782
|
+
# 2. register the scorecard. Its OpenAPI 3.1 spec is already served,
|
|
783
|
+
# so there is nothing to write for this step.
|
|
784
|
+
API=https://scorecard.wanessalabs.com
|
|
785
|
+
baz gateway add --endpoint $API --spec-url $API/openapi.json \
|
|
786
|
+
--auth-type api-key \
|
|
787
|
+
--name "MCP Scorecard" --status draft --json
|
|
788
|
+
|
|
789
|
+
# 3. a capped, revocable grant for the doorman to spend from.
|
|
790
|
+
baz grant create --name doorman --cap 5 --service <slug>
|
|
791
|
+
baz curl https://bazgateway.com/<slug>/grade \
|
|
792
|
+
--account doorman --max-amount 0.05 --source hosted --json
|
|
793
|
+
|
|
794
|
+
# 4. the Recipe is DASHBOARD-ONLY on the released CLI. See below.
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
### Verified against the installed CLI, 2026-09-08
|
|
798
|
+
|
|
799
|
+
`@bazantic/cli@0.8.0` was installed and its command surface read directly. Two
|
|
800
|
+
families the docs describe **do not exist in the released build**:
|
|
801
|
+
|
|
802
|
+
| Documented | `baz` 0.8.0 |
|
|
803
|
+
|---|---|
|
|
804
|
+
| `baz recipe list/get/create/update/publish/unpublish/delete` | **absent** (`unknown command: recipe`) |
|
|
805
|
+
| `baz gateway domains add/status/verify/rm` | **absent** (`unknown gateway command: domains`) |
|
|
806
|
+
| `--auth-type none`, documented as the default | **not offered.** The CLI takes `api-key \| jwt \| x402-mpp \| basic` and defaults to `x402-mpp`, which the docs describe as retired and credential-free |
|
|
807
|
+
| `bazantic.yaml` manifest | absent, and the docs do say it is preview |
|
|
808
|
+
|
|
809
|
+
What the released CLI does have: `login`, `whoami`, `gateway add`, `gateway
|
|
810
|
+
list`, `curl`, `wallet`, `grant`. That covers registration and the whole payment
|
|
811
|
+
path. **Recipes and custom domains have to go through the dashboard.**
|
|
812
|
+
|
|
813
|
+
We use `--auth-type api-key`, which exists in both, so the gateway forwards
|
|
814
|
+
`GRADE_TOKEN` upstream and the calling agent never sees it.
|
|
815
|
+
|
|
816
|
+
### Two things that cost nothing, worth doing before paying
|
|
817
|
+
|
|
818
|
+
Straight from the CLI docs, and they are the reason a gateway can be mapped for
|
|
819
|
+
free:
|
|
820
|
+
|
|
821
|
+
- **List the tools.** `POST {endpointUrl}/mcp` with a JSON-RPC `tools/list`
|
|
822
|
+
returns every operation and its parameters.
|
|
823
|
+
- **Probe for a price.** A wrong path returns 404; a correct one returns 402
|
|
824
|
+
with the exact price in the body. Neither costs anything, so every route can
|
|
825
|
+
be mapped with `curl` and paid for only once confirmed.
|
|
826
|
+
|
|
827
|
+
Prices come back in base units of a 6-decimal token: `10000` means `$0.01`.
|
|
828
|
+
|
|
829
|
+
### The served spec is narrower than the routes
|
|
830
|
+
|
|
831
|
+
`/openapi.json` describes eight operations. The Worker answers ten. `GET
|
|
832
|
+
/api/pending` and `POST /api/result` are the probe-runner control plane, they
|
|
833
|
+
stay routed, and a self-hoster running their own runner needs them, but they are
|
|
834
|
+
not described on the public document.
|
|
835
|
+
|
|
836
|
+
The reason is specific to how a gateway ingests a spec. Bazantic derives one MCP
|
|
837
|
+
tool per operation, and it derives them from the whole document, not from the
|
|
838
|
+
methods you priced. Excluding the two runner methods from pricing removed them
|
|
839
|
+
from routing, so they 404 through the gateway, while `tools/list` went on
|
|
840
|
+
offering them as callable tools. Two surfaces, one allow-list.
|
|
841
|
+
|
|
842
|
+
An advertised tool that cannot be called is a false description on the exact
|
|
843
|
+
surface this project exists to grade. So the filter lives in
|
|
844
|
+
`src/routes/openapi.ts` as `stripPrivate()`: it removes every operation tagged
|
|
845
|
+
`runner`, then the paths those emptied, the tag itself, and the `runnerToken`
|
|
846
|
+
security scheme that nothing left referenced. The full document is still built
|
|
847
|
+
and still tested, because deleting the operations outright would leave the
|
|
848
|
+
Worker answering routes nothing described.
|
|
849
|
+
|
|
850
|
+
### The spec URL is fetched by THEIR servers, not yours
|
|
851
|
+
|
|
852
|
+
`--spec-url` is fetched server-side. A spec behind localhost, a VPN, or auth
|
|
853
|
+
fails with `spec rejected: could not fetch --spec-url` even though it loads in
|
|
854
|
+
your browser.
|
|
855
|
+
|
|
856
|
+
**Measured on this host, 2026-09-08:** `scorecard.wanessalabs.com/openapi.json`
|
|
857
|
+
returns **403 to `Python-urllib/3.12`** and 200 to `curl`, `Go-http-client`,
|
|
858
|
+
`node-fetch` and a request with no user-agent at all. That is our own Cloudflare
|
|
859
|
+
WAF. If Bazantic's fetcher presents a blocked agent, registration fails for a
|
|
860
|
+
reason that looks like a Bazantic problem and is ours. The fix is to paste the
|
|
861
|
+
document into the dashboard field instead of pointing at the URL.
|
|
862
|
+
|
|
863
|
+
### `input_schema` dialect: resolved
|
|
864
|
+
|
|
865
|
+
**JSON Schema Draft 2020-12, with local references.** This was marked `[VERIFY]`
|
|
866
|
+
until the CLI docs stated it.
|
|
867
|
+
|
|
868
|
+
Read the gateway URL out of `baz gateway list --json` as `endpointUrl` rather
|
|
869
|
+
than assembling it by hand. More than one URL form is served and which one
|
|
870
|
+
applies depends on the deployment environment.
|
|
871
|
+
|
|
872
|
+
### `--source hosted` is not optional
|
|
873
|
+
|
|
874
|
+
Bazantic documents this and it is worth repeating, because it is the exact
|
|
875
|
+
failure mode this project exists to refuse:
|
|
876
|
+
|
|
877
|
+
> If the CLI cannot find a grant's key on this device it warns on stderr and
|
|
878
|
+
> falls back to your self-custody wallet for that call, which changes the call
|
|
879
|
+
> from capped and revocable to uncapped and irrevocable.
|
|
880
|
+
|
|
881
|
+
That is a fail-open on the spend path. It is disclosed, and there is a flag for
|
|
882
|
+
it, so the doorman always passes `--source hosted` and takes the hard failure.
|
|
883
|
+
The same reasoning as `budget.mjs` refusing an unreadable price: a cap that
|
|
884
|
+
silently stops applying is worse than no cap, because you stop watching.
|
|
885
|
+
|
|
886
|
+
`--max-amount` defaults to `0.01` and is checked before anything is signed.
|
|
887
|
+
|
|
888
|
+
### The Recipe file
|
|
889
|
+
|
|
890
|
+
The Recipe definition has **exactly** these fields. The docs describe a
|
|
891
|
+
`baz recipe create <file>` command that takes them as JSON; that command is
|
|
892
|
+
absent from CLI 0.8.0, so today this is what the dashboard editor is filling in.
|
|
893
|
+
Unknown fields error before any network request.
|
|
894
|
+
|
|
895
|
+
| Field | Notes |
|
|
896
|
+
|---|---|
|
|
897
|
+
| `name` | The handle is derived from it and is immutable. |
|
|
898
|
+
| `description` | |
|
|
899
|
+
| `input_schema` | JSON Schema **Draft 2020-12**, local references only |
|
|
900
|
+
| `input_example` | |
|
|
901
|
+
| `output_example` | `Use as output example` on a real test run fills this. |
|
|
902
|
+
| `prompt_template` | Exactly one `{{inputs}}` placeholder. 4000 chars max. |
|
|
903
|
+
| `model` | Allowed values come from `baz recipe --help`, which **does not exist in CLI 0.8.0**. Read them off the dashboard editor instead. `[VERIFY]` |
|
|
904
|
+
| `tool_bindings` | Each entry carries only `gateway_slug` and `tool_name`. 1 to 64. |
|
|
905
|
+
|
|
906
|
+
Whole definition caps at 24 KiB of compact UTF-8 JSON. An update file takes a
|
|
907
|
+
nonempty subset of the same fields. `create` produces a draft; `update` only
|
|
908
|
+
works on a draft; `delete` only works on a never-published draft.
|
|
909
|
+
|
|
910
|
+
### The async problem, stated rather than hidden
|
|
911
|
+
|
|
912
|
+
A Recipe run is one pass. A cold audit takes minutes, and `POST /grade` returns
|
|
913
|
+
202 with an audit id rather than a grade. So the Recipe must return one of:
|
|
914
|
+
|
|
915
|
+
- a cached grade, when a recent audit exists, which is instant, or
|
|
916
|
+
- an audit id and a transcripts url, saying plainly that grading is running.
|
|
917
|
+
|
|
918
|
+
It must not stall waiting, and it must not synthesise a provisional score. The
|
|
919
|
+
`grade` tool description already commits to this and the Recipe prompt inherits
|
|
920
|
+
it. Whether a Recipe run can poll across several tool calls inside its own
|
|
921
|
+
timeout is not documented. `[VERIFY]`
|
|
922
|
+
|
|
923
|
+
### A Recipe is itself an ungraded MCP surface
|
|
924
|
+
|
|
925
|
+
Worth stating because it is the most interesting thing here. A published Recipe
|
|
926
|
+
is one tool whose behaviour is a prompt the caller never reads and a tool set
|
|
927
|
+
the caller never sees. That is the same shape as the finding this project leads
|
|
928
|
+
with, one layer up.
|
|
929
|
+
|
|
930
|
+
Bazantic is also the first surface where it is fixable. `bazantic_recipe_get`
|
|
931
|
+
returns the definition, and `bazantic_gateway_list_tools` returns tool names,
|
|
932
|
+
descriptions, input schemas and annotations for a gateway. Our static layer is a
|
|
933
|
+
pure function over exactly those strings and never calls anything, so it can run
|
|
934
|
+
across a whole inventory before a single paid call.
|
|
935
|
+
|
|
936
|
+
### Not usable: `bazantic.yaml`
|
|
937
|
+
|
|
938
|
+
The gateway manifest page is marked preview and says the released CLI cannot
|
|
939
|
+
create a gateway from the file, calculate a plan, or apply one. Do not write one.
|
|
940
|
+
`baz gateway add` is the released path.
|
|
941
|
+
|
|
942
|
+
### Data note
|
|
943
|
+
|
|
944
|
+
Bazantic keeps the prompts, drafts and test inputs entered in the editor and
|
|
945
|
+
uses them to improve the product. That is a reason to keep the grading rubric
|
|
946
|
+
inside this service and let the Recipe prompt stay thin, which is better design
|
|
947
|
+
regardless.
|
|
948
|
+
|
|
949
|
+
---
|
|
950
|
+
|
|
951
|
+
See `roadmap.md` for what is done, what is stubbed, and what is untested.
|