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.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/dist/audit/checks/agent-manifest.d.ts +12 -0
- package/dist/audit/checks/agent-manifest.js +72 -0
- package/dist/audit/checks/agent-manifest.js.map +1 -0
- package/dist/audit/checks/annotations.d.ts +10 -0
- package/dist/audit/checks/annotations.js +54 -0
- package/dist/audit/checks/annotations.js.map +1 -0
- package/dist/audit/checks/manifest-discoverability.d.ts +13 -0
- package/dist/audit/checks/manifest-discoverability.js +48 -0
- package/dist/audit/checks/manifest-discoverability.js.map +1 -0
- package/dist/audit/checks/mutation-gating.d.ts +14 -0
- package/dist/audit/checks/mutation-gating.js +58 -0
- package/dist/audit/checks/mutation-gating.js.map +1 -0
- package/dist/audit/checks/privacy-modes.d.ts +17 -0
- package/dist/audit/checks/privacy-modes.js +62 -0
- package/dist/audit/checks/privacy-modes.js.map +1 -0
- package/dist/audit/checks/resources.d.ts +10 -0
- package/dist/audit/checks/resources.js +39 -0
- package/dist/audit/checks/resources.js.map +1 -0
- package/dist/audit/checks/schema-validity.d.ts +9 -0
- package/dist/audit/checks/schema-validity.js +59 -0
- package/dist/audit/checks/schema-validity.js.map +1 -0
- package/dist/audit/checks/smoke-test.d.ts +14 -0
- package/dist/audit/checks/smoke-test.js +59 -0
- package/dist/audit/checks/smoke-test.js.map +1 -0
- package/dist/audit/checks/tool-descriptions.d.ts +13 -0
- package/dist/audit/checks/tool-descriptions.js +59 -0
- package/dist/audit/checks/tool-descriptions.js.map +1 -0
- package/dist/audit/checks/tool-naming.d.ts +16 -0
- package/dist/audit/checks/tool-naming.js +81 -0
- package/dist/audit/checks/tool-naming.js.map +1 -0
- package/dist/audit/probe.d.ts +20 -0
- package/dist/audit/probe.js +145 -0
- package/dist/audit/probe.js.map +1 -0
- package/dist/audit/runner.d.ts +9 -0
- package/dist/audit/runner.js +77 -0
- package/dist/audit/runner.js.map +1 -0
- package/dist/audit/scorer.d.ts +8 -0
- package/dist/audit/scorer.js +17 -0
- package/dist/audit/scorer.js.map +1 -0
- package/dist/cli/commands.d.ts +10 -0
- package/dist/cli/commands.js +123 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/output.d.ts +13 -0
- package/dist/cli/output.js +76 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/constants.d.ts +28 -0
- package/dist/constants.js +54 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/resolvers/github-resolver.d.ts +10 -0
- package/dist/resolvers/github-resolver.js +65 -0
- package/dist/resolvers/github-resolver.js.map +1 -0
- package/dist/resolvers/local-resolver.d.ts +8 -0
- package/dist/resolvers/local-resolver.js +50 -0
- package/dist/resolvers/local-resolver.js.map +1 -0
- package/dist/resolvers/npm-resolver.d.ts +11 -0
- package/dist/resolvers/npm-resolver.js +100 -0
- package/dist/resolvers/npm-resolver.js.map +1 -0
- package/dist/types.d.ts +84 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- 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;
|