@kryptosai/mcp-observatory 1.28.2 → 1.29.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.
- package/CONTRIBUTORS.md +4 -1
- package/README.md +45 -9
- package/README.zh-CN.md +591 -0
- package/dist/src/adapters/http.js +13 -4
- package/dist/src/adapters/http.js.map +1 -1
- package/dist/src/checks/security.js +33 -65
- package/dist/src/checks/security.js.map +1 -1
- package/dist/src/cli.js +9 -1
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/helpers.d.ts +2 -1
- package/dist/src/commands/helpers.js +9 -1
- package/dist/src/commands/helpers.js.map +1 -1
- package/dist/src/commands/legacy.js +3 -0
- package/dist/src/commands/legacy.js.map +1 -1
- package/dist/src/commands/risk-graph.d.ts +2 -0
- package/dist/src/commands/risk-graph.js +64 -0
- package/dist/src/commands/risk-graph.js.map +1 -0
- package/dist/src/commands/score.js.map +1 -1
- package/dist/src/commands/watch.js +13 -8
- package/dist/src/commands/watch.js.map +1 -1
- package/dist/src/discovery.js +32 -26
- package/dist/src/discovery.js.map +1 -1
- package/dist/src/reporters/terminal.js +2 -1
- package/dist/src/reporters/terminal.js.map +1 -1
- package/dist/src/risk-graph.d.ts +64 -0
- package/dist/src/risk-graph.js +452 -0
- package/dist/src/risk-graph.js.map +1 -0
- package/dist/src/server.d.ts +1 -3
- package/dist/src/server.js +29 -645
- package/dist/src/server.js.map +1 -1
- package/dist/src/storage.js +13 -9
- package/dist/src/storage.js.map +1 -1
- package/dist/src/telemetry.d.ts +7 -0
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/tools/check-server.d.ts +27 -0
- package/dist/src/tools/check-server.js +46 -0
- package/dist/src/tools/check-server.js.map +1 -0
- package/dist/src/tools/ci-report.d.ts +21 -0
- package/dist/src/tools/ci-report.js +38 -0
- package/dist/src/tools/ci-report.js.map +1 -0
- package/dist/src/tools/diff-runs.d.ts +28 -0
- package/dist/src/tools/diff-runs.js +39 -0
- package/dist/src/tools/diff-runs.js.map +1 -0
- package/dist/src/tools/get-history.d.ts +21 -0
- package/dist/src/tools/get-history.js +37 -0
- package/dist/src/tools/get-history.js.map +1 -0
- package/dist/src/tools/get-last-run.d.ts +21 -0
- package/dist/src/tools/get-last-run.js +47 -0
- package/dist/src/tools/get-last-run.js.map +1 -0
- package/dist/src/tools/helpers.d.ts +8 -0
- package/dist/src/tools/helpers.js +32 -0
- package/dist/src/tools/helpers.js.map +1 -0
- package/dist/src/tools/lock-verify.d.ts +21 -0
- package/dist/src/tools/lock-verify.js +47 -0
- package/dist/src/tools/lock-verify.js.map +1 -0
- package/dist/src/tools/record.d.ts +23 -0
- package/dist/src/tools/record.js +48 -0
- package/dist/src/tools/record.js.map +1 -0
- package/dist/src/tools/replay.d.ts +21 -0
- package/dist/src/tools/replay.js +54 -0
- package/dist/src/tools/replay.js.map +1 -0
- package/dist/src/tools/scan.d.ts +18 -0
- package/dist/src/tools/scan.js +43 -0
- package/dist/src/tools/scan.js.map +1 -0
- package/dist/src/tools/score-server.d.ts +23 -0
- package/dist/src/tools/score-server.js +53 -0
- package/dist/src/tools/score-server.js.map +1 -0
- package/dist/src/tools/suggest-servers.d.ts +21 -0
- package/dist/src/tools/suggest-servers.js +114 -0
- package/dist/src/tools/suggest-servers.js.map +1 -0
- package/dist/src/tools/verify-tool.d.ts +25 -0
- package/dist/src/tools/verify-tool.js +48 -0
- package/dist/src/tools/verify-tool.js.map +1 -0
- package/dist/src/tools/watch.d.ts +23 -0
- package/dist/src/tools/watch.js +67 -0
- package/dist/src/tools/watch.js.map +1 -0
- package/dist/src/utils/security.d.ts +3 -0
- package/dist/src/utils/security.js +37 -0
- package/dist/src/utils/security.js.map +1 -0
- package/dist/src/validate.d.ts +1 -0
- package/dist/src/validate.js +119 -35
- package/dist/src/validate.js.map +1 -1
- package/docs/agent-tasks.md +1 -1
- package/docs/commercial-boundary.md +9 -1
- package/docs/contributor-recognition.md +2 -0
- package/docs/demo.svg +1 -59
- package/docs/mcp-receipts.md +9 -0
- package/docs/mcp-server-safety-index.md +19 -16
- package/docs/metrics-dashboard.md +17 -1
- package/docs/private-mcp-fleet-risk-graph.md +69 -0
- package/docs/receipt-graph.md +118 -31
- package/docs/safety-index/artifacts/chrome-devtools-mcp-server.json +1745 -0
- package/docs/safety-index/artifacts/chrome-devtools-mcp-server.md +169 -0
- package/docs/safety-index/artifacts/filesystem-mcp-server.json +786 -0
- package/docs/safety-index/artifacts/filesystem-mcp-server.md +169 -0
- package/docs/safety-index/mcp-risk-graph.html +167 -0
- package/docs/safety-index/mcp-risk-graph.json +669 -0
- package/docs/safety-index/mcp-risk-graph.md +47 -0
- package/docs/safety-index/targets.json +40 -0
- package/docs/sample-private-fleet-risk-graph.md +90 -0
- package/package.json +21 -11
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "lock_verify";
|
|
3
|
+
export declare const description = "Verify that live MCP servers still match a previously saved lock file. Detects schema drift, added/removed tools, and breaking changes.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
config: z.ZodOptional<z.ZodString>;
|
|
6
|
+
};
|
|
7
|
+
export declare function handler({ config }: {
|
|
8
|
+
config?: string;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
content: {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
isError?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
content: {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
isError: boolean;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { scanForTargets } from "../discovery.js";
|
|
3
|
+
import { readLockFile, verifyAgainstLock } from "../lockfile.js";
|
|
4
|
+
import { runTarget } from "../runner.js";
|
|
5
|
+
import { logRequest } from "./helpers.js";
|
|
6
|
+
export const name = "lock_verify";
|
|
7
|
+
export const description = "Verify that live MCP servers still match a previously saved lock file. Detects schema drift, added/removed tools, and breaking changes.";
|
|
8
|
+
export const schema = {
|
|
9
|
+
config: z.string().optional().describe("Path to MCP config file."),
|
|
10
|
+
};
|
|
11
|
+
export async function handler({ config }) {
|
|
12
|
+
const startMs = Date.now();
|
|
13
|
+
try {
|
|
14
|
+
const lockFile = await readLockFile();
|
|
15
|
+
const targets = await scanForTargets(config);
|
|
16
|
+
const results = [];
|
|
17
|
+
let anyFailed = false;
|
|
18
|
+
for (const t of targets) {
|
|
19
|
+
const lockEntry = lockFile.servers.find(s => s.targetId === t.config.targetId);
|
|
20
|
+
if (!lockEntry)
|
|
21
|
+
continue;
|
|
22
|
+
const artifact = await runTarget(t.config);
|
|
23
|
+
const result = verifyAgainstLock(lockEntry, artifact);
|
|
24
|
+
if (result.passed) {
|
|
25
|
+
results.push(`\u2713 ${t.config.targetId}: no drift`);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
anyFailed = true;
|
|
29
|
+
results.push(`\u2717 ${t.config.targetId}: ${result.drift.length} changes`);
|
|
30
|
+
for (const d of result.drift) {
|
|
31
|
+
results.push(` - ${d.category}: ${d.name} \u2014 ${d.change}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (results.length === 0) {
|
|
36
|
+
results.push("No servers in lock file match discovered targets.");
|
|
37
|
+
}
|
|
38
|
+
logRequest("lock_verify", startMs, anyFailed);
|
|
39
|
+
return { content: [{ type: "text", text: results.join("\n") }] };
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
43
|
+
logRequest("lock_verify", startMs, true);
|
|
44
|
+
return { content: [{ type: "text", text: `Lock verify failed: ${msg}` }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=lock-verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock-verify.js","sourceRoot":"","sources":["../../../src/tools/lock-verify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "record";
|
|
3
|
+
export declare const description = "Use this to capture a baseline of a working MCP server. Records all JSON-RPC traffic to a cassette file that can be replayed offline (no server needed) or used to verify future versions haven't broken anything. Like VCR for MCP.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
command: z.ZodString;
|
|
6
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
+
};
|
|
8
|
+
export declare function handler({ command, args }: {
|
|
9
|
+
command: string;
|
|
10
|
+
args?: string[];
|
|
11
|
+
}): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
isError: boolean;
|
|
17
|
+
} | {
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
isError?: undefined;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defaultCassettesDirectory, saveCassette } from "../cassette.js";
|
|
3
|
+
import { runTargetRecording } from "../runner.js";
|
|
4
|
+
import { errorMessage } from "../utils/errors.js";
|
|
5
|
+
import { validateArgs, validateCommand } from "../utils/security.js";
|
|
6
|
+
import { formatRun, logRequest } from "./helpers.js";
|
|
7
|
+
export const name = "record";
|
|
8
|
+
export const description = "Use this to capture a baseline of a working MCP server. Records all JSON-RPC traffic to a cassette file that can be replayed offline (no server needed) or used to verify future versions haven't broken anything. Like VCR for MCP.";
|
|
9
|
+
export const schema = {
|
|
10
|
+
command: z.string().describe("The command to launch the MCP server."),
|
|
11
|
+
args: z.array(z.string()).optional().describe("Additional arguments for the command."),
|
|
12
|
+
};
|
|
13
|
+
export async function handler({ command, args }) {
|
|
14
|
+
const startMs = Date.now();
|
|
15
|
+
try {
|
|
16
|
+
validateCommand(command);
|
|
17
|
+
validateArgs(args ?? []);
|
|
18
|
+
const target = {
|
|
19
|
+
targetId: command,
|
|
20
|
+
adapter: "local-process",
|
|
21
|
+
command,
|
|
22
|
+
args: args ?? [],
|
|
23
|
+
timeoutMs: 15_000,
|
|
24
|
+
};
|
|
25
|
+
const { artifact, cassetteEntries } = await runTargetRecording(target, { invokeTools: true });
|
|
26
|
+
if (!cassetteEntries || cassetteEntries.length === 0) {
|
|
27
|
+
return { content: [{ type: "text", text: "No traffic recorded." }], isError: true };
|
|
28
|
+
}
|
|
29
|
+
const cassette = {
|
|
30
|
+
version: 1,
|
|
31
|
+
targetId: target.targetId,
|
|
32
|
+
recordedAt: new Date().toISOString(),
|
|
33
|
+
transport: "stdio",
|
|
34
|
+
entries: cassetteEntries,
|
|
35
|
+
};
|
|
36
|
+
const cassettePath = await saveCassette(cassette, defaultCassettesDirectory());
|
|
37
|
+
logRequest("record", startMs);
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: `${formatRun(artifact)}\n\nCassette saved: ${cassettePath}\n${cassetteEntries.length} entries recorded.\n\nReplay offline: replay({ cassette: "${cassettePath}" })\nVerify live: verify({ cassette: "${cassettePath}", command: "${command}" })` }],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const msg = errorMessage(error);
|
|
44
|
+
logRequest("record", startMs, true);
|
|
45
|
+
return { content: [{ type: "text", text: `Error recording: ${msg}` }], isError: true };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../../src/tools/record.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,sOAAsO,CAAC;AAClQ,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACrE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;CACvF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAwC;IACnF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACzB,MAAM,MAAM,GAAG;YACb,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,eAAwB;YACjC,OAAO;YACP,IAAI,EAAE,IAAI,IAAI,EAAE;YAChB,SAAS,EAAE,MAAM;SAClB,CAAC;QACF,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,GAAG,MAAM,kBAAkB,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;QAE9F,IAAI,CAAC,eAAe,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrD,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,sBAAsB,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC/F,CAAC;QAED,MAAM,QAAQ,GAAa;YACzB,OAAO,EAAE,CAAC;YACV,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACpC,SAAS,EAAE,OAAO;YAClB,OAAO,EAAE,eAAe;SACzB,CAAC;QAEF,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,yBAAyB,EAAE,CAAC,CAAC;QAC/E,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9B,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,uBAAuB,YAAY,KAAK,eAAe,CAAC,MAAM,6DAA6D,YAAY,0CAA0C,YAAY,gBAAgB,OAAO,MAAM,EAAE,CAAC;SAC7R,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACpC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,GAAG,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAClG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "replay";
|
|
3
|
+
export declare const description = "Use this to test a server without running it. Replays a previously recorded cassette offline and runs all checks against the recorded responses. Useful in CI or when the live server is unavailable.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
cassette: z.ZodString;
|
|
6
|
+
};
|
|
7
|
+
export declare function handler({ cassette: cassettePath }: {
|
|
8
|
+
cassette: string;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
content: {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
isError?: undefined;
|
|
15
|
+
} | {
|
|
16
|
+
content: {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
isError: boolean;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defaultCassettesDirectory, loadCassette } from "../cassette.js";
|
|
3
|
+
import { TOOL_VERSION } from "../version.js";
|
|
4
|
+
import { errorMessage } from "../utils/errors.js";
|
|
5
|
+
import { validatePath } from "../utils/security.js";
|
|
6
|
+
import { logRequest } from "./helpers.js";
|
|
7
|
+
export const name = "replay";
|
|
8
|
+
export const description = "Use this to test a server without running it. Replays a previously recorded cassette offline and runs all checks against the recorded responses. Useful in CI or when the live server is unavailable.";
|
|
9
|
+
export const schema = {
|
|
10
|
+
cassette: z.string().describe("Path to a cassette JSON file."),
|
|
11
|
+
};
|
|
12
|
+
export async function handler({ cassette: cassettePath }) {
|
|
13
|
+
const startMs = Date.now();
|
|
14
|
+
try {
|
|
15
|
+
const cassettesDir = defaultCassettesDirectory();
|
|
16
|
+
validatePath(cassettePath, cassettesDir);
|
|
17
|
+
const { ReplayTransport } = await import("../transport/replay-transport.js");
|
|
18
|
+
const { Client } = await import("@modelcontextprotocol/sdk/client/index.js");
|
|
19
|
+
const { runToolsCheck } = await import("../checks/tools.js");
|
|
20
|
+
const { runPromptsCheck } = await import("../checks/prompts.js");
|
|
21
|
+
const { runResourcesCheck } = await import("../checks/resources.js");
|
|
22
|
+
const { runToolsInvokeCheck } = await import("../checks/tools-invoke.js");
|
|
23
|
+
const cassette = await loadCassette(cassettePath);
|
|
24
|
+
const replayTransport = new ReplayTransport(cassette.entries);
|
|
25
|
+
const client = new Client({ name: "mcp-observatory", version: TOOL_VERSION }, { capabilities: {} });
|
|
26
|
+
await client.connect(replayTransport);
|
|
27
|
+
const checkContext = {
|
|
28
|
+
client,
|
|
29
|
+
serverCapabilities: client.getServerCapabilities(),
|
|
30
|
+
target: { targetId: cassette.targetId, adapter: "local-process", command: "replay", args: [] },
|
|
31
|
+
timeoutMs: 10_000,
|
|
32
|
+
stderrLines: [],
|
|
33
|
+
};
|
|
34
|
+
const checks = [
|
|
35
|
+
(await runToolsCheck(checkContext)).result,
|
|
36
|
+
(await runPromptsCheck(checkContext)).result,
|
|
37
|
+
(await runResourcesCheck(checkContext)).result,
|
|
38
|
+
(await runToolsInvokeCheck(checkContext)).result,
|
|
39
|
+
];
|
|
40
|
+
await client.close();
|
|
41
|
+
const lines = [`Replay of ${cassette.targetId} (${cassette.entries.length} entries):\n`];
|
|
42
|
+
for (const check of checks) {
|
|
43
|
+
lines.push(` [${check.status}] ${check.id}: ${check.message}`);
|
|
44
|
+
}
|
|
45
|
+
logRequest("replay", startMs);
|
|
46
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const msg = errorMessage(error);
|
|
50
|
+
logRequest("replay", startMs, true);
|
|
51
|
+
return { content: [{ type: "text", text: `Error replaying: ${msg}` }], isError: true };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../../src/tools/replay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,CAAC,MAAM,IAAI,GAAG,QAAQ,CAAC;AAC7B,MAAM,CAAC,MAAM,WAAW,GAAG,uMAAuM,CAAC;AACnO,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,+BAA+B,CAAC;CAC/D,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAwB;IAC5E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,yBAAyB,EAAE,CAAC;QACjD,YAAY,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;QACzC,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,kCAAkC,CAAC,CAAC;QAC7E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,2CAA2C,CAAC,CAAC;QAC7E,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC7D,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;QACjE,MAAM,EAAE,iBAAiB,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;QACrE,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;QAE1E,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,YAAY,CAAC,CAAC;QAClD,MAAM,eAAe,GAAG,IAAI,eAAe,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,iBAAiB,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAC,CAAC;QACpG,MAAM,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAEtC,MAAM,YAAY,GAAG;YACnB,MAAM;YACN,kBAAkB,EAAE,MAAM,CAAC,qBAAqB,EAAE;YAClD,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,eAAwB,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAc,EAAE;YACnH,SAAS,EAAE,MAAM;YACjB,WAAW,EAAE,EAAc;SAC5B,CAAC;QAEF,MAAM,MAAM,GAAG;YACb,CAAC,MAAM,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM;YAC1C,CAAC,MAAM,eAAe,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM;YAC5C,CAAC,MAAM,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM;YAC9C,CAAC,MAAM,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM;SACjD,CAAC;QACF,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QAErB,MAAM,KAAK,GAAG,CAAC,aAAa,QAAQ,CAAC,QAAQ,KAAK,QAAQ,CAAC,OAAO,CAAC,MAAM,cAAc,CAAC,CAAC;QACzF,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAClE,CAAC;QACD,UAAU,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,UAAU,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACpC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,oBAAoB,GAAG,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAClG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "scan";
|
|
3
|
+
export declare const description = "Use this to check if all your MCP servers are healthy. Auto-discovers servers from Claude config files, connects to each one, and verifies tools/prompts/resources respond correctly. Use with deep=true to also invoke tools and confirm they actually execute. Returns pass/fail status for every server.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
config: z.ZodOptional<z.ZodString>;
|
|
6
|
+
deep: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
security: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
};
|
|
9
|
+
export declare function handler({ config, deep, security }: {
|
|
10
|
+
config?: string;
|
|
11
|
+
deep?: boolean;
|
|
12
|
+
security?: boolean;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { scanForTargets } from "../discovery.js";
|
|
3
|
+
import { runTarget } from "../runner.js";
|
|
4
|
+
import { errorMessage } from "../utils/errors.js";
|
|
5
|
+
import { formatRun, logRequest } from "./helpers.js";
|
|
6
|
+
export const name = "scan";
|
|
7
|
+
export const description = "Use this to check if all your MCP servers are healthy. Auto-discovers servers from Claude config files, connects to each one, and verifies tools/prompts/resources respond correctly. Use with deep=true to also invoke tools and confirm they actually execute. Returns pass/fail status for every server.";
|
|
8
|
+
export const schema = {
|
|
9
|
+
config: z.string().optional().describe("Path to a specific MCP config file. If omitted, scans default locations."),
|
|
10
|
+
deep: z.boolean().optional().describe("Also invoke safe tools to verify they execute."),
|
|
11
|
+
security: z.boolean().optional().describe("Run security analysis on tool schemas."),
|
|
12
|
+
};
|
|
13
|
+
export async function handler({ config, deep, security }) {
|
|
14
|
+
const startMs = Date.now();
|
|
15
|
+
const targets = await scanForTargets(config);
|
|
16
|
+
if (targets.length === 0) {
|
|
17
|
+
logRequest("scan", startMs);
|
|
18
|
+
return { content: [{ type: "text", text: "No MCP server configs found." }] };
|
|
19
|
+
}
|
|
20
|
+
const opts = {};
|
|
21
|
+
if (deep)
|
|
22
|
+
opts.invokeTools = true;
|
|
23
|
+
if (security)
|
|
24
|
+
opts.securityCheck = true;
|
|
25
|
+
const lines = [`Discovered ${targets.length} server(s):\n`];
|
|
26
|
+
for (const t of targets) {
|
|
27
|
+
if (t.config.targetId === "mcp-observatory")
|
|
28
|
+
continue;
|
|
29
|
+
lines.push(`--- ${t.config.targetId} (from ${t.source}) ---`);
|
|
30
|
+
try {
|
|
31
|
+
const artifact = await runTarget(t.config, opts);
|
|
32
|
+
lines.push(formatRun(artifact));
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
const msg = errorMessage(error);
|
|
36
|
+
lines.push(` Error: ${msg}`);
|
|
37
|
+
}
|
|
38
|
+
lines.push("");
|
|
39
|
+
}
|
|
40
|
+
logRequest("scan", startMs);
|
|
41
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan.js","sourceRoot":"","sources":["../../../src/tools/scan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,MAAM,IAAI,GAAG,MAAM,CAAC;AAC3B,MAAM,CAAC,MAAM,WAAW,GAAG,6SAA6S,CAAC;AACzU,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,0EAA0E,CAAC;IAClH,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,gDAAgD,CAAC;IACvF,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,wCAAwC,CAAC;CACpF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAA2D;IAC/G,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,8BAA8B,EAAE,CAAC,EAAE,CAAC;IACxF,CAAC;IAED,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,IAAI,IAAI;QAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;IAClC,IAAI,QAAQ;QAAE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;IAExC,MAAM,KAAK,GAAa,CAAC,cAAc,OAAO,CAAC,MAAM,eAAe,CAAC,CAAC;IACtE,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,iBAAiB;YAAE,SAAS;QAEtD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,QAAQ,UAAU,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC;QAC9D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACjD,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,EAAE,CAAC,CAAC;QAChC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC5B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "score_server";
|
|
3
|
+
export declare const description = "Use this to get a quick health grade for an MCP server. Runs all checks (capabilities, tool invocation, security) and returns a 0-100 score with A-F grade and detailed breakdown across protocol compliance, schema quality, security, reliability, and performance.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
command: z.ZodString;
|
|
6
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
|
+
};
|
|
8
|
+
export declare function handler({ command, args }: {
|
|
9
|
+
command: string;
|
|
10
|
+
args?: string[];
|
|
11
|
+
}): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
isError?: undefined;
|
|
17
|
+
} | {
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
isError: boolean;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runTarget } from "../runner.js";
|
|
3
|
+
import { defaultRunsDirectory, writeRunArtifact } from "../storage.js";
|
|
4
|
+
import { errorMessage } from "../utils/errors.js";
|
|
5
|
+
import { validateArgs, validateCommand } from "../utils/security.js";
|
|
6
|
+
import { formatRun, logRequest } from "./helpers.js";
|
|
7
|
+
export const name = "score_server";
|
|
8
|
+
export const description = "Use this to get a quick health grade for an MCP server. Runs all checks (capabilities, tool invocation, security) and returns a 0-100 score with A-F grade and detailed breakdown across protocol compliance, schema quality, security, reliability, and performance.";
|
|
9
|
+
export const schema = {
|
|
10
|
+
command: z.string().describe("The command to launch the MCP server."),
|
|
11
|
+
args: z.array(z.string()).optional().describe("Additional arguments for the command."),
|
|
12
|
+
};
|
|
13
|
+
export async function handler({ command, args }) {
|
|
14
|
+
const startMs = Date.now();
|
|
15
|
+
try {
|
|
16
|
+
validateCommand(command);
|
|
17
|
+
validateArgs(args ?? []);
|
|
18
|
+
const target = {
|
|
19
|
+
targetId: command,
|
|
20
|
+
adapter: "local-process",
|
|
21
|
+
command,
|
|
22
|
+
args: args ?? [],
|
|
23
|
+
timeoutMs: 15_000,
|
|
24
|
+
};
|
|
25
|
+
const artifact = await runTarget(target, { invokeTools: true, securityCheck: true });
|
|
26
|
+
const outDir = defaultRunsDirectory();
|
|
27
|
+
await writeRunArtifact(artifact, outDir);
|
|
28
|
+
const score = artifact.healthScore;
|
|
29
|
+
if (!score) {
|
|
30
|
+
logRequest("score_server", startMs);
|
|
31
|
+
return { content: [{ type: "text", text: `${formatRun(artifact)}\n\nCould not compute health score.` }] };
|
|
32
|
+
}
|
|
33
|
+
const lines = [
|
|
34
|
+
`MCP Health Score: ${score.overall}/100 (${score.grade})`,
|
|
35
|
+
"",
|
|
36
|
+
];
|
|
37
|
+
for (const dim of score.dimensions) {
|
|
38
|
+
lines.push(` ${dim.name}: ${dim.score}/100 (weight: ${Math.round(dim.weight * 100)}%)`);
|
|
39
|
+
for (const detail of dim.details) {
|
|
40
|
+
lines.push(` → ${detail}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
lines.push("", formatRun(artifact));
|
|
44
|
+
logRequest("score_server", startMs);
|
|
45
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
const msg = errorMessage(error);
|
|
49
|
+
logRequest("score_server", startMs, true);
|
|
50
|
+
return { content: [{ type: "text", text: `Error scoring server: ${msg}` }], isError: true };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=score-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score-server.js","sourceRoot":"","sources":["../../../src/tools/score-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAErD,MAAM,CAAC,MAAM,IAAI,GAAG,cAAc,CAAC;AACnC,MAAM,CAAC,MAAM,WAAW,GAAG,uQAAuQ,CAAC;AACnS,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACrE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,uCAAuC,CAAC;CACvF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,OAAO,EAAE,IAAI,EAAwC;IACnF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,eAAe,CAAC,OAAO,CAAC,CAAC;QACzB,YAAY,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;QACzB,MAAM,MAAM,GAAG;YACb,QAAQ,EAAE,OAAO;YACjB,OAAO,EAAE,eAAwB;YACjC,OAAO;YACP,IAAI,EAAE,IAAI,IAAI,EAAE;YAChB,SAAS,EAAE,MAAM;SAClB,CAAC;QACF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACrF,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAC;QACtC,MAAM,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAEzC,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,CAAC;QACnC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,UAAU,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;YACpC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,qCAAqC,EAAE,CAAC,EAAE,CAAC;QACrH,CAAC;QAED,MAAM,KAAK,GAAa;YACtB,qBAAqB,KAAK,CAAC,OAAO,SAAS,KAAK,CAAC,KAAK,GAAG;YACzD,EAAE;SACH,CAAC;QACF,KAAK,MAAM,GAAG,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,KAAK,iBAAiB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;YACzF,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjC,KAAK,CAAC,IAAI,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;QAEpC,UAAU,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;QACpC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAC1E,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAChC,UAAU,CAAC,cAAc,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QAC1C,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,yBAAyB,GAAG,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IACvG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "suggest_servers";
|
|
3
|
+
export declare const description = "Use this when setting up a project or wondering what MCP servers to add. Scans the working directory for languages, frameworks, databases, and cloud providers, lists currently configured servers, and cross-references the MCP registry to recommend servers you're missing.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
cwd: z.ZodOptional<z.ZodString>;
|
|
6
|
+
};
|
|
7
|
+
export declare function handler({ cwd }: {
|
|
8
|
+
cwd?: string;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
content: {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
isError: boolean;
|
|
15
|
+
} | {
|
|
16
|
+
content: {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
isError?: undefined;
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { scanForTargets } from "../discovery.js";
|
|
4
|
+
import { detectEnvironment } from "../environment.js";
|
|
5
|
+
import { errorMessage } from "../utils/errors.js";
|
|
6
|
+
import { logRequest } from "./helpers.js";
|
|
7
|
+
export const name = "suggest_servers";
|
|
8
|
+
export const description = "Use this when setting up a project or wondering what MCP servers to add. Scans the working directory for languages, frameworks, databases, and cloud providers, lists currently configured servers, and cross-references the MCP registry to recommend servers you're missing.";
|
|
9
|
+
export const schema = {
|
|
10
|
+
cwd: z.string().optional().describe("Working directory to scan for environment signals. Defaults to process.cwd()."),
|
|
11
|
+
};
|
|
12
|
+
export async function handler({ cwd }) {
|
|
13
|
+
const startMs = Date.now();
|
|
14
|
+
const workDir = cwd ?? process.cwd();
|
|
15
|
+
const resolvedDir = path.resolve(workDir);
|
|
16
|
+
const cwdRoot = path.resolve(process.cwd());
|
|
17
|
+
if (!resolvedDir.startsWith(cwdRoot + path.sep) && resolvedDir !== cwdRoot) {
|
|
18
|
+
logRequest("suggest_servers", startMs, true);
|
|
19
|
+
return { content: [{ type: "text", text: `Path "${cwd}" resolves outside the working directory.` }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
const sections = [];
|
|
22
|
+
try {
|
|
23
|
+
const targets = await scanForTargets();
|
|
24
|
+
if (targets.length > 0) {
|
|
25
|
+
const serverLines = targets.map((t) => {
|
|
26
|
+
const id = t.config.targetId;
|
|
27
|
+
const detail = t.config.adapter === "http"
|
|
28
|
+
? `(HTTP: ${t.config.url})`
|
|
29
|
+
: `(command: ${t.config.command} ${t.config.args.join(" ")})`;
|
|
30
|
+
return ` - ${id} ${detail} [source: ${t.source}]`;
|
|
31
|
+
});
|
|
32
|
+
sections.push(`## Currently Configured MCP Servers\n${serverLines.join("\n")}`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
sections.push("## Currently Configured MCP Servers\nNone found.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const msg = errorMessage(error);
|
|
40
|
+
sections.push(`## Currently Configured MCP Servers\nError scanning: ${msg}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const env = await detectEnvironment(workDir);
|
|
44
|
+
const envLines = [];
|
|
45
|
+
if (env.languages.length > 0)
|
|
46
|
+
envLines.push(` Languages: ${env.languages.join(", ")}`);
|
|
47
|
+
if (env.frameworks.length > 0)
|
|
48
|
+
envLines.push(` Frameworks: ${env.frameworks.join(", ")}`);
|
|
49
|
+
if (env.databases.length > 0)
|
|
50
|
+
envLines.push(` Databases: ${env.databases.join(", ")}`);
|
|
51
|
+
if (env.cloud.length > 0)
|
|
52
|
+
envLines.push(` Cloud: ${env.cloud.join(", ")}`);
|
|
53
|
+
if (env.cicd.length > 0)
|
|
54
|
+
envLines.push(` CI/CD: ${env.cicd.join(", ")}`);
|
|
55
|
+
if (env.services.length > 0)
|
|
56
|
+
envLines.push(` Services (detected from .env keys): ${env.services.join(", ")}`);
|
|
57
|
+
if (envLines.length > 0) {
|
|
58
|
+
sections.push(`## Detected Environment (${workDir})\n${envLines.join("\n")}`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
sections.push(`## Detected Environment (${workDir})\nNo recognizable project signals found.`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const msg = errorMessage(error);
|
|
66
|
+
sections.push(`## Detected Environment\nError: ${msg}`);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const registryUrl = "https://registry.modelcontextprotocol.io/v0/servers";
|
|
70
|
+
const response = await fetch(registryUrl, {
|
|
71
|
+
signal: AbortSignal.timeout(10_000),
|
|
72
|
+
headers: { "Accept": "application/json" },
|
|
73
|
+
});
|
|
74
|
+
if (response.ok) {
|
|
75
|
+
const data = await response.json();
|
|
76
|
+
function formatRegistryEntry(entry) {
|
|
77
|
+
const srv = (typeof entry["server"] === "object" && entry["server"] !== null
|
|
78
|
+
? entry["server"] : entry);
|
|
79
|
+
const rawName = srv["name"] ?? entry["name"] ?? entry["id"];
|
|
80
|
+
const name = typeof rawName === "string" ? rawName : "unknown";
|
|
81
|
+
const rawDesc = srv["description"] ?? entry["description"];
|
|
82
|
+
const desc = typeof rawDesc === "string" ? ` — ${rawDesc}` : "";
|
|
83
|
+
return ` - ${name}${desc}`;
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(data)) {
|
|
86
|
+
const entries = data.slice(0, 50);
|
|
87
|
+
const registryLines = entries.map(formatRegistryEntry);
|
|
88
|
+
sections.push(`## MCP Registry (${String(entries.length)} of ${String(data.length)} servers shown)\n${registryLines.join("\n")}`);
|
|
89
|
+
}
|
|
90
|
+
else if (typeof data === "object" && data !== null) {
|
|
91
|
+
const obj = data;
|
|
92
|
+
const servers = obj["servers"] ?? obj["results"] ?? obj["items"];
|
|
93
|
+
if (Array.isArray(servers)) {
|
|
94
|
+
const entries = servers.slice(0, 50);
|
|
95
|
+
const registryLines = entries.map(formatRegistryEntry);
|
|
96
|
+
sections.push(`## MCP Registry (${String(entries.length)} of ${String(servers.length)} servers shown)\n${registryLines.join("\n")}`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
sections.push(`## MCP Registry\nRegistry returned data but in an unexpected format. Keys: ${Object.keys(obj).join(", ")}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
sections.push(`## MCP Registry\nRegistry returned HTTP ${String(response.status)}. The listing endpoint may not be publicly available.`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
const msg = errorMessage(error);
|
|
109
|
+
sections.push(`## MCP Registry\nCould not reach registry: ${msg}`);
|
|
110
|
+
}
|
|
111
|
+
logRequest("suggest_servers", startMs);
|
|
112
|
+
return { content: [{ type: "text", text: sections.join("\n\n") }] };
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=suggest-servers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggest-servers.js","sourceRoot":"","sources":["../../../src/tools/suggest-servers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const name = "verify";
|
|
3
|
+
export declare const description = "Use this after updating a server to confirm nothing broke. Connects to the live server, sends the same requests from a recorded cassette, and compares responses. Reports exactly what changed \u2014 added tools, removed parameters, different response shapes.";
|
|
4
|
+
export declare const schema: {
|
|
5
|
+
cassette: z.ZodString;
|
|
6
|
+
command: z.ZodString;
|
|
7
|
+
args: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
8
|
+
};
|
|
9
|
+
export declare function handler({ cassette: cassettePath, command, args }: {
|
|
10
|
+
cassette: string;
|
|
11
|
+
command: string;
|
|
12
|
+
args?: string[];
|
|
13
|
+
}): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
isError: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
content: {
|
|
21
|
+
type: "text";
|
|
22
|
+
text: string;
|
|
23
|
+
}[];
|
|
24
|
+
isError?: undefined;
|
|
25
|
+
}>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defaultCassettesDirectory, loadCassette } from "../cassette.js";
|
|
3
|
+
import { runTargetRecording } from "../runner.js";
|
|
4
|
+
import { compareResponses } from "../verify.js";
|
|
5
|
+
import { errorMessage } from "../utils/errors.js";
|
|
6
|
+
import { validateCommand, validatePath } from "../utils/security.js";
|
|
7
|
+
import { logRequest } from "./helpers.js";
|
|
8
|
+
export const name = "verify";
|
|
9
|
+
export const description = "Use this after updating a server to confirm nothing broke. Connects to the live server, sends the same requests from a recorded cassette, and compares responses. Reports exactly what changed — added tools, removed parameters, different response shapes.";
|
|
10
|
+
export const schema = {
|
|
11
|
+
cassette: z.string().describe("Path to a cassette JSON file."),
|
|
12
|
+
command: z.string().describe("The command to launch the MCP server."),
|
|
13
|
+
args: z.array(z.string()).optional().describe("Additional arguments for the command."),
|
|
14
|
+
};
|
|
15
|
+
export async function handler({ cassette: cassettePath, command, args }) {
|
|
16
|
+
const startMs = Date.now();
|
|
17
|
+
try {
|
|
18
|
+
validateCommand(command);
|
|
19
|
+
const cassettesDir = defaultCassettesDirectory();
|
|
20
|
+
validatePath(cassettePath, cassettesDir);
|
|
21
|
+
const cassette = await loadCassette(cassettePath);
|
|
22
|
+
const target = {
|
|
23
|
+
targetId: command,
|
|
24
|
+
adapter: "local-process",
|
|
25
|
+
command,
|
|
26
|
+
args: args ?? [],
|
|
27
|
+
timeoutMs: 15_000,
|
|
28
|
+
};
|
|
29
|
+
const { cassetteEntries } = await runTargetRecording(target, { invokeTools: true });
|
|
30
|
+
if (!cassetteEntries) {
|
|
31
|
+
return { content: [{ type: "text", text: "Failed to record live session for comparison." }], isError: true };
|
|
32
|
+
}
|
|
33
|
+
const result = compareResponses(cassette, cassetteEntries);
|
|
34
|
+
const lines = [`Verify: ${result.passed} passed, ${result.failed} changed, ${result.missing} missing\n`];
|
|
35
|
+
for (const entry of result.entries) {
|
|
36
|
+
const icon = entry.status === "pass" ? "\u2713" : entry.status === "fail" ? "\u2717" : "?";
|
|
37
|
+
lines.push(` ${icon} ${entry.method}${entry.diff ? ` \u2014 ${entry.diff}` : ""}`);
|
|
38
|
+
}
|
|
39
|
+
logRequest("verify", startMs);
|
|
40
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const msg = errorMessage(error);
|
|
44
|
+
logRequest("verify", startMs, true);
|
|
45
|
+
return { content: [{ type: "text", text: `Error verifying: ${msg}` }], isError: true };
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=verify-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-tool.js","sourceRoot":"","sources":["../../../src/tools/verify-tool.ts"],"names":[],"mappings":"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"}
|