mcp-scorecard 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 (67) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +187 -0
  4. package/dist/audit/checks/agent-manifest.d.ts +12 -0
  5. package/dist/audit/checks/agent-manifest.js +72 -0
  6. package/dist/audit/checks/agent-manifest.js.map +1 -0
  7. package/dist/audit/checks/annotations.d.ts +10 -0
  8. package/dist/audit/checks/annotations.js +54 -0
  9. package/dist/audit/checks/annotations.js.map +1 -0
  10. package/dist/audit/checks/manifest-discoverability.d.ts +13 -0
  11. package/dist/audit/checks/manifest-discoverability.js +48 -0
  12. package/dist/audit/checks/manifest-discoverability.js.map +1 -0
  13. package/dist/audit/checks/mutation-gating.d.ts +14 -0
  14. package/dist/audit/checks/mutation-gating.js +58 -0
  15. package/dist/audit/checks/mutation-gating.js.map +1 -0
  16. package/dist/audit/checks/privacy-modes.d.ts +17 -0
  17. package/dist/audit/checks/privacy-modes.js +62 -0
  18. package/dist/audit/checks/privacy-modes.js.map +1 -0
  19. package/dist/audit/checks/resources.d.ts +10 -0
  20. package/dist/audit/checks/resources.js +39 -0
  21. package/dist/audit/checks/resources.js.map +1 -0
  22. package/dist/audit/checks/schema-validity.d.ts +9 -0
  23. package/dist/audit/checks/schema-validity.js +59 -0
  24. package/dist/audit/checks/schema-validity.js.map +1 -0
  25. package/dist/audit/checks/smoke-test.d.ts +14 -0
  26. package/dist/audit/checks/smoke-test.js +59 -0
  27. package/dist/audit/checks/smoke-test.js.map +1 -0
  28. package/dist/audit/checks/tool-descriptions.d.ts +13 -0
  29. package/dist/audit/checks/tool-descriptions.js +59 -0
  30. package/dist/audit/checks/tool-descriptions.js.map +1 -0
  31. package/dist/audit/checks/tool-naming.d.ts +16 -0
  32. package/dist/audit/checks/tool-naming.js +81 -0
  33. package/dist/audit/checks/tool-naming.js.map +1 -0
  34. package/dist/audit/probe.d.ts +20 -0
  35. package/dist/audit/probe.js +145 -0
  36. package/dist/audit/probe.js.map +1 -0
  37. package/dist/audit/runner.d.ts +9 -0
  38. package/dist/audit/runner.js +77 -0
  39. package/dist/audit/runner.js.map +1 -0
  40. package/dist/audit/scorer.d.ts +8 -0
  41. package/dist/audit/scorer.js +17 -0
  42. package/dist/audit/scorer.js.map +1 -0
  43. package/dist/cli/commands.d.ts +10 -0
  44. package/dist/cli/commands.js +123 -0
  45. package/dist/cli/commands.js.map +1 -0
  46. package/dist/cli/output.d.ts +13 -0
  47. package/dist/cli/output.js +76 -0
  48. package/dist/cli/output.js.map +1 -0
  49. package/dist/constants.d.ts +28 -0
  50. package/dist/constants.js +54 -0
  51. package/dist/constants.js.map +1 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/index.js +13 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/resolvers/github-resolver.d.ts +10 -0
  56. package/dist/resolvers/github-resolver.js +65 -0
  57. package/dist/resolvers/github-resolver.js.map +1 -0
  58. package/dist/resolvers/local-resolver.d.ts +8 -0
  59. package/dist/resolvers/local-resolver.js +50 -0
  60. package/dist/resolvers/local-resolver.js.map +1 -0
  61. package/dist/resolvers/npm-resolver.d.ts +11 -0
  62. package/dist/resolvers/npm-resolver.js +100 -0
  63. package/dist/resolvers/npm-resolver.js.map +1 -0
  64. package/dist/types.d.ts +84 -0
  65. package/dist/types.js +6 -0
  66. package/dist/types.js.map +1 -0
  67. package/package.json +61 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,33 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-05-23
