lurqrun 0.1.6 → 0.1.8
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/README.md +311 -139
- package/dist/auditHandler-VWCXSDVO.js +24 -0
- package/dist/autopilotInit-Q62MX5M6.js +178 -0
- package/dist/bin/lurq.js +3255 -23319
- package/dist/browserAuth-J7APSD4P.js +96 -0
- package/dist/cache-AEAKCV4I.js +12 -0
- package/dist/check-2JTQDQA6.js +2613 -0
- package/dist/check-PQSQ2MMA.js +192 -0
- package/dist/checkUpgrade-5YW77OBL.js +24 -0
- package/dist/chunk-2GAYNIHG.js +40 -0
- package/dist/chunk-2O6Z37IT.js +423 -0
- package/dist/chunk-2WD4QAH4.js +116 -0
- package/dist/chunk-3676XOBU.js +2235 -0
- package/dist/chunk-3D2MPGOQ.js +181 -0
- package/dist/chunk-3GHJR7XR.js +2049 -0
- package/dist/chunk-4ESO33J6.js +34 -0
- package/dist/chunk-4JHDSVIN.js +1229 -0
- package/dist/chunk-4KCPOMG6.js +68 -0
- package/dist/chunk-53SG6XWA.js +141 -0
- package/dist/chunk-5ABNDB3I.js +37 -0
- package/dist/chunk-5ALIJFDA.js +114 -0
- package/dist/chunk-63MUJNZL.js +318 -0
- package/dist/chunk-64ZF7KO4.js +1571 -0
- package/dist/chunk-6DM6UQFD.js +318 -0
- package/dist/chunk-6EQ3ARXA.js +304 -0
- package/dist/chunk-6L67QMCP.js +92 -0
- package/dist/chunk-72Y2FDVS.js +172 -0
- package/dist/chunk-A3TYI2TF.js +288 -0
- package/dist/chunk-B2UUYRBX.js +536 -0
- package/dist/chunk-BJT2LHAR.js +320 -0
- package/dist/chunk-BM3LF5T6.js +132 -0
- package/dist/chunk-BN3NM47Q.js +214 -0
- package/dist/chunk-BUALU27S.js +123 -0
- package/dist/chunk-C6GLE626.js +160 -0
- package/dist/chunk-D3K6IZVR.js +12845 -0
- package/dist/chunk-D3VTZS7V.js +222 -0
- package/dist/chunk-DM37MTOO.js +636 -0
- package/dist/chunk-DONTW3NE.js +171 -0
- package/dist/chunk-DSROTZ3Z.js +38 -0
- package/dist/chunk-ELGCAM7P.js +184 -0
- package/dist/chunk-EQCXENKZ.js +130 -0
- package/dist/chunk-EYAKM6YN.js +96 -0
- package/dist/chunk-FDFPMBCA.js +112 -0
- package/dist/chunk-FVYFV5HA.js +154 -0
- package/dist/chunk-GE37NVLN.js +15 -0
- package/dist/chunk-GM5Y555O.js +143 -0
- package/dist/chunk-GRJXQBQZ.js +793 -0
- package/dist/chunk-HWFX4BC4.js +177 -0
- package/dist/chunk-I3M2KJXB.js +56 -0
- package/dist/chunk-IFNONGRQ.js +603 -0
- package/dist/chunk-J3U2CPSK.js +136 -0
- package/dist/chunk-J6XQ7OPZ.js +74 -0
- package/dist/chunk-KMV4Y6R3.js +127 -0
- package/dist/chunk-KP5ZDDPE.js +340 -0
- package/dist/chunk-KYKSZ3XZ.js +202 -0
- package/dist/chunk-LEGPB7DO.js +81 -0
- package/dist/chunk-LUAKDHLZ.js +40 -0
- package/dist/chunk-MHUEVBVO.js +35 -0
- package/dist/chunk-MYGSLK44.js +37 -0
- package/dist/chunk-NEZNAPPN.js +41 -0
- package/dist/chunk-NPVN2THM.js +197 -0
- package/dist/chunk-NYZU5NCW.js +23 -0
- package/dist/chunk-O4KHCV6I.js +320 -0
- package/dist/chunk-ONDVTXUO.js +240 -0
- package/dist/chunk-OTJFHESY.js +176 -0
- package/dist/chunk-P6MIKSPY.js +243 -0
- package/dist/chunk-Q5VPF4WI.js +26 -0
- package/dist/chunk-QCRBX3LV.js +165 -0
- package/dist/chunk-QKGYDMWB.js +7409 -0
- package/dist/chunk-QSL6QN5X.js +115 -0
- package/dist/chunk-QUIMQ7YL.js +73 -0
- package/dist/chunk-RG3YLU6E.js +49 -0
- package/dist/chunk-SHCEBP3J.js +107 -0
- package/dist/chunk-SHZFOSXC.js +462 -0
- package/dist/chunk-SOXFMMXC.js +151 -0
- package/dist/chunk-TDTYV6QI.js +119 -0
- package/dist/chunk-TNTXWSYA.js +19 -0
- package/dist/chunk-TUXQ32WE.js +2251 -0
- package/dist/chunk-VGCDRK3S.js +37 -0
- package/dist/chunk-VLRHFMGJ.js +58 -0
- package/dist/chunk-WHX2ZFCG.js +4048 -0
- package/dist/chunk-X4W64MHU.js +2005 -0
- package/dist/chunk-XBQA4I36.js +92 -0
- package/dist/chunk-XGOV6KV4.js +413 -0
- package/dist/chunk-XIFQIFSZ.js +940 -0
- package/dist/chunk-XLXPVN3Y.js +15393 -0
- package/dist/chunk-ZJMLQ37G.js +90 -0
- package/dist/client-MU4EUDZV.js +15 -0
- package/dist/commands-UOOTMBKA.js +1023 -0
- package/dist/compat-Q5DVPITN.js +201 -0
- package/dist/constants-DJFOFZ44.js +24 -0
- package/dist/dashboardIssue-KBKLQMGC.js +274 -0
- package/dist/diff-I4SNUHU3.js +13 -0
- package/dist/dist-4XXIXHHZ.js +12945 -0
- package/dist/domains-VRT4BRVZ.js +14 -0
- package/dist/e2b-FVPHDULZ.js +172 -0
- package/dist/env-UN6KGZ7S.js +15 -0
- package/dist/envCheck-KUPTORFQ.js +71 -0
- package/dist/extract-6WG265BL.js +455 -0
- package/dist/fetch-PJ3SC32M.js +22 -0
- package/dist/firstTouch-COJCMOB3.js +55 -0
- package/dist/fix-3EDWNKW3.js +220 -0
- package/dist/handlers-4Q37WIFT.js +55 -0
- package/dist/hook-NA66XJLC.js +412 -0
- package/dist/http-25GJ6SKK.js +6928 -0
- package/dist/ingestQueue-FSVJC3AA.js +37 -0
- package/dist/install-G2HHXYJ2.js +20 -0
- package/dist/installSkill-5TIMYIJZ.js +70 -0
- package/dist/inventory-5AWSLJYU.js +31 -0
- package/dist/local-XPZMRH5R.js +13 -0
- package/dist/manifest-ENMIC54B.js +13 -0
- package/dist/mcp-PENLL2PK.js +500 -0
- package/dist/mcpConfig-3LYPRY5Q.js +80 -0
- package/dist/mcpHandlers-PO7GENA7.js +22 -0
- package/dist/mcpScan-ULP626TQ.js +1735 -0
- package/dist/mcpScanWorkflow-27O44FOE.js +98 -0
- package/dist/mcpStack-ZNYIFXZ5.js +20 -0
- package/dist/model-ASMZWLM7.js +214 -0
- package/dist/packages-RWF2LXST.js +40 -0
- package/dist/plan-SDILIDZT.js +465 -0
- package/dist/planView-ZAKRTGVS.js +82 -0
- package/dist/policy-JAGNMQJI.js +115 -0
- package/dist/probe-3IK646WN.js +610 -0
- package/dist/publicUpgrades-WTBGLDIK.js +42 -0
- package/dist/references-I6Y4BSRR.js +16 -0
- package/dist/release-7IU2R4YG.js +128 -0
- package/dist/remote-FN6225AH.js +42 -0
- package/dist/rename-OS6JYBZQ.js +204 -0
- package/dist/reportRuns-O4X7ZFTX.js +77 -0
- package/dist/safeFetch-COERXGFN.js +19 -0
- package/dist/sandbox-SAPSRKEI.js +39 -0
- package/dist/sarif-WLVJFJ7U.js +125 -0
- package/dist/server-OLTY3NYT.js +59 -0
- package/dist/surface-7MHE3RL7.js +143 -0
- package/dist/surface-JS2XU3U3.js +38 -0
- package/dist/surfaceHandlers-4B6TMZ34.js +20 -0
- package/dist/typecheck-NGOKNAJY.js +282 -0
- package/dist/types-M5JP2W3D.js +10 -0
- package/dist/uninstall-I2IJP5JZ.js +200 -0
- package/dist/upgrade-FS26DJGG.js +650 -0
- package/dist/upgradePlan-ZS5IOVMZ.js +181 -0
- package/dist/upkeepHandler-VFGYXJKB.js +13 -0
- package/dist/userConfig-ICASHI47.js +20 -0
- package/dist/watch-OCNEUXG6.js +204 -0
- package/package.json +26 -20
- package/templates/agent-rules.md +4 -4
- package/templates/skill-instructions.md +35 -17
- package/dist/bin/lurq.js.map +0 -1
- package/dist/index.d.ts +0 -805
- package/dist/index.js +0 -23570
- package/dist/index.js.map +0 -1
- package/drizzle/0000_confused_fabian_cortez.sql +0 -54
- package/drizzle/0001_aspiring_timeslip.sql +0 -15
- package/drizzle/0002_lyrical_lila_cheney.sql +0 -14
- package/drizzle/0003_violet_giant_girl.sql +0 -14
- package/drizzle/0004_slim_valeria_richards.sql +0 -15
- package/drizzle/0005_petite_luke_cage.sql +0 -12
- package/drizzle/0006_calm_nighthawk.sql +0 -3
- package/drizzle/0007_slimy_naoko.sql +0 -1
- package/drizzle/0008_peaceful_tigra.sql +0 -1
- package/drizzle/0009_smooth_harrier.sql +0 -10
- package/drizzle/0010_curved_amphibian.sql +0 -2
- package/drizzle/0011_compat_provenance_closures.sql +0 -11
- package/drizzle/0012_api_surfaces.sql +0 -9
- package/drizzle/0013_nice_thanos.sql +0 -1
- package/drizzle/0014_luxuriant_malcolm_colcord.sql +0 -10
- package/drizzle/0015_past_fat_cobra.sql +0 -11
- package/drizzle/0016_futuristic_junta.sql +0 -50
- package/drizzle/0017_absent_grandmaster.sql +0 -20
- package/drizzle/0018_rainy_proemial_gods.sql +0 -1
- package/drizzle/0019_dashing_korvac.sql +0 -11
- package/drizzle/0020_normal_vulture.sql +0 -3
- package/drizzle/0021_dazzling_blue_shield.sql +0 -17
- package/drizzle/0022_massive_ghost_rider.sql +0 -24
- package/drizzle/0023_next_shatterstar.sql +0 -1
- package/drizzle/0024_dusty_maelstrom.sql +0 -15
- package/drizzle/0025_gifted_harpoon.sql +0 -5
- package/drizzle/0026_awesome_mordo.sql +0 -1
- package/drizzle/0027_charming_ulik.sql +0 -2
- package/drizzle/0028_romantic_loners.sql +0 -2
- package/drizzle/0029_busy_korvac.sql +0 -16
- package/drizzle/0030_steady_mathemanic.sql +0 -6
- package/drizzle/0031_redundant_maginty.sql +0 -2
- package/drizzle/0032_stack_resolutions.sql +0 -14
- package/drizzle/0033_graceful_tyger_tiger.sql +0 -3
- package/drizzle/0034_uneven_thunderbird.sql +0 -1
- package/drizzle/0035_api_key_scopes.sql +0 -1
- package/drizzle/0036_policy_audit.sql +0 -21
- package/drizzle/0037_subscription_seats.sql +0 -1
- package/drizzle/0038_symbol_offsets.sql +0 -2
- package/drizzle/0039_mcp_runtime_scans.sql +0 -80
- package/drizzle/0040_subscription_overage_and_interval.sql +0 -4
- package/drizzle/0041_account_email.sql +0 -38
- package/drizzle/0042_alert_channels.sql +0 -21
- package/drizzle/meta/0000_snapshot.json +0 -370
- package/drizzle/meta/0001_snapshot.json +0 -482
- package/drizzle/meta/0002_snapshot.json +0 -575
- package/drizzle/meta/0003_snapshot.json +0 -667
- package/drizzle/meta/0004_snapshot.json +0 -769
- package/drizzle/meta/0005_snapshot.json +0 -864
- package/drizzle/meta/0006_snapshot.json +0 -882
- package/drizzle/meta/0007_snapshot.json +0 -876
- package/drizzle/meta/0008_snapshot.json +0 -882
- package/drizzle/meta/0009_snapshot.json +0 -948
- package/drizzle/meta/0010_snapshot.json +0 -969
- package/drizzle/meta/0011_snapshot.json +0 -1049
- package/drizzle/meta/0012_snapshot.json +0 -1115
- package/drizzle/meta/0013_snapshot.json +0 -1121
- package/drizzle/meta/0014_snapshot.json +0 -1190
- package/drizzle/meta/0015_snapshot.json +0 -1280
- package/drizzle/meta/0016_snapshot.json +0 -1683
- package/drizzle/meta/0017_snapshot.json +0 -1834
- package/drizzle/meta/0018_snapshot.json +0 -1834
- package/drizzle/meta/0019_snapshot.json +0 -1909
- package/drizzle/meta/0020_snapshot.json +0 -1921
- package/drizzle/meta/0021_snapshot.json +0 -2045
- package/drizzle/meta/0022_snapshot.json +0 -2246
- package/drizzle/meta/0023_snapshot.json +0 -2252
- package/drizzle/meta/0024_snapshot.json +0 -2375
- package/drizzle/meta/0025_snapshot.json +0 -2407
- package/drizzle/meta/0026_snapshot.json +0 -2413
- package/drizzle/meta/0027_snapshot.json +0 -2425
- package/drizzle/meta/0028_snapshot.json +0 -2438
- package/drizzle/meta/0029_snapshot.json +0 -2546
- package/drizzle/meta/0030_snapshot.json +0 -2586
- package/drizzle/meta/0031_snapshot.json +0 -2598
- package/drizzle/meta/0032_snapshot.json +0 -2699
- package/drizzle/meta/0033_snapshot.json +0 -2712
- package/drizzle/meta/0034_snapshot.json +0 -2719
- package/drizzle/meta/0035_snapshot.json +0 -2726
- package/drizzle/meta/0036_snapshot.json +0 -2876
- package/drizzle/meta/0037_snapshot.json +0 -2883
- package/drizzle/meta/0038_snapshot.json +0 -2895
- package/drizzle/meta/0039_snapshot.json +0 -3501
- package/drizzle/meta/0040_snapshot.json +0 -3527
- package/drizzle/meta/0041_snapshot.json +0 -3824
- package/drizzle/meta/0042_snapshot.json +0 -3960
- package/drizzle/meta/_journal.json +0 -307
- package/src/data/seed.json +0 -253
|
@@ -0,0 +1,1735 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ReadBuffer,
|
|
4
|
+
serializeMessage
|
|
5
|
+
} from "./chunk-NEZNAPPN.js";
|
|
6
|
+
import {
|
|
7
|
+
analyzeServer,
|
|
8
|
+
analyzeStackScan,
|
|
9
|
+
diffSnapshots,
|
|
10
|
+
sortFindings,
|
|
11
|
+
worst
|
|
12
|
+
} from "./chunk-IFNONGRQ.js";
|
|
13
|
+
import {
|
|
14
|
+
isCrowded
|
|
15
|
+
} from "./chunk-FDFPMBCA.js";
|
|
16
|
+
import {
|
|
17
|
+
Client,
|
|
18
|
+
LIMITS,
|
|
19
|
+
addPrompts,
|
|
20
|
+
addTemplates,
|
|
21
|
+
addTools,
|
|
22
|
+
contentHash,
|
|
23
|
+
emptySnapshot
|
|
24
|
+
} from "./chunk-XIFQIFSZ.js";
|
|
25
|
+
import "./chunk-XLXPVN3Y.js";
|
|
26
|
+
import {
|
|
27
|
+
readServerConfigs,
|
|
28
|
+
scrub,
|
|
29
|
+
scrubDeep
|
|
30
|
+
} from "./chunk-XGOV6KV4.js";
|
|
31
|
+
import "./chunk-63MUJNZL.js";
|
|
32
|
+
import "./chunk-QKGYDMWB.js";
|
|
33
|
+
import {
|
|
34
|
+
classifyError
|
|
35
|
+
} from "./chunk-53SG6XWA.js";
|
|
36
|
+
import {
|
|
37
|
+
SSEClientTransport,
|
|
38
|
+
StreamableHTTPClientTransport,
|
|
39
|
+
StreamableHTTPError
|
|
40
|
+
} from "./chunk-X4W64MHU.js";
|
|
41
|
+
import {
|
|
42
|
+
array,
|
|
43
|
+
looseObject,
|
|
44
|
+
string,
|
|
45
|
+
unknown
|
|
46
|
+
} from "./chunk-D3K6IZVR.js";
|
|
47
|
+
import "./chunk-6EQ3ARXA.js";
|
|
48
|
+
import "./chunk-SHCEBP3J.js";
|
|
49
|
+
import {
|
|
50
|
+
SEVERITY_RANK
|
|
51
|
+
} from "./chunk-Q5VPF4WI.js";
|
|
52
|
+
import {
|
|
53
|
+
pMap
|
|
54
|
+
} from "./chunk-4ESO33J6.js";
|
|
55
|
+
import "./chunk-WHX2ZFCG.js";
|
|
56
|
+
import {
|
|
57
|
+
bold,
|
|
58
|
+
dim,
|
|
59
|
+
green,
|
|
60
|
+
red,
|
|
61
|
+
table,
|
|
62
|
+
yellow
|
|
63
|
+
} from "./chunk-4KCPOMG6.js";
|
|
64
|
+
import {
|
|
65
|
+
RemoteError,
|
|
66
|
+
uploadMcpScan
|
|
67
|
+
} from "./chunk-OTJFHESY.js";
|
|
68
|
+
import {
|
|
69
|
+
lurqHome,
|
|
70
|
+
resolveApiKey
|
|
71
|
+
} from "./chunk-J6XQ7OPZ.js";
|
|
72
|
+
import {
|
|
73
|
+
VERSION
|
|
74
|
+
} from "./chunk-72Y2FDVS.js";
|
|
75
|
+
import {
|
|
76
|
+
__commonJS,
|
|
77
|
+
__require,
|
|
78
|
+
__toESM,
|
|
79
|
+
init_esm_shims
|
|
80
|
+
} from "./chunk-I3M2KJXB.js";
|
|
81
|
+
|
|
82
|
+
// node_modules/isexe/windows.js
|
|
83
|
+
var require_windows = __commonJS({
|
|
84
|
+
"node_modules/isexe/windows.js"(exports, module) {
|
|
85
|
+
"use strict";
|
|
86
|
+
init_esm_shims();
|
|
87
|
+
module.exports = isexe;
|
|
88
|
+
isexe.sync = sync;
|
|
89
|
+
var fs = __require("fs");
|
|
90
|
+
function checkPathExt(path, options) {
|
|
91
|
+
var pathext = options.pathExt !== void 0 ? options.pathExt : process.env.PATHEXT;
|
|
92
|
+
if (!pathext) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
pathext = pathext.split(";");
|
|
96
|
+
if (pathext.indexOf("") !== -1) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
for (var i = 0; i < pathext.length; i++) {
|
|
100
|
+
var p = pathext[i].toLowerCase();
|
|
101
|
+
if (p && path.substr(-p.length).toLowerCase() === p) {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
function checkStat(stat, path, options) {
|
|
108
|
+
if (!stat.isSymbolicLink() && !stat.isFile()) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return checkPathExt(path, options);
|
|
112
|
+
}
|
|
113
|
+
function isexe(path, options, cb) {
|
|
114
|
+
fs.stat(path, function(er, stat) {
|
|
115
|
+
cb(er, er ? false : checkStat(stat, path, options));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function sync(path, options) {
|
|
119
|
+
return checkStat(fs.statSync(path), path, options);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// node_modules/isexe/mode.js
|
|
125
|
+
var require_mode = __commonJS({
|
|
126
|
+
"node_modules/isexe/mode.js"(exports, module) {
|
|
127
|
+
"use strict";
|
|
128
|
+
init_esm_shims();
|
|
129
|
+
module.exports = isexe;
|
|
130
|
+
isexe.sync = sync;
|
|
131
|
+
var fs = __require("fs");
|
|
132
|
+
function isexe(path, options, cb) {
|
|
133
|
+
fs.stat(path, function(er, stat) {
|
|
134
|
+
cb(er, er ? false : checkStat(stat, options));
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
function sync(path, options) {
|
|
138
|
+
return checkStat(fs.statSync(path), options);
|
|
139
|
+
}
|
|
140
|
+
function checkStat(stat, options) {
|
|
141
|
+
return stat.isFile() && checkMode(stat, options);
|
|
142
|
+
}
|
|
143
|
+
function checkMode(stat, options) {
|
|
144
|
+
var mod = stat.mode;
|
|
145
|
+
var uid = stat.uid;
|
|
146
|
+
var gid = stat.gid;
|
|
147
|
+
var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
|
|
148
|
+
var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
|
|
149
|
+
var u = parseInt("100", 8);
|
|
150
|
+
var g = parseInt("010", 8);
|
|
151
|
+
var o = parseInt("001", 8);
|
|
152
|
+
var ug = u | g;
|
|
153
|
+
var ret = mod & o || mod & g && gid === myGid || mod & u && uid === myUid || mod & ug && myUid === 0;
|
|
154
|
+
return ret;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// node_modules/isexe/index.js
|
|
160
|
+
var require_isexe = __commonJS({
|
|
161
|
+
"node_modules/isexe/index.js"(exports, module) {
|
|
162
|
+
"use strict";
|
|
163
|
+
init_esm_shims();
|
|
164
|
+
var fs = __require("fs");
|
|
165
|
+
var core;
|
|
166
|
+
if (process.platform === "win32" || global.TESTING_WINDOWS) {
|
|
167
|
+
core = require_windows();
|
|
168
|
+
} else {
|
|
169
|
+
core = require_mode();
|
|
170
|
+
}
|
|
171
|
+
module.exports = isexe;
|
|
172
|
+
isexe.sync = sync;
|
|
173
|
+
function isexe(path, options, cb) {
|
|
174
|
+
if (typeof options === "function") {
|
|
175
|
+
cb = options;
|
|
176
|
+
options = {};
|
|
177
|
+
}
|
|
178
|
+
if (!cb) {
|
|
179
|
+
if (typeof Promise !== "function") {
|
|
180
|
+
throw new TypeError("callback not provided");
|
|
181
|
+
}
|
|
182
|
+
return new Promise(function(resolve2, reject) {
|
|
183
|
+
isexe(path, options || {}, function(er, is) {
|
|
184
|
+
if (er) {
|
|
185
|
+
reject(er);
|
|
186
|
+
} else {
|
|
187
|
+
resolve2(is);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
core(path, options || {}, function(er, is) {
|
|
193
|
+
if (er) {
|
|
194
|
+
if (er.code === "EACCES" || options && options.ignoreErrors) {
|
|
195
|
+
er = null;
|
|
196
|
+
is = false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
cb(er, is);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function sync(path, options) {
|
|
203
|
+
try {
|
|
204
|
+
return core.sync(path, options || {});
|
|
205
|
+
} catch (er) {
|
|
206
|
+
if (options && options.ignoreErrors || er.code === "EACCES") {
|
|
207
|
+
return false;
|
|
208
|
+
} else {
|
|
209
|
+
throw er;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// node_modules/which/which.js
|
|
217
|
+
var require_which = __commonJS({
|
|
218
|
+
"node_modules/which/which.js"(exports, module) {
|
|
219
|
+
"use strict";
|
|
220
|
+
init_esm_shims();
|
|
221
|
+
var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
|
|
222
|
+
var path = __require("path");
|
|
223
|
+
var COLON = isWindows ? ";" : ":";
|
|
224
|
+
var isexe = require_isexe();
|
|
225
|
+
var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
|
|
226
|
+
var getPathInfo = (cmd, opt) => {
|
|
227
|
+
const colon = opt.colon || COLON;
|
|
228
|
+
const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [""] : [
|
|
229
|
+
// windows always checks the cwd first
|
|
230
|
+
...isWindows ? [process.cwd()] : [],
|
|
231
|
+
...(opt.path || process.env.PATH || /* istanbul ignore next: very unusual */
|
|
232
|
+
"").split(colon)
|
|
233
|
+
];
|
|
234
|
+
const pathExtExe = isWindows ? opt.pathExt || process.env.PATHEXT || ".EXE;.CMD;.BAT;.COM" : "";
|
|
235
|
+
const pathExt = isWindows ? pathExtExe.split(colon) : [""];
|
|
236
|
+
if (isWindows) {
|
|
237
|
+
if (cmd.indexOf(".") !== -1 && pathExt[0] !== "")
|
|
238
|
+
pathExt.unshift("");
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
pathEnv,
|
|
242
|
+
pathExt,
|
|
243
|
+
pathExtExe
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
var which = (cmd, opt, cb) => {
|
|
247
|
+
if (typeof opt === "function") {
|
|
248
|
+
cb = opt;
|
|
249
|
+
opt = {};
|
|
250
|
+
}
|
|
251
|
+
if (!opt)
|
|
252
|
+
opt = {};
|
|
253
|
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
254
|
+
const found = [];
|
|
255
|
+
const step = (i) => new Promise((resolve2, reject) => {
|
|
256
|
+
if (i === pathEnv.length)
|
|
257
|
+
return opt.all && found.length ? resolve2(found) : reject(getNotFoundError(cmd));
|
|
258
|
+
const ppRaw = pathEnv[i];
|
|
259
|
+
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
260
|
+
const pCmd = path.join(pathPart, cmd);
|
|
261
|
+
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
262
|
+
resolve2(subStep(p, i, 0));
|
|
263
|
+
});
|
|
264
|
+
const subStep = (p, i, ii) => new Promise((resolve2, reject) => {
|
|
265
|
+
if (ii === pathExt.length)
|
|
266
|
+
return resolve2(step(i + 1));
|
|
267
|
+
const ext = pathExt[ii];
|
|
268
|
+
isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
|
|
269
|
+
if (!er && is) {
|
|
270
|
+
if (opt.all)
|
|
271
|
+
found.push(p + ext);
|
|
272
|
+
else
|
|
273
|
+
return resolve2(p + ext);
|
|
274
|
+
}
|
|
275
|
+
return resolve2(subStep(p, i, ii + 1));
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
|
|
279
|
+
};
|
|
280
|
+
var whichSync = (cmd, opt) => {
|
|
281
|
+
opt = opt || {};
|
|
282
|
+
const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
|
|
283
|
+
const found = [];
|
|
284
|
+
for (let i = 0; i < pathEnv.length; i++) {
|
|
285
|
+
const ppRaw = pathEnv[i];
|
|
286
|
+
const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
|
|
287
|
+
const pCmd = path.join(pathPart, cmd);
|
|
288
|
+
const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
|
|
289
|
+
for (let j = 0; j < pathExt.length; j++) {
|
|
290
|
+
const cur = p + pathExt[j];
|
|
291
|
+
try {
|
|
292
|
+
const is = isexe.sync(cur, { pathExt: pathExtExe });
|
|
293
|
+
if (is) {
|
|
294
|
+
if (opt.all)
|
|
295
|
+
found.push(cur);
|
|
296
|
+
else
|
|
297
|
+
return cur;
|
|
298
|
+
}
|
|
299
|
+
} catch (ex) {
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (opt.all && found.length)
|
|
304
|
+
return found;
|
|
305
|
+
if (opt.nothrow)
|
|
306
|
+
return null;
|
|
307
|
+
throw getNotFoundError(cmd);
|
|
308
|
+
};
|
|
309
|
+
module.exports = which;
|
|
310
|
+
which.sync = whichSync;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// node_modules/path-key/index.js
|
|
315
|
+
var require_path_key = __commonJS({
|
|
316
|
+
"node_modules/path-key/index.js"(exports, module) {
|
|
317
|
+
"use strict";
|
|
318
|
+
init_esm_shims();
|
|
319
|
+
var pathKey = (options = {}) => {
|
|
320
|
+
const environment = options.env || process.env;
|
|
321
|
+
const platform = options.platform || process.platform;
|
|
322
|
+
if (platform !== "win32") {
|
|
323
|
+
return "PATH";
|
|
324
|
+
}
|
|
325
|
+
return Object.keys(environment).reverse().find((key) => key.toUpperCase() === "PATH") || "Path";
|
|
326
|
+
};
|
|
327
|
+
module.exports = pathKey;
|
|
328
|
+
module.exports.default = pathKey;
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// node_modules/cross-spawn/lib/util/resolveCommand.js
|
|
333
|
+
var require_resolveCommand = __commonJS({
|
|
334
|
+
"node_modules/cross-spawn/lib/util/resolveCommand.js"(exports, module) {
|
|
335
|
+
"use strict";
|
|
336
|
+
init_esm_shims();
|
|
337
|
+
var path = __require("path");
|
|
338
|
+
var which = require_which();
|
|
339
|
+
var getPathKey = require_path_key();
|
|
340
|
+
function resolveCommandAttempt(parsed, withoutPathExt) {
|
|
341
|
+
const env = parsed.options.env || process.env;
|
|
342
|
+
const cwd = process.cwd();
|
|
343
|
+
const hasCustomCwd = parsed.options.cwd != null;
|
|
344
|
+
const shouldSwitchCwd = hasCustomCwd && process.chdir !== void 0 && !process.chdir.disabled;
|
|
345
|
+
if (shouldSwitchCwd) {
|
|
346
|
+
try {
|
|
347
|
+
process.chdir(parsed.options.cwd);
|
|
348
|
+
} catch (err) {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
let resolved;
|
|
352
|
+
try {
|
|
353
|
+
resolved = which.sync(parsed.command, {
|
|
354
|
+
path: env[getPathKey({ env })],
|
|
355
|
+
pathExt: withoutPathExt ? path.delimiter : void 0
|
|
356
|
+
});
|
|
357
|
+
} catch (e) {
|
|
358
|
+
} finally {
|
|
359
|
+
if (shouldSwitchCwd) {
|
|
360
|
+
process.chdir(cwd);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (resolved) {
|
|
364
|
+
resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
|
|
365
|
+
}
|
|
366
|
+
return resolved;
|
|
367
|
+
}
|
|
368
|
+
function resolveCommand(parsed) {
|
|
369
|
+
return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true);
|
|
370
|
+
}
|
|
371
|
+
module.exports = resolveCommand;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// node_modules/cross-spawn/lib/util/escape.js
|
|
376
|
+
var require_escape = __commonJS({
|
|
377
|
+
"node_modules/cross-spawn/lib/util/escape.js"(exports, module) {
|
|
378
|
+
"use strict";
|
|
379
|
+
init_esm_shims();
|
|
380
|
+
var metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g;
|
|
381
|
+
function escapeCommand(arg) {
|
|
382
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
383
|
+
return arg;
|
|
384
|
+
}
|
|
385
|
+
function escapeArgument(arg, doubleEscapeMetaChars) {
|
|
386
|
+
arg = `${arg}`;
|
|
387
|
+
arg = arg.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
|
|
388
|
+
arg = arg.replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
389
|
+
arg = `"${arg}"`;
|
|
390
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
391
|
+
if (doubleEscapeMetaChars) {
|
|
392
|
+
arg = arg.replace(metaCharsRegExp, "^$1");
|
|
393
|
+
}
|
|
394
|
+
return arg;
|
|
395
|
+
}
|
|
396
|
+
module.exports.command = escapeCommand;
|
|
397
|
+
module.exports.argument = escapeArgument;
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// node_modules/shebang-regex/index.js
|
|
402
|
+
var require_shebang_regex = __commonJS({
|
|
403
|
+
"node_modules/shebang-regex/index.js"(exports, module) {
|
|
404
|
+
"use strict";
|
|
405
|
+
init_esm_shims();
|
|
406
|
+
module.exports = /^#!(.*)/;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// node_modules/shebang-command/index.js
|
|
411
|
+
var require_shebang_command = __commonJS({
|
|
412
|
+
"node_modules/shebang-command/index.js"(exports, module) {
|
|
413
|
+
"use strict";
|
|
414
|
+
init_esm_shims();
|
|
415
|
+
var shebangRegex = require_shebang_regex();
|
|
416
|
+
module.exports = (string2 = "") => {
|
|
417
|
+
const match = string2.match(shebangRegex);
|
|
418
|
+
if (!match) {
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
const [path, argument] = match[0].replace(/#! ?/, "").split(" ");
|
|
422
|
+
const binary = path.split("/").pop();
|
|
423
|
+
if (binary === "env") {
|
|
424
|
+
return argument;
|
|
425
|
+
}
|
|
426
|
+
return argument ? `${binary} ${argument}` : binary;
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
// node_modules/cross-spawn/lib/util/readShebang.js
|
|
432
|
+
var require_readShebang = __commonJS({
|
|
433
|
+
"node_modules/cross-spawn/lib/util/readShebang.js"(exports, module) {
|
|
434
|
+
"use strict";
|
|
435
|
+
init_esm_shims();
|
|
436
|
+
var fs = __require("fs");
|
|
437
|
+
var shebangCommand = require_shebang_command();
|
|
438
|
+
function readShebang(command) {
|
|
439
|
+
const size = 150;
|
|
440
|
+
const buffer = Buffer.alloc(size);
|
|
441
|
+
let fd;
|
|
442
|
+
try {
|
|
443
|
+
fd = fs.openSync(command, "r");
|
|
444
|
+
fs.readSync(fd, buffer, 0, size, 0);
|
|
445
|
+
fs.closeSync(fd);
|
|
446
|
+
} catch (e) {
|
|
447
|
+
}
|
|
448
|
+
return shebangCommand(buffer.toString());
|
|
449
|
+
}
|
|
450
|
+
module.exports = readShebang;
|
|
451
|
+
}
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
// node_modules/cross-spawn/lib/parse.js
|
|
455
|
+
var require_parse = __commonJS({
|
|
456
|
+
"node_modules/cross-spawn/lib/parse.js"(exports, module) {
|
|
457
|
+
"use strict";
|
|
458
|
+
init_esm_shims();
|
|
459
|
+
var path = __require("path");
|
|
460
|
+
var resolveCommand = require_resolveCommand();
|
|
461
|
+
var escape = require_escape();
|
|
462
|
+
var readShebang = require_readShebang();
|
|
463
|
+
var isWin = process.platform === "win32";
|
|
464
|
+
var isExecutableRegExp = /\.(?:com|exe)$/i;
|
|
465
|
+
var isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i;
|
|
466
|
+
function detectShebang(parsed) {
|
|
467
|
+
parsed.file = resolveCommand(parsed);
|
|
468
|
+
const shebang = parsed.file && readShebang(parsed.file);
|
|
469
|
+
if (shebang) {
|
|
470
|
+
parsed.args.unshift(parsed.file);
|
|
471
|
+
parsed.command = shebang;
|
|
472
|
+
return resolveCommand(parsed);
|
|
473
|
+
}
|
|
474
|
+
return parsed.file;
|
|
475
|
+
}
|
|
476
|
+
function parseNonShell(parsed) {
|
|
477
|
+
if (!isWin) {
|
|
478
|
+
return parsed;
|
|
479
|
+
}
|
|
480
|
+
const commandFile = detectShebang(parsed);
|
|
481
|
+
const needsShell = !isExecutableRegExp.test(commandFile);
|
|
482
|
+
if (parsed.options.forceShell || needsShell) {
|
|
483
|
+
const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
|
|
484
|
+
parsed.command = path.normalize(parsed.command);
|
|
485
|
+
parsed.command = escape.command(parsed.command);
|
|
486
|
+
parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars));
|
|
487
|
+
const shellCommand = [parsed.command].concat(parsed.args).join(" ");
|
|
488
|
+
parsed.args = ["/d", "/s", "/c", `"${shellCommand}"`];
|
|
489
|
+
parsed.command = process.env.comspec || "cmd.exe";
|
|
490
|
+
parsed.options.windowsVerbatimArguments = true;
|
|
491
|
+
}
|
|
492
|
+
return parsed;
|
|
493
|
+
}
|
|
494
|
+
function parse(command, args, options) {
|
|
495
|
+
if (args && !Array.isArray(args)) {
|
|
496
|
+
options = args;
|
|
497
|
+
args = null;
|
|
498
|
+
}
|
|
499
|
+
args = args ? args.slice(0) : [];
|
|
500
|
+
options = Object.assign({}, options);
|
|
501
|
+
const parsed = {
|
|
502
|
+
command,
|
|
503
|
+
args,
|
|
504
|
+
options,
|
|
505
|
+
file: void 0,
|
|
506
|
+
original: {
|
|
507
|
+
command,
|
|
508
|
+
args
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
return options.shell ? parsed : parseNonShell(parsed);
|
|
512
|
+
}
|
|
513
|
+
module.exports = parse;
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// node_modules/cross-spawn/lib/enoent.js
|
|
518
|
+
var require_enoent = __commonJS({
|
|
519
|
+
"node_modules/cross-spawn/lib/enoent.js"(exports, module) {
|
|
520
|
+
"use strict";
|
|
521
|
+
init_esm_shims();
|
|
522
|
+
var isWin = process.platform === "win32";
|
|
523
|
+
function notFoundError(original, syscall) {
|
|
524
|
+
return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), {
|
|
525
|
+
code: "ENOENT",
|
|
526
|
+
errno: "ENOENT",
|
|
527
|
+
syscall: `${syscall} ${original.command}`,
|
|
528
|
+
path: original.command,
|
|
529
|
+
spawnargs: original.args
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
function hookChildProcess(cp, parsed) {
|
|
533
|
+
if (!isWin) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const originalEmit = cp.emit;
|
|
537
|
+
cp.emit = function(name, arg1) {
|
|
538
|
+
if (name === "exit") {
|
|
539
|
+
const err = verifyENOENT(arg1, parsed);
|
|
540
|
+
if (err) {
|
|
541
|
+
return originalEmit.call(cp, "error", err);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return originalEmit.apply(cp, arguments);
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function verifyENOENT(status, parsed) {
|
|
548
|
+
if (isWin && status === 1 && !parsed.file) {
|
|
549
|
+
return notFoundError(parsed.original, "spawn");
|
|
550
|
+
}
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
function verifyENOENTSync(status, parsed) {
|
|
554
|
+
if (isWin && status === 1 && !parsed.file) {
|
|
555
|
+
return notFoundError(parsed.original, "spawnSync");
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
module.exports = {
|
|
560
|
+
hookChildProcess,
|
|
561
|
+
verifyENOENT,
|
|
562
|
+
verifyENOENTSync,
|
|
563
|
+
notFoundError
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// node_modules/cross-spawn/index.js
|
|
569
|
+
var require_cross_spawn = __commonJS({
|
|
570
|
+
"node_modules/cross-spawn/index.js"(exports, module) {
|
|
571
|
+
"use strict";
|
|
572
|
+
init_esm_shims();
|
|
573
|
+
var cp = __require("child_process");
|
|
574
|
+
var parse = require_parse();
|
|
575
|
+
var enoent = require_enoent();
|
|
576
|
+
function spawn2(command, args, options) {
|
|
577
|
+
const parsed = parse(command, args, options);
|
|
578
|
+
const spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
|
|
579
|
+
enoent.hookChildProcess(spawned, parsed);
|
|
580
|
+
return spawned;
|
|
581
|
+
}
|
|
582
|
+
function spawnSync(command, args, options) {
|
|
583
|
+
const parsed = parse(command, args, options);
|
|
584
|
+
const result2 = cp.spawnSync(parsed.command, parsed.args, parsed.options);
|
|
585
|
+
result2.error = result2.error || enoent.verifyENOENTSync(result2.status, parsed);
|
|
586
|
+
return result2;
|
|
587
|
+
}
|
|
588
|
+
module.exports = spawn2;
|
|
589
|
+
module.exports.spawn = spawn2;
|
|
590
|
+
module.exports.sync = spawnSync;
|
|
591
|
+
module.exports._parse = parse;
|
|
592
|
+
module.exports._enoent = enoent;
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// src/cli/mcpScan.ts
|
|
597
|
+
init_esm_shims();
|
|
598
|
+
import { resolve } from "path";
|
|
599
|
+
|
|
600
|
+
// src/mcpScan/connect.ts
|
|
601
|
+
init_esm_shims();
|
|
602
|
+
|
|
603
|
+
// node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js
|
|
604
|
+
init_esm_shims();
|
|
605
|
+
var import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
606
|
+
import process2 from "process";
|
|
607
|
+
import { PassThrough } from "stream";
|
|
608
|
+
var DEFAULT_INHERITED_ENV_VARS = process2.platform === "win32" ? [
|
|
609
|
+
"APPDATA",
|
|
610
|
+
"HOMEDRIVE",
|
|
611
|
+
"HOMEPATH",
|
|
612
|
+
"LOCALAPPDATA",
|
|
613
|
+
"PATH",
|
|
614
|
+
"PROCESSOR_ARCHITECTURE",
|
|
615
|
+
"SYSTEMDRIVE",
|
|
616
|
+
"SYSTEMROOT",
|
|
617
|
+
"TEMP",
|
|
618
|
+
"USERNAME",
|
|
619
|
+
"USERPROFILE",
|
|
620
|
+
"PROGRAMFILES"
|
|
621
|
+
] : (
|
|
622
|
+
/* list inspired by the default env inheritance of sudo */
|
|
623
|
+
["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"]
|
|
624
|
+
);
|
|
625
|
+
function getDefaultEnvironment() {
|
|
626
|
+
const env = {};
|
|
627
|
+
for (const key of DEFAULT_INHERITED_ENV_VARS) {
|
|
628
|
+
const value = process2.env[key];
|
|
629
|
+
if (value === void 0) {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (value.startsWith("()")) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
env[key] = value;
|
|
636
|
+
}
|
|
637
|
+
return env;
|
|
638
|
+
}
|
|
639
|
+
var StdioClientTransport = class {
|
|
640
|
+
constructor(server) {
|
|
641
|
+
this._readBuffer = new ReadBuffer();
|
|
642
|
+
this._stderrStream = null;
|
|
643
|
+
this._serverParams = server;
|
|
644
|
+
if (server.stderr === "pipe" || server.stderr === "overlapped") {
|
|
645
|
+
this._stderrStream = new PassThrough();
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Starts the server process and prepares to communicate with it.
|
|
650
|
+
*/
|
|
651
|
+
async start() {
|
|
652
|
+
if (this._process) {
|
|
653
|
+
throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
|
|
654
|
+
}
|
|
655
|
+
return new Promise((resolve2, reject) => {
|
|
656
|
+
this._process = (0, import_cross_spawn.default)(this._serverParams.command, this._serverParams.args ?? [], {
|
|
657
|
+
// merge default env with server env because mcp server needs some env vars
|
|
658
|
+
env: {
|
|
659
|
+
...getDefaultEnvironment(),
|
|
660
|
+
...this._serverParams.env
|
|
661
|
+
},
|
|
662
|
+
stdio: ["pipe", "pipe", this._serverParams.stderr ?? "inherit"],
|
|
663
|
+
shell: false,
|
|
664
|
+
windowsHide: process2.platform === "win32",
|
|
665
|
+
cwd: this._serverParams.cwd
|
|
666
|
+
});
|
|
667
|
+
this._process.on("error", (error) => {
|
|
668
|
+
reject(error);
|
|
669
|
+
this.onerror?.(error);
|
|
670
|
+
});
|
|
671
|
+
this._process.on("spawn", () => {
|
|
672
|
+
resolve2();
|
|
673
|
+
});
|
|
674
|
+
this._process.on("close", (_code) => {
|
|
675
|
+
this._process = void 0;
|
|
676
|
+
this.onclose?.();
|
|
677
|
+
});
|
|
678
|
+
this._process.stdin?.on("error", (error) => {
|
|
679
|
+
this.onerror?.(error);
|
|
680
|
+
});
|
|
681
|
+
this._process.stdout?.on("data", (chunk) => {
|
|
682
|
+
this._readBuffer.append(chunk);
|
|
683
|
+
this.processReadBuffer();
|
|
684
|
+
});
|
|
685
|
+
this._process.stdout?.on("error", (error) => {
|
|
686
|
+
this.onerror?.(error);
|
|
687
|
+
});
|
|
688
|
+
if (this._stderrStream && this._process.stderr) {
|
|
689
|
+
this._process.stderr.pipe(this._stderrStream);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* The stderr stream of the child process, if `StdioServerParameters.stderr` was set to "pipe" or "overlapped".
|
|
695
|
+
*
|
|
696
|
+
* If stderr piping was requested, a PassThrough stream is returned _immediately_, allowing callers to
|
|
697
|
+
* attach listeners before the start method is invoked. This prevents loss of any early
|
|
698
|
+
* error output emitted by the child process.
|
|
699
|
+
*/
|
|
700
|
+
get stderr() {
|
|
701
|
+
if (this._stderrStream) {
|
|
702
|
+
return this._stderrStream;
|
|
703
|
+
}
|
|
704
|
+
return this._process?.stderr ?? null;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* The child process pid spawned by this transport.
|
|
708
|
+
*
|
|
709
|
+
* This is only available after the transport has been started.
|
|
710
|
+
*/
|
|
711
|
+
get pid() {
|
|
712
|
+
return this._process?.pid ?? null;
|
|
713
|
+
}
|
|
714
|
+
processReadBuffer() {
|
|
715
|
+
while (true) {
|
|
716
|
+
try {
|
|
717
|
+
const message = this._readBuffer.readMessage();
|
|
718
|
+
if (message === null) {
|
|
719
|
+
break;
|
|
720
|
+
}
|
|
721
|
+
this.onmessage?.(message);
|
|
722
|
+
} catch (error) {
|
|
723
|
+
this.onerror?.(error);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
async close() {
|
|
728
|
+
if (this._process) {
|
|
729
|
+
const processToClose = this._process;
|
|
730
|
+
this._process = void 0;
|
|
731
|
+
const closePromise = new Promise((resolve2) => {
|
|
732
|
+
processToClose.once("close", () => {
|
|
733
|
+
resolve2();
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
try {
|
|
737
|
+
processToClose.stdin?.end();
|
|
738
|
+
} catch {
|
|
739
|
+
}
|
|
740
|
+
await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
|
|
741
|
+
if (processToClose.exitCode === null) {
|
|
742
|
+
try {
|
|
743
|
+
processToClose.kill("SIGTERM");
|
|
744
|
+
} catch {
|
|
745
|
+
}
|
|
746
|
+
await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2e3).unref())]);
|
|
747
|
+
}
|
|
748
|
+
if (processToClose.exitCode === null) {
|
|
749
|
+
try {
|
|
750
|
+
processToClose.kill("SIGKILL");
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
this._readBuffer.clear();
|
|
756
|
+
}
|
|
757
|
+
send(message) {
|
|
758
|
+
return new Promise((resolve2) => {
|
|
759
|
+
if (!this._process?.stdin) {
|
|
760
|
+
throw new Error("Not connected");
|
|
761
|
+
}
|
|
762
|
+
const json = serializeMessage(message);
|
|
763
|
+
if (this._process.stdin.write(json)) {
|
|
764
|
+
resolve2();
|
|
765
|
+
} else {
|
|
766
|
+
this._process.stdin.once("drain", resolve2);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
// src/mcpScan/connect.ts
|
|
773
|
+
var DEFAULTS = {
|
|
774
|
+
connectTimeoutMs: 6e4,
|
|
775
|
+
requestTimeoutMs: 2e4,
|
|
776
|
+
serverBudgetMs: 12e4,
|
|
777
|
+
concurrency: 4
|
|
778
|
+
};
|
|
779
|
+
var STDERR_KEEP = 8e3;
|
|
780
|
+
function childEnv(base, extra) {
|
|
781
|
+
const out = {};
|
|
782
|
+
for (const [k, v] of Object.entries(base)) {
|
|
783
|
+
if (v === void 0 || k.startsWith("LURQ_")) continue;
|
|
784
|
+
out[k] = v;
|
|
785
|
+
}
|
|
786
|
+
return { ...out, ...extra };
|
|
787
|
+
}
|
|
788
|
+
var Page = (key) => looseObject({ [key]: array(unknown()).optional(), nextCursor: string().optional() });
|
|
789
|
+
var Deadline = class extends Error {
|
|
790
|
+
constructor(what, ms) {
|
|
791
|
+
super(`${what} did not finish within ${Math.round(ms / 1e3)}s`);
|
|
792
|
+
this.name = "Deadline";
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
function before(p, ms, what) {
|
|
796
|
+
let timer;
|
|
797
|
+
return Promise.race([
|
|
798
|
+
p,
|
|
799
|
+
new Promise((_, reject) => {
|
|
800
|
+
timer = setTimeout(() => reject(new Deadline(what, ms)), ms);
|
|
801
|
+
timer.unref();
|
|
802
|
+
})
|
|
803
|
+
]).finally(() => clearTimeout(timer));
|
|
804
|
+
}
|
|
805
|
+
function openStdio(spec, opts) {
|
|
806
|
+
const transport = new StdioClientTransport({
|
|
807
|
+
command: spec.command,
|
|
808
|
+
args: spec.args,
|
|
809
|
+
env: childEnv(opts.env ?? process.env, spec.env),
|
|
810
|
+
cwd: spec.cwd ?? void 0,
|
|
811
|
+
stderr: "pipe"
|
|
812
|
+
});
|
|
813
|
+
let tail = "";
|
|
814
|
+
transport.stderr?.on("data", (chunk) => {
|
|
815
|
+
tail = (tail + chunk.toString("utf8")).slice(-STDERR_KEEP);
|
|
816
|
+
});
|
|
817
|
+
return wrap(transport, "stdio", () => tail);
|
|
818
|
+
}
|
|
819
|
+
function openHttp(spec, kind) {
|
|
820
|
+
const url = new URL(spec.url);
|
|
821
|
+
const requestInit = { headers: spec.headers };
|
|
822
|
+
const transport = kind === "http" ? new StreamableHTTPClientTransport(url, {
|
|
823
|
+
requestInit,
|
|
824
|
+
reconnectionOptions: {
|
|
825
|
+
maxRetries: 0,
|
|
826
|
+
initialReconnectionDelay: 1e3,
|
|
827
|
+
maxReconnectionDelay: 1e3,
|
|
828
|
+
reconnectionDelayGrowFactor: 1
|
|
829
|
+
}
|
|
830
|
+
}) : new SSEClientTransport(url, {
|
|
831
|
+
requestInit,
|
|
832
|
+
eventSourceInit: {
|
|
833
|
+
fetch: (u, init) => fetch(u, {
|
|
834
|
+
...init,
|
|
835
|
+
headers: { ...init?.headers, ...spec.headers }
|
|
836
|
+
})
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
return wrap(transport, kind, () => "");
|
|
840
|
+
}
|
|
841
|
+
function wrap(transport, kind, stderr) {
|
|
842
|
+
const client = new Client({ name: "lurq-scan", version: VERSION }, { capabilities: {} });
|
|
843
|
+
let noise = 0;
|
|
844
|
+
client.onerror = (err) => {
|
|
845
|
+
if (err instanceof SyntaxError || err.name === "ZodError") noise++;
|
|
846
|
+
};
|
|
847
|
+
return { client, transport, kind, stderr, noise: () => noise };
|
|
848
|
+
}
|
|
849
|
+
async function close(conn) {
|
|
850
|
+
await before(conn.client.close(), 6e3, "close").catch(() => {
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
async function paginate(conn, method, key, list, snapshot, opts, onPage, maxPages = LIMITS.pages) {
|
|
854
|
+
const seen = /* @__PURE__ */ new Set();
|
|
855
|
+
let cursor;
|
|
856
|
+
for (let page = 0; ; page++) {
|
|
857
|
+
if (page >= maxPages) {
|
|
858
|
+
snapshot.issues.push({
|
|
859
|
+
list,
|
|
860
|
+
kind: "truncated",
|
|
861
|
+
detail: `still paginating after ${maxPages} pages`
|
|
862
|
+
});
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
const result2 = await conn.client.request(
|
|
866
|
+
{ method, params: cursor ? { cursor } : {} },
|
|
867
|
+
Page(key),
|
|
868
|
+
{ timeout: opts.requestTimeoutMs, signal: opts.signal }
|
|
869
|
+
);
|
|
870
|
+
const entries = result2[key];
|
|
871
|
+
if (!onPage(Array.isArray(entries) ? entries : [])) return;
|
|
872
|
+
const next = result2.nextCursor;
|
|
873
|
+
if (!next) return;
|
|
874
|
+
if (seen.has(next)) {
|
|
875
|
+
snapshot.issues.push({
|
|
876
|
+
list,
|
|
877
|
+
kind: "truncated",
|
|
878
|
+
detail: "the server repeated a pagination cursor"
|
|
879
|
+
});
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
seen.add(next);
|
|
883
|
+
cursor = next;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
var isMethodNotFound = (err) => err?.code === -32601;
|
|
887
|
+
async function readContract(conn, opts) {
|
|
888
|
+
const snapshot = emptySnapshot();
|
|
889
|
+
const caps = conn.client.getServerCapabilities() ?? {};
|
|
890
|
+
const info = conn.client.getServerVersion();
|
|
891
|
+
snapshot.serverInfo = {
|
|
892
|
+
name: typeof info?.name === "string" ? info.name.slice(0, LIMITS.name) : null,
|
|
893
|
+
version: typeof info?.version === "string" ? info.version.slice(0, 100) : null,
|
|
894
|
+
title: typeof info?.title === "string" ? info.title.slice(0, LIMITS.name) : null
|
|
895
|
+
};
|
|
896
|
+
snapshot.protocolVersion = conn.transport.protocolVersion ?? null;
|
|
897
|
+
snapshot.capabilities = {
|
|
898
|
+
tools: !!caps.tools,
|
|
899
|
+
prompts: !!caps.prompts,
|
|
900
|
+
resources: !!caps.resources,
|
|
901
|
+
logging: !!caps.logging,
|
|
902
|
+
completions: !!caps.completions,
|
|
903
|
+
toolsListChanged: !!caps.tools?.listChanged
|
|
904
|
+
};
|
|
905
|
+
const instructions = conn.client.getInstructions();
|
|
906
|
+
if (typeof instructions === "string" && instructions) {
|
|
907
|
+
snapshot.instructions = instructions.slice(0, LIMITS.instructions);
|
|
908
|
+
if (instructions.length > LIMITS.instructions) {
|
|
909
|
+
snapshot.issues.push({
|
|
910
|
+
list: "initialize",
|
|
911
|
+
kind: "oversized",
|
|
912
|
+
detail: `instructions are ${instructions.length} chars`
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const tools = /* @__PURE__ */ new Map();
|
|
917
|
+
try {
|
|
918
|
+
await paginate(
|
|
919
|
+
conn,
|
|
920
|
+
"tools/list",
|
|
921
|
+
"tools",
|
|
922
|
+
"tools",
|
|
923
|
+
snapshot,
|
|
924
|
+
opts,
|
|
925
|
+
(e) => addTools(e, tools, snapshot.issues)
|
|
926
|
+
);
|
|
927
|
+
} catch (err) {
|
|
928
|
+
if (!(isMethodNotFound(err) && !caps.tools)) throw err;
|
|
929
|
+
}
|
|
930
|
+
snapshot.tools = [...tools.values()];
|
|
931
|
+
const secondary = async (list, run) => {
|
|
932
|
+
try {
|
|
933
|
+
await run();
|
|
934
|
+
} catch (err) {
|
|
935
|
+
if (isMethodNotFound(err)) return;
|
|
936
|
+
snapshot.issues.push({
|
|
937
|
+
list,
|
|
938
|
+
kind: "list_failed",
|
|
939
|
+
detail: (err instanceof Error ? err.message : String(err)).slice(0, 300)
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
if (caps.prompts) {
|
|
944
|
+
const prompts = /* @__PURE__ */ new Map();
|
|
945
|
+
await secondary(
|
|
946
|
+
"prompts",
|
|
947
|
+
() => paginate(
|
|
948
|
+
conn,
|
|
949
|
+
"prompts/list",
|
|
950
|
+
"prompts",
|
|
951
|
+
"prompts",
|
|
952
|
+
snapshot,
|
|
953
|
+
opts,
|
|
954
|
+
(e) => addPrompts(e, prompts, snapshot.issues)
|
|
955
|
+
)
|
|
956
|
+
);
|
|
957
|
+
snapshot.prompts = [...prompts.values()];
|
|
958
|
+
}
|
|
959
|
+
if (caps.resources) {
|
|
960
|
+
const templates = /* @__PURE__ */ new Map();
|
|
961
|
+
await secondary(
|
|
962
|
+
"resourceTemplates",
|
|
963
|
+
() => paginate(
|
|
964
|
+
conn,
|
|
965
|
+
"resources/templates/list",
|
|
966
|
+
"resourceTemplates",
|
|
967
|
+
"resourceTemplates",
|
|
968
|
+
snapshot,
|
|
969
|
+
opts,
|
|
970
|
+
(e) => addTemplates(e, templates, snapshot.issues)
|
|
971
|
+
)
|
|
972
|
+
);
|
|
973
|
+
snapshot.resourceTemplates = [...templates.values()];
|
|
974
|
+
let count = 0;
|
|
975
|
+
await secondary(
|
|
976
|
+
"resources",
|
|
977
|
+
() => paginate(
|
|
978
|
+
conn,
|
|
979
|
+
"resources/list",
|
|
980
|
+
"resources",
|
|
981
|
+
"resources",
|
|
982
|
+
snapshot,
|
|
983
|
+
opts,
|
|
984
|
+
(e) => {
|
|
985
|
+
count += e.length;
|
|
986
|
+
return true;
|
|
987
|
+
},
|
|
988
|
+
LIMITS.resourcePages
|
|
989
|
+
)
|
|
990
|
+
);
|
|
991
|
+
snapshot.resourceCount = count;
|
|
992
|
+
}
|
|
993
|
+
if (conn.noise() > 0) {
|
|
994
|
+
snapshot.issues.push({
|
|
995
|
+
list: "initialize",
|
|
996
|
+
kind: "stdout_noise",
|
|
997
|
+
detail: `${conn.noise()} non-protocol line(s) on stdout; some clients will fail on this`
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return snapshot;
|
|
1001
|
+
}
|
|
1002
|
+
function shouldTrySse(err) {
|
|
1003
|
+
if (err instanceof StreamableHTTPError)
|
|
1004
|
+
return [400, 404, 405].includes(err.code ?? 0) || err.code === -1;
|
|
1005
|
+
return false;
|
|
1006
|
+
}
|
|
1007
|
+
function result(spec, started, fields) {
|
|
1008
|
+
return {
|
|
1009
|
+
alias: spec.alias,
|
|
1010
|
+
serverKey: spec.serverKey,
|
|
1011
|
+
configFingerprint: spec.configFingerprint,
|
|
1012
|
+
registry: spec.registry,
|
|
1013
|
+
packageName: spec.packageName,
|
|
1014
|
+
pinnedVersion: spec.pinnedVersion,
|
|
1015
|
+
transport: spec.transport,
|
|
1016
|
+
transportUsed: null,
|
|
1017
|
+
error: null,
|
|
1018
|
+
hint: null,
|
|
1019
|
+
stderrTail: null,
|
|
1020
|
+
snapshot: null,
|
|
1021
|
+
durationMs: Date.now() - started,
|
|
1022
|
+
...fields
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
async function attempt(spec, kind, opts, started) {
|
|
1026
|
+
const connectTimeoutMs = opts.connectTimeoutMs ?? (kind === "stdio" ? DEFAULTS.connectTimeoutMs : 2e4);
|
|
1027
|
+
const requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULTS.requestTimeoutMs;
|
|
1028
|
+
const budget = opts.serverBudgetMs ?? DEFAULTS.serverBudgetMs;
|
|
1029
|
+
let conn = null;
|
|
1030
|
+
let stage = "connect";
|
|
1031
|
+
try {
|
|
1032
|
+
conn = kind === "stdio" ? openStdio(spec, opts) : openHttp(spec, kind);
|
|
1033
|
+
const c = conn;
|
|
1034
|
+
const work = (async () => {
|
|
1035
|
+
await before(
|
|
1036
|
+
c.client.connect(c.transport, { timeout: connectTimeoutMs, signal: opts.signal }),
|
|
1037
|
+
connectTimeoutMs + 2e3,
|
|
1038
|
+
"connect"
|
|
1039
|
+
);
|
|
1040
|
+
stage = "list";
|
|
1041
|
+
return readContract(c, { requestTimeoutMs, signal: opts.signal });
|
|
1042
|
+
})();
|
|
1043
|
+
const snapshot = await before(work, Math.max(1e3, budget - (Date.now() - started)), "scan");
|
|
1044
|
+
const clean = scrubDeep(snapshot, spec.secrets);
|
|
1045
|
+
const partial = clean.issues.some((i) => i.kind === "list_failed" || i.kind === "truncated");
|
|
1046
|
+
return result(spec, started, {
|
|
1047
|
+
status: partial ? "partial" : "ok",
|
|
1048
|
+
transportUsed: kind,
|
|
1049
|
+
snapshot: clean,
|
|
1050
|
+
stderrTail: tailOf(conn, spec)
|
|
1051
|
+
});
|
|
1052
|
+
} catch (err) {
|
|
1053
|
+
if (kind === "http" && spec.transport === "auto" && shouldTrySse(err))
|
|
1054
|
+
return { retrySse: true };
|
|
1055
|
+
if (opts.signal?.aborted) {
|
|
1056
|
+
return result(spec, started, {
|
|
1057
|
+
status: "cancelled",
|
|
1058
|
+
transportUsed: kind,
|
|
1059
|
+
error: "scan interrupted"
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
const c = err instanceof Deadline ? {
|
|
1063
|
+
status: "timeout",
|
|
1064
|
+
error: err.message,
|
|
1065
|
+
hint: stage === "connect" && kind === "stdio" ? "a first run of npx/uvx downloads the package; retry with a longer --timeout" : null
|
|
1066
|
+
} : classifyError(err, {
|
|
1067
|
+
stage,
|
|
1068
|
+
transport: kind,
|
|
1069
|
+
command: spec.command,
|
|
1070
|
+
stderrTail: conn?.stderr() ?? "",
|
|
1071
|
+
stdoutNoise: conn?.noise() ?? 0
|
|
1072
|
+
});
|
|
1073
|
+
return result(spec, started, {
|
|
1074
|
+
status: c.status,
|
|
1075
|
+
transportUsed: kind,
|
|
1076
|
+
error: scrub(c.error, spec.secrets),
|
|
1077
|
+
hint: c.hint,
|
|
1078
|
+
stderrTail: tailOf(conn, spec)
|
|
1079
|
+
});
|
|
1080
|
+
} finally {
|
|
1081
|
+
if (conn) await close(conn);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function tailOf(conn, spec) {
|
|
1085
|
+
const t = conn?.stderr().trim();
|
|
1086
|
+
return t ? scrub(t.split("\n").slice(-20).join("\n"), spec.secrets).slice(-2e3) : null;
|
|
1087
|
+
}
|
|
1088
|
+
async function scanServer(spec, opts = {}) {
|
|
1089
|
+
const started = Date.now();
|
|
1090
|
+
if (spec.disabled)
|
|
1091
|
+
return result(spec, started, { status: "disabled", hint: "disabled in its config" });
|
|
1092
|
+
if (!spec.trusted) return result(spec, started, { status: "untrusted", hint: spec.trustReason });
|
|
1093
|
+
if (spec.unresolved.length) {
|
|
1094
|
+
return result(spec, started, {
|
|
1095
|
+
status: "needs_config",
|
|
1096
|
+
error: `no value for ${spec.unresolved.join(", ")}`,
|
|
1097
|
+
hint: `export ${spec.unresolved.filter((v) => !v.startsWith("input:")).join(", ") || "the missing values"} before scanning`
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
if (opts.signal?.aborted)
|
|
1101
|
+
return result(spec, started, { status: "cancelled", error: "scan interrupted" });
|
|
1102
|
+
try {
|
|
1103
|
+
if (spec.transport === "stdio") {
|
|
1104
|
+
if (!spec.command)
|
|
1105
|
+
return result(spec, started, { status: "spawn_failed", error: "no command configured" });
|
|
1106
|
+
return await attempt(spec, "stdio", opts, started);
|
|
1107
|
+
}
|
|
1108
|
+
if (!spec.url)
|
|
1109
|
+
return result(spec, started, { status: "unreachable", error: "no url configured" });
|
|
1110
|
+
try {
|
|
1111
|
+
new URL(spec.url);
|
|
1112
|
+
} catch {
|
|
1113
|
+
return result(spec, started, {
|
|
1114
|
+
status: "unreachable",
|
|
1115
|
+
error: "the configured url is not valid",
|
|
1116
|
+
hint: "check the url in the config"
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
const first = await attempt(spec, spec.transport === "sse" ? "sse" : "http", opts, started);
|
|
1120
|
+
if ("retrySse" in first) return await attempt(spec, "sse", opts, started);
|
|
1121
|
+
return first;
|
|
1122
|
+
} catch (err) {
|
|
1123
|
+
return result(spec, started, {
|
|
1124
|
+
status: "protocol_error",
|
|
1125
|
+
error: scrub(err instanceof Error ? err.message : String(err), spec.secrets)
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
async function scanServers(specs, opts = {}) {
|
|
1130
|
+
return pMap(
|
|
1131
|
+
specs,
|
|
1132
|
+
async (spec) => {
|
|
1133
|
+
const scan = await scanServer(spec, opts);
|
|
1134
|
+
opts.onResult?.(scan);
|
|
1135
|
+
return scan;
|
|
1136
|
+
},
|
|
1137
|
+
Math.max(1, opts.concurrency ?? DEFAULTS.concurrency)
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/mcpScan/localHistory.ts
|
|
1142
|
+
init_esm_shims();
|
|
1143
|
+
import { createHash } from "crypto";
|
|
1144
|
+
import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
1145
|
+
import { join } from "path";
|
|
1146
|
+
var dir = () => join(lurqHome(), "mcp-snapshots");
|
|
1147
|
+
var fileFor = (serverKey, fingerprint) => join(
|
|
1148
|
+
dir(),
|
|
1149
|
+
`${createHash("sha256").update(`${serverKey}#${fingerprint}`).digest("hex").slice(0, 32)}.json`
|
|
1150
|
+
);
|
|
1151
|
+
function loadLocal(serverKey, fingerprint) {
|
|
1152
|
+
try {
|
|
1153
|
+
const parsed = JSON.parse(
|
|
1154
|
+
readFileSync(fileFor(serverKey, fingerprint), "utf8")
|
|
1155
|
+
);
|
|
1156
|
+
if (parsed?.version !== 1 || !Array.isArray(parsed.snapshot?.tools)) return null;
|
|
1157
|
+
return parsed;
|
|
1158
|
+
} catch {
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function saveLocal(entry) {
|
|
1163
|
+
mkdirSync(dir(), { recursive: true, mode: 448 });
|
|
1164
|
+
const target = fileFor(entry.serverKey, entry.configFingerprint);
|
|
1165
|
+
const tmp = `${target}.${process.pid}.tmp`;
|
|
1166
|
+
writeFileSync(tmp, JSON.stringify(entry), { mode: 384 });
|
|
1167
|
+
renameSync(tmp, target);
|
|
1168
|
+
try {
|
|
1169
|
+
chmodSync(target, 384);
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// src/cli/mcpScan.ts
|
|
1175
|
+
var THRESHOLDS = ["critical", "high", "moderate", "low", "info", "none"];
|
|
1176
|
+
function parseThreshold(v) {
|
|
1177
|
+
const t = (v ?? "none").toLowerCase();
|
|
1178
|
+
if (!THRESHOLDS.includes(t)) {
|
|
1179
|
+
throw new Error(`--fail-on must be one of ${THRESHOLDS.join(", ")}; got "${v}"`);
|
|
1180
|
+
}
|
|
1181
|
+
return t === "none" ? null : t;
|
|
1182
|
+
}
|
|
1183
|
+
function parsePositive(v, name, fallback, max) {
|
|
1184
|
+
if (v === void 0) return fallback;
|
|
1185
|
+
const n = Number(v);
|
|
1186
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
1187
|
+
throw new Error(`${name} must be a positive number; got "${v}"`);
|
|
1188
|
+
return Math.min(n, max);
|
|
1189
|
+
}
|
|
1190
|
+
var canPrompt = (opts) => !!process.stdin.isTTY && !!process.stdout.isTTY && !process.env.CI && !opts.json;
|
|
1191
|
+
var commandLine = (s) => scrub([s.command ?? s.url ?? "", ...s.args].join(" "), s.secrets).slice(0, 200);
|
|
1192
|
+
async function confirmProjectServers(specs, opts) {
|
|
1193
|
+
const pending = specs.filter(
|
|
1194
|
+
(s) => !s.trusted && s.scope === "project" && !/declined/.test(s.trustReason)
|
|
1195
|
+
);
|
|
1196
|
+
if (!pending.length || !canPrompt(opts)) return;
|
|
1197
|
+
console.error(
|
|
1198
|
+
yellow(
|
|
1199
|
+
`
|
|
1200
|
+
${pending.length} server(s) are committed to this repository and have not been approved:`
|
|
1201
|
+
)
|
|
1202
|
+
);
|
|
1203
|
+
for (const s of pending) console.error(` ${bold(s.alias)} ${dim(commandLine(s))}`);
|
|
1204
|
+
const { confirm } = await import("./dist-4XXIXHHZ.js");
|
|
1205
|
+
const yes = await confirm({ message: "Launch them for this scan?", default: false });
|
|
1206
|
+
if (!yes) return;
|
|
1207
|
+
for (const s of pending) {
|
|
1208
|
+
s.trusted = true;
|
|
1209
|
+
s.trustReason = "approved for this run";
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
function comparable(prev, next) {
|
|
1213
|
+
if (next.issues.some((i) => i.list === "tools" && i.kind === "truncated")) return null;
|
|
1214
|
+
const failed = new Set(next.issues.filter((i) => i.kind === "list_failed").map((i) => i.list));
|
|
1215
|
+
return {
|
|
1216
|
+
tools: next.tools,
|
|
1217
|
+
prompts: failed.has("prompts") ? prev.prompts : next.prompts,
|
|
1218
|
+
resourceTemplates: failed.has("resourceTemplates") ? prev.resourceTemplates : next.resourceTemplates,
|
|
1219
|
+
instructions: next.instructions
|
|
1220
|
+
};
|
|
1221
|
+
}
|
|
1222
|
+
function withHistory(scan, analysis, useHistory) {
|
|
1223
|
+
const report = { ...scan, analysis, sinceLastScan: null };
|
|
1224
|
+
if (!useHistory || !scan.snapshot) return report;
|
|
1225
|
+
const prev = loadLocal(scan.serverKey, scan.configFingerprint);
|
|
1226
|
+
const next = scan.snapshot;
|
|
1227
|
+
if (prev) {
|
|
1228
|
+
const input = comparable(prev.snapshot, next);
|
|
1229
|
+
if (input) {
|
|
1230
|
+
const d = diffSnapshots(prev.snapshot, input, scan.alias);
|
|
1231
|
+
report.sinceLastScan = {
|
|
1232
|
+
...d,
|
|
1233
|
+
at: prev.scannedAt,
|
|
1234
|
+
contract: {
|
|
1235
|
+
breaking: d.contract.breaking,
|
|
1236
|
+
removedTools: d.contract.removedTools,
|
|
1237
|
+
addedTools: d.contract.addedTools,
|
|
1238
|
+
silentDrift: d.contract.silentDrift,
|
|
1239
|
+
annotationFlips: d.contract.annotationFlips
|
|
1240
|
+
}
|
|
1241
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
if (scan.status === "ok") {
|
|
1245
|
+
saveLocal({
|
|
1246
|
+
version: 1,
|
|
1247
|
+
serverKey: scan.serverKey,
|
|
1248
|
+
configFingerprint: scan.configFingerprint,
|
|
1249
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1250
|
+
contentHash: contentHash(next),
|
|
1251
|
+
snapshot: next
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
return report;
|
|
1255
|
+
}
|
|
1256
|
+
async function collectScan(dir2, opts) {
|
|
1257
|
+
const root = resolve(dir2 ?? process.cwd());
|
|
1258
|
+
const cfg = readServerConfigs(root, {
|
|
1259
|
+
projectOnly: opts.projectOnly,
|
|
1260
|
+
trustProject: opts.trustProject
|
|
1261
|
+
});
|
|
1262
|
+
const notes = [...cfg.notes];
|
|
1263
|
+
let specs = cfg.servers;
|
|
1264
|
+
if (opts.only) {
|
|
1265
|
+
const wanted = new Set(
|
|
1266
|
+
opts.only.split(",").map((s) => s.trim()).filter(Boolean)
|
|
1267
|
+
);
|
|
1268
|
+
specs = specs.filter((s) => wanted.has(s.alias));
|
|
1269
|
+
const missing = [...wanted].filter((w) => !specs.some((s) => s.alias === w));
|
|
1270
|
+
if (missing.length) notes.push(`no configured server named ${missing.join(", ")}`);
|
|
1271
|
+
}
|
|
1272
|
+
await confirmProjectServers(specs, opts);
|
|
1273
|
+
const timeoutMs = parsePositive(opts.timeout, "--timeout", DEFAULTS.connectTimeoutMs / 1e3, 600) * 1e3;
|
|
1274
|
+
const concurrency = Math.floor(
|
|
1275
|
+
parsePositive(opts.concurrency, "--concurrency", DEFAULTS.concurrency, 16)
|
|
1276
|
+
);
|
|
1277
|
+
const ctl = new AbortController();
|
|
1278
|
+
const onSignal = () => {
|
|
1279
|
+
if (ctl.signal.aborted) process.exit(130);
|
|
1280
|
+
ctl.abort();
|
|
1281
|
+
console.error(
|
|
1282
|
+
yellow("\nstopping: closing the servers already running (Ctrl-C again to force)")
|
|
1283
|
+
);
|
|
1284
|
+
};
|
|
1285
|
+
process.on("SIGINT", onSignal);
|
|
1286
|
+
const progress = !opts.json && process.stderr.isTTY;
|
|
1287
|
+
let scans;
|
|
1288
|
+
try {
|
|
1289
|
+
scans = await scanServers(specs, {
|
|
1290
|
+
concurrency,
|
|
1291
|
+
connectTimeoutMs: timeoutMs,
|
|
1292
|
+
serverBudgetMs: Math.max(DEFAULTS.serverBudgetMs, timeoutMs + 6e4),
|
|
1293
|
+
signal: ctl.signal,
|
|
1294
|
+
onResult: (s) => {
|
|
1295
|
+
if (!progress) return;
|
|
1296
|
+
const mark = s.status === "ok" ? green("\u2713") : s.status === "partial" ? yellow("~") : dim("\xB7");
|
|
1297
|
+
const what = s.snapshot ? `${s.snapshot.tools.length} tools` : s.status;
|
|
1298
|
+
console.error(
|
|
1299
|
+
`${mark} ${s.alias} ${dim(`${what} (${(s.durationMs / 1e3).toFixed(1)}s)`)}`
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
} finally {
|
|
1304
|
+
process.off("SIGINT", onSignal);
|
|
1305
|
+
}
|
|
1306
|
+
const servers = scans.map(
|
|
1307
|
+
(s) => withHistory(s, s.snapshot ? analyzeServer(s.snapshot) : null, opts.history !== false)
|
|
1308
|
+
);
|
|
1309
|
+
const stackScan = analyzeStackScan(
|
|
1310
|
+
// Servers the user chose not to run are not part of the namespace being
|
|
1311
|
+
// assembled; servers that failed are, and make the stack unknown.
|
|
1312
|
+
servers.filter((s) => s.status !== "disabled" && s.status !== "untrusted").map((s) => ({ alias: s.alias, snapshot: s.snapshot }))
|
|
1313
|
+
);
|
|
1314
|
+
const severities = [
|
|
1315
|
+
...servers.flatMap((s) => s.analysis?.findings ?? []),
|
|
1316
|
+
...stackScan.findings,
|
|
1317
|
+
...servers.flatMap(
|
|
1318
|
+
(s) => s.sinceLastScan && !s.sinceLastScan.unchanged ? [s.sinceLastScan] : []
|
|
1319
|
+
)
|
|
1320
|
+
];
|
|
1321
|
+
return {
|
|
1322
|
+
root,
|
|
1323
|
+
filesRead: cfg.filesRead,
|
|
1324
|
+
notes,
|
|
1325
|
+
servers,
|
|
1326
|
+
stack: stackScan.stack,
|
|
1327
|
+
findings: stackScan.findings,
|
|
1328
|
+
// First source per alias: an alias declared in two files is still one
|
|
1329
|
+
// server, and the first declaration is the one that won.
|
|
1330
|
+
configSources: Object.fromEntries(
|
|
1331
|
+
specs.flatMap((s) => s.sources[0] ? [[s.alias, s.sources[0]]] : [])
|
|
1332
|
+
),
|
|
1333
|
+
worst: worst(severities),
|
|
1334
|
+
account: null,
|
|
1335
|
+
uploadProblem: null
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
var sevColour = (s) => s === "critical" || s === "high" ? red : s === "moderate" ? yellow : dim;
|
|
1339
|
+
function ago(iso) {
|
|
1340
|
+
const mins = Math.round((Date.now() - Date.parse(iso)) / 6e4);
|
|
1341
|
+
if (!Number.isFinite(mins)) return iso;
|
|
1342
|
+
if (mins < 60) return `${Math.max(mins, 1)}m ago`;
|
|
1343
|
+
if (mins < 60 * 48) return `${Math.round(mins / 60)}h ago`;
|
|
1344
|
+
return `${Math.round(mins / 1440)}d ago`;
|
|
1345
|
+
}
|
|
1346
|
+
function does(a) {
|
|
1347
|
+
const caps = Object.entries(a?.stats.capabilities ?? {}).sort((x, y) => (y[1] ?? 0) - (x[1] ?? 0)).map(([c]) => c);
|
|
1348
|
+
if (!caps.length) return dim("\u2014");
|
|
1349
|
+
return caps.length > 2 ? `${caps.slice(0, 2).join(", ")} +${caps.length - 2}` : caps.join(", ");
|
|
1350
|
+
}
|
|
1351
|
+
function statusCell(s) {
|
|
1352
|
+
if (s.status === "ok") return green("ok");
|
|
1353
|
+
if (s.status === "partial") return yellow("partial");
|
|
1354
|
+
if (s.status === "untrusted" || s.status === "disabled") return dim(s.status);
|
|
1355
|
+
return red(s.status);
|
|
1356
|
+
}
|
|
1357
|
+
function render(report) {
|
|
1358
|
+
console.log(bold(report.root));
|
|
1359
|
+
console.log(
|
|
1360
|
+
dim(`read ${report.filesRead.length} config file(s): ${report.filesRead.join(", ") || "\u2014"}`)
|
|
1361
|
+
);
|
|
1362
|
+
console.log("");
|
|
1363
|
+
console.log(
|
|
1364
|
+
table(
|
|
1365
|
+
["Server", "Via", "Status", "Tools", "Writes", "Destroys", "Does"],
|
|
1366
|
+
report.servers.map((s) => [
|
|
1367
|
+
s.alias,
|
|
1368
|
+
s.transportUsed ?? s.transport,
|
|
1369
|
+
statusCell(s),
|
|
1370
|
+
s.analysis ? String(s.analysis.stats.tools) : "\u2014",
|
|
1371
|
+
s.analysis ? String(s.analysis.stats.writes) : "\u2014",
|
|
1372
|
+
s.analysis ? s.analysis.stats.destroys ? red(String(s.analysis.stats.destroys)) : "0" : "\u2014",
|
|
1373
|
+
does(s.analysis)
|
|
1374
|
+
])
|
|
1375
|
+
)
|
|
1376
|
+
);
|
|
1377
|
+
const accountSince = new Map(
|
|
1378
|
+
(report.account?.results ?? []).filter((r) => r.since).map((r) => [r.alias, r.since])
|
|
1379
|
+
);
|
|
1380
|
+
const changes = report.servers.flatMap((s) => {
|
|
1381
|
+
const acc2 = accountSince.get(s.alias);
|
|
1382
|
+
if (acc2)
|
|
1383
|
+
return [
|
|
1384
|
+
{ alias: s.alias, severity: acc2.severity, at: acc2.at, summary: acc2.summary }
|
|
1385
|
+
];
|
|
1386
|
+
const d = s.sinceLastScan;
|
|
1387
|
+
return d && !d.unchanged ? [{ alias: s.alias, severity: d.severity, at: d.at, summary: d.summary }] : [];
|
|
1388
|
+
});
|
|
1389
|
+
if (changes.length) {
|
|
1390
|
+
console.log(`
|
|
1391
|
+
${bold("Since the last scan")}`);
|
|
1392
|
+
for (const c of changes) {
|
|
1393
|
+
console.log(
|
|
1394
|
+
` ${sevColour(c.severity)(c.severity.toUpperCase().padEnd(8))} ${bold(c.alias)} ${dim(ago(c.at))} ${c.summary}`
|
|
1395
|
+
);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
const findings = sortFindings([
|
|
1399
|
+
...report.servers.flatMap(
|
|
1400
|
+
(s) => (s.analysis?.findings ?? []).map((f) => ({ ...f, server: s.alias }))
|
|
1401
|
+
),
|
|
1402
|
+
...report.findings
|
|
1403
|
+
]).filter((f) => f.severity !== "info");
|
|
1404
|
+
if (findings.length) {
|
|
1405
|
+
console.log(`
|
|
1406
|
+
${bold("Findings")}`);
|
|
1407
|
+
for (const f of findings.slice(0, 40)) {
|
|
1408
|
+
const where = [f.server, f.tool].filter(Boolean).join(" \u203A ");
|
|
1409
|
+
console.log(
|
|
1410
|
+
` ${sevColour(f.severity)(f.severity.toUpperCase().padEnd(8))} ${bold(where || f.where)} ${f.detail}`
|
|
1411
|
+
);
|
|
1412
|
+
if (f.evidence) console.log(` ${dim(f.evidence)}`);
|
|
1413
|
+
}
|
|
1414
|
+
if (findings.length > 40) console.log(dim(` \u2026 ${findings.length - 40} more (use --json)`));
|
|
1415
|
+
}
|
|
1416
|
+
const unread = report.servers.filter((s) => !s.snapshot);
|
|
1417
|
+
if (unread.length) {
|
|
1418
|
+
console.log(`
|
|
1419
|
+
${bold("Not read")}`);
|
|
1420
|
+
for (const s of unread) {
|
|
1421
|
+
console.log(` ${bold(s.alias)} ${statusCell(s)}${s.error ? ` ${s.error}` : ""}`);
|
|
1422
|
+
if (s.hint) console.log(` ${dim(s.hint)}`);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
console.log("");
|
|
1426
|
+
console.log(dim(report.stack.note));
|
|
1427
|
+
if (isCrowded(report.stack)) {
|
|
1428
|
+
console.log(
|
|
1429
|
+
yellow(
|
|
1430
|
+
`~${report.stack.estimatedContextTokens.toLocaleString()} tokens of tool schema ride in every request \u2014 worth trimming`
|
|
1431
|
+
)
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
for (const n of report.notes) console.log(dim(`\xB7 ${n}`));
|
|
1435
|
+
const acc = report.account;
|
|
1436
|
+
if (acc) {
|
|
1437
|
+
if (acc.recorded) {
|
|
1438
|
+
console.log(
|
|
1439
|
+
dim(
|
|
1440
|
+
`recorded ${acc.recorded} server(s) to your account${acc.changed ? `, ${acc.changed} changed since the last upload` : ""}`
|
|
1441
|
+
)
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
for (const r of acc.rejected)
|
|
1445
|
+
console.log(yellow(`not recorded: ${r.alias ?? "a server"}: ${r.reason}`));
|
|
1446
|
+
if (acc.error) console.log(yellow(`could not record this scan to your account: ${acc.error}`));
|
|
1447
|
+
} else if (!resolveApiKey()) {
|
|
1448
|
+
console.log(
|
|
1449
|
+
dim("run `lurq setup` to keep this history across machines and see it in the dashboard")
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
function toJson(report) {
|
|
1454
|
+
return {
|
|
1455
|
+
...report,
|
|
1456
|
+
servers: report.servers.map(({ snapshot, ...rest }) => ({
|
|
1457
|
+
...rest,
|
|
1458
|
+
serverInfo: snapshot?.serverInfo ?? null,
|
|
1459
|
+
tools: snapshot?.tools.map((t) => t.name) ?? null,
|
|
1460
|
+
issues: snapshot?.issues ?? []
|
|
1461
|
+
}))
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
var NOT_UPLOADED = /* @__PURE__ */ new Set(["untrusted", "disabled", "cancelled"]);
|
|
1465
|
+
function toUpload(s) {
|
|
1466
|
+
return {
|
|
1467
|
+
alias: s.alias,
|
|
1468
|
+
serverKey: s.serverKey,
|
|
1469
|
+
configFingerprint: s.configFingerprint,
|
|
1470
|
+
registry: s.registry,
|
|
1471
|
+
packageName: s.packageName,
|
|
1472
|
+
pinnedVersion: s.pinnedVersion,
|
|
1473
|
+
transport: s.transport,
|
|
1474
|
+
status: s.status,
|
|
1475
|
+
error: s.error,
|
|
1476
|
+
snapshot: s.snapshot ? {
|
|
1477
|
+
serverInfo: s.snapshot.serverInfo,
|
|
1478
|
+
instructions: s.snapshot.instructions,
|
|
1479
|
+
tools: s.snapshot.tools,
|
|
1480
|
+
prompts: s.snapshot.prompts,
|
|
1481
|
+
resourceTemplates: s.snapshot.resourceTemplates
|
|
1482
|
+
} : null
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
function chunkUploads(items, maxBytes = 3e6, maxCount = 50) {
|
|
1486
|
+
const chunks = [];
|
|
1487
|
+
let current = [];
|
|
1488
|
+
let bytes = 0;
|
|
1489
|
+
for (const item of items) {
|
|
1490
|
+
const size = JSON.stringify(item).length;
|
|
1491
|
+
if (current.length && (bytes + size > maxBytes || current.length >= maxCount)) {
|
|
1492
|
+
chunks.push(current);
|
|
1493
|
+
current = [];
|
|
1494
|
+
bytes = 0;
|
|
1495
|
+
}
|
|
1496
|
+
current.push(item);
|
|
1497
|
+
bytes += size;
|
|
1498
|
+
}
|
|
1499
|
+
if (current.length) chunks.push(current);
|
|
1500
|
+
return chunks;
|
|
1501
|
+
}
|
|
1502
|
+
async function syncToAccount(report, opts) {
|
|
1503
|
+
if (opts.upload === false || !resolveApiKey()) return null;
|
|
1504
|
+
const servers = report.servers.filter((s) => !NOT_UPLOADED.has(s.status)).map(toUpload);
|
|
1505
|
+
if (!servers.length) return null;
|
|
1506
|
+
const out = { recorded: 0, changed: 0, results: [], rejected: [], error: null };
|
|
1507
|
+
for (const chunk of chunkUploads(servers)) {
|
|
1508
|
+
try {
|
|
1509
|
+
const r = await uploadMcpScan({
|
|
1510
|
+
source: process.env.CI ? "ci" : "cli",
|
|
1511
|
+
clientVersion: VERSION,
|
|
1512
|
+
contribute: opts.contribute !== false,
|
|
1513
|
+
servers: chunk
|
|
1514
|
+
});
|
|
1515
|
+
out.results.push(...r.servers);
|
|
1516
|
+
out.rejected.push(...r.rejected.map((x) => ({ alias: x.alias, reason: x.reason })));
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
out.error = err instanceof RemoteError ? err.message : err instanceof Error ? err.message : String(err);
|
|
1519
|
+
break;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
out.recorded = out.results.length;
|
|
1523
|
+
out.changed = out.results.filter((r) => r.change === "changed").length;
|
|
1524
|
+
return out;
|
|
1525
|
+
}
|
|
1526
|
+
function uploadProblem(report, opts) {
|
|
1527
|
+
if (!opts.requireUpload) return null;
|
|
1528
|
+
if (!resolveApiKey()) return "no API key is configured; set LURQ_API_KEY";
|
|
1529
|
+
const acc = report.account;
|
|
1530
|
+
if (!acc) return null;
|
|
1531
|
+
if (acc.error) return `the upload failed: ${acc.error}`;
|
|
1532
|
+
if (acc.rejected.length) {
|
|
1533
|
+
return `${acc.rejected.length} server(s) were not recorded: ${acc.rejected.map((r) => `${r.alias ?? "a server"} (${r.reason})`).join(", ")}`;
|
|
1534
|
+
}
|
|
1535
|
+
return null;
|
|
1536
|
+
}
|
|
1537
|
+
async function runMcpScan(dir2, opts) {
|
|
1538
|
+
const threshold = parseThreshold(opts.failOn);
|
|
1539
|
+
if (opts.requireUpload && opts.upload === false) {
|
|
1540
|
+
throw new Error("--require-upload and --no-upload contradict each other");
|
|
1541
|
+
}
|
|
1542
|
+
const report = await collectScan(dir2, opts);
|
|
1543
|
+
report.account = await syncToAccount(report, opts);
|
|
1544
|
+
if (report.account) {
|
|
1545
|
+
report.worst = worst([
|
|
1546
|
+
...report.worst ? [{ severity: report.worst }] : [],
|
|
1547
|
+
...report.account.results.flatMap(
|
|
1548
|
+
(r) => r.since ? [{ severity: r.since.severity }] : []
|
|
1549
|
+
)
|
|
1550
|
+
]);
|
|
1551
|
+
}
|
|
1552
|
+
report.uploadProblem = uploadProblem(report, opts);
|
|
1553
|
+
if (opts.sarif) {
|
|
1554
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
1555
|
+
const { scanFindings } = await import("./mcpConfig-3LYPRY5Q.js");
|
|
1556
|
+
const { toSarif } = await import("./sarif-WLVJFJ7U.js");
|
|
1557
|
+
const doc = toSarif(scanFindings(report), { version: VERSION, read: () => null });
|
|
1558
|
+
writeFileSync2(opts.sarif, `${JSON.stringify(doc, null, 2)}
|
|
1559
|
+
`, "utf8");
|
|
1560
|
+
console.error(`wrote ${opts.sarif}`);
|
|
1561
|
+
}
|
|
1562
|
+
if (opts.json) console.log(JSON.stringify(toJson(report), null, 2));
|
|
1563
|
+
else if (report.servers.length === 0) {
|
|
1564
|
+
console.log(dim(`no MCP servers configured for ${report.root}`));
|
|
1565
|
+
for (const n of report.notes) console.log(dim(`\xB7 ${n}`));
|
|
1566
|
+
} else render(report);
|
|
1567
|
+
if (threshold && report.worst && SEVERITY_RANK[report.worst] <= SEVERITY_RANK[threshold]) {
|
|
1568
|
+
process.exitCode = 1;
|
|
1569
|
+
}
|
|
1570
|
+
if (report.uploadProblem) {
|
|
1571
|
+
console.error(
|
|
1572
|
+
red(`upload required, and the scan was not fully recorded: ${report.uploadProblem}`)
|
|
1573
|
+
);
|
|
1574
|
+
process.exitCode = 1;
|
|
1575
|
+
}
|
|
1576
|
+
if (opts.githubIssue) await syncDashboardIssue(report);
|
|
1577
|
+
}
|
|
1578
|
+
async function syncDashboardIssue(report, env = process.env) {
|
|
1579
|
+
const { githubEnvFrom, renderDashboardIssue, upsertDashboardIssue } = await import("./dashboardIssue-KBKLQMGC.js");
|
|
1580
|
+
const gh = githubEnvFrom(env);
|
|
1581
|
+
if (!gh) {
|
|
1582
|
+
console.error(
|
|
1583
|
+
yellow("--github-issue needs GITHUB_TOKEN and GITHUB_REPOSITORY; set by GitHub Actions")
|
|
1584
|
+
);
|
|
1585
|
+
return;
|
|
1586
|
+
}
|
|
1587
|
+
let plan = null;
|
|
1588
|
+
let planNote = null;
|
|
1589
|
+
if (resolveApiKey()) {
|
|
1590
|
+
try {
|
|
1591
|
+
const { buildUpgradePlan } = await import("./upgradePlan-ZS5IOVMZ.js");
|
|
1592
|
+
plan = await buildUpgradePlan(report.root, { repo: gh.repo });
|
|
1593
|
+
} catch (err) {
|
|
1594
|
+
planNote = `Not checked: ${err instanceof Error ? err.message : String(err)}`.slice(0, 200);
|
|
1595
|
+
}
|
|
1596
|
+
} else {
|
|
1597
|
+
planNote = "Add a LURQ_API_KEY secret to include dependency upgrades here.";
|
|
1598
|
+
}
|
|
1599
|
+
const runUrl = env.GITHUB_RUN_ID ? `${env.GITHUB_SERVER_URL ?? "https://github.com"}/${gh.repo}/actions/runs/${env.GITHUB_RUN_ID}` : null;
|
|
1600
|
+
const rendered = renderDashboardIssue({
|
|
1601
|
+
repo: gh.repo,
|
|
1602
|
+
generatedAt: /* @__PURE__ */ new Date(),
|
|
1603
|
+
runUrl,
|
|
1604
|
+
dashboardUrl: "https://lurq.run/dashboard/mcp",
|
|
1605
|
+
scan: report,
|
|
1606
|
+
plan,
|
|
1607
|
+
planNote
|
|
1608
|
+
});
|
|
1609
|
+
try {
|
|
1610
|
+
const r = await upsertDashboardIssue(gh, rendered);
|
|
1611
|
+
const what = r.action === "closed" ? "is closed, so it was left alone" : r.action;
|
|
1612
|
+
console.error(
|
|
1613
|
+
dim(
|
|
1614
|
+
`dashboard issue ${what}${r.url ? `: ${r.url}` : ""}${r.commented ? " (commented on new urgent findings)" : ""}`
|
|
1615
|
+
)
|
|
1616
|
+
);
|
|
1617
|
+
} catch (err) {
|
|
1618
|
+
console.error(
|
|
1619
|
+
yellow(
|
|
1620
|
+
`::warning::could not update the lurq dashboard issue: ${err instanceof Error ? err.message : String(err)}`
|
|
1621
|
+
)
|
|
1622
|
+
);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function runMcpStackLive(dir2, opts) {
|
|
1626
|
+
const report = await collectScan(dir2, { ...opts, history: false });
|
|
1627
|
+
if (opts.json) {
|
|
1628
|
+
console.log(JSON.stringify({ ...report.stack, findings: report.findings }, null, 2));
|
|
1629
|
+
return;
|
|
1630
|
+
}
|
|
1631
|
+
if (report.servers.length === 0) {
|
|
1632
|
+
console.log(dim(`no MCP servers configured for ${report.root}`));
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
const colour = report.stack.overall === "conflict" ? red : report.stack.overall === "unknown" ? yellow : green;
|
|
1636
|
+
console.log(`${bold(report.root)} ${colour(report.stack.overall)}`);
|
|
1637
|
+
for (const c of report.stack.collisions) {
|
|
1638
|
+
const tag = c.writes ? red("writes") : yellow("read-only");
|
|
1639
|
+
console.log(`${red("\u2717")} ${bold(c.tool)} exposed by ${c.servers.join(" and ")} [${tag}]`);
|
|
1640
|
+
}
|
|
1641
|
+
for (const f of report.findings.filter((x) => x.kind === "cross_server_reference")) {
|
|
1642
|
+
console.log(`${sevColour(f.severity)("!")} ${bold(`${f.server} \u203A ${f.tool}`)} ${f.detail}`);
|
|
1643
|
+
}
|
|
1644
|
+
console.log(
|
|
1645
|
+
table(
|
|
1646
|
+
["Server", "Status", "Tools", "Writes", "Destroys"],
|
|
1647
|
+
report.servers.map((s) => [
|
|
1648
|
+
s.alias,
|
|
1649
|
+
statusCell(s),
|
|
1650
|
+
s.analysis ? String(s.analysis.stats.tools) : "\u2014",
|
|
1651
|
+
s.analysis ? String(s.analysis.stats.writes) : "\u2014",
|
|
1652
|
+
s.analysis ? s.analysis.stats.destroys ? red(String(s.analysis.stats.destroys)) : "0" : "\u2014"
|
|
1653
|
+
])
|
|
1654
|
+
)
|
|
1655
|
+
);
|
|
1656
|
+
console.log(dim(report.stack.note));
|
|
1657
|
+
if (isCrowded(report.stack)) {
|
|
1658
|
+
console.log(
|
|
1659
|
+
yellow(
|
|
1660
|
+
`~${report.stack.estimatedContextTokens.toLocaleString()} tokens of tool schema ride in every request \u2014 worth trimming`
|
|
1661
|
+
)
|
|
1662
|
+
);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
async function runMcpCi(dir2, opts) {
|
|
1666
|
+
const threshold = parseThreshold(opts.failOn ?? "high");
|
|
1667
|
+
const root = resolve(dir2 ?? process.cwd());
|
|
1668
|
+
const cfg = readServerConfigs(root, { projectOnly: true, trustProject: true, env: {} });
|
|
1669
|
+
const { MCP_SCAN_WORKFLOW_PATH, renderMcpScanWorkflow, secretNameFor } = await import("./mcpScanWorkflow-27O44FOE.js");
|
|
1670
|
+
const secrets = [
|
|
1671
|
+
...new Set(cfg.servers.flatMap((s) => s.unresolved.filter((v) => !v.startsWith("input:"))))
|
|
1672
|
+
];
|
|
1673
|
+
const needsUv = cfg.servers.some(
|
|
1674
|
+
(s) => s.registry === "pypi" || /^(uvx|uv|pipx)$/.test((s.command ?? "").split(/[\\/]/).pop() ?? "")
|
|
1675
|
+
);
|
|
1676
|
+
const yaml = renderMcpScanWorkflow({
|
|
1677
|
+
cron: opts.cron,
|
|
1678
|
+
failOn: threshold ?? "none",
|
|
1679
|
+
secrets,
|
|
1680
|
+
needsUv,
|
|
1681
|
+
githubIssue: opts.issue !== false,
|
|
1682
|
+
sarif: opts.sarif === true
|
|
1683
|
+
});
|
|
1684
|
+
if (opts.print) {
|
|
1685
|
+
process.stdout.write(yaml);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
const { existsSync, mkdirSync: mkdirSync2, writeFileSync: writeFileSync2 } = await import("fs");
|
|
1689
|
+
const { dirname, join: join2 } = await import("path");
|
|
1690
|
+
const target = join2(root, MCP_SCAN_WORKFLOW_PATH);
|
|
1691
|
+
if (existsSync(target) && !opts.force) {
|
|
1692
|
+
throw new Error(`${MCP_SCAN_WORKFLOW_PATH} already exists; pass --force to replace it`);
|
|
1693
|
+
}
|
|
1694
|
+
mkdirSync2(dirname(target), { recursive: true });
|
|
1695
|
+
writeFileSync2(target, yaml);
|
|
1696
|
+
console.log(`wrote ${MCP_SCAN_WORKFLOW_PATH}`);
|
|
1697
|
+
if (cfg.servers.length === 0) {
|
|
1698
|
+
console.log(
|
|
1699
|
+
yellow(
|
|
1700
|
+
"no MCP servers are committed to this repository yet; the workflow scans only committed configs (.mcp.json)"
|
|
1701
|
+
)
|
|
1702
|
+
);
|
|
1703
|
+
} else {
|
|
1704
|
+
console.log(
|
|
1705
|
+
dim(
|
|
1706
|
+
`scans ${cfg.servers.length} committed server(s): ${cfg.servers.map((s) => s.alias).join(", ")}`
|
|
1707
|
+
)
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
if (opts.sarif) {
|
|
1711
|
+
console.log(
|
|
1712
|
+
dim(
|
|
1713
|
+
"findings upload to code scanning; enable it under Settings \u2192 Code security, or drop --sarif"
|
|
1714
|
+
)
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
console.log("add these repository secrets:");
|
|
1718
|
+
console.log(
|
|
1719
|
+
` ${bold("LURQ_API_KEY")} ${dim("your lurq key, so scans are recorded to your account")}`
|
|
1720
|
+
);
|
|
1721
|
+
for (const s of secrets.sort())
|
|
1722
|
+
console.log(
|
|
1723
|
+
` ${bold(secretNameFor(s))}${secretNameFor(s) !== s ? dim(` (read by the server as ${s})`) : ""}`
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
export {
|
|
1727
|
+
chunkUploads,
|
|
1728
|
+
collectScan,
|
|
1729
|
+
runMcpCi,
|
|
1730
|
+
runMcpScan,
|
|
1731
|
+
runMcpStackLive,
|
|
1732
|
+
syncDashboardIssue,
|
|
1733
|
+
syncToAccount,
|
|
1734
|
+
uploadProblem
|
|
1735
|
+
};
|