@kryptosai/mcp-observatory 0.14.0 → 0.14.1

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 (58) hide show
  1. package/dist/src/adapters/http.js +6 -2
  2. package/dist/src/adapters/http.js.map +1 -1
  3. package/dist/src/adapters/local-process.js +25 -3
  4. package/dist/src/adapters/local-process.js.map +1 -1
  5. package/dist/src/checks/conformance.js +5 -4
  6. package/dist/src/checks/conformance.js.map +1 -1
  7. package/dist/src/checks/list-check.js +2 -1
  8. package/dist/src/checks/list-check.js.map +1 -1
  9. package/dist/src/checks/resources.js +3 -2
  10. package/dist/src/checks/resources.js.map +1 -1
  11. package/dist/src/checks/tools-invoke.js +3 -2
  12. package/dist/src/checks/tools-invoke.js.map +1 -1
  13. package/dist/src/cli.js +36 -901
  14. package/dist/src/cli.js.map +1 -1
  15. package/dist/src/commands/diff.d.ts +2 -0
  16. package/dist/src/commands/diff.js +27 -0
  17. package/dist/src/commands/diff.js.map +1 -0
  18. package/dist/src/commands/helpers.d.ts +25 -0
  19. package/dist/src/commands/helpers.js +131 -0
  20. package/dist/src/commands/helpers.js.map +1 -0
  21. package/dist/src/commands/legacy.d.ts +2 -0
  22. package/dist/src/commands/legacy.js +77 -0
  23. package/dist/src/commands/legacy.js.map +1 -0
  24. package/dist/src/commands/record-replay.d.ts +2 -0
  25. package/dist/src/commands/record-replay.js +181 -0
  26. package/dist/src/commands/record-replay.js.map +1 -0
  27. package/dist/src/commands/scan.d.ts +2 -0
  28. package/dist/src/commands/scan.js +155 -0
  29. package/dist/src/commands/scan.js.map +1 -0
  30. package/dist/src/commands/score.d.ts +2 -0
  31. package/dist/src/commands/score.js +88 -0
  32. package/dist/src/commands/score.js.map +1 -0
  33. package/dist/src/commands/serve.d.ts +2 -0
  34. package/dist/src/commands/serve.js +10 -0
  35. package/dist/src/commands/serve.js.map +1 -0
  36. package/dist/src/commands/suggest.d.ts +2 -0
  37. package/dist/src/commands/suggest.js +126 -0
  38. package/dist/src/commands/suggest.js.map +1 -0
  39. package/dist/src/commands/telemetry.d.ts +2 -0
  40. package/dist/src/commands/telemetry.js +65 -0
  41. package/dist/src/commands/telemetry.js.map +1 -0
  42. package/dist/src/commands/test.d.ts +2 -0
  43. package/dist/src/commands/test.js +37 -0
  44. package/dist/src/commands/test.js.map +1 -0
  45. package/dist/src/commands/watch.d.ts +5 -0
  46. package/dist/src/commands/watch.js +46 -0
  47. package/dist/src/commands/watch.js.map +1 -0
  48. package/dist/src/environment.js +12 -4
  49. package/dist/src/environment.js.map +1 -1
  50. package/dist/src/runner.js +2 -1
  51. package/dist/src/runner.js.map +1 -1
  52. package/dist/src/server.d.ts +2 -0
  53. package/dist/src/server.js +30 -14
  54. package/dist/src/server.js.map +1 -1
  55. package/dist/src/utils/errors.d.ts +4 -0
  56. package/dist/src/utils/errors.js +7 -0
  57. package/dist/src/utils/errors.js.map +1 -0
  58. package/package.json +1 -1