9
+
10
+ ### Added
11
+
12
+ - Initial release.
13
+ - CLI `mcp-scorecard <subject> [--json] [--min-score N]`.
14
+ - Three resolvers: npm package (via `npm pack`), absolute local path, and GitHub URL.
15
+ - Probe over `StdioClientTransport` with `MCP_PROBE=1` env flag set on the target.
16
+ - Ten quality checks, each scored 0-10:
17
+ 1. Schema validity (ajv-compiled inputSchema per tool)
18
+ 2. Tool naming convention (shared prefix + snake_case)
19
+ 3. Privacy modes documented (`privacy_mode` param or summary/structured/raw in descriptions)
20
+ 4. Mutation gating (write tools must document a gate)
21
+ 5. Agent manifest (`*_agent_manifest` returning `recommended_first_calls` + `standard_tools`)
22
+ 6. Smoke test (presence of `scripts/smoke*.{mjs,js,ts}` or a real `test` script)
23
+ 7. Resources advertised (count via `listResources`)
24
+ 8. Tool descriptions (average length across tools)
25
+ 9. Annotations (`annotations.readOnlyHint` on read tools)
26
+ 10. Manifest discoverability (any of `*_agent_manifest`, `*_data_inventory`, `*_capabilities`, `*_connection_status`)
27
+ - Markdown and JSON output formats.
28
+ - Redaction pass on probe-derived strings: `customer_id`, `email`, `phone`,
29
+ `access_token`, `refresh_token`, `client_secret`, `developer_token`, `api_key`.
30
+ - Four synthetic fixtures (`good`, `medium`, `bad`, `readonly`) under
31
+ `tests/fixtures/`. Test runner asserts each lands in its expected band.
32
+ - Self-test (`scripts/smoke-self.mjs`) that audits the good fixture
33
+ end-to-end and asserts score >= 80 before publish.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 David Batista
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,187 @@
1
+ <h1 align="center">mcp-scorecard</h1>
2
+
3
+ <p align="center">
4
+ <a href="https://github.com/davidmosiah/mcp-scorecard/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/davidmosiah/mcp-scorecard/ci.yml?style=for-the-badge&labelColor=0F172A&color=10B981&logo=github" alt="CI" /></a>
5
+ <a href="https://www.npmjs.com/package/mcp-scorecard"><img src="https://img.shields.io/npm/v/mcp-scorecard?style=for-the-badge&labelColor=0F172A&color=10B981&logo=npm&logoColor=white" alt="npm version" /></a>
6
+ <a href="https://www.npmjs.com/package/mcp-scorecard"><img src="https://img.shields.io/npm/dm/mcp-scorecard?style=for-the-badge&labelColor=0F172A&color=0EA5A3&logo=npm&logoColor=white" alt="npm downloads" /></a>
7
+ <a href="LICENSE"><img src="https://img.shields.io/badge/LICENSE-MIT-22C55E?style=for-the-badge&labelColor=0F172A" alt="License MIT" /></a>
8
+ <a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/BUILT_FOR-MCP-7C3AED?style=for-the-badge&labelColor=0F172A" alt="Built for MCP" /></a>
9
+ </p>
10
+
11
+ <h3 align="center">
12
+ Agent-readiness scorecard for any MCP server.<br>
13
+ Probes a target over stdio, runs 10 checks, outputs a 0-100 score with itemized findings.
14
+ </h3>
15
+
16
+ ---
17
+
18
+ ## Quick start
19
+
20
+ ```bash
21
+ # Audit a published npm package
22
+ npx -y mcp-scorecard whoop-mcp-unofficial
23
+
24
+ # Audit a GitHub repo (auto-resolves to the published npm package, or local dist)
25
+ npx -y mcp-scorecard https://github.com/davidmosiah/whoop-mcp
26
+
27
+ # Audit a local build
28
+ npx -y mcp-scorecard /Users/you/Desktop/my-mcp/dist/index.js
29
+
30
+ # CI gate: fail the build if the score drops
31
+ npx -y mcp-scorecard my-mcp --min-score 80
32
+
33
+ # Structured JSON for piping into your own tooling
34
+ npx -y mcp-scorecard my-mcp --json
35
+ ```
36
+
37
+ ## What it checks
38
+
39
+ Ten quality dimensions, each scored 0-10. Final score is the sum, capped at 100.
40
+
41
+ 1. **Schema validity** - boots the server, lists tools, and validates each
42
+ tool's `inputSchema` with ajv. Missing or non-object schemas score zero
43
+ per tool.
44
+ 2. **Tool naming convention** - rewards a shared prefix and snake_case
45
+ (e.g. `whoop_get_sleep`). Mixed case, hyphens, and missing prefixes lose
46
+ points.
47
+ 3. **Privacy modes documented** - looks for a `privacy_mode` parameter on
48
+ any tool, or descriptions that mention `summary | structured | raw`.
49
+ 4. **Mutation gating** - any tool name matching `(set|update|delete|create|
50
+ pause|resume|enable|disable|cancel|publish|send)` must document a gate
51
+ in its description (`Gated by ALLOW_MUTATIONS`, `requires explicit user
52
+ intent`, `dry-run`, `confirm`).
53
+ 5. **Agent manifest** - calls `<prefix>_agent_manifest` and checks the
54
+ response object has `recommended_first_calls` (non-empty array) AND
55
+ `standard_tools` (non-empty array). The probe NEVER persists the payload
56
+ - only field names and lengths are recorded.
57
+ 6. **Smoke test present** - looks for `scripts/smoke*.{mjs,js,ts}` in the
58
+ package, or a real `test` script in `package.json` (not the npm default
59
+ echo-and-fail).
60
+ 7. **Resources advertised** - counts what `listResources()` returns. Zero
61
+ scores zero; one or two scores 5; three or more scores 10.
62
+ 8. **Tool descriptions** - average description length across all tools.
63
+ Below 30 chars scores 0; 30-60 scores 5; 60+ scores 10.
64
+ 9. **Annotations** - counts what fraction of read tools (any non-mutation
65
+ tool) carry `annotations.readOnlyHint = true`. Score scales linearly.
66
+ 10. **Manifest discoverability** - has any of `*_agent_manifest`,
67
+ `*_data_inventory`, `*_capabilities`, `*_connection_status`. Two or
68
+ more scores 10; exactly one scores 7; none scores 0.
69
+
70
+ ## Output format
71
+
72
+ ### Markdown (default)
73
+
74
+ ```
75
+ # mcp-scorecard - whoop-mcp-unofficial @0.4.3
76
+
77
+ **Agent-readiness score:** 88/100
78
+
79
+ - [PASS] Schema validity (28/28 tools have valid input schema)
80
+ - [PASS] Tool naming convention (consistent `whoop_` prefix, snake_case)
81
+ - [PASS] Privacy modes documented (privacy_mode parameter on 6 tool(s))
82
+ - [PASS] Mutation gating (no write tools - n/a)
83
+ - [PASS] Agent manifest (recommended_first_calls present, 5 entries)
84
+ - [PASS] Smoke test (scripts/smoke-tools.mjs found)
85
+ - [PASS] Resources advertised (8 resources registered)
86
+ - [PASS] Tool descriptions (avg 142 chars across 28 tools)
87
+ - [WARN] Annotations (20/28 read tools annotated)
88
+ - [PASS] Manifest discoverability (4/4 discovery tools present (agent_manifest, data_inventory, capabilities, connection_status))
89
+
90
+ ## Suggested fixes
91
+ - Add `annotations: { readOnlyHint: true, openWorldHint: false }` to every read tool definition.
92
+
93
+ _Generated by mcp-scorecard v0.1.0 at 2026-05-23T15:42:11.000Z_
94
+ ```
95
+
96
+ ### JSON (`--json`)
97
+
98
+ ```json
99
+ {
100
+ "target": {
101
+ "displayName": "whoop-mcp-unofficial",
102
+ "version": "0.4.3",
103
+ "serverName": "whoop-mcp",
104
+ "serverVersion": "0.4.3"
105
+ },
106
+ "totalScore": 88,
107
+ "checks": [
108
+ {
109
+ "id": "schema_validity",
110
+ "label": "Schema validity",
111
+ "score": 10,
112
+ "status": "pass",
113
+ "summary": "28/28 tools have valid input schema",
114
+ "details": [],
115
+ "fixes": []
116
+ }
117
+ ],
118
+ "generatedAt": "2026-05-23T15:42:11.000Z",
119
+ "scorecardVersion": "0.1.0"
120
+ }
121
+ ```
122
+
123
+ ## How it probes
124
+
125
+ The scorecard launches the target MCP server over stdio with
126
+ `MCP_PROBE=1` set on the child's env. **Author hook:** if your MCP needs
127
+ OAuth or other credentials to even list tools, detect this env var and
128
+ return your tool/resource/prompt manifests anyway. The scorecard expects
129
+ to be able to read your contract without making any auth-requiring API
130
+ calls.
131
+
132
+ ### Privacy model
133
+
134
+ - The probe response is **never persisted**. Only counts and field names
135
+ are recorded into the in-memory snapshot used by checks.
136
+ - Any string going into the report is run through a redaction pass that
137
+ replaces values for `customer_id`, `email`, `phone`, `access_token`,
138
+ `refresh_token`, `client_secret`, `developer_token`, and `api_key` with
139
+ `[REDACTED]`.
140
+ - No telemetry. No network calls beyond `npm pack` (when auditing a
141
+ package by name) and `gh repo clone` (when auditing a GitHub URL).
142
+
143
+ ## Use cases
144
+
145
+ - **CI gate** - block a PR if the score drops below a threshold:
146
+ `npx -y mcp-scorecard my-mcp --min-score 85`.
147
+ - **Registry curation** - run the audit across a list of MCPs to pick
148
+ the best-documented and most agent-friendly options for a directory or
149
+ catalog.
150
+ - **Pre-publish self-check** - add it to `prepublishOnly` so you never
151
+ ship a regressed contract by accident.
152
+ - **Comparative review** - score two MCPs that ostensibly cover the same
153
+ API and see which is friendlier to agents.
154
+
155
+ ## Limitations
156
+
157
+ - Cannot probe MCPs that REQUIRE auth before `listTools()` returns. If
158
+ your server is one of these, support the `MCP_PROBE` env hook so the
159
+ scorecard can still read your contract.
160
+ - Cannot grade logic correctness - the scorecard only inspects shape,
161
+ metadata, and discoverability. A server can score 100/100 and still
162
+ return wrong data.
163
+ - Mutation detection is name-based - if you call a write tool
164
+ `prepare_thing` instead of `set_thing`, the check will miss it. This
165
+ is intentional: we reward clear naming.
166
+
167
+ ## Roadmap
168
+
169
+ - **v0.2** - rule pack for agent-rules-of-thumb (output shape stability,
170
+ pagination patterns, error envelope consistency), a perf benchmark
171
+ (median `listTools()` latency), and an OAuth probe with a mock token.
172
+
173
+ ## Support
174
+
175
+ - Issues: <https://github.com/davidmosiah/mcp-scorecard/issues>
176
+ - Email: support@delx.ai
177
+
178
+ ## Disclaimer
179
+
180
+ This is a quality audit tool. It does not certify security, privacy,
181
+ or correctness; it only measures whether a server follows agent-friendly
182
+ conventions. Always do your own review before plugging an MCP into a
183
+ production agent.
184
+
185
+ ## License
186
+
187
+ MIT - see [LICENSE](LICENSE).
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Check 5 — Agent manifest.
3
+ *
4
+ * The probe already attempted to call `*_agent_manifest`. We grade what
5
+ * came back:
6
+ * 10 — recommended_first_calls (length > 0) AND standard_tools (length > 0)
7
+ * 7 — has one of those two
8
+ * 3 — manifest tool exists but returned an unusable shape
9
+ * 0 — no manifest tool at all
10
+ */
11
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
12
+ export declare function checkAgentManifest(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Check 5 — Agent manifest.
3
+ *
4
+ * The probe already attempted to call `*_agent_manifest`. We grade what
5
+ * came back:
6
+ * 10 — recommended_first_calls (length > 0) AND standard_tools (length > 0)
7
+ * 7 — has one of those two
8
+ * 3 — manifest tool exists but returned an unusable shape
9
+ * 0 — no manifest tool at all
10
+ */
11
+ export function checkAgentManifest(snapshot) {
12
+ const hasManifestTool = snapshot.tools.some((t) => /(^|_)agent_manifest$/.test(t.name));
13
+ const manifest = snapshot.agentManifest;
14
+ if (!hasManifestTool) {
15
+ return {
16
+ id: 'agent_manifest',
17
+ label: 'Agent manifest',
18
+ score: 0,
19
+ status: 'fail',
20
+ summary: 'no agent_manifest tool',
21
+ details: [],
22
+ fixes: [
23
+ 'Expose a `<prefix>_agent_manifest` tool that returns { recommended_first_calls, standard_tools, ... } so agents can self-onboard.'
24
+ ]
25
+ };
26
+ }
27
+ if (!manifest) {
28
+ return {
29
+ id: 'agent_manifest',
30
+ label: 'Agent manifest',
31
+ score: 3,
32
+ status: 'fail',
33
+ summary: 'agent_manifest tool present but call failed',
34
+ details: ['Calling the manifest tool did not return parseable JSON.'],
35
+ fixes: ['Ensure `<prefix>_agent_manifest` returns text content containing JSON with recommended_first_calls + standard_tools.']
36
+ };
37
+ }
38
+ const hasFC = manifest.has_recommended_first_calls && manifest.recommended_first_calls_count > 0;
39
+ const hasST = manifest.has_standard_tools && manifest.standard_tools_count > 0;
40
+ let score;
41
+ let summary;
42
+ if (hasFC && hasST) {
43
+ score = 10;
44
+ summary = `recommended_first_calls present (${manifest.recommended_first_calls_count} entries)`;
45
+ }
46
+ else if (hasFC || hasST) {
47
+ score = 7;
48
+ summary = hasFC
49
+ ? 'recommended_first_calls present; standard_tools missing'
50
+ : 'standard_tools present; recommended_first_calls missing';
51
+ }
52
+ else {
53
+ score = 3;
54
+ summary = 'agent_manifest returned object but no expected arrays';
55
+ }
56
+ const status = score >= 9 ? 'pass' : score >= 5 ? 'warn' : 'fail';
57
+ const fixes = [];
58
+ if (!hasFC)
59
+ fixes.push('Add `recommended_first_calls: [...]` to the manifest response.');
60
+ if (!hasST)
61
+ fixes.push('Add `standard_tools: [...]` to the manifest response.');
62
+ return {
63
+ id: 'agent_manifest',
64
+ label: 'Agent manifest',
65
+ score,
66
+ status,
67
+ summary,
68
+ details: [],
69
+ fixes
70
+ };
71
+ }
72
+ //# sourceMappingURL=agent-manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-manifest.js","sourceRoot":"","sources":["../../../src/audit/checks/agent-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,MAAM,UAAU,kBAAkB,CAAC,QAAuB;IACxD,MAAM,eAAe,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACxF,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC;IAExC,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO;YACL,EAAE,EAAE,gBAAgB;YACpB,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,wBAAwB;YACjC,OAAO,EAAE,EAAE;YACX,KAAK,EAAE;gBACL,mIAAmI;aACpI;SACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO;YACL,EAAE,EAAE,gBAAgB;YACpB,KAAK,EAAE,gBAAgB;YACvB,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,6CAA6C;YACtD,OAAO,EAAE,CAAC,0DAA0D,CAAC;YACrE,KAAK,EAAE,CAAC,sHAAsH,CAAC;SAChI,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,2BAA2B,IAAI,QAAQ,CAAC,6BAA6B,GAAG,CAAC,CAAC;IACjG,MAAM,KAAK,GAAG,QAAQ,CAAC,kBAAkB,IAAI,QAAQ,CAAC,oBAAoB,GAAG,CAAC,CAAC;IAE/E,IAAI,KAAa,CAAC;IAClB,IAAI,OAAe,CAAC;IACpB,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;QACnB,KAAK,GAAG,EAAE,CAAC;QACX,OAAO,GAAG,oCAAoC,QAAQ,CAAC,6BAA6B,WAAW,CAAC;IAClG,CAAC;SAAM,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,GAAG,CAAC,CAAC;QACV,OAAO,GAAG,KAAK;YACb,CAAC,CAAC,yDAAyD;YAC3D,CAAC,CAAC,yDAAyD,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,CAAC,CAAC;QACV,OAAO,GAAG,uDAAuD,CAAC;IACpE,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAElE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;IACzF,IAAI,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;IAEhF,OAAO;QACL,EAAE,EAAE,gBAAgB;QACpB,KAAK,EAAE,gBAAgB;QACvB,KAAK;QACL,MAAM;QACN,OAAO;QACP,OAAO,EAAE,EAAE;QACX,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Check 9 — Annotations.
3
+ *
4
+ * Counts what fraction of read tools (any non-mutation tool name) carry the
5
+ * MCP 1.20+ `annotations.readOnlyHint` field. Score = 10 * (annotated / read).
6
+ *
7
+ * If no read tools exist (rare — server is pure write) → score = 10 (n/a).
8
+ */
9
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
10
+ export declare function checkAnnotations(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Check 9 — Annotations.
3
+ *
4
+ * Counts what fraction of read tools (any non-mutation tool name) carry the
5
+ * MCP 1.20+ `annotations.readOnlyHint` field. Score = 10 * (annotated / read).
6
+ *
7
+ * If no read tools exist (rare — server is pure write) → score = 10 (n/a).
8
+ */
9
+ import { MUTATION_PATTERNS } from '../../constants.js';
10
+ function isReadTool(name) {
11
+ return !MUTATION_PATTERNS.some((p) => p.test(name));
12
+ }
13
+ function hasReadOnlyHint(annotations) {
14
+ if (!annotations)
15
+ return false;
16
+ const v = annotations.readOnlyHint;
17
+ return v === true;
18
+ }
19
+ export function checkAnnotations(snapshot) {
20
+ const readTools = snapshot.tools.filter((t) => isReadTool(t.name));
21
+ if (readTools.length === 0) {
22
+ return {
23
+ id: 'annotations',
24
+ label: 'Annotations',
25
+ score: 10,
26
+ status: 'pass',
27
+ summary: 'no read tools — n/a',
28
+ details: [],
29
+ fixes: []
30
+ };
31
+ }
32
+ const annotated = readTools.filter((t) => hasReadOnlyHint(t.annotations));
33
+ const missing = readTools.filter((t) => !hasReadOnlyHint(t.annotations)).map((t) => t.name);
34
+ const score = Math.round((annotated.length / readTools.length) * 10);
35
+ const status = score >= 9 ? 'pass' : score >= 5 ? 'warn' : 'fail';
36
+ const details = [];
37
+ if (missing.length) {
38
+ details.push(`Missing readOnlyHint: ${missing.slice(0, 10).join(', ')}`);
39
+ }
40
+ const fixes = [];
41
+ if (score < 10) {
42
+ fixes.push('Add `annotations: { readOnlyHint: true, openWorldHint: false }` to every read tool definition.');
43
+ }
44
+ return {
45
+ id: 'annotations',
46
+ label: 'Annotations',
47
+ score,
48
+ status,
49
+ summary: `${annotated.length}/${readTools.length} read tools annotated`,
50
+ details,
51
+ fixes
52
+ };
53
+ }
54
+ //# sourceMappingURL=annotations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../../src/audit/checks/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,eAAe,CAAC,WAAgD;IACvE,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,CAAC,GAAG,WAAW,CAAC,YAAY,CAAC;IACnC,OAAO,CAAC,KAAK,IAAI,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAuB;IACtD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,EAAE,EAAE,aAAa;YACjB,KAAK,EAAE,aAAa;YACpB,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,qBAAqB;YAC9B,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,EAAE;SACV,CAAC;IACJ,CAAC;IAED,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IACrE,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAElE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,yBAAyB,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,gGAAgG,CACjG,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,aAAa;QACjB,KAAK,EAAE,aAAa;QACpB,KAAK;QACL,MAAM;QACN,OAAO,EAAE,GAAG,SAAS,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM,uBAAuB;QACvE,OAAO;QACP,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Check 10 — Manifest discoverability.
3
+ *
4
+ * Server gets full credit if it exposes ANY of:
5
+ * *_agent_manifest, *_data_inventory, *_capabilities, *_connection_status
6
+ *
7
+ * Score:
8
+ * 10 — has 2+ of these tools
9
+ * 7 — has exactly 1
10
+ * 0 — has none
11
+ */
12
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
13
+ export declare function checkManifestDiscoverability(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Check 10 — Manifest discoverability.
3
+ *
4
+ * Server gets full credit if it exposes ANY of:
5
+ * *_agent_manifest, *_data_inventory, *_capabilities, *_connection_status
6
+ *
7
+ * Score:
8
+ * 10 — has 2+ of these tools
9
+ * 7 — has exactly 1
10
+ * 0 — has none
11
+ */
12
+ import { DISCOVERY_TOOL_SUFFIXES } from '../../constants.js';
13
+ function matchesSuffix(name, suffix) {
14
+ return name === suffix || name.endsWith(`_${suffix}`);
15
+ }
16
+ export function checkManifestDiscoverability(snapshot) {
17
+ const present = DISCOVERY_TOOL_SUFFIXES.filter((suf) => snapshot.tools.some((t) => matchesSuffix(t.name, suf)));
18
+ let score;
19
+ let status;
20
+ if (present.length >= 2) {
21
+ score = 10;
22
+ status = 'pass';
23
+ }
24
+ else if (present.length === 1) {
25
+ score = 7;
26
+ status = 'warn';
27
+ }
28
+ else {
29
+ score = 0;
30
+ status = 'fail';
31
+ }
32
+ const fixes = [];
33
+ if (score < 10) {
34
+ fixes.push('Expose discovery tools so agents can self-onboard: `*_agent_manifest`, `*_data_inventory`, `*_capabilities`, `*_connection_status`.');
35
+ }
36
+ return {
37
+ id: 'manifest_discoverability',
38
+ label: 'Manifest discoverability',
39
+ score,
40
+ status,
41
+ summary: present.length
42
+ ? `${present.length}/${DISCOVERY_TOOL_SUFFIXES.length} discovery tools present (${present.join(', ')})`
43
+ : 'no discovery tools',
44
+ details: [],
45
+ fixes
46
+ };
47
+ }
48
+ //# sourceMappingURL=manifest-discoverability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-discoverability.js","sourceRoot":"","sources":["../../../src/audit/checks/manifest-discoverability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAG7D,SAAS,aAAa,CAAC,IAAY,EAAE,MAAc;IACjD,OAAO,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,MAAM,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,QAAuB;IAClE,MAAM,OAAO,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CACrD,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CACvD,CAAC;IAEF,IAAI,KAAa,CAAC;IAClB,IAAI,MAA6B,CAAC;IAClC,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACxB,KAAK,GAAG,EAAE,CAAC;QACX,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;SAAM,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAChC,KAAK,GAAG,CAAC,CAAC;QACV,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,CAAC,CAAC;QACV,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,qIAAqI,CACtI,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,0BAA0B;QAC9B,KAAK,EAAE,0BAA0B;QACjC,KAAK;QACL,MAAM;QACN,OAAO,EAAE,OAAO,CAAC,MAAM;YACrB,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,uBAAuB,CAAC,MAAM,6BAA6B,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;YACvG,CAAC,CAAC,oBAAoB;QACxB,OAAO,EAAE,EAAE;QACX,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Check 4 — Mutation gating.
3
+ *
4
+ * A tool is treated as mutating if its name matches MUTATION_PATTERNS (set,
5
+ * update, delete, create, pause, resume, enable, disable, cancel, publish,
6
+ * send, etc.). For each mutation tool, we check if the description mentions
7
+ * any MUTATION_GATE_HINTS phrase.
8
+ *
9
+ * Scoring:
10
+ * - if NO mutation tools exist → 10 (vacuously gated)
11
+ * - else: 10 * (gated / total_mutations)
12
+ */
13
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
14
+ export declare function checkMutationGating(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Check 4 — Mutation gating.
3
+ *
4
+ * A tool is treated as mutating if its name matches MUTATION_PATTERNS (set,
5
+ * update, delete, create, pause, resume, enable, disable, cancel, publish,
6
+ * send, etc.). For each mutation tool, we check if the description mentions
7
+ * any MUTATION_GATE_HINTS phrase.
8
+ *
9
+ * Scoring:
10
+ * - if NO mutation tools exist → 10 (vacuously gated)
11
+ * - else: 10 * (gated / total_mutations)
12
+ */
13
+ import { MUTATION_GATE_HINTS, MUTATION_PATTERNS } from '../../constants.js';
14
+ function isMutation(name) {
15
+ return MUTATION_PATTERNS.some((p) => p.test(name));
16
+ }
17
+ function descriptionMentionsGate(desc) {
18
+ if (!desc)
19
+ return false;
20
+ const lower = desc.toLowerCase();
21
+ return MUTATION_GATE_HINTS.some((hint) => lower.includes(hint));
22
+ }
23
+ export function checkMutationGating(snapshot) {
24
+ const mutations = snapshot.tools.filter((t) => isMutation(t.name));
25
+ if (mutations.length === 0) {
26
+ return {
27
+ id: 'mutation_gating',
28
+ label: 'Mutation gating',
29
+ score: 10,
30
+ status: 'pass',
31
+ summary: 'no write tools — n/a',
32
+ details: [],
33
+ fixes: []
34
+ };
35
+ }
36
+ const gated = mutations.filter((t) => descriptionMentionsGate(t.description));
37
+ const ungated = mutations.filter((t) => !descriptionMentionsGate(t.description));
38
+ const score = Math.round((gated.length / mutations.length) * 10);
39
+ const status = score >= 9 ? 'pass' : score >= 5 ? 'warn' : 'fail';
40
+ const details = [];
41
+ if (ungated.length) {
42
+ details.push(`Ungated mutations: ${ungated.slice(0, 10).map((t) => t.name).join(', ')}`);
43
+ }
44
+ const fixes = [];
45
+ if (ungated.length) {
46
+ fixes.push('Document the gating mechanism in each mutation tool description (e.g. "Gated by ALLOW_MUTATIONS=1" or "Requires explicit user intent").');
47
+ }
48
+ return {
49
+ id: 'mutation_gating',
50
+ label: 'Mutation gating',
51
+ score,
52
+ status,
53
+ summary: `${gated.length}/${mutations.length} mutation tools document gating`,
54
+ details,
55
+ fixes
56
+ };
57
+ }
58
+ //# sourceMappingURL=mutation-gating.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mutation-gating.js","sourceRoot":"","sources":["../../../src/audit/checks/mutation-gating.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG5E,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,uBAAuB,CAAC,IAAwB;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAuB;IACzD,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAEnE,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO;YACL,EAAE,EAAE,iBAAiB;YACrB,KAAK,EAAE,iBAAiB;YACxB,KAAK,EAAE,EAAE;YACT,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,sBAAsB;YAC/B,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,EAAE;SACV,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC9E,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IACjF,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IACjE,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAElE,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,sBAAsB,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CACR,yIAAyI,CAC1I,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,iBAAiB;QACxB,KAAK;QACL,MAAM;QACN,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC,MAAM,iCAAiC;QAC7E,OAAO;QACP,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Check 3 — Privacy modes documented.
3
+ *
4
+ * Two signals:
5
+ * 1. any tool input schema has a `privacy_mode` property
6
+ * 2. any tool description mentions "summary|structured|raw" as modes
7
+ *
8
+ * Score:
9
+ * 10 — privacy_mode parameter is on >=1 tool
10
+ * 7 — >=3 tools mention summary/structured/raw in descriptions
11
+ * 4 — exactly 1-2 tools mention them
12
+ * 0 — no signal at all
13
+ *
14
+ * We look at the probe snapshot only — no source-file scraping needed.
15
+ */
16
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
17
+ export declare function checkPrivacyModes(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Check 3 — Privacy modes documented.
3
+ *
4
+ * Two signals:
5
+ * 1. any tool input schema has a `privacy_mode` property
6
+ * 2. any tool description mentions "summary|structured|raw" as modes
7
+ *
8
+ * Score:
9
+ * 10 — privacy_mode parameter is on >=1 tool
10
+ * 7 — >=3 tools mention summary/structured/raw in descriptions
11
+ * 4 — exactly 1-2 tools mention them
12
+ * 0 — no signal at all
13
+ *
14
+ * We look at the probe snapshot only — no source-file scraping needed.
15
+ */
16
+ const MODE_RE = /\b(summary|structured|raw)\b/i;
17
+ function hasPrivacyModeParam(inputSchema) {
18
+ if (!inputSchema || typeof inputSchema !== 'object')
19
+ return false;
20
+ const props = inputSchema.properties;
21
+ if (!props || typeof props !== 'object')
22
+ return false;
23
+ return 'privacy_mode' in props || 'privacyMode' in props;
24
+ }
25
+ export function checkPrivacyModes(snapshot) {
26
+ const tools = snapshot.tools;
27
+ const withParam = tools.filter((t) => hasPrivacyModeParam(t.inputSchema));
28
+ const mentioning = tools.filter((t) => t.description && MODE_RE.test(t.description));
29
+ let score;
30
+ let summary;
31
+ if (withParam.length >= 1) {
32
+ score = 10;
33
+ summary = `privacy_mode parameter on ${withParam.length} tool(s)`;
34
+ }
35
+ else if (mentioning.length >= 3) {
36
+ score = 7;
37
+ summary = `${mentioning.length} tools document summary/structured/raw modes`;
38
+ }
39
+ else if (mentioning.length >= 1) {
40
+ score = 4;
41
+ summary = `only ${mentioning.length} tool(s) mention privacy modes`;
42
+ }
43
+ else {
44
+ score = 0;
45
+ summary = 'no privacy modes documented';
46
+ }
47
+ const status = score >= 9 ? 'pass' : score >= 5 ? 'warn' : 'fail';
48
+ const fixes = [];
49
+ if (score < 10) {
50
+ fixes.push('Add a `privacy_mode` parameter (summary | structured | raw) on read tools so agents can request only what they need.');
51
+ }
52
+ return {
53
+ id: 'privacy_modes',
54
+ label: 'Privacy modes documented',
55
+ score,
56
+ status,
57
+ summary,
58
+ details: [],
59
+ fixes
60
+ };
61
+ }
62
+ //# sourceMappingURL=privacy-modes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"privacy-modes.js","sourceRoot":"","sources":["../../../src/audit/checks/privacy-modes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,MAAM,OAAO,GAAG,+BAA+B,CAAC;AAEhD,SAAS,mBAAmB,CAAC,WAAoB;IAC/C,IAAI,CAAC,WAAW,IAAI,OAAO,WAAW,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAClE,MAAM,KAAK,GAAI,WAAuC,CAAC,UAAU,CAAC;IAClE,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACtD,OAAO,cAAc,IAAK,KAAgB,IAAI,aAAa,IAAK,KAAgB,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAuB;IACvD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC;IAErF,IAAI,KAAa,CAAC;IAClB,IAAI,OAAe,CAAC;IACpB,IAAI,SAAS,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAC1B,KAAK,GAAG,EAAE,CAAC;QACX,OAAO,GAAG,6BAA6B,SAAS,CAAC,MAAM,UAAU,CAAC;IACpE,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAClC,KAAK,GAAG,CAAC,CAAC;QACV,OAAO,GAAG,GAAG,UAAU,CAAC,MAAM,8CAA8C,CAAC;IAC/E,CAAC;SAAM,IAAI,UAAU,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QAClC,KAAK,GAAG,CAAC,CAAC;QACV,OAAO,GAAG,QAAQ,UAAU,CAAC,MAAM,gCAAgC,CAAC;IACtE,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,CAAC,CAAC;QACV,OAAO,GAAG,6BAA6B,CAAC;IAC1C,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAElE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,sHAAsH,CACvH,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,eAAe;QACnB,KAAK,EAAE,0BAA0B;QACjC,KAAK;QACL,MAAM;QACN,OAAO;QACP,OAAO,EAAE,EAAE;QACX,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Check 7 — Resources advertised.
3
+ *
4
+ * Score from count of MCP resources returned by listResources():
5
+ * 0 resources → 0
6
+ * 1-2 resources → 5
7
+ * 3+ resources → 10
8
+ */
9
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
10
+ export declare function checkResources(snapshot: ProbeSnapshot): CheckResult;