@quantakrypto/mcp 0.4.4 → 0.5.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/HOSTING.md +15 -1
- package/README.md +22 -1
- package/dist/fsconfig.d.ts +4 -3
- package/dist/fsconfig.d.ts.map +1 -1
- package/dist/fsconfig.js.map +1 -1
- package/dist/http.d.ts +21 -18
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +23 -12
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/protocol.d.ts +3 -6
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js.map +1 -1
- package/dist/resources.d.ts +4 -3
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js.map +1 -1
- package/dist/rules.d.ts +2 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +6 -0
- package/dist/server.js.map +1 -1
- package/dist/stdio.d.ts +0 -3
- package/dist/stdio.d.ts.map +1 -1
- package/dist/stdio.js +0 -5
- package/dist/stdio.js.map +1 -1
- package/dist/tools.d.ts +9 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +181 -5
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
package/dist/tools.js
CHANGED
|
@@ -824,6 +824,58 @@ const checkDependencyTool = {
|
|
|
824
824
|
};
|
|
825
825
|
},
|
|
826
826
|
};
|
|
827
|
+
/**
|
|
828
|
+
* JSON-schema shape of one finding from `scan_path --format json` (mirrors the
|
|
829
|
+
* core {@link Finding} type). Used as the `items` schema for every findings
|
|
830
|
+
* array below so MCP clients/inspectors get a described element shape instead of
|
|
831
|
+
* a bare `type: "array"`. Extra finding fields are allowed (no
|
|
832
|
+
* additionalProperties:false), only ruleId + location.file are required — which
|
|
833
|
+
* is exactly what {@link areFindings} enforces.
|
|
834
|
+
*/
|
|
835
|
+
const FINDING_ITEM_SCHEMA = {
|
|
836
|
+
type: "object",
|
|
837
|
+
description: "A single finding from `scan_path --format json`.",
|
|
838
|
+
properties: {
|
|
839
|
+
ruleId: { type: "string", description: 'Stable rule id, e.g. "rsa-keygen".' },
|
|
840
|
+
title: { type: "string" },
|
|
841
|
+
category: { type: "string" },
|
|
842
|
+
severity: { type: "string", description: "critical | high | medium | low | info." },
|
|
843
|
+
confidence: { type: "string" },
|
|
844
|
+
algorithm: { type: "string", description: "Classical algorithm family, when applicable." },
|
|
845
|
+
hndl: { type: "boolean", description: "Exposed to harvest-now-decrypt-later." },
|
|
846
|
+
message: { type: "string" },
|
|
847
|
+
remediation: { type: "string" },
|
|
848
|
+
cwe: { type: "string", description: 'e.g. "CWE-327".' },
|
|
849
|
+
location: {
|
|
850
|
+
type: "object",
|
|
851
|
+
description: "Where the finding is.",
|
|
852
|
+
properties: {
|
|
853
|
+
file: { type: "string" },
|
|
854
|
+
line: { type: "number" },
|
|
855
|
+
},
|
|
856
|
+
required: ["file"],
|
|
857
|
+
},
|
|
858
|
+
},
|
|
859
|
+
required: ["ruleId", "location"],
|
|
860
|
+
};
|
|
861
|
+
/** JSON-schema shape of one triage verdict for `apply_triage`. */
|
|
862
|
+
const VERDICT_ITEM_SCHEMA = {
|
|
863
|
+
type: "object",
|
|
864
|
+
description: "A triage verdict for one finding.",
|
|
865
|
+
properties: {
|
|
866
|
+
fingerprint: {
|
|
867
|
+
type: "string",
|
|
868
|
+
description: "Fingerprint of the finding this verdict applies to.",
|
|
869
|
+
},
|
|
870
|
+
exposureScore: {
|
|
871
|
+
type: "number",
|
|
872
|
+
description: "Real-world exposure (higher = more exposed).",
|
|
873
|
+
},
|
|
874
|
+
priority: { type: "string", enum: ["now", "soon", "later"] },
|
|
875
|
+
rationale: { type: "string", description: "Why this exposure score / priority." },
|
|
876
|
+
},
|
|
877
|
+
required: ["fingerprint", "exposureScore", "priority", "rationale"],
|
|
878
|
+
};
|
|
827
879
|
const scoreDeltaTool = {
|
|
828
880
|
name: "score_delta",
|
|
829
881
|
description: "Compute the readiness-score and HNDL change between two finding sets (e.g. before " +
|
|
@@ -834,9 +886,14 @@ const scoreDeltaTool = {
|
|
|
834
886
|
properties: {
|
|
835
887
|
before: {
|
|
836
888
|
type: "array",
|
|
889
|
+
items: FINDING_ITEM_SCHEMA,
|
|
837
890
|
description: "Findings before the change (from a scan's JSON findings).",
|
|
838
891
|
},
|
|
839
|
-
after: {
|
|
892
|
+
after: {
|
|
893
|
+
type: "array",
|
|
894
|
+
items: FINDING_ITEM_SCHEMA,
|
|
895
|
+
description: "Findings after the change.",
|
|
896
|
+
},
|
|
840
897
|
},
|
|
841
898
|
required: ["before", "after"],
|
|
842
899
|
additionalProperties: false,
|
|
@@ -918,7 +975,11 @@ const triageFindingsTool = {
|
|
|
918
975
|
inputSchema: {
|
|
919
976
|
type: "object",
|
|
920
977
|
properties: {
|
|
921
|
-
findings: {
|
|
978
|
+
findings: {
|
|
979
|
+
type: "array",
|
|
980
|
+
items: FINDING_ITEM_SCHEMA,
|
|
981
|
+
description: "Findings from a scan's JSON output.",
|
|
982
|
+
},
|
|
922
983
|
},
|
|
923
984
|
required: ["findings"],
|
|
924
985
|
additionalProperties: false,
|
|
@@ -988,8 +1049,16 @@ const applyTriageTool = {
|
|
|
988
1049
|
inputSchema: {
|
|
989
1050
|
type: "object",
|
|
990
1051
|
properties: {
|
|
991
|
-
findings: {
|
|
992
|
-
|
|
1052
|
+
findings: {
|
|
1053
|
+
type: "array",
|
|
1054
|
+
items: FINDING_ITEM_SCHEMA,
|
|
1055
|
+
description: "The findings that were triaged.",
|
|
1056
|
+
},
|
|
1057
|
+
verdicts: {
|
|
1058
|
+
type: "array",
|
|
1059
|
+
items: VERDICT_ITEM_SCHEMA,
|
|
1060
|
+
description: "One verdict per finding, keyed by fingerprint.",
|
|
1061
|
+
},
|
|
993
1062
|
},
|
|
994
1063
|
required: ["findings", "verdicts"],
|
|
995
1064
|
additionalProperties: false,
|
|
@@ -1053,7 +1122,11 @@ const remediateFindingsTool = {
|
|
|
1053
1122
|
inputSchema: {
|
|
1054
1123
|
type: "object",
|
|
1055
1124
|
properties: {
|
|
1056
|
-
findings: {
|
|
1125
|
+
findings: {
|
|
1126
|
+
type: "array",
|
|
1127
|
+
items: FINDING_ITEM_SCHEMA,
|
|
1128
|
+
description: "Findings from a scan's JSON output.",
|
|
1129
|
+
},
|
|
1057
1130
|
},
|
|
1058
1131
|
required: ["findings"],
|
|
1059
1132
|
additionalProperties: false,
|
|
@@ -1142,6 +1215,100 @@ const applyVerifiedPatchTool = {
|
|
|
1142
1215
|
}, null, 2));
|
|
1143
1216
|
},
|
|
1144
1217
|
};
|
|
1218
|
+
/**
|
|
1219
|
+
* `probe_endpoint` — actively probe ONE live TLS/SSH endpoint the caller OWNS for
|
|
1220
|
+
* post-quantum readiness. This is the ONLY MCP tool that opens a network socket;
|
|
1221
|
+
* the qprobe plane is loaded via dynamic import so the server stays offline until
|
|
1222
|
+
* the tool is actually invoked, and the ownership attestation gate is enforced in
|
|
1223
|
+
* qProbe's `runProbe` before any connection. On the HTTP transport it is OFF by
|
|
1224
|
+
* default (see {@link NETWORK_TOOL_NAMES}) — a hosted endpoint should not probe
|
|
1225
|
+
* arbitrary hosts — but a trusted operator can opt in with
|
|
1226
|
+
* QUANTAKRYPTO_MCP_ALLOW_NETWORK=1.
|
|
1227
|
+
*/
|
|
1228
|
+
const probeEndpointTool = {
|
|
1229
|
+
name: "probe_endpoint",
|
|
1230
|
+
description: "Actively probe ONE live TLS/SSH endpoint YOU OWN for post-quantum readiness " +
|
|
1231
|
+
"(PQC-hybrid key exchange X25519MLKEM768, classical certificate posture). REQUIRES " +
|
|
1232
|
+
"an ownership attestation: set i_own_this=true to confirm you are authorized to test " +
|
|
1233
|
+
"the target. Refuses CIDR ranges / wildcards / lists — one host at a time. Performs " +
|
|
1234
|
+
"only a benign, unauthenticated handshake and never modifies the endpoint. NOTE: this " +
|
|
1235
|
+
"is the ONLY quantakrypto MCP tool that opens a network connection; the server is " +
|
|
1236
|
+
"otherwise offline. Over HTTP it is disabled unless the operator sets " +
|
|
1237
|
+
"QUANTAKRYPTO_MCP_ALLOW_NETWORK=1.",
|
|
1238
|
+
inputSchema: {
|
|
1239
|
+
type: "object",
|
|
1240
|
+
properties: {
|
|
1241
|
+
target: {
|
|
1242
|
+
type: "string",
|
|
1243
|
+
description: "A single host or host:port you own (no ranges/CIDRs/wildcards).",
|
|
1244
|
+
},
|
|
1245
|
+
mode: {
|
|
1246
|
+
type: "string",
|
|
1247
|
+
enum: ["tls", "ssh", "auto"],
|
|
1248
|
+
description: "Probe mode (default: auto — SSH on :22, TLS otherwise).",
|
|
1249
|
+
},
|
|
1250
|
+
i_own_this: {
|
|
1251
|
+
type: "boolean",
|
|
1252
|
+
description: "Attestation that you are authorized to probe this endpoint. Must be true; the probe is refused otherwise.",
|
|
1253
|
+
},
|
|
1254
|
+
timeout_ms: { type: "number", description: "Per-connection timeout in ms (default 8000)." },
|
|
1255
|
+
},
|
|
1256
|
+
required: ["target", "i_own_this"],
|
|
1257
|
+
additionalProperties: false,
|
|
1258
|
+
},
|
|
1259
|
+
async handler(args) {
|
|
1260
|
+
const target = args.target;
|
|
1261
|
+
if (typeof target !== "string" || target.length === 0) {
|
|
1262
|
+
return errorResult("probe_endpoint requires a non-empty 'target' string.");
|
|
1263
|
+
}
|
|
1264
|
+
if (args.i_own_this !== true) {
|
|
1265
|
+
return errorResult("probe_endpoint refused: set i_own_this=true to attest you are authorized to probe this endpoint. Active probing of endpoints you do not own may be unlawful.");
|
|
1266
|
+
}
|
|
1267
|
+
const mode = args.mode === "tls" || args.mode === "ssh" ? args.mode : "auto";
|
|
1268
|
+
const timeoutMs = typeof args.timeout_ms === "number" ? args.timeout_ms : undefined;
|
|
1269
|
+
// Dynamic import keeps the networked qprobe plane out of the server process
|
|
1270
|
+
// until this tool is actually used; runProbe enforces the attestation gate.
|
|
1271
|
+
const qprobe = await import("@quantakrypto/qprobe");
|
|
1272
|
+
// Parse the target explicitly so a CIDR/range/list refusal returns a helpful
|
|
1273
|
+
// message rather than a scrubbed internal error.
|
|
1274
|
+
let parsed;
|
|
1275
|
+
try {
|
|
1276
|
+
parsed = qprobe.parseTarget(target, mode === "ssh" ? 22 : 443);
|
|
1277
|
+
}
|
|
1278
|
+
catch (e) {
|
|
1279
|
+
return errorResult(`probe_endpoint: ${e instanceof Error ? e.message : String(e)}`);
|
|
1280
|
+
}
|
|
1281
|
+
const probed = await safe("probe_endpoint", () => qprobe.runProbe({ targets: [parsed], mode, attest: { iOwnThis: true }, timeoutMs }));
|
|
1282
|
+
if (!probed.ok)
|
|
1283
|
+
return probed.result;
|
|
1284
|
+
const { reports, findings, inventory } = probed.value;
|
|
1285
|
+
const lines = [];
|
|
1286
|
+
let unreachable = 0;
|
|
1287
|
+
for (const r of reports) {
|
|
1288
|
+
lines.push(`${r.target.host}:${r.target.port} [${r.mode}]`);
|
|
1289
|
+
const err = r.ssh?.error ?? r.tls?.error ?? r.hybrid?.error;
|
|
1290
|
+
if (err && r.findings.length === 0) {
|
|
1291
|
+
unreachable++;
|
|
1292
|
+
// Do NOT let an unreachable/errored endpoint read as a clean 100/100.
|
|
1293
|
+
lines.push(` ⚠ probe error: ${err} — endpoint NOT assessed (not a clean result)`);
|
|
1294
|
+
}
|
|
1295
|
+
for (const p of r.positives)
|
|
1296
|
+
lines.push(` ✓ ${p}`);
|
|
1297
|
+
for (const f of r.findings)
|
|
1298
|
+
lines.push(` [${f.severity}] ${f.title} — ${f.message}`);
|
|
1299
|
+
}
|
|
1300
|
+
const note = unreachable > 0
|
|
1301
|
+
? " — NOTE: an endpoint could not be reached/handshaken, so this is NOT a clean bill of health"
|
|
1302
|
+
: "";
|
|
1303
|
+
lines.push(`\n${findings.length} finding${findings.length === 1 ? "" : "s"} · ${inventory.hndlCount} HNDL-exposed · readiness ${inventory.readinessScore}/100${note}`);
|
|
1304
|
+
return {
|
|
1305
|
+
content: [
|
|
1306
|
+
{ type: "text", text: lines.join("\n") },
|
|
1307
|
+
{ type: "text", text: JSON.stringify({ findings, inventory }, null, 2) },
|
|
1308
|
+
],
|
|
1309
|
+
};
|
|
1310
|
+
},
|
|
1311
|
+
};
|
|
1145
1312
|
/**
|
|
1146
1313
|
* Tools that read arbitrary filesystem paths. Disabled by default on the HTTP
|
|
1147
1314
|
* transport (see {@link ./http.ts}) because a hosted endpoint must not be an
|
|
@@ -1154,6 +1321,13 @@ export const FS_TOOL_NAMES = [
|
|
|
1154
1321
|
"generate_cbom",
|
|
1155
1322
|
"plan_migration",
|
|
1156
1323
|
];
|
|
1324
|
+
/**
|
|
1325
|
+
* Tools that open network connections. On the HTTP transport they are OFF by
|
|
1326
|
+
* default — a hosted MCP should not probe arbitrary hosts — but a trusted operator
|
|
1327
|
+
* can opt in with QUANTAKRYPTO_MCP_ALLOW_NETWORK=1 (mirroring the FS-tools opt-in).
|
|
1328
|
+
* Always available on the local stdio transport, which trusts the local user.
|
|
1329
|
+
*/
|
|
1330
|
+
export const NETWORK_TOOL_NAMES = ["probe_endpoint"];
|
|
1157
1331
|
/** All quantakrypto MCP tools, in a stable order. */
|
|
1158
1332
|
export const quantakryptoTools = [
|
|
1159
1333
|
scanPathTool,
|
|
@@ -1173,6 +1347,8 @@ export const quantakryptoTools = [
|
|
|
1173
1347
|
applyTriageTool,
|
|
1174
1348
|
remediateFindingsTool,
|
|
1175
1349
|
applyVerifiedPatchTool,
|
|
1350
|
+
// The only networked tool — offline until invoked; refused on the HTTP transport.
|
|
1351
|
+
probeEndpointTool,
|
|
1176
1352
|
];
|
|
1177
1353
|
/** The core version these tools are built against (re-exported for diagnostics). */
|
|
1178
1354
|
export const CORE_VERSION = VERSION;
|