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,100 @@
1
+ /**
2
+ * npm resolver — given a package name (optionally @version), runs `npm pack`
3
+ * into a fresh temp dir, unpacks the tarball, locates the bin from
4
+ * package.json, and returns a ResolvedTarget ready for probing.
5
+ *
6
+ * Security:
7
+ * - works in /tmp/scorecard-work/probe-<random>/, never in the user's HOME.
8
+ * - uses --no-fund --no-audit to keep output clean and avoid any phone-home.
9
+ */
10
+ import { spawnSync } from 'node:child_process';
11
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, statSync } from 'node:fs';
12
+ import { tmpdir } from 'node:os';
13
+ import { join, resolve as pathResolve } from 'node:path';
14
+ const SCRATCH_ROOT = '/tmp/scorecard-work';
15
+ function ensureScratchRoot() {
16
+ if (!existsSync(SCRATCH_ROOT)) {
17
+ try {
18
+ mkdirSync(SCRATCH_ROOT, { recursive: true });
19
+ return SCRATCH_ROOT;
20
+ }
21
+ catch {
22
+ // fall back to OS tmpdir if /tmp/scorecard-work is unwritable
23
+ }
24
+ }
25
+ if (existsSync(SCRATCH_ROOT))
26
+ return SCRATCH_ROOT;
27
+ return tmpdir();
28
+ }
29
+ function pickBin(pkg) {
30
+ const bin = pkg.bin;
31
+ if (typeof bin === 'string')
32
+ return bin;
33
+ if (bin && typeof bin === 'object') {
34
+ const entries = Object.values(bin);
35
+ return entries[0];
36
+ }
37
+ // some MCPs export `main` and rely on `node dist/index.js`
38
+ if (typeof pkg.main === 'string')
39
+ return pkg.main;
40
+ return undefined;
41
+ }
42
+ export async function resolveNpmPackage(spec) {
43
+ const scratch = ensureScratchRoot();
44
+ const dest = mkdtempSync(join(scratch, 'probe-'));
45
+ const pack = spawnSync('npm', ['pack', spec, '--pack-destination', dest, '--no-fund', '--no-audit'], {
46
+ encoding: 'utf8',
47
+ stdio: ['ignore', 'pipe', 'pipe']
48
+ });
49
+ if (pack.status !== 0) {
50
+ throw new Error(`npm pack failed for ${spec}: ${pack.stderr || pack.stdout}`);
51
+ }
52
+ const tarball = readdirSync(dest).find((f) => f.endsWith('.tgz'));
53
+ if (!tarball)
54
+ throw new Error(`npm pack produced no .tgz under ${dest}`);
55
+ const tarPath = join(dest, tarball);
56
+ const extractDir = join(dest, 'pkg');
57
+ mkdirSync(extractDir, { recursive: true });
58
+ const tar = spawnSync('tar', ['xzf', tarPath, '-C', extractDir, '--strip-components=1'], {
59
+ encoding: 'utf8'
60
+ });
61
+ if (tar.status !== 0) {
62
+ throw new Error(`tar extract failed for ${tarPath}: ${tar.stderr}`);
63
+ }
64
+ const pkgJsonPath = join(extractDir, 'package.json');
65
+ if (!existsSync(pkgJsonPath)) {
66
+ throw new Error(`Extracted package has no package.json at ${pkgJsonPath}`);
67
+ }
68
+ const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
69
+ // Install production deps inside the extracted tarball so the launched
70
+ // binary can `import` its declared dependencies. We skip devDeps,
71
+ // scripts, fund/audit chatter, and any postinstall side-effects.
72
+ const hasDeps = pkg.dependencies && Object.keys(pkg.dependencies).length > 0;
73
+ if (hasDeps) {
74
+ const inst = spawnSync('npm', ['install', '--omit=dev', '--no-fund', '--no-audit', '--ignore-scripts', '--no-package-lock'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], cwd: extractDir });
75
+ if (inst.status !== 0) {
76
+ throw new Error(`npm install (prod deps) failed in ${extractDir}: ${inst.stderr || inst.stdout}`);
77
+ }
78
+ }
79
+ const binRel = pickBin(pkg);
80
+ if (!binRel) {
81
+ throw new Error(`Package ${pkg.name} has no bin or main — cannot launch.`);
82
+ }
83
+ const binAbs = pathResolve(extractDir, binRel);
84
+ if (!existsSync(binAbs)) {
85
+ throw new Error(`Resolved bin does not exist after extract: ${binAbs}`);
86
+ }
87
+ // Ensure it's a file, not a dir.
88
+ if (!statSync(binAbs).isFile()) {
89
+ throw new Error(`Resolved bin is not a file: ${binAbs}`);
90
+ }
91
+ return {
92
+ displayName: pkg.name,
93
+ version: pkg.version,
94
+ command: 'node',
95
+ args: [binAbs],
96
+ packageDir: extractDir,
97
+ packageJson: pkg
98
+ };
99
+ }
100
+ //# sourceMappingURL=npm-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npm-resolver.js","sourceRoot":"","sources":["../../src/resolvers/npm-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Shared types: the snapshot the probe captures and the shape of a check.
3
+ * Kept deliberately minimal so the audit pipeline stays portable.
4
+ */
5
+ export interface ToolSnapshot {
6
+ name: string;
7
+ description?: string;
8
+ inputSchema?: unknown;
9
+ annotations?: Record<string, unknown>;
10
+ }
11
+ export interface ResourceSnapshot {
12
+ uri: string;
13
+ name?: string;
14
+ description?: string;
15
+ mimeType?: string;
16
+ }
17
+ export interface PromptSnapshot {
18
+ name: string;
19
+ description?: string;
20
+ }
21
+ /**
22
+ * What the probe captures from one MCP target. We carry just enough to
23
+ * answer every check without round-tripping back to the target server.
24
+ */
25
+ export interface ProbeSnapshot {
26
+ serverInfo: {
27
+ name?: string;
28
+ version?: string;
29
+ };
30
+ tools: ToolSnapshot[];
31
+ resources: ResourceSnapshot[];
32
+ prompts: PromptSnapshot[];
33
+ /** Counted only — never the actual payload (privacy). */
34
+ agentManifest?: {
35
+ has_recommended_first_calls: boolean;
36
+ recommended_first_calls_count: number;
37
+ has_standard_tools: boolean;
38
+ standard_tools_count: number;
39
+ raw_keys: string[];
40
+ };
41
+ }
42
+ /**
43
+ * Where the audit target's source files live on disk. Some checks (smoke
44
+ * test detection) read from this; the probe only needs `command + args`.
45
+ */
46
+ export interface ResolvedTarget {
47
+ /** Display label used in output. */
48
+ displayName: string;
49
+ /** What version, if known. */
50
+ version?: string;
51
+ /** Command to spawn for probe (usually 'node'). */
52
+ command: string;
53
+ /** Args to pass to command. */
54
+ args: string[];
55
+ /** Directory holding the package source — used for file-presence checks. */
56
+ packageDir: string;
57
+ /** package.json contents, if any. */
58
+ packageJson?: Record<string, unknown>;
59
+ }
60
+ export type CheckId = 'schema_validity' | 'tool_naming' | 'privacy_modes' | 'mutation_gating' | 'agent_manifest' | 'smoke_test' | 'resources' | 'tool_descriptions' | 'annotations' | 'manifest_discoverability';
61
+ export interface CheckResult {
62
+ id: CheckId;
63
+ label: string;
64
+ score: number;
65
+ status: 'pass' | 'warn' | 'fail';
66
+ /** One-line headline for the scorecard table. */
67
+ summary: string;
68
+ /** Optional bullets shown in the Details section. */
69
+ details: string[];
70
+ /** Optional suggestions for Suggested fixes section. */
71
+ fixes: string[];
72
+ }
73
+ export interface AuditReport {
74
+ target: {
75
+ displayName: string;
76
+ version?: string;
77
+ serverName?: string;
78
+ serverVersion?: string;
79
+ };
80
+ totalScore: number;
81
+ checks: CheckResult[];
82
+ generatedAt: string;
83
+ scorecardVersion: string;
84
+ }
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared types: the snapshot the probe captures and the shape of a check.
3
+ * Kept deliberately minimal so the audit pipeline stays portable.
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "mcp-scorecard",
3
+ "version": "0.1.0",
4
+ "description": "Agent-readiness scorecard for any MCP server. Probes a target, runs 10 checks, outputs a 0-100 score with actionable findings.",
5
+ "type": "module",
6
+ "bin": {
7
+ "mcp-scorecard": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "typecheck": "tsc --noEmit -p tsconfig.json",
18
+ "start": "node dist/index.js",
19
+ "dev": "tsx src/index.ts",
20
+ "test:checks": "node scripts/test-checks.mjs",
21
+ "test:self": "node scripts/smoke-self.mjs",
22
+ "test": "npm run typecheck && npm run build && npm run test:checks && npm run test:self",
23
+ "prepublishOnly": "npm test"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "scorecard",
29
+ "audit",
30
+ "agent-readiness",
31
+ "quality"
32
+ ],
33
+ "author": "David Mosiah",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.21.0",
37
+ "ajv": "^8.17.1",
38
+ "zod": "^4.4.1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^22.0.0",
42
+ "tsx": "^4.20.6",
43
+ "typescript": "^5.6.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=22"
47
+ },
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/davidmosiah/mcp-scorecard.git"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/davidmosiah/mcp-scorecard/issues",
54
+ "email": "support@delx.ai"
55
+ },
56
+ "homepage": "https://github.com/davidmosiah/mcp-scorecard",
57
+ "publishConfig": {
58
+ "access": "public"
59
+ },
60
+ "main": "dist/index.js"
61
+ }