@@ -0,0 +1,181 @@
1
+ import os from "node:os";
2
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3
+ import { defaultCassettesDirectory, loadCassette, saveCassette } from "../cassette.js";
4
+ import { runPromptsCheck } from "../checks/prompts.js";
5
+ import { runResourcesCheck } from "../checks/resources.js";
6
+ import { runToolsCheck } from "../checks/tools.js";
7
+ import { runToolsInvokeCheck } from "../checks/tools-invoke.js";
8
+ import { renderTerminal, } from "../index.js";
9
+ import { runTargetRecording } from "../runner.js";
10
+ import { ReplayTransport } from "../transport/replay-transport.js";
11
+ import { SCHEMA_VERSION } from "../types.js";
12
+ import { buildRunId } from "../utils/ids.js";
13
+ import { compareResponses } from "../verify.js";
14
+ import { TOOL_VERSION } from "../version.js";
15
+ import { ANSI, c, readTargetConfig, targetFromCommand, getPassthroughArgs } from "./helpers.js";
16
+ export function registerRecordReplayCommands(program, bin) {
17
+ // ── record ─────────────────────────────────────────────────────────────
18
+ program
19
+ .command("record")
20
+ .passThroughOptions()
21
+ .description("Record a server session to a cassette file for replay.")
22
+ .argument("[command...]", "Server command and arguments to run.")
23
+ .option("--target <config>", "Path to a target config JSON file.")
24
+ .option("--no-color", "Disable colored output.")
25
+ .action(async (commandArgs, options) => {
26
+ const target = options.target
27
+ ? await readTargetConfig(options.target)
28
+ : targetFromCommand(commandArgs.length > 0 ? commandArgs : getPassthroughArgs());
29
+ process.stdout.write(`${c(ANSI.dim, "⟳")} Recording session with ${c(ANSI.bold, target.targetId)}...\n`);
30
+ const { artifact, cassetteEntries } = await runTargetRecording(target, { invokeTools: true });
31
+ if (!cassetteEntries || cassetteEntries.length === 0) {
32
+ process.stdout.write(`${c(ANSI.yellow, "⚠")} No traffic recorded.\n`);
33
+ process.exitCode = 1;
34
+ return;
35
+ }
36
+ const cassette = {
37
+ version: 1,
38
+ targetId: target.targetId,
39
+ recordedAt: new Date().toISOString(),
40
+ transport: target.adapter === "http" ? "http" : "stdio",
41
+ entries: cassetteEntries,
42
+ };
43
+ const cassettePath = await saveCassette(cassette, defaultCassettesDirectory(process.cwd()));
44
+ const summary = renderTerminal(artifact);
45
+ process.stdout.write(`\n${summary}\n`);
46
+ process.stdout.write(`\n${c(ANSI.green, "✓")} Cassette saved: ${cassettePath}\n`);
47
+ process.stdout.write(` ${c(ANSI.dim, `${cassetteEntries.length} entries recorded`)}\n`);
48
+ process.stdout.write(`\n Replay offline: ${c(ANSI.cyan, `${bin} replay ${cassettePath}`)}\n`);
49
+ process.stdout.write(` Verify live: ${c(ANSI.cyan, `${bin} verify ${cassettePath} ${target.adapter === "http" ? `--target <config>` : commandArgs.join(" ")}`)}\n\n`);
50
+ if (artifact.gate === "fail") {
51
+ process.exitCode = 1;
52
+ }
53
+ });
54
+ // ── replay ─────────────────────────────────────────────────────────────
55
+ program
56
+ .command("replay")
57
+ .description("Replay a cassette file offline — no live server needed.")
58
+ .argument("<cassette>", "Path to a cassette JSON file.")
59
+ .option("--no-color", "Disable colored output.")
60
+ .action(async (cassettePath) => {
61
+ const cassette = await loadCassette(cassettePath);
62
+ process.stdout.write(`${c(ANSI.dim, "⟳")} Replaying cassette for ${c(ANSI.bold, cassette.targetId)} (${cassette.entries.length} entries)...\n`);
63
+ // Create a target config for the replay
64
+ const replayTarget = {
65
+ targetId: cassette.targetId,
66
+ adapter: "local-process",
67
+ command: "replay",
68
+ args: [],
69
+ };
70
+ // Build a ReplayTransport and run checks against it
71
+ const transport = new ReplayTransport(cassette.entries);
72
+ const client = new Client({ name: "mcp-observatory", version: TOOL_VERSION }, { capabilities: {} });
73
+ await client.connect(transport);
74
+ const serverCapabilities = client.getServerCapabilities();
75
+ const checkContext = {
76
+ client,
77
+ serverCapabilities,
78
+ target: replayTarget,
79
+ timeoutMs: 10_000,
80
+ stderrLines: [],
81
+ };
82
+ const toolsCheck = await runToolsCheck(checkContext);
83
+ const promptsCheck = await runPromptsCheck(checkContext);
84
+ const resourcesCheck = await runResourcesCheck(checkContext);
85
+ const invokeCheck = await runToolsInvokeCheck(checkContext);
86
+ await client.close();
87
+ const checks = [
88
+ toolsCheck.result,
89
+ promptsCheck.result,
90
+ resourcesCheck.result,
91
+ invokeCheck.result,
92
+ ];
93
+ const failCount = checks.filter((ch) => ch.status === "fail").length;
94
+ const gate = failCount > 0 ? "fail" : "pass";
95
+ const artifact = {
96
+ artifactType: "run",
97
+ schemaVersion: SCHEMA_VERSION,
98
+ gate,
99
+ runId: buildRunId(),
100
+ createdAt: new Date().toISOString(),
101
+ toolVersion: TOOL_VERSION,
102
+ target: {
103
+ targetId: cassette.targetId,
104
+ adapter: "local-process",
105
+ command: "replay",
106
+ args: [],
107
+ metadata: { source: "cassette", cassettePath },
108
+ },
109
+ environment: {
110
+ platform: `${os.platform()} ${os.release()}`,
111
+ nodeVersion: process.version,
112
+ },
113
+ summary: {
114
+ total: checks.length,
115
+ pass: checks.filter((ch) => ch.status === "pass").length,
116
+ fail: failCount,
117
+ partial: checks.filter((ch) => ch.status === "partial").length,
118
+ unsupported: checks.filter((ch) => ch.status === "unsupported").length,
119
+ flaky: checks.filter((ch) => ch.status === "flaky").length,
120
+ skipped: checks.filter((ch) => ch.status === "skipped").length,
121
+ gate,
122
+ },
123
+ checks,
124
+ };
125
+ process.stdout.write(`\n${renderTerminal(artifact)}\n`);
126
+ process.stdout.write(`\n${c(ANSI.dim, `Replayed from: ${cassettePath}`)}\n\n`);
127
+ if (artifact.gate === "fail") {
128
+ process.exitCode = 1;
129
+ }
130
+ });
131
+ // ── verify ─────────────────────────────────────────────────────────────
132
+ program
133
+ .command("verify")
134
+ .passThroughOptions()
135
+ .description("Verify a live server still matches a recorded cassette.")
136
+ .argument("<cassette>", "Path to a cassette JSON file.")
137
+ .argument("[command...]", "Server command and arguments to run.")
138
+ .option("--target <config>", "Path to a target config JSON file.")
139
+ .option("--no-color", "Disable colored output.")
140
+ .action(async (cassettePath, commandArgs, options) => {
141
+ const cassette = await loadCassette(cassettePath);
142
+ const target = options.target
143
+ ? await readTargetConfig(options.target)
144
+ : targetFromCommand(commandArgs.length > 0 ? commandArgs : getPassthroughArgs());
145
+ process.stdout.write(`${c(ANSI.dim, "⟳")} Verifying ${c(ANSI.bold, target.targetId)} against cassette...\n`);
146
+ const { cassetteEntries } = await runTargetRecording(target, { invokeTools: true });
147
+ if (!cassetteEntries) {
148
+ process.stdout.write(`${c(ANSI.red, "✗")} Failed to record live session for comparison.\n`);
149
+ process.exitCode = 1;
150
+ return;
151
+ }
152
+ const verifyResult = compareResponses(cassette, cassetteEntries);
153
+ process.stdout.write("\n");
154
+ for (const entry of verifyResult.entries) {
155
+ if (entry.status === "pass") {
156
+ process.stdout.write(` ${c(ANSI.green, "✓")} ${entry.method}\n`);
157
+ }
158
+ else if (entry.status === "fail") {
159
+ process.stdout.write(` ${c(ANSI.red, "✗")} ${entry.method}\n`);
160
+ if (entry.diff) {
161
+ for (const line of entry.diff.split("\n")) {
162
+ process.stdout.write(` ${c(ANSI.dim, line)}\n`);
163
+ }
164
+ }
165
+ }
166
+ else {
167
+ process.stdout.write(` ${c(ANSI.yellow, "?")} ${entry.method} ${c(ANSI.dim, "(missing — server did not respond)")}\n`);
168
+ }
169
+ }
170
+ process.stdout.write("\n");
171
+ if (verifyResult.failed === 0 && verifyResult.missing === 0) {
172
+ process.stdout.write(c(ANSI.green, ` ✓ All ${verifyResult.passed} responses match cassette\n`));
173
+ }
174
+ else {
175
+ process.stdout.write(c(ANSI.red, ` ✗ ${verifyResult.failed} changed, ${verifyResult.missing} missing out of ${verifyResult.totalEntries} responses\n`));
176
+ process.exitCode = 1;
177
+ }
178
+ process.stdout.write("\n");
179
+ });
180
+ }
181
+ //# sourceMappingURL=record-replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"record-replay.js","sourceRoot":"","sources":["../../../src/commands/record-replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerScanCommands(program: Command, bin: string): void;
@@ -0,0 +1,155 @@
1
+ import { access } from "node:fs/promises";
2
+ import { scanForTargets } from "../discovery.js";
3
+ import { runTarget, } from "../index.js";
4
+ import { TOOL_VERSION } from "../version.js";
5
+ import { ANSI, LOGO, c, useColor } from "./helpers.js";
6
+ // ── Scan implementation ─────────────────────────────────────────────────────
7
+ async function runScan(bin, configPath, invokeTools, securityCheck) {
8
+ process.stdout.write(useColor() ? c(ANSI.cyan, LOGO) + ` ${c(ANSI.dim, `v${TOOL_VERSION}`)}\n\n` : LOGO + ` v${TOOL_VERSION}\n\n`);
9
+ if (configPath) {
10
+ try {
11
+ await access(configPath);
12
+ }
13
+ catch {
14
+ process.stdout.write(c(ANSI.red, ` ✗ Config file not found: ${configPath}\n\n`));
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ }
19
+ const targets = await scanForTargets(configPath);
20
+ if (targets.length === 0) {
21
+ process.stdout.write(c(ANSI.yellow, " No MCP servers found.\n\n"));
22
+ process.stdout.write(c(ANSI.dim, " Looked in ~/.claude.json, Claude Desktop config, .mcp.json\n\n"));
23
+ process.stdout.write(" Test a specific server:\n");
24
+ process.stdout.write(` ${c(ANSI.dim, "$")} ${c(ANSI.cyan, `${bin} test npx -y @modelcontextprotocol/server-filesystem .`)}\n\n`);
25
+ return;
26
+ }
27
+ process.stdout.write(c(ANSI.bold, ` Found ${targets.length} MCP server${targets.length === 1 ? "" : "s"}:\n`));
28
+ for (const t of targets) {
29
+ process.stdout.write(` ${c(ANSI.cyan, "●")} ${c(ANSI.bold, t.config.targetId)} ${c(ANSI.dim, `← ${t.source}`)}\n`);
30
+ }
31
+ process.stdout.write("\n");
32
+ const results = [];
33
+ let passCount = 0;
34
+ let failCount = 0;
35
+ let totalTools = 0;
36
+ let totalPrompts = 0;
37
+ let totalResources = 0;
38
+ for (const t of targets) {
39
+ process.stdout.write(` ${c(ANSI.dim, "⟳")} Checking ${c(ANSI.bold, t.config.targetId)}...`);
40
+ try {
41
+ const artifact = await runTarget(t.config, { invokeTools, securityCheck });
42
+ const toolsCheck = artifact.checks.find((ch) => ch.id === "tools");
43
+ const promptsCheck = artifact.checks.find((ch) => ch.id === "prompts");
44
+ const resourcesCheck = artifact.checks.find((ch) => ch.id === "resources");
45
+ const toolCount = toolsCheck?.evidence[0]?.itemCount ?? 0;
46
+ const promptCount = promptsCheck?.evidence[0]?.itemCount ?? 0;
47
+ const resourceCount = resourcesCheck?.evidence[0]?.itemCount ?? 0;
48
+ totalTools += toolCount;
49
+ totalPrompts += promptCount;
50
+ totalResources += resourceCount;
51
+ const diagnostics = [];
52
+ for (const check of artifact.checks) {
53
+ if (check.status === "fail" || check.status === "partial") {
54
+ diagnostics.push(`${check.id}: ${check.message}`);
55
+ }
56
+ }
57
+ const gateIcon = artifact.gate === "pass" ? c(ANSI.green, " ✓") : c(ANSI.red, " ✗");
58
+ process.stdout.write(`\r ${gateIcon} ${c(ANSI.bold, t.config.targetId)}${" ".repeat(Math.max(1, 40 - t.config.targetId.length))}`);
59
+ process.stdout.write(`${c(ANSI.dim, `${toolCount} tools, ${promptCount} prompts, ${resourceCount} resources`)}\n`);
60
+ if (artifact.fatalError) {
61
+ process.stdout.write(` ${c(ANSI.red, "→")} ${artifact.fatalError.split("\n")[0]}\n`);
62
+ }
63
+ else if (artifact.gate === "fail" && diagnostics.length > 0) {
64
+ process.stdout.write(` ${c(ANSI.dim, "→")} ${diagnostics[0]}\n`);
65
+ }
66
+ results.push({ targetId: t.config.targetId, gate: artifact.gate, toolCount, promptCount, resourceCount, diagnostics });
67
+ if (artifact.gate === "pass")
68
+ passCount++;
69
+ else
70
+ failCount++;
71
+ }
72
+ catch (error) {
73
+ const msg = error instanceof Error ? error.message : String(error);
74
+ let friendlyMsg = msg;
75
+ if (msg.includes("ENOENT") || msg.includes("not found")) {
76
+ const cmd = t.config.adapter === "http" ? t.config.url : t.config.command;
77
+ friendlyMsg = `Could not start server — "${cmd}" not found. Is it installed?`;
78
+ }
79
+ else if (msg.includes("ECONNREFUSED")) {
80
+ friendlyMsg = `Server is not running or refused the connection.`;
81
+ }
82
+ else if (msg.includes("timed out") || msg.includes("timeout")) {
83
+ friendlyMsg = `Server took too long to respond.`;
84
+ }
85
+ process.stdout.write(`\r ${c(ANSI.red, "✗")} ${c(ANSI.bold, t.config.targetId)}\n`);
86
+ process.stdout.write(` ${c(ANSI.red, friendlyMsg)}\n`);
87
+ results.push({ targetId: t.config.targetId, gate: "fail", toolCount: 0, promptCount: 0, resourceCount: 0, error: friendlyMsg, diagnostics: [] });
88
+ failCount++;
89
+ }
90
+ }
91
+ // ── Summary ──────────────────────────────────────────────────────────
92
+ process.stdout.write("\n");
93
+ if (failCount === 0) {
94
+ process.stdout.write(c(ANSI.green, ` ✓ All ${passCount} server${passCount === 1 ? "" : "s"} healthy`));
95
+ process.stdout.write(c(ANSI.dim, ` — ${totalTools} tools, ${totalPrompts} prompts, ${totalResources} resources\n`));
96
+ }
97
+ else {
98
+ process.stdout.write(c(ANSI.red, ` ✗ ${failCount} of ${passCount + failCount} server${passCount + failCount === 1 ? "" : "s"} failing`));
99
+ if (totalTools > 0 || totalPrompts > 0 || totalResources > 0) {
100
+ process.stdout.write(c(ANSI.dim, ` — ${totalTools} tools, ${totalPrompts} prompts, ${totalResources} resources found\n`));
101
+ }
102
+ else {
103
+ process.stdout.write("\n");
104
+ }
105
+ }
106
+ // Show diagnostics for failures or notable partials
107
+ const issues = results.filter((r) => r.diagnostics.length > 0 && !r.error);
108
+ if (issues.length > 0) {
109
+ process.stdout.write("\n");
110
+ for (const r of issues) {
111
+ process.stdout.write(` ${c(ANSI.yellow, r.targetId)}:\n`);
112
+ for (const d of r.diagnostics.slice(0, 3)) {
113
+ process.stdout.write(` ${c(ANSI.dim, "→")} ${d}\n`);
114
+ }
115
+ }
116
+ }
117
+ // ── Next step ────────────────────────────────────────────────────────
118
+ process.stdout.write("\n");
119
+ if (!invokeTools && totalTools > 0) {
120
+ process.stdout.write(c(ANSI.dim, ` Next: ${c(ANSI.cyan, `${bin} scan deep`)} to also test that tools run\n`));
121
+ }
122
+ else {
123
+ process.stdout.write(c(ANSI.dim, ` Run ${c(ANSI.cyan, `${bin} --help`)} for more commands\n`));
124
+ }
125
+ process.stdout.write("\n");
126
+ if (failCount > 0) {
127
+ process.exitCode = 1;
128
+ }
129
+ }
130
+ // ── Register ────────────────────────────────────────────────────────────────
131
+ export function registerScanCommands(program, bin) {
132
+ const scanCmd = program
133
+ .command("scan")
134
+ .description("Check all MCP servers in your Claude configs.")
135
+ .option("--config <path>", "Path to a specific MCP config file.")
136
+ .option("--security", "Run security analysis on tool schemas.")
137
+ .option("--no-color", "Disable colored output.");
138
+ // `scan` with no subcommand — basic scan
139
+ scanCmd.action(async (options) => {
140
+ await runScan(bin, options.config, false, options.security);
141
+ });
142
+ // `scan deep` — scan + invoke tools
143
+ scanCmd
144
+ .command("deep")
145
+ .description("Scan and also invoke safe tools to verify they execute.")
146
+ .option("--config <path>", "Path to a specific MCP config file.")
147
+ .option("--security", "Run security analysis on tool schemas.")
148
+ .action(async (options) => {
149
+ // Inherit parent config option if set
150
+ const parentConfig = scanCmd.opts().config;
151
+ const parentSecurity = scanCmd.opts().security;
152
+ await runScan(bin, options.config ?? parentConfig, true, options.security ?? parentSecurity ?? true);
153
+ });
154
+ }
155
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/commands/scan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerScoreCommands(program: Command): void;
@@ -0,0 +1,88 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { generateBadgeSvg } from "../badge.js";
4
+ import { runTarget, writeRunArtifact, } from "../index.js";
5
+ import { defaultRunsDirectory } from "../storage.js";
6
+ import { ANSI, c, formatOutput, targetFromCommand, writeOutput } from "./helpers.js";
7
+ export function registerScoreCommands(program) {
8
+ // ── score ────────────────────────────────────────────────────────────
9
+ program
10
+ .command("score")
11
+ .passThroughOptions()
12
+ .description("Score an MCP server's health (0-100).")
13
+ .argument("<command...>", "Server command and arguments to run.")
14
+ .option("--format <format>", "terminal, json, junit, markdown, html, or sarif", "terminal")
15
+ .option("--output <file>", "Write to file instead of stdout.")
16
+ .option("--no-color", "Disable colored output.")
17
+ .action(async (commandArgs, options) => {
18
+ const target = targetFromCommand(commandArgs);
19
+ process.stdout.write(`${c(ANSI.dim, "⟳")} Scoring ${c(ANSI.bold, target.targetId)}...\n\n`);
20
+ const artifact = await runTarget(target, { invokeTools: true, securityCheck: true });
21
+ await writeRunArtifact(artifact, defaultRunsDirectory(process.cwd()));
22
+ if (options.format !== "terminal") {
23
+ const output = formatOutput(artifact, options.format);
24
+ await writeOutput(output, options.format, options.output);
25
+ return;
26
+ }
27
+ const score = artifact.healthScore;
28
+ if (!score) {
29
+ process.stdout.write(" Could not compute health score.\n\n");
30
+ return;
31
+ }
32
+ const gradeColor = score.grade === "A" || score.grade === "B" ? ANSI.green
33
+ : score.grade === "C" ? ANSI.yellow
34
+ : ANSI.red;
35
+ process.stdout.write(c(ANSI.bold, ` MCP Health Score: ${c(gradeColor, `${score.overall}/100`)} (${c(gradeColor, score.grade)})\n\n`));
36
+ for (const dim of score.dimensions) {
37
+ const filled = Math.round(dim.score / 5);
38
+ const empty = 20 - filled;
39
+ const bar = "█".repeat(filled) + "░".repeat(empty);
40
+ const dimColor = dim.score >= 80 ? ANSI.green : dim.score >= 60 ? ANSI.yellow : ANSI.red;
41
+ const weightPct = Math.round(dim.weight * 100);
42
+ process.stdout.write(` ${dim.name.padEnd(22)} ${c(dimColor, bar)} ${String(dim.score).padStart(3)} ${c(ANSI.dim, `(weight: ${weightPct}%)`)}\n`);
43
+ }
44
+ process.stdout.write("\n");
45
+ // Show details for dimensions that aren't perfect
46
+ for (const dim of score.dimensions) {
47
+ if (dim.score < 100 && dim.details.length > 0) {
48
+ process.stdout.write(` ${c(ANSI.dim, dim.name + ":")}\n`);
49
+ for (const detail of dim.details) {
50
+ process.stdout.write(` ${c(ANSI.dim, "→")} ${detail}\n`);
51
+ }
52
+ }
53
+ }
54
+ process.stdout.write("\n");
55
+ if (artifact.gate === "fail") {
56
+ process.exitCode = 1;
57
+ }
58
+ });
59
+ // ── badge ───────────────────────────────────────────────────────────
60
+ program
61
+ .command("badge")
62
+ .passThroughOptions()
63
+ .description("Generate an SVG health score badge for your README.")
64
+ .argument("<command...>", "Server command and arguments to run.")
65
+ .option("--output <file>", "Write SVG to file (default: stdout).")
66
+ .option("--label <text>", "Badge label text.", "MCP Health")
67
+ .action(async (commandArgs, options) => {
68
+ const target = targetFromCommand(commandArgs);
69
+ process.stderr.write(`${c(ANSI.dim, "⟳")} Scoring ${c(ANSI.bold, target.targetId)}...\n`);
70
+ const artifact = await runTarget(target, { invokeTools: true, securityCheck: true });
71
+ const score = artifact.healthScore;
72
+ if (!score) {
73
+ process.stderr.write(" Could not compute health score.\n");
74
+ process.exitCode = 1;
75
+ return;
76
+ }
77
+ const svg = generateBadgeSvg({ score: score.overall, grade: score.grade, label: options.label });
78
+ if (options.output) {
79
+ await mkdir(path.dirname(options.output), { recursive: true });
80
+ await writeFile(options.output, svg, "utf8");
81
+ process.stderr.write(` Badge written to ${options.output}\n`);
82
+ }
83
+ else {
84
+ process.stdout.write(svg);
85
+ }
86
+ });
87
+ }
88
+ //# sourceMappingURL=score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.js","sourceRoot":"","sources":["../../../src/commands/score.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EACL,SAAS,EACT,gBAAgB,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAErF,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,wEAAwE;IAExE,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,kBAAkB,EAAE;SACpB,WAAW,CAAC,uCAAuC,CAAC;SACpD,QAAQ,CAAC,cAAc,EAAE,sCAAsC,CAAC;SAChE,MAAM,CAAC,mBAAmB,EAAE,iDAAiD,EAAE,UAAU,CAAC;SAC1F,MAAM,CAAC,iBAAiB,EAAE,kCAAkC,CAAC;SAC7D,MAAM,CAAC,YAAY,EAAE,yBAAyB,CAAC;SAC/C,MAAM,CAAC,KAAK,EAAE,WAAqB,EAAE,OAA4C,EAAE,EAAE;QACpF,MAAM,MAAM,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5F,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACrF,MAAM,gBAAgB,CAAC,QAAQ,EAAE,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAEtE,IAAI,OAAO,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;YAClC,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAuE,CAAC,CAAC;YACvH,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;YAC1D,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,KAAK,GAAG,IAAI,KAAK,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK;YACxE,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM;gBACnC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAEb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,uBAAuB,CAAC,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAEvI,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACnC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,EAAE,GAAG,MAAM,CAAC;YAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACnD,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;YACzF,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;YAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,SAAS,IAAI,CAAC,IAAI,CAAC,CAAC;QACrJ,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE3B,kDAAkD;QAClD,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACnC,IAAI,GAAG,CAAC,KAAK,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;gBAC3D,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;oBACjC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,IAAI,CAAC,CAAC;gBAC9D,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE3B,IAAI,QAAQ,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC7B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,uEAAuE;IAEvE,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,kBAAkB,EAAE;SACpB,WAAW,CAAC,qDAAqD,CAAC;SAClE,QAAQ,CAAC,cAAc,EAAE,sCAAsC,CAAC;SAChE,MAAM,CAAC,iBAAiB,EAAE,sCAAsC,CAAC;SACjE,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,EAAE,YAAY,CAAC;SAC3D,MAAM,CAAC,KAAK,EAAE,WAAqB,EAAE,OAA2C,EAAE,EAAE;QACnF,MAAM,MAAM,GAAG,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC1F,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAErF,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;YAC5D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAEjG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/D,MAAM,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;YAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QACjE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerServeCommands(program: Command): void;
@@ -0,0 +1,10 @@
1
+ export function registerServeCommands(program) {
2
+ program
3
+ .command("serve")
4
+ .description("Start as an MCP server for AI agents.")
5
+ .action(async () => {
6
+ const { startServer } = await import("../server.js");
7
+ await startServer();
8
+ });
9
+ }
10
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../../../src/commands/serve.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,uCAAuC,CAAC;SACpD,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACrD,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerSuggestCommands(program: Command): void;
@@ -0,0 +1,126 @@
1
+ import { scanForTargets } from "../discovery.js";
2
+ import { detectEnvironment } from "../environment.js";
3
+ import { ANSI, c } from "./helpers.js";
4
+ export function registerSuggestCommands(program) {
5
+ program
6
+ .command("suggest")
7
+ .description("Detect your stack and recommend MCP servers.")
8
+ .option("--cwd <path>", "Directory to scan for project signals.", process.cwd())
9
+ .option("--no-color", "Disable colored output.")
10
+ .action(async (options) => {
11
+ process.stdout.write(`${c(ANSI.dim, "⟳")} Scanning environment...\n\n`);
12
+ // 1. Current MCP servers
13
+ const targets = await scanForTargets();
14
+ if (targets.length > 0) {
15
+ process.stdout.write(c(ANSI.bold, " Configured MCP Servers\n"));
16
+ for (const t of targets) {
17
+ const detail = t.config.adapter === "http"
18
+ ? t.config.url
19
+ : `${t.config.command} ${t.config.args.join(" ")}`;
20
+ process.stdout.write(` ${c(ANSI.cyan, "●")} ${c(ANSI.bold, t.config.targetId)} ${c(ANSI.dim, detail)} ${c(ANSI.dim, `← ${t.source}`)}\n`);
21
+ }
22
+ }
23
+ else {
24
+ process.stdout.write(` ${c(ANSI.yellow, "No MCP servers configured.")}\n`);
25
+ }
26
+ process.stdout.write("\n");
27
+ // 2. Environment detection
28
+ const env = await detectEnvironment(options.cwd);
29
+ const hasSignals = env.languages.length > 0 || env.frameworks.length > 0 || env.databases.length > 0;
30
+ if (hasSignals) {
31
+ process.stdout.write(c(ANSI.bold, " Detected Stack\n"));
32
+ if (env.languages.length > 0)
33
+ process.stdout.write(` ${c(ANSI.dim, "Languages:")} ${env.languages.join(", ")}\n`);
34
+ if (env.frameworks.length > 0)
35
+ process.stdout.write(` ${c(ANSI.dim, "Frameworks:")} ${env.frameworks.join(", ")}\n`);
36
+ if (env.databases.length > 0)
37
+ process.stdout.write(` ${c(ANSI.dim, "Databases:")} ${env.databases.join(", ")}\n`);
38
+ if (env.cloud.length > 0)
39
+ process.stdout.write(` ${c(ANSI.dim, "Cloud:")} ${env.cloud.join(", ")}\n`);
40
+ if (env.cicd.length > 0)
41
+ process.stdout.write(` ${c(ANSI.dim, "CI/CD:")} ${env.cicd.join(", ")}\n`);
42
+ if (env.services.length > 0)
43
+ process.stdout.write(` ${c(ANSI.dim, "Services:")} ${env.services.join(", ")}\n`);
44
+ }
45
+ else {
46
+ process.stdout.write(` ${c(ANSI.dim, "No recognizable project signals in")} ${options.cwd}\n`);
47
+ }
48
+ process.stdout.write("\n");
49
+ // 3. MCP Registry — filtered by detected stack
50
+ try {
51
+ const response = await fetch("https://registry.modelcontextprotocol.io/v0/servers", {
52
+ signal: AbortSignal.timeout(10_000),
53
+ headers: { "Accept": "application/json" },
54
+ });
55
+ if (response.ok) {
56
+ const data = await response.json();
57
+ const raw = Array.isArray(data) ? data : (typeof data === "object" && data !== null
58
+ ? (data["servers"] ?? data["results"] ?? data["items"])
59
+ : null);
60
+ if (Array.isArray(raw)) {
61
+ const allEntries = raw;
62
+ // Build keyword set from detected environment
63
+ const keywords = new Set([
64
+ ...env.languages, ...env.frameworks, ...env.databases,
65
+ ...env.services, ...env.cloud, ...env.cicd,
66
+ ].map(s => s.toLowerCase()));
67
+ // Also add common aliases
68
+ const aliases = {
69
+ typescript: ["ts"], javascript: ["js", "node"], python: ["py"],
70
+ postgresql: ["postgres"], mongodb: ["mongo"], github: ["gh"],
71
+ };
72
+ for (const kw of [...keywords]) {
73
+ for (const [full, abbrs] of Object.entries(aliases)) {
74
+ if (kw === full)
75
+ for (const a of abbrs)
76
+ keywords.add(a);
77
+ if (abbrs.includes(kw))
78
+ keywords.add(full);
79
+ }
80
+ }
81
+ // Score each entry against detected stack
82
+ const scored = allEntries.map(entry => {
83
+ const srv = (typeof entry["server"] === "object" && entry["server"] !== null ? entry["server"] : entry);
84
+ const name = typeof srv["name"] === "string" ? srv["name"] : (typeof entry["name"] === "string" ? entry["name"] : "unknown");
85
+ const desc = typeof srv["description"] === "string" ? srv["description"] : (typeof entry["description"] === "string" ? entry["description"] : "");
86
+ const text = `${name} ${desc}`.toLowerCase();
87
+ const matches = [...keywords].filter(k => text.includes(k)).length;
88
+ return { name, desc, matches };
89
+ });
90
+ const recommended = scored.filter(s => s.matches > 0).sort((a, b) => b.matches - a.matches);
91
+ const others = scored.filter(s => s.matches === 0);
92
+ if (recommended.length > 0) {
93
+ process.stdout.write(c(ANSI.bold, " Recommended for Your Stack\n"));
94
+ for (const r of recommended.slice(0, 10)) {
95
+ process.stdout.write(` ${c(ANSI.green, "★")} ${c(ANSI.bold, r.name)}${r.desc ? ` ${c(ANSI.dim, "—")} ${r.desc}` : ""}\n`);
96
+ }
97
+ if (recommended.length > 10) {
98
+ process.stdout.write(` ${c(ANSI.dim, `... and ${recommended.length - 10} more matches`)}\n`);
99
+ }
100
+ }
101
+ else {
102
+ process.stdout.write(c(ANSI.bold, " MCP Registry\n"));
103
+ for (const s of scored.slice(0, 10)) {
104
+ process.stdout.write(` ${c(ANSI.dim, "●")} ${c(ANSI.bold, s.name)}${s.desc ? ` ${c(ANSI.dim, "—")} ${s.desc}` : ""}\n`);
105
+ }
106
+ }
107
+ if (others.length > 0) {
108
+ process.stdout.write(`\n ${c(ANSI.dim, `${others.length} more servers at registry.modelcontextprotocol.io`)}\n`);
109
+ }
110
+ }
111
+ else {
112
+ process.stdout.write(` ${c(ANSI.dim, "Registry returned unexpected format.")}\n`);
113
+ }
114
+ }
115
+ else {
116
+ process.stdout.write(` ${c(ANSI.dim, `Registry returned HTTP ${response.status}`)}\n`);
117
+ }
118
+ }
119
+ catch (error) {
120
+ const msg = error instanceof Error ? error.message : String(error);
121
+ process.stdout.write(` ${c(ANSI.yellow, "Could not reach registry:")} ${msg}\n`);
122
+ }
123
+ process.stdout.write("\n");
124
+ });
125
+ }
126
+ //# sourceMappingURL=suggest.js.map