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.
Files changed (54) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/LICENSE +21 -0
  3. package/README.md +951 -0
  4. package/WALKTHROUGH.md +224 -0
  5. package/doorman/.claude/hooks/mcp-gate.sh +205 -0
  6. package/doorman/.claude/settings.json +16 -0
  7. package/doorman/.claude-plugin/plugin.json +22 -0
  8. package/doorman/.mcp.json +24 -0
  9. package/doorman/README.md +259 -0
  10. package/doorman/agents/doorman.md +104 -0
  11. package/doorman/cli/agents.mjs +128 -0
  12. package/doorman/cli/allow.mjs +128 -0
  13. package/doorman/cli/cost.mjs +119 -0
  14. package/doorman/cli/discover.mjs +265 -0
  15. package/doorman/cli/doctor.mjs +282 -0
  16. package/doorman/cli/doorman.mjs +345 -0
  17. package/doorman/cli/eval.mjs +320 -0
  18. package/doorman/cli/harness.mjs +179 -0
  19. package/doorman/cli/install.mjs +175 -0
  20. package/doorman/cli/needs.mjs +116 -0
  21. package/doorman/cli/report.mjs +89 -0
  22. package/doorman/cli/sandbox.mjs +177 -0
  23. package/doorman/cli/task.mjs +239 -0
  24. package/doorman/cli/verdict.mjs +199 -0
  25. package/doorman/cli/watch.mjs +218 -0
  26. package/doorman/commands/doorman.md +116 -0
  27. package/doorman/commands/vet.md +69 -0
  28. package/doorman/hooks/hooks.json +30 -0
  29. package/doorman/install.sh +186 -0
  30. package/doorman/package.json +38 -0
  31. package/doorman/recipes/README.md +36 -0
  32. package/doorman/recipes/deepwiki.md +10 -0
  33. package/doorman/recipes/planted-bad.md +27 -0
  34. package/doorman/recipes/scorecard.md +10 -0
  35. package/doorman/registry/allowlist.json +37 -0
  36. package/doorman/registry/denylist.json +23 -0
  37. package/doorman/registry/ledger.jsonl +1 -0
  38. package/doorman/scripts/poller.mjs +292 -0
  39. package/doorman/scripts/resolve-cli.sh +58 -0
  40. package/doorman/scripts/vet.mjs +190 -0
  41. package/doorman/skills/doorman-guide/SKILL.md +69 -0
  42. package/doorman/src/budget.mjs +236 -0
  43. package/doorman/src/candidate.mjs +132 -0
  44. package/doorman/src/fit-review.mjs +255 -0
  45. package/doorman/src/injection.mjs +189 -0
  46. package/doorman/src/instructions.mjs +134 -0
  47. package/doorman/src/inventory.mjs +411 -0
  48. package/doorman/src/llm.mjs +87 -0
  49. package/doorman/src/needs.mjs +491 -0
  50. package/doorman/src/note.mjs +213 -0
  51. package/doorman/src/reviews.mjs +120 -0
  52. package/doorman/src/scorecard.mjs +123 -0
  53. package/doorman/src/vet.mjs +174 -0
  54. package/package.json +54 -0
package/WALKTHROUGH.md ADDED
@@ -0,0 +1,224 @@
1
+ # The first ten minutes
2
+
3
+ Three real builds, three different answers. Every block of output below was
4
+ captured from an actual run on 2026-09-11 against the live feed, not written by
5
+ hand. Where a run produced nothing useful, that is shown too, because "nothing
6
+ matched" is a result and hiding it would make this document a brochure.
7
+
8
+ ---
9
+
10
+ ## Install both halves
11
+
12
+ ```bash
13
+ claude plugin marketplace add clemenswan/clembot-doorman
14
+ claude plugin install clembot-doorman # the gate, /doorman, /vet, the subagent
15
+
16
+ git clone https://github.com/clemenswan/clembot-doorman
17
+ npm i -g ./clembot-doorman # doctor, needs, report, watch, eval
18
+ ```
19
+
20
+ Check what actually loaded. A manifest can validate and still ship components
21
+ that never register, which is exactly what this project shipped once:
22
+
23
+ ```bash
24
+ claude plugin details clembot-doorman
25
+ ```
26
+
27
+ ```
28
+ Skills (3) doorman, doorman-guide, vet
29
+ Agents (1) doorman
30
+ Hooks (1) PreToolUse
31
+ MCP servers (1) scorecard
32
+ ```
33
+
34
+ If `Agents` or `Skills` reads `(0)`, the plugin is broken. Say so rather than
35
+ working around it.
36
+
37
+ ---
38
+
39
+ ## Expect to be blocked first
40
+
41
+ The gate ships trusting two servers: `deepwiki` and `scorecard`, both graded A.
42
+ Everything else is UNKNOWN and fails closed, including connectors you already
43
+ use every day. This is not a misconfiguration:
44
+
45
+ ```
46
+ doorman: 'claude_ai_Notion' is UNKNOWN. Blocking until it has been graded.
47
+
48
+ An ungraded MCP server is not a trusted one. Nothing about
49
+ 'mcp__claude_ai_Notion__notion-search' has been verified: not its tool
50
+ descriptions, not its error handling, not whether its descriptions contain
51
+ instructions aimed at you.
52
+
53
+ Measure it (free, no key): doorman report <server-url>
54
+ Trust it without measuring: doorman allow claude_ai_Notion
55
+ Or ask: /doorman
56
+ ```
57
+
58
+ Two honest ways forward, and they are different claims:
59
+
60
+ ```bash
61
+ doorman allow claude_ai_Notion # a DECISION. grade stays null.
62
+ doorman report https://... # a MEASUREMENT. free, no key.
63
+ ```
64
+
65
+ `allow` writes `basis: operator` with a null grade, because nothing graded it.
66
+ An allowed server is **permitted**, not vetted. Do not let anything, including
67
+ an agent summarising this file, describe it as safe.
68
+
69
+ A connector often has no URL you can point `report` at. That is why `allow`
70
+ takes a name: `mcp__<server>__<tool>` is all the gate can see.
71
+
72
+ ---
73
+
74
+ ## Build A: a long-running build
75
+
76
+ 545 prompts across 48 transcripts.
77
+
78
+ ```bash
79
+ doorman needs .
80
+ ```
81
+
82
+ ```
83
+ doorman needs — 545 prompts read from this build's own history
84
+ feed: 26 graded rows
85
+
86
+ UNMET Deploying, and reading back what deployed
87
+ 19 prompts across 11 sessions · matched "cloudflare", "deploy it", "wrangler"
88
+ > merge and deploy it
89
+ worth-measuring docs-ai-search [A (88.57)] matched "cloudflare"
90
+ https://docs.mcp.cloudflare.com/mcp
91
+ worth-measuring mcp-typescript server on vercel [A (85.14)] matched "vercel"
92
+
93
+ GAP Design files and rendered output
94
+ 15 prompts across 8 sessions · matched "brand.md", "design tokens"
95
+ nothing graded covers this. The feed has the gap, not your build.
96
+
97
+ GAP Driving a real browser
98
+ 13 prompts across 8 sessions · matched "playwright", "in a real browser"
99
+ nothing graded covers this. The feed has the gap, not your build.
100
+ ```
101
+
102
+ **What next, in order:**
103
+
104
+ 1. **Measure the one candidate with the strongest signal.** Nineteen prompts
105
+ about deploying, and an A-graded server whose own text claims that
106
+ capability. `doorman report https://docs.mcp.cloudflare.com/mcp` is free and
107
+ takes seconds.
108
+ 2. **Read `worth-measuring` as exactly that.** Nothing drove that server. The
109
+ match means its published text claims what you keep asking for. Whether it
110
+ makes *your* agent better is `doorman eval`, which needs your key and runs
111
+ on your machine.
112
+ 3. **Leave the GAPs alone.** Two of the three biggest needs have nothing graded
113
+ against them. That is a hole in the catalogue, not a problem with your build,
114
+ and the useful response is `doorman discover` to find candidates, not to
115
+ install something unmeasured because the row looked empty.
116
+
117
+ ---
118
+
119
+ ## Build B: a brand new build
120
+
121
+ No history at all.
122
+
123
+ ```bash
124
+ doorman needs .
125
+ ```
126
+
127
+ ```
128
+ doorman needs — 0 prompts read from this build's own history
129
+ No readable prompt history for this path. Claude Code keeps it under
130
+ ~/.claude/projects/<path-with-dashes>; other harnesses keep none that
131
+ doorman can read. Pass --history DIR if yours lives elsewhere.
132
+
133
+ Nothing in the taxonomy matched. That is a real answer: either this
134
+ build has not asked for any of the twelve capabilities doorman knows
135
+ how to look for, or the history it could read is too short to tell.
136
+ ```
137
+
138
+ This is the correct output, not a failure. A new build has told doorman nothing
139
+ about itself, so doorman says nothing about it. Compare that to a recommender
140
+ that would happily suggest ten servers to an empty directory.
141
+
142
+ **What next:**
143
+
144
+ 1. **Point it at a history you already have.** The capabilities you reach for
145
+ travel with you, not with the directory:
146
+
147
+ ```bash
148
+ doorman needs . --history ~/.claude/projects/<a-project-you-have-used>
149
+ ```
150
+
151
+ 2. **Or just start working**, and run it again in a week. The signal is your own
152
+ sentences; there have to be some.
153
+ 3. **Meanwhile `doorman doctor .` still works**, because it reads configuration
154
+ rather than history. It will tell you the gate is running from the plugin and
155
+ which trust list is in force.
156
+
157
+ ---
158
+
159
+ ## Build C: a second, different build
160
+
161
+ 151 prompts across 22 transcripts, same machine, different work.
162
+
163
+ ```
164
+ GAP Driving a real browser
165
+ 14 prompts across 4 sessions · matched "playwright", "in a real browser"
166
+ nothing graded covers this. The feed has the gap, not your build.
167
+
168
+ GAP Design files and rendered output
169
+ 9 prompts across 6 sessions · matched "design tokens", "brand.md"
170
+ nothing graded covers this. The feed has the gap, not your build.
171
+
172
+ UNMET Deploying, and reading back what deployed
173
+ 8 prompts across 6 sessions · matched "cloudflare"
174
+ ```
175
+
176
+ **The interesting part is what this shares with Build A.** Different project,
177
+ different code, and the same two GAPs at the top: browser automation and design
178
+ assets. One build reporting a gap is a fact about that build. Two unrelated
179
+ builds reporting the same gap is a fact about the **catalogue**, and it is the
180
+ most actionable thing on this page:
181
+
182
+ ```bash
183
+ doorman discover --pages 2
184
+ ```
185
+
186
+ That sweeps a public registry for candidates and writes a file. It never
187
+ enqueues anything and never spends. Curate it by hand, then grade what survives.
188
+
189
+ **What next:**
190
+
191
+ 1. `doorman needs . --candidates candidates/smithery.json` to match the sweep
192
+ against these gaps.
193
+ 2. Anything that looks plausible gets `doorman report <url>`: free, no key, and
194
+ it is the stage that can cap a server at F on its tool descriptions alone.
195
+ 3. Only then is anything worth paying to grade, and only then is `doorman eval`
196
+ worth your key.
197
+
198
+ ---
199
+
200
+ ## The shape of the whole thing
201
+
202
+ ```
203
+ doctor what this build HAS free, local
204
+ needs what it keeps ASKING for free, local
205
+ watch what has been graded lately free, one anonymous GET
206
+ report what a candidate implements free, no key
207
+ grade is it safe and competent $0.01, runs on our machine
208
+ eval does it make YOUR agent better your key, your machine
209
+ ```
210
+
211
+ Every stage can end the run, and three end it free. The cheapest way to evaluate
212
+ a tool is to establish that you do not need it.
213
+
214
+ Five of the six run on your machine and cost nothing, because they are about
215
+ your build and that information should never leave it. One does not, and that is
216
+ the one with a price on it.
217
+
218
+ ## Two things this will never do
219
+
220
+ - **Invent a grade.** An unmeasured layer is `null`, never `0` and never a
221
+ plausible letter. If no key was supplied, the run stops and says so.
222
+ - **Call a match a fit.** `needs` and `watch` both do mechanical matching.
223
+ Neither drove anything, so neither may claim a server will work. The verdict
224
+ word is `worth-measuring`, and the stage that earns a stronger word is `eval`.
@@ -0,0 +1,205 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # mcp-gate.sh - PreToolUse gate for every MCP tool call.
4
+ #
5
+ # Wired to matcher "mcp__.*". Claude Code passes the tool call as JSON on
6
+ # stdin. This script decides whether the call proceeds.
7
+ #
8
+ # exit 0 allow
9
+ # exit 2 BLOCK, and stderr is shown to the model
10
+ #
11
+ # Exit 2 is the only code that blocks. Exit 1 is treated as a non-blocking
12
+ # script error and the tool call PROCEEDS, which for a security gate means
13
+ # failing open. Every refusal path in this file must exit 2.
14
+ #
15
+ # FOUR RULES, none of them negotiable:
16
+ #
17
+ # 1. NO NETWORK. Not a curl, not a DNS lookup, nothing. A gate that asks a
18
+ # server for permission is offline the moment the network is, and
19
+ # "offline" would mean "allow". Decisions come from local files only.
20
+ #
21
+ # 2. NO DEPENDENCIES. No jq, no node, no python. Only bash builtins and
22
+ # coreutils, because a gate that fails to start is a gate that fails open
23
+ # on the machine where the dependency is missing.
24
+ #
25
+ # 3. FAIL CLOSED. Unparseable input, missing registry, unreadable file,
26
+ # unknown server: all block. The default answer is no.
27
+ #
28
+ # 4. DETERMINISTIC. Same input, same registry, same answer, every time. No
29
+ # clocks, no randomness, no model in the loop.
30
+ #
31
+ # The registry is synced from the scorecard by a human running `/vet` or the
32
+ # poller. This script never writes to it and never fetches it.
33
+
34
+ set -uo pipefail
35
+
36
+ # Resolve the registry relative to THIS SCRIPT, never to a git root.
37
+ # `git rev-parse --show-toplevel` returns whatever repo the cwd happens to be
38
+ # in, which in a worktree or a submodule is the wrong repo entirely.
39
+ HOOK_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
40
+ REPO_ROOT="$(cd -- "$HOOK_DIR/../.." && pwd)"
41
+
42
+ # THE USER'S TRUST LIST OUTRANKS THE ONE WE SHIPPED. Three sources, in order:
43
+ #
44
+ # 1. $DOORMAN_REGISTRY_DIR explicit, and what every test passes
45
+ # 2. the PROJECT's registry/ the user's list, which we never write
46
+ # 3. beside this script the default we shipped
47
+ #
48
+ # Order 2 before 3 is what makes invariant 24 structurally true rather than
49
+ # merely observed. As a plugin, this script lives inside the plugin directory
50
+ # and a plugin UPDATE replaces that directory wholesale. If the gate read its
51
+ # allowlist from beside itself, an update would silently replace the user's
52
+ # trust list with our three entries, which is the single most destructive
53
+ # thing this project could do. It cannot now: an update rewrites path 3 and
54
+ # path 2 is not in the plugin at all.
55
+ #
56
+ # A USER-LEVEL list sits between the two, and it exists because the plugin does.
57
+ # A plugin installs at user scope and gates every project on the machine, while
58
+ # a project registry gates one. Without a user level, trusting a server you use
59
+ # everywhere means copying the same file into every repo you own, and the ones
60
+ # you forget fail closed on servers you already trusted. That is a rule nobody
61
+ # can follow, and an unfollowable rule gets switched off.
62
+ REGISTRY_DIR="${DOORMAN_REGISTRY_DIR:-}"
63
+ if [ -z "$REGISTRY_DIR" ]; then
64
+ USER_REGISTRY="${DOORMAN_HOME:-${HOME:-$USERPROFILE}}/.doorman/registry"
65
+ if [ -n "${CLAUDE_PROJECT_DIR:-}" ] && [ -f "$CLAUDE_PROJECT_DIR/registry/allowlist.json" ]; then
66
+ REGISTRY_DIR="$CLAUDE_PROJECT_DIR/registry"
67
+ elif [ -f "$USER_REGISTRY/allowlist.json" ]; then
68
+ REGISTRY_DIR="$USER_REGISTRY"
69
+ else
70
+ REGISTRY_DIR="$REPO_ROOT/registry"
71
+ fi
72
+ fi
73
+ ALLOWLIST="$REGISTRY_DIR/allowlist.json"
74
+ DENYLIST="$REGISTRY_DIR/denylist.json"
75
+
76
+ block() {
77
+ # stderr reaches the model on exit 2. Say what to do next, not just "no".
78
+ printf '%s\n' "$1" >&2
79
+ exit 2
80
+ }
81
+
82
+ allow() {
83
+ exit 0
84
+ }
85
+
86
+ # --- read the tool call ------------------------------------------------------
87
+ # Read stdin without hanging forever if nothing arrives.
88
+ input=""
89
+ if ! IFS= read -r -d '' -t 5 input; then
90
+ # read -d '' returns non-zero at EOF even when it read everything, so an
91
+ # empty result is the only real failure.
92
+ :
93
+ fi
94
+
95
+ if [ -z "$input" ]; then
96
+ block "doorman: no tool-call payload on stdin. Blocking (the gate fails closed)."
97
+ fi
98
+
99
+ # --- extract the tool name ---------------------------------------------------
100
+ # Only ever matches mcp__<server>__<tool>, which is a constrained identifier:
101
+ # ASCII word characters and dashes, no JSON escapes possible. A regex is safe
102
+ # here and costs no dependency.
103
+ tool_name=""
104
+ if [[ "$input" =~ \"tool_name\"[[:space:]]*:[[:space:]]*\"(mcp__[A-Za-z0-9_.-]+)\" ]]; then
105
+ tool_name="${BASH_REMATCH[1]}"
106
+ fi
107
+
108
+ if [ -z "$tool_name" ]; then
109
+ # The matcher should only route mcp__* here. Anything else means the wiring
110
+ # changed or the payload shape did, and we do not guess.
111
+ block "doorman: could not read an mcp__ tool name from the hook payload. Blocking."
112
+ fi
113
+
114
+ # mcp__<server>__<tool> -> <server>
115
+ rest="${tool_name#mcp__}"
116
+ server="${rest%%__*}"
117
+
118
+ if [ -z "$server" ] || [ "$server" = "$rest" ] && [[ "$rest" != *__* ]]; then
119
+ # No "__" separator: cannot identify a server, so cannot vouch for one.
120
+ server="$rest"
121
+ fi
122
+
123
+ if [ -z "$server" ]; then
124
+ block "doorman: could not identify an MCP server in '$tool_name'. Blocking."
125
+ fi
126
+
127
+ # --- registry must exist -----------------------------------------------------
128
+ if [ ! -r "$ALLOWLIST" ]; then
129
+ block "doorman: no readable allowlist at $ALLOWLIST.
130
+ Blocking '$server' because an absent registry is not the same as an empty one.
131
+ Run: /vet <server-url> (or restore registry/allowlist.json)"
132
+ fi
133
+
134
+ # --- lookup ------------------------------------------------------------------
135
+ # Matches a top-level key in the "servers" object: "<server>": { ... "decision": "allow" ...
136
+ # Deliberately literal string matching. No JSON parser, no eval, no expansion
137
+ # of anything read from the file.
138
+ lookup_decision() {
139
+ local file="$1" key="$2"
140
+ [ -r "$file" ] || return 1
141
+ # Flatten to one line, then find the object that follows "<key>":
142
+ tr -d '\n\r\t' < "$file" \
143
+ | grep -o "\"${key}\"[[:space:]]*:[[:space:]]*{[^}]*}" \
144
+ | grep -o '"decision"[[:space:]]*:[[:space:]]*"[a-z]*"' \
145
+ | grep -o '"[a-z]*"$' \
146
+ | tr -d '"' \
147
+ | head -n 1
148
+ }
149
+
150
+ # Deny wins over allow, always. A server present in both lists is a mistake,
151
+ # and the safe reading of a mistake is "deny".
152
+ #
153
+ # DENIALS ACCUMULATE ACROSS REGISTRIES; ALLOWS DO NOT. Choosing a narrower
154
+ # registry is a deliberate scoping decision about what you trust, so a user or
155
+ # project allowlist replacing the shipped one is correct. Doing the same to a
156
+ # denylist is not: it silently discards a refusal that was earned by an audit.
157
+ # Writing a user allowlist did exactly that here, and a server that had been
158
+ # explicitly denied came back as merely UNKNOWN. It still blocked, because the
159
+ # gate fails closed, but "unknown" is one allow away from running and "denied"
160
+ # is not, so the distinction is the whole safety margin.
161
+ for dl in "$DENYLIST" "$REPO_ROOT/registry/denylist.json"; do
162
+ [ -r "$dl" ] || continue
163
+ deny_decision="$(lookup_decision "$dl" "$server" 2>/dev/null || true)"
164
+ if [ "$deny_decision" = "deny" ] || [ "$deny_decision" = "allow" ]; then
165
+ block "doorman: '$server' is on a DENYLIST. Blocking.
166
+ Reason and evidence: $dl
167
+ This server was graded and failed. Do not work around this by calling it another way."
168
+ fi
169
+ done
170
+
171
+ allow_decision="$(lookup_decision "$ALLOWLIST" "$server" 2>/dev/null || true)"
172
+
173
+ case "$allow_decision" in
174
+ allow)
175
+ allow
176
+ ;;
177
+ deny)
178
+ block "doorman: '$server' is marked deny in the allowlist. Blocking."
179
+ ;;
180
+ "")
181
+ block "doorman: '$server' is UNKNOWN. Blocking until it has been graded.
182
+
183
+ An ungraded MCP server is not a trusted one. Nothing about '$tool_name' has
184
+ been verified: not its tool descriptions, not its error handling, not whether
185
+ its descriptions contain instructions aimed at you.
186
+
187
+ Measure it (free, no key): doorman report <server-url>
188
+ Trust it without measuring: doorman allow $server
189
+ Or ask: /doorman
190
+
191
+ The second one records a DECISION, not a measurement: the entry is written
192
+ with a null grade because nothing graded it. That is a legitimate choice for
193
+ a server you already run, and it is not the same as this server being safe.
194
+
195
+ A url is not always available: a connector only ever tells this gate the name
196
+ '$server', which is why \`doorman allow\` takes the name."
197
+ ;;
198
+ *)
199
+ block "doorman: '$server' has an unrecognised decision '$allow_decision'. Blocking."
200
+ ;;
201
+ esac
202
+
203
+ # Unreachable. Present so that a future edit that falls through this far still
204
+ # fails closed rather than returning bash's last exit status.
205
+ block "doorman: fell through the decision table. Blocking."
@@ -0,0 +1,16 @@
1
+ {
2
+ "hooks": {
3
+ "PreToolUse": [
4
+ {
5
+ "matcher": "mcp__.*",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/mcp-gate.sh",
10
+ "timeout": 5
11
+ }
12
+ ]
13
+ }
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
+ "name": "clembot-doorman",
4
+ "version": "0.1.0",
5
+ "description": "A gate on what your agent is allowed to use. Blocks MCP servers that are not on your trust list, reads your prompt history to find what the build actually needs, and grades candidates before you install them.",
6
+ "author": {
7
+ "name": "Clemens Wan",
8
+ "url": "https://github.com/clemenswan"
9
+ },
10
+ "homepage": "https://clembot-doorman.wanessalabs.com",
11
+ "repository": "https://github.com/clemenswan/clembot-doorman",
12
+ "license": "MIT",
13
+ "keywords": [
14
+ "mcp",
15
+ "security",
16
+ "prompt-injection",
17
+ "tool-adoption",
18
+ "x402"
19
+ ],
20
+ "hooks": "./hooks/hooks.json",
21
+ "mcpServers": "./.mcp.json"
22
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$comment": [
3
+ "The one MCP server the doorman subagent is allowed to call.",
4
+ "",
5
+ "The name `scorecard` is the load-bearing part, not the url. The gate reads",
6
+ "`mcp__<server>__<tool>` and looks `scorecard` up in registry/allowlist.json,",
7
+ "so the name is what the trust decision is keyed on.",
8
+ "",
9
+ "When the Bazantic gateway lands, only the url below changes: the agent's",
10
+ "tool stays `mcp__scorecard__grade`, the registry key stays `scorecard`, and",
11
+ "nothing else in this repo moves. That is deliberate. The doorman should not",
12
+ "be blocked on a third-party account existing.",
13
+ "",
14
+ "Yes, the scorecard is itself in the allowlist, and yes it had to be graded",
15
+ "to get there: audit d4bc490c, A 98.63. A grader that exempted itself from",
16
+ "its own gate would be the first thing worth distrusting."
17
+ ],
18
+ "mcpServers": {
19
+ "scorecard": {
20
+ "type": "http",
21
+ "url": "https://scorecard.wanessalabs.com/mcp"
22
+ }
23
+ }
24
+ }