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
@@ -0,0 +1,39 @@
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
+ export function checkResources(snapshot) {
10
+ const count = snapshot.resources.length;
11
+ let score;
12
+ let status;
13
+ if (count >= 3) {
14
+ score = 10;
15
+ status = 'pass';
16
+ }
17
+ else if (count >= 1) {
18
+ score = 5;
19
+ status = 'warn';
20
+ }
21
+ else {
22
+ score = 0;
23
+ status = 'fail';
24
+ }
25
+ const fixes = [];
26
+ if (count < 3) {
27
+ fixes.push('Register MCP resources (e.g. `whoop://summary/daily`, `whoop://agent-manifest`) so agents can subscribe to context instead of polling tools.');
28
+ }
29
+ return {
30
+ id: 'resources',
31
+ label: 'Resources advertised',
32
+ score,
33
+ status,
34
+ summary: `${count} resource${count === 1 ? '' : 's'} registered`,
35
+ details: [],
36
+ fixes
37
+ };
38
+ }
39
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../../../src/audit/checks/resources.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,UAAU,cAAc,CAAC,QAAuB;IACpD,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC;IACxC,IAAI,KAAa,CAAC;IAClB,IAAI,MAA6B,CAAC;IAClC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,KAAK,GAAG,EAAE,CAAC;QACX,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;SAAM,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACtB,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,CAAC,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CACR,8IAA8I,CAC/I,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,WAAW;QACf,KAAK,EAAE,sBAAsB;QAC7B,KAAK;QACL,MAAM;QACN,OAAO,EAAE,GAAG,KAAK,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,aAAa;QAChE,OAAO,EAAE,EAAE;QACX,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Check 1 — Schema validity.
3
+ *
4
+ * Score = 10 * (tools_with_valid_input_schema / total_tools).
5
+ * Uses ajv to compile each tool's inputSchema. "Valid" means ajv compiles
6
+ * without throwing AND the schema is an object (not primitive).
7
+ */
8
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
9
+ export declare function checkSchemaValidity(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Check 1 — Schema validity.
3
+ *
4
+ * Score = 10 * (tools_with_valid_input_schema / total_tools).
5
+ * Uses ajv to compile each tool's inputSchema. "Valid" means ajv compiles
6
+ * without throwing AND the schema is an object (not primitive).
7
+ */
8
+ // ajv's default export gymnastic for ESM + TS: cast through the namespace.
9
+ import AjvModule from 'ajv';
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
+ const Ajv = AjvModule.default ?? AjvModule;
12
+ export function checkSchemaValidity(snapshot) {
13
+ const tools = snapshot.tools;
14
+ // strict:false + logger:false keeps ajv from writing "unknown format"
15
+ // warnings to stderr when MCP schemas use date-time, uri, etc. without
16
+ // registering ajv-formats.
17
+ const ajv = new Ajv({ strict: false, allErrors: true, logger: false });
18
+ if (tools.length === 0) {
19
+ return {
20
+ id: 'schema_validity',
21
+ label: 'Schema validity',
22
+ score: 0,
23
+ status: 'fail',
24
+ summary: 'no tools to validate',
25
+ details: ['Target exposed zero tools.'],
26
+ fixes: ['Register at least one tool with a valid JSON Schema.']
27
+ };
28
+ }
29
+ const broken = [];
30
+ let valid = 0;
31
+ for (const tool of tools) {
32
+ if (!tool.inputSchema || typeof tool.inputSchema !== 'object') {
33
+ broken.push(`${tool.name}: missing or non-object inputSchema`);
34
+ continue;
35
+ }
36
+ try {
37
+ ajv.compile(tool.inputSchema);
38
+ valid += 1;
39
+ }
40
+ catch (err) {
41
+ const msg = err instanceof Error ? err.message : String(err);
42
+ broken.push(`${tool.name}: ${msg.slice(0, 120)}`);
43
+ }
44
+ }
45
+ const score = Math.round((valid / tools.length) * 10);
46
+ const status = score >= 9 ? 'pass' : score >= 6 ? 'warn' : 'fail';
47
+ return {
48
+ id: 'schema_validity',
49
+ label: 'Schema validity',
50
+ score,
51
+ status,
52
+ summary: `${valid}/${tools.length} tools have valid input schema`,
53
+ details: broken.slice(0, 10).map((b) => `Invalid schema: ${b}`),
54
+ fixes: broken.length
55
+ ? ['Ensure every tool registers a JSON Schema object as inputSchema (zod-to-json-schema works well).']
56
+ : []
57
+ };
58
+ }
59
+ //# sourceMappingURL=schema-validity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-validity.js","sourceRoot":"","sources":["../../../src/audit/checks/schema-validity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,OAAO,SAAS,MAAM,KAAK,CAAC;AAC5B,8DAA8D;AAC9D,MAAM,GAAG,GAAI,SAAiB,CAAC,OAAO,IAAI,SAAS,CAAC;AAGpD,MAAM,UAAU,mBAAmB,CAAC,QAAuB;IACzD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,sEAAsE;IACtE,uEAAuE;IACvE,2BAA2B;IAC3B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IAEvE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,iBAAiB;YACrB,KAAK,EAAE,iBAAiB;YACxB,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,sBAAsB;YAC/B,OAAO,EAAE,CAAC,4BAA4B,CAAC;YACvC,KAAK,EAAE,CAAC,sDAAsD,CAAC;SAChE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,WAAW,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;YAC9D,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,qCAAqC,CAAC,CAAC;YAC/D,SAAS;QACX,CAAC;QACD,IAAI,CAAC;YACH,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC9B,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC;IACtD,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,OAAO;QACL,EAAE,EAAE,iBAAiB;QACrB,KAAK,EAAE,iBAAiB;QACxB,KAAK;QACL,MAAM;QACN,OAAO,EAAE,GAAG,KAAK,IAAI,KAAK,CAAC,MAAM,gCAAgC;QACjE,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC/D,KAAK,EAAE,MAAM,CAAC,MAAM;YAClB,CAAC,CAAC,CAAC,kGAAkG,CAAC;YACtG,CAAC,CAAC,EAAE;KACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Check 6 — Smoke test present.
3
+ *
4
+ * File-system check on the resolved package dir:
5
+ * - looks for scripts/smoke*.mjs, scripts/smoke*.js, scripts/smoke*.ts
6
+ * - looks for a "test" script in package.json that isn't the literal default
7
+ *
8
+ * Score:
9
+ * 10 — has scripts/smoke*.{mjs,js,ts}
10
+ * 7 — has a `test` script in package.json
11
+ * 0 — neither
12
+ */
13
+ import type { CheckResult, ProbeSnapshot, ResolvedTarget } from '../../types.js';
14
+ export declare function checkSmokeTest(_snapshot: ProbeSnapshot, target: ResolvedTarget): CheckResult;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Check 6 — Smoke test present.
3
+ *
4
+ * File-system check on the resolved package dir:
5
+ * - looks for scripts/smoke*.mjs, scripts/smoke*.js, scripts/smoke*.ts
6
+ * - looks for a "test" script in package.json that isn't the literal default
7
+ *
8
+ * Score:
9
+ * 10 — has scripts/smoke*.{mjs,js,ts}
10
+ * 7 — has a `test` script in package.json
11
+ * 0 — neither
12
+ */
13
+ import { existsSync, readdirSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ const SMOKE_NAME_RE = /^smoke[\w-]*\.(mjs|js|ts)$/i;
16
+ const DEFAULT_TEST = 'echo "Error: no test specified" && exit 1';
17
+ export function checkSmokeTest(_snapshot, target) {
18
+ const scriptsDir = join(target.packageDir, 'scripts');
19
+ let smokeFiles = [];
20
+ if (existsSync(scriptsDir)) {
21
+ try {
22
+ smokeFiles = readdirSync(scriptsDir).filter((f) => SMOKE_NAME_RE.test(f));
23
+ }
24
+ catch {
25
+ smokeFiles = [];
26
+ }
27
+ }
28
+ const pkg = target.packageJson;
29
+ const scripts = (pkg?.scripts ?? {});
30
+ const hasRealTest = typeof scripts.test === 'string' && scripts.test.trim().length > 0 && scripts.test !== DEFAULT_TEST;
31
+ let score;
32
+ let summary;
33
+ let status;
34
+ const details = [];
35
+ if (smokeFiles.length > 0) {
36
+ score = 10;
37
+ status = 'pass';
38
+ summary = `scripts/${smokeFiles[0]} found`;
39
+ if (smokeFiles.length > 1)
40
+ details.push(`Also: ${smokeFiles.slice(1).join(', ')}`);
41
+ }
42
+ else if (hasRealTest) {
43
+ score = 7;
44
+ status = 'warn';
45
+ summary = 'has `test` script in package.json';
46
+ details.push('Add a dedicated smoke runner under `scripts/` for clearer CI signal.');
47
+ }
48
+ else {
49
+ score = 0;
50
+ status = 'fail';
51
+ summary = 'no smoke script and no test script';
52
+ }
53
+ const fixes = [];
54
+ if (score < 10) {
55
+ fixes.push('Add `scripts/smoke-tools.mjs` that boots the server via StdioClientTransport and asserts the tool list.');
56
+ }
57
+ return { id: 'smoke_test', label: 'Smoke test', score, status, summary, details, fixes };
58
+ }
59
+ //# sourceMappingURL=smoke-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"smoke-test.js","sourceRoot":"","sources":["../../../src/audit/checks/smoke-test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,MAAM,aAAa,GAAG,6BAA6B,CAAC;AACpD,MAAM,YAAY,GAAG,2CAA2C,CAAC;AAEjE,MAAM,UAAU,cAAc,CAAC,SAAwB,EAAE,MAAsB;IAC7E,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACtD,IAAI,UAAU,GAAa,EAAE,CAAC;IAC9B,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,UAAU,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,UAAU,GAAG,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC;IAC/B,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE,OAAO,IAAI,EAAE,CAA2B,CAAC;IAC/D,MAAM,WAAW,GACf,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,KAAK,YAAY,CAAC;IAEtG,IAAI,KAAa,CAAC;IAClB,IAAI,OAAe,CAAC;IACpB,IAAI,MAA6B,CAAC;IAClC,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,GAAG,EAAE,CAAC;QACX,MAAM,GAAG,MAAM,CAAC;QAChB,OAAO,GAAG,WAAW,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC3C,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,SAAS,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrF,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,KAAK,GAAG,CAAC,CAAC;QACV,MAAM,GAAG,MAAM,CAAC;QAChB,OAAO,GAAG,mCAAmC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,sEAAsE,CAAC,CAAC;IACvF,CAAC;SAAM,CAAC;QACN,KAAK,GAAG,CAAC,CAAC;QACV,MAAM,GAAG,MAAM,CAAC;QAChB,OAAO,GAAG,oCAAoC,CAAC;IACjD,CAAC;IAED,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,yGAAyG,CAC1G,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Check 8 — Tool descriptions.
3
+ *
4
+ * Average `description` length:
5
+ * < 30 → 0
6
+ * 30-60 → 5
7
+ * 60+ → 10
8
+ *
9
+ * Missing descriptions count as 0 chars in the average, then we also flag
10
+ * the names with no description at all.
11
+ */
12
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
13
+ export declare function checkToolDescriptions(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Check 8 — Tool descriptions.
3
+ *
4
+ * Average `description` length:
5
+ * < 30 → 0
6
+ * 30-60 → 5
7
+ * 60+ → 10
8
+ *
9
+ * Missing descriptions count as 0 chars in the average, then we also flag
10
+ * the names with no description at all.
11
+ */
12
+ export function checkToolDescriptions(snapshot) {
13
+ const tools = snapshot.tools;
14
+ if (tools.length === 0) {
15
+ return {
16
+ id: 'tool_descriptions',
17
+ label: 'Tool descriptions',
18
+ score: 0,
19
+ status: 'fail',
20
+ summary: 'no tools',
21
+ details: [],
22
+ fixes: []
23
+ };
24
+ }
25
+ const lengths = tools.map((t) => (t.description ?? '').length);
26
+ const missing = tools.filter((t) => !t.description || t.description.length === 0).map((t) => t.name);
27
+ const avg = lengths.reduce((s, n) => s + n, 0) / tools.length;
28
+ let score;
29
+ let status;
30
+ if (avg >= 60) {
31
+ score = 10;
32
+ status = 'pass';
33
+ }
34
+ else if (avg >= 30) {
35
+ score = 5;
36
+ status = 'warn';
37
+ }
38
+ else {
39
+ score = 0;
40
+ status = 'fail';
41
+ }
42
+ const details = [];
43
+ if (missing.length)
44
+ details.push(`Missing descriptions: ${missing.slice(0, 10).join(', ')}`);
45
+ const fixes = [];
46
+ if (score < 10) {
47
+ fixes.push('Write a one-paragraph description per tool that names the inputs, the output shape, and any side effects.');
48
+ }
49
+ return {
50
+ id: 'tool_descriptions',
51
+ label: 'Tool descriptions',
52
+ score,
53
+ status,
54
+ summary: `avg ${avg.toFixed(0)} chars across ${tools.length} tool${tools.length === 1 ? '' : 's'}`,
55
+ details,
56
+ fixes
57
+ };
58
+ }
59
+ //# sourceMappingURL=tool-descriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-descriptions.js","sourceRoot":"","sources":["../../../src/audit/checks/tool-descriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,UAAU,qBAAqB,CAAC,QAAuB;IAC3D,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,EAAE,EAAE,mBAAmB;YACvB,KAAK,EAAE,mBAAmB;YAC1B,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,UAAU;YACnB,OAAO,EAAE,EAAE;YACX,KAAK,EAAE,EAAE;SACV,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrG,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;IAE9D,IAAI,KAAa,CAAC;IAClB,IAAI,MAA6B,CAAC;IAClC,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;QACd,KAAK,GAAG,EAAE,CAAC;QACX,MAAM,GAAG,MAAM,CAAC;IAClB,CAAC;SAAM,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC;QACrB,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,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,yBAAyB,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAE7F,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,GAAG,EAAE,EAAE,CAAC;QACf,KAAK,CAAC,IAAI,CACR,2GAA2G,CAC5G,CAAC;IACJ,CAAC;IAED,OAAO;QACL,EAAE,EAAE,mBAAmB;QACvB,KAAK,EAAE,mBAAmB;QAC1B,KAAK;QACL,MAAM;QACN,OAAO,EAAE,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,KAAK,CAAC,MAAM,QAAQ,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;QAClG,OAAO;QACP,KAAK;KACN,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Check 2 — Tool naming convention.
3
+ *
4
+ * Looks for:
5
+ * - all tools share a common prefix (e.g. "whoop_", "nourish_")
6
+ * - all tools are snake_case (lowercase a-z, digits, underscores)
7
+ * - no whitespace, hyphens, dots, uppercase
8
+ *
9
+ * Score:
10
+ * 10 — single prefix + all snake_case
11
+ * 7 — all snake_case but no common prefix
12
+ * 4 — some non-snake_case
13
+ * 0 — chaotic
14
+ */
15
+ import type { CheckResult, ProbeSnapshot } from '../../types.js';
16
+ export declare function checkToolNaming(snapshot: ProbeSnapshot): CheckResult;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Check 2 — Tool naming convention.
3
+ *
4
+ * Looks for:
5
+ * - all tools share a common prefix (e.g. "whoop_", "nourish_")
6
+ * - all tools are snake_case (lowercase a-z, digits, underscores)
7
+ * - no whitespace, hyphens, dots, uppercase
8
+ *
9
+ * Score:
10
+ * 10 — single prefix + all snake_case
11
+ * 7 — all snake_case but no common prefix
12
+ * 4 — some non-snake_case
13
+ * 0 — chaotic
14
+ */
15
+ const SNAKE_RE = /^[a-z][a-z0-9_]*$/;
16
+ function longestCommonPrefix(names) {
17
+ if (names.length === 0)
18
+ return '';
19
+ let prefix = names[0];
20
+ for (const n of names.slice(1)) {
21
+ let i = 0;
22
+ while (i < prefix.length && i < n.length && prefix[i] === n[i])
23
+ i++;
24
+ prefix = prefix.slice(0, i);
25
+ if (!prefix)
26
+ break;
27
+ }
28
+ // We want a meaningful prefix ending at "_" — e.g. "whoop_" not "wh".
29
+ const cut = prefix.lastIndexOf('_');
30
+ return cut > 0 ? prefix.slice(0, cut + 1) : '';
31
+ }
32
+ export function checkToolNaming(snapshot) {
33
+ const names = snapshot.tools.map((t) => t.name);
34
+ if (names.length === 0) {
35
+ return {
36
+ id: 'tool_naming',
37
+ label: 'Tool naming convention',
38
+ score: 0,
39
+ status: 'fail',
40
+ summary: 'no tools to inspect',
41
+ details: [],
42
+ fixes: []
43
+ };
44
+ }
45
+ const offenders = names.filter((n) => !SNAKE_RE.test(n));
46
+ const prefix = longestCommonPrefix(names);
47
+ let score;
48
+ let summary;
49
+ if (offenders.length === 0 && prefix && names.every((n) => n.startsWith(prefix))) {
50
+ score = 10;
51
+ summary = `consistent \`${prefix}\` prefix, snake_case`;
52
+ }
53
+ else if (offenders.length === 0) {
54
+ score = 7;
55
+ summary = 'all snake_case but no shared prefix';
56
+ }
57
+ else if (offenders.length < names.length / 3) {
58
+ score = 4;
59
+ summary = `${offenders.length}/${names.length} tools violate snake_case`;
60
+ }
61
+ else {
62
+ score = 0;
63
+ summary = `${offenders.length}/${names.length} tools violate snake_case`;
64
+ }
65
+ const status = score >= 9 ? 'pass' : score >= 5 ? 'warn' : 'fail';
66
+ const details = [];
67
+ if (offenders.length) {
68
+ details.push(`Non-snake_case names: ${offenders.slice(0, 10).join(', ')}`);
69
+ }
70
+ if (!prefix && offenders.length === 0) {
71
+ details.push('No common prefix — agents discovering many MCPs find prefixed tools easier to disambiguate.');
72
+ }
73
+ const fixes = [];
74
+ if (offenders.length)
75
+ fixes.push('Rename tools to lowercase snake_case (a-z, 0-9, _).');
76
+ if (!prefix && offenders.length === 0) {
77
+ fixes.push('Adopt a single prefix per server (e.g. `myserver_*`) so agents can scope discovery.');
78
+ }
79
+ return { id: 'tool_naming', label: 'Tool naming convention', score, status, summary, details, fixes };
80
+ }
81
+ //# sourceMappingURL=tool-naming.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-naming.js","sourceRoot":"","sources":["../../../src/audit/checks/tool-naming.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Probe: connects to one MCP target over stdio, captures tools/resources/
3
+ * prompts, optionally calls *_agent_manifest, then closes the transport.
4
+ *
5
+ * The captured snapshot is the only thing checks see — we never re-probe
6
+ * during scoring. This keeps the audit deterministic and avoids hammering
7
+ * the target.
8
+ *
9
+ * Privacy: we never log full responses to stdout/stderr; only counts and
10
+ * field names. If a probe lands on a logged-in server, no user data leaks.
11
+ */
12
+ import type { ProbeSnapshot, ResolvedTarget } from '../types.js';
13
+ /**
14
+ * Spawn the target MCP server and capture a snapshot.
15
+ *
16
+ * The transport inherits a sanitized env: the original env is passed
17
+ * through (so the target finds node, paths, etc.) plus MCP_PROBE=1 so
18
+ * authors can detect they are being audited and short-circuit auth.
19
+ */
20
+ export declare function probeTarget(target: ResolvedTarget): Promise<ProbeSnapshot>;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Probe: connects to one MCP target over stdio, captures tools/resources/
3
+ * prompts, optionally calls *_agent_manifest, then closes the transport.
4
+ *
5
+ * The captured snapshot is the only thing checks see — we never re-probe
6
+ * during scoring. This keeps the audit deterministic and avoids hammering
7
+ * the target.
8
+ *
9
+ * Privacy: we never log full responses to stdout/stderr; only counts and
10
+ * field names. If a probe lands on a logged-in server, no user data leaks.
11
+ */
12
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
13
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
14
+ import { PROBE_CLIENT_NAME, PROBE_ENV_FLAG, SERVER_VERSION } from '../constants.js';
15
+ const PROBE_TIMEOUT_MS = 30_000;
16
+ function withTimeout(promise, ms, label) {
17
+ return new Promise((resolve, reject) => {
18
+ const timer = setTimeout(() => {
19
+ reject(new Error(`Probe step "${label}" timed out after ${ms}ms`));
20
+ }, ms);
21
+ promise.then((value) => {
22
+ clearTimeout(timer);
23
+ resolve(value);
24
+ }, (err) => {
25
+ clearTimeout(timer);
26
+ reject(err);
27
+ });
28
+ });
29
+ }
30
+ /** Find an *_agent_manifest tool from the captured list, if any. */
31
+ function findManifestTool(tools) {
32
+ return tools.find((t) => /(^|_)agent_manifest$/.test(t.name))?.name;
33
+ }
34
+ /**
35
+ * Sanitize a probe response into counts + key names only. NEVER returns
36
+ * nested values — strings/numbers/booleans/arrays are reduced to lengths
37
+ * or omitted entirely.
38
+ */
39
+ function sanitizeManifestResponse(raw) {
40
+ if (!raw || typeof raw !== 'object')
41
+ return undefined;
42
+ const obj = raw;
43
+ const rec = obj.recommended_first_calls;
44
+ const std = obj.standard_tools;
45
+ return {
46
+ has_recommended_first_calls: Array.isArray(rec),
47
+ recommended_first_calls_count: Array.isArray(rec) ? rec.length : 0,
48
+ has_standard_tools: Array.isArray(std),
49
+ standard_tools_count: Array.isArray(std) ? std.length : 0,
50
+ raw_keys: Object.keys(obj).slice(0, 50)
51
+ };
52
+ }
53
+ /**
54
+ * Spawn the target MCP server and capture a snapshot.
55
+ *
56
+ * The transport inherits a sanitized env: the original env is passed
57
+ * through (so the target finds node, paths, etc.) plus MCP_PROBE=1 so
58
+ * authors can detect they are being audited and short-circuit auth.
59
+ */
60
+ export async function probeTarget(target) {
61
+ const env = {};
62
+ for (const [k, v] of Object.entries(process.env)) {
63
+ if (typeof v === 'string')
64
+ env[k] = v;
65
+ }
66
+ env[PROBE_ENV_FLAG] = '1';
67
+ const transport = new StdioClientTransport({
68
+ command: target.command,
69
+ args: target.args,
70
+ env,
71
+ cwd: target.packageDir
72
+ });
73
+ const client = new Client({ name: PROBE_CLIENT_NAME, version: SERVER_VERSION });
74
+ await withTimeout(client.connect(transport), PROBE_TIMEOUT_MS, 'connect');
75
+ let toolsRes = { tools: [] };
76
+ let resourcesRes = { resources: [] };
77
+ let promptsRes = { prompts: [] };
78
+ let agentManifest;
79
+ let serverInfo = {};
80
+ try {
81
+ // serverInfo from the negotiated server (available after connect)
82
+ const sv = client.getServerVersion?.();
83
+ if (sv) {
84
+ serverInfo = { name: sv.name, version: sv.version };
85
+ }
86
+ toolsRes = (await withTimeout(client.listTools(), PROBE_TIMEOUT_MS, 'listTools'));
87
+ // Resources and prompts are optional — some servers don't implement them.
88
+ try {
89
+ resourcesRes = (await withTimeout(client.listResources(), PROBE_TIMEOUT_MS, 'listResources'));
90
+ }
91
+ catch {
92
+ resourcesRes = { resources: [] };
93
+ }
94
+ try {
95
+ promptsRes = (await withTimeout(client.listPrompts(), PROBE_TIMEOUT_MS, 'listPrompts'));
96
+ }
97
+ catch {
98
+ promptsRes = { prompts: [] };
99
+ }
100
+ // Call agent_manifest if present, sanitize, never log raw payload.
101
+ const manifestToolName = findManifestTool(toolsRes.tools);
102
+ if (manifestToolName) {
103
+ try {
104
+ const callRes = (await withTimeout(client.callTool({ name: manifestToolName, arguments: {} }), PROBE_TIMEOUT_MS, `call ${manifestToolName}`));
105
+ // Most servers return a single JSON-encoded text content; try to parse.
106
+ const text = callRes.content?.find((c) => c.type === 'text')?.text;
107
+ if (text) {
108
+ try {
109
+ agentManifest = sanitizeManifestResponse(JSON.parse(text));
110
+ }
111
+ catch {
112
+ // not JSON — still record that it returned something
113
+ agentManifest = {
114
+ has_recommended_first_calls: false,
115
+ recommended_first_calls_count: 0,
116
+ has_standard_tools: false,
117
+ standard_tools_count: 0,
118
+ raw_keys: []
119
+ };
120
+ }
121
+ }
122
+ }
123
+ catch {
124
+ // call failed — leave agentManifest undefined; agent_manifest check
125
+ // will treat that as missing.
126
+ }
127
+ }
128
+ }
129
+ finally {
130
+ try {
131
+ await transport.close();
132
+ }
133
+ catch {
134
+ // already closed
135
+ }
136
+ }
137
+ return {
138
+ serverInfo,
139
+ tools: toolsRes.tools ?? [],
140
+ resources: resourcesRes.resources ?? [],
141
+ prompts: promptsRes.prompts ?? [],
142
+ agentManifest
143
+ };
144
+ }
145
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/audit/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Runner — orchestrates probe + 10 checks → AuditReport.
3
+ *
4
+ * Order matters only for the displayed list; checks are independent and
5
+ * read from the same probe snapshot. If a check throws, we record a 0
6
+ * with a synthetic detail entry rather than crashing the audit.
7
+ */
8
+ import type { AuditReport, ResolvedTarget } from '../types.js';
9
+ export declare function runAudit(target: ResolvedTarget): Promise<AuditReport>;
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Runner — orchestrates probe + 10 checks → AuditReport.
3
+ *
4
+ * Order matters only for the displayed list; checks are independent and
5
+ * read from the same probe snapshot. If a check throws, we record a 0
6
+ * with a synthetic detail entry rather than crashing the audit.
7
+ */
8
+ import { SERVER_VERSION } from '../constants.js';
9
+ import { checkAgentManifest } from './checks/agent-manifest.js';
10
+ import { checkAnnotations } from './checks/annotations.js';
11
+ import { checkManifestDiscoverability } from './checks/manifest-discoverability.js';
12
+ import { checkMutationGating } from './checks/mutation-gating.js';
13
+ import { checkPrivacyModes } from './checks/privacy-modes.js';
14
+ import { checkResources } from './checks/resources.js';
15
+ import { checkSchemaValidity } from './checks/schema-validity.js';
16
+ import { checkSmokeTest } from './checks/smoke-test.js';
17
+ import { checkToolDescriptions } from './checks/tool-descriptions.js';
18
+ import { checkToolNaming } from './checks/tool-naming.js';
19
+ import { probeTarget } from './probe.js';
20
+ import { aggregateScore } from './scorer.js';
21
+ const CHECK_LABELS = {
22
+ schema_validity: 'Schema validity',
23
+ tool_naming: 'Tool naming convention',
24
+ privacy_modes: 'Privacy modes documented',
25
+ mutation_gating: 'Mutation gating',
26
+ agent_manifest: 'Agent manifest',
27
+ smoke_test: 'Smoke test',
28
+ resources: 'Resources advertised',
29
+ tool_descriptions: 'Tool descriptions',
30
+ annotations: 'Annotations',
31
+ manifest_discoverability: 'Manifest discoverability'
32
+ };
33
+ function safeRun(id, fn) {
34
+ try {
35
+ return fn();
36
+ }
37
+ catch (err) {
38
+ const msg = err instanceof Error ? err.message : String(err);
39
+ return {
40
+ id,
41
+ label: CHECK_LABELS[id],
42
+ score: 0,
43
+ status: 'fail',
44
+ summary: 'check threw an error',
45
+ details: [msg.slice(0, 200)],
46
+ fixes: []
47
+ };
48
+ }
49
+ }
50
+ export async function runAudit(target) {
51
+ const snapshot = await probeTarget(target);
52
+ const checks = [
53
+ safeRun('schema_validity', () => checkSchemaValidity(snapshot)),
54
+ safeRun('tool_naming', () => checkToolNaming(snapshot)),
55
+ safeRun('privacy_modes', () => checkPrivacyModes(snapshot)),
56
+ safeRun('mutation_gating', () => checkMutationGating(snapshot)),
57
+ safeRun('agent_manifest', () => checkAgentManifest(snapshot)),
58
+ safeRun('smoke_test', () => checkSmokeTest(snapshot, target)),
59
+ safeRun('resources', () => checkResources(snapshot)),
60
+ safeRun('tool_descriptions', () => checkToolDescriptions(snapshot)),
61
+ safeRun('annotations', () => checkAnnotations(snapshot)),
62
+ safeRun('manifest_discoverability', () => checkManifestDiscoverability(snapshot))
63
+ ];
64
+ return {
65
+ target: {
66
+ displayName: target.displayName,
67
+ version: target.version,
68
+ serverName: snapshot.serverInfo.name,
69
+ serverVersion: snapshot.serverInfo.version
70
+ },
71
+ totalScore: aggregateScore(checks),
72
+ checks,
73
+ generatedAt: new Date().toISOString(),
74
+ scorecardVersion: SERVER_VERSION
75
+ };
76
+ }
77
+ //# sourceMappingURL=runner.js.map