@bryan-thompson/inspector-assessment-cli 1.38.3 → 1.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/__tests__/flag-parsing.test.js +607 -0
- package/build/assess-full.js +19 -2
- package/build/lib/assessment-runner/assessment-executor.js +28 -3
- package/build/lib/assessment-runner/index.js +2 -0
- package/build/lib/assessment-runner/single-module-runner.js +315 -0
- package/build/lib/cli-parser.js +130 -2
- package/build/lib/result-output.js +130 -0
- package/package.json +1 -1
|
@@ -840,3 +840,610 @@ describe("parseArgs Zod Schema Integration", () => {
|
|
|
840
840
|
});
|
|
841
841
|
});
|
|
842
842
|
});
|
|
843
|
+
/**
|
|
844
|
+
* Transport Flag Tests (--http, --sse)
|
|
845
|
+
*
|
|
846
|
+
* Tests for the convenience transport flags that allow quick testing
|
|
847
|
+
* without creating a config file.
|
|
848
|
+
*/
|
|
849
|
+
describe("Transport Flags (--http, --sse)", () => {
|
|
850
|
+
let processExitSpy;
|
|
851
|
+
let consoleErrorSpy;
|
|
852
|
+
beforeEach(() => {
|
|
853
|
+
jest.useFakeTimers();
|
|
854
|
+
processExitSpy = jest
|
|
855
|
+
.spyOn(process, "exit")
|
|
856
|
+
.mockImplementation((() => { }));
|
|
857
|
+
consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
858
|
+
});
|
|
859
|
+
afterEach(() => {
|
|
860
|
+
jest.runAllTimers();
|
|
861
|
+
jest.useRealTimers();
|
|
862
|
+
processExitSpy?.mockRestore();
|
|
863
|
+
consoleErrorSpy?.mockRestore();
|
|
864
|
+
});
|
|
865
|
+
describe("--http flag", () => {
|
|
866
|
+
it("should accept valid HTTP URL", () => {
|
|
867
|
+
const result = parseArgs([
|
|
868
|
+
"test-server",
|
|
869
|
+
"--http",
|
|
870
|
+
"http://localhost:10900/mcp",
|
|
871
|
+
]);
|
|
872
|
+
expect(result.httpUrl).toBe("http://localhost:10900/mcp");
|
|
873
|
+
expect(result.helpRequested).toBeFalsy();
|
|
874
|
+
});
|
|
875
|
+
it("should accept valid HTTPS URL", () => {
|
|
876
|
+
const result = parseArgs([
|
|
877
|
+
"test-server",
|
|
878
|
+
"--http",
|
|
879
|
+
"https://api.example.com/mcp",
|
|
880
|
+
]);
|
|
881
|
+
expect(result.httpUrl).toBe("https://api.example.com/mcp");
|
|
882
|
+
expect(result.helpRequested).toBeFalsy();
|
|
883
|
+
});
|
|
884
|
+
it("should reject invalid URL", () => {
|
|
885
|
+
const result = parseArgs(["test-server", "--http", "not-a-valid-url"]);
|
|
886
|
+
expect(result.helpRequested).toBe(true);
|
|
887
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid URL for --http"));
|
|
888
|
+
});
|
|
889
|
+
it("should reject missing URL argument", () => {
|
|
890
|
+
const result = parseArgs(["test-server", "--http"]);
|
|
891
|
+
expect(result.helpRequested).toBe(true);
|
|
892
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http requires a URL argument"));
|
|
893
|
+
});
|
|
894
|
+
it("should reject when next argument is another flag", () => {
|
|
895
|
+
const result = parseArgs(["test-server", "--http", "--verbose"]);
|
|
896
|
+
expect(result.helpRequested).toBe(true);
|
|
897
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http requires a URL argument"));
|
|
898
|
+
});
|
|
899
|
+
it("should reject non-HTTP protocol (file://)", () => {
|
|
900
|
+
const result = parseArgs(["test-server", "--http", "file:///etc/passwd"]);
|
|
901
|
+
expect(result.helpRequested).toBe(true);
|
|
902
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http requires HTTP or HTTPS URL, got: file:"));
|
|
903
|
+
});
|
|
904
|
+
it("should reject non-HTTP protocol (ftp://)", () => {
|
|
905
|
+
const result = parseArgs([
|
|
906
|
+
"test-server",
|
|
907
|
+
"--http",
|
|
908
|
+
"ftp://example.com/file",
|
|
909
|
+
]);
|
|
910
|
+
expect(result.helpRequested).toBe(true);
|
|
911
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http requires HTTP or HTTPS URL, got: ftp:"));
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
describe("--sse flag", () => {
|
|
915
|
+
it("should accept valid SSE URL", () => {
|
|
916
|
+
const result = parseArgs([
|
|
917
|
+
"test-server",
|
|
918
|
+
"--sse",
|
|
919
|
+
"http://localhost:9002/sse",
|
|
920
|
+
]);
|
|
921
|
+
expect(result.sseUrl).toBe("http://localhost:9002/sse");
|
|
922
|
+
expect(result.helpRequested).toBeFalsy();
|
|
923
|
+
});
|
|
924
|
+
it("should accept valid HTTPS SSE URL", () => {
|
|
925
|
+
const result = parseArgs([
|
|
926
|
+
"test-server",
|
|
927
|
+
"--sse",
|
|
928
|
+
"https://api.example.com/sse",
|
|
929
|
+
]);
|
|
930
|
+
expect(result.sseUrl).toBe("https://api.example.com/sse");
|
|
931
|
+
expect(result.helpRequested).toBeFalsy();
|
|
932
|
+
});
|
|
933
|
+
it("should reject invalid URL", () => {
|
|
934
|
+
const result = parseArgs(["test-server", "--sse", "invalid-url"]);
|
|
935
|
+
expect(result.helpRequested).toBe(true);
|
|
936
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid URL for --sse"));
|
|
937
|
+
});
|
|
938
|
+
it("should reject missing URL argument", () => {
|
|
939
|
+
const result = parseArgs(["test-server", "--sse"]);
|
|
940
|
+
expect(result.helpRequested).toBe(true);
|
|
941
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--sse requires a URL argument"));
|
|
942
|
+
});
|
|
943
|
+
it("should reject non-HTTP protocol (file://)", () => {
|
|
944
|
+
const result = parseArgs(["test-server", "--sse", "file:///etc/passwd"]);
|
|
945
|
+
expect(result.helpRequested).toBe(true);
|
|
946
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--sse requires HTTP or HTTPS URL, got: file:"));
|
|
947
|
+
});
|
|
948
|
+
});
|
|
949
|
+
describe("mutual exclusivity", () => {
|
|
950
|
+
it("should reject --http with --config", () => {
|
|
951
|
+
const result = parseArgs([
|
|
952
|
+
"test-server",
|
|
953
|
+
"--http",
|
|
954
|
+
"http://localhost:10900/mcp",
|
|
955
|
+
"--config",
|
|
956
|
+
"config.json",
|
|
957
|
+
]);
|
|
958
|
+
expect(result.helpRequested).toBe(true);
|
|
959
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http/--sse cannot be used with --config"));
|
|
960
|
+
});
|
|
961
|
+
it("should reject --sse with --config", () => {
|
|
962
|
+
const result = parseArgs([
|
|
963
|
+
"test-server",
|
|
964
|
+
"--sse",
|
|
965
|
+
"http://localhost:9002/sse",
|
|
966
|
+
"--config",
|
|
967
|
+
"config.json",
|
|
968
|
+
]);
|
|
969
|
+
expect(result.helpRequested).toBe(true);
|
|
970
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http/--sse cannot be used with --config"));
|
|
971
|
+
});
|
|
972
|
+
it("should reject --http with --sse", () => {
|
|
973
|
+
const result = parseArgs([
|
|
974
|
+
"test-server",
|
|
975
|
+
"--http",
|
|
976
|
+
"http://localhost:10900/mcp",
|
|
977
|
+
"--sse",
|
|
978
|
+
"http://localhost:9002/sse",
|
|
979
|
+
]);
|
|
980
|
+
expect(result.helpRequested).toBe(true);
|
|
981
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--http and --sse are mutually exclusive"));
|
|
982
|
+
});
|
|
983
|
+
it("should allow --http without --config or --sse", () => {
|
|
984
|
+
const result = parseArgs([
|
|
985
|
+
"test-server",
|
|
986
|
+
"--http",
|
|
987
|
+
"http://localhost:10900/mcp",
|
|
988
|
+
]);
|
|
989
|
+
expect(result.httpUrl).toBe("http://localhost:10900/mcp");
|
|
990
|
+
expect(result.sseUrl).toBeUndefined();
|
|
991
|
+
expect(result.serverConfigPath).toBeUndefined();
|
|
992
|
+
expect(result.helpRequested).toBeFalsy();
|
|
993
|
+
});
|
|
994
|
+
it("should allow --sse without --config or --http", () => {
|
|
995
|
+
const result = parseArgs([
|
|
996
|
+
"test-server",
|
|
997
|
+
"--sse",
|
|
998
|
+
"http://localhost:9002/sse",
|
|
999
|
+
]);
|
|
1000
|
+
expect(result.sseUrl).toBe("http://localhost:9002/sse");
|
|
1001
|
+
expect(result.httpUrl).toBeUndefined();
|
|
1002
|
+
expect(result.serverConfigPath).toBeUndefined();
|
|
1003
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
describe("combined with other options", () => {
|
|
1007
|
+
it("should work with --http and --temporal-invocations", () => {
|
|
1008
|
+
const result = parseArgs([
|
|
1009
|
+
"test-server",
|
|
1010
|
+
"--http",
|
|
1011
|
+
"http://localhost:10900/mcp",
|
|
1012
|
+
"--temporal-invocations",
|
|
1013
|
+
"5",
|
|
1014
|
+
]);
|
|
1015
|
+
expect(result.httpUrl).toBe("http://localhost:10900/mcp");
|
|
1016
|
+
expect(result.temporalInvocations).toBe(5);
|
|
1017
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1018
|
+
});
|
|
1019
|
+
it("should work with --sse and --profile", () => {
|
|
1020
|
+
const result = parseArgs([
|
|
1021
|
+
"test-server",
|
|
1022
|
+
"--sse",
|
|
1023
|
+
"http://localhost:9002/sse",
|
|
1024
|
+
"--profile",
|
|
1025
|
+
"quick",
|
|
1026
|
+
]);
|
|
1027
|
+
expect(result.sseUrl).toBe("http://localhost:9002/sse");
|
|
1028
|
+
expect(result.profile).toBe("quick");
|
|
1029
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1030
|
+
});
|
|
1031
|
+
it("should work with --http and --output", () => {
|
|
1032
|
+
const result = parseArgs([
|
|
1033
|
+
"test-server",
|
|
1034
|
+
"--http",
|
|
1035
|
+
"http://localhost:10900/mcp",
|
|
1036
|
+
"--output",
|
|
1037
|
+
"/tmp/results.json",
|
|
1038
|
+
]);
|
|
1039
|
+
expect(result.httpUrl).toBe("http://localhost:10900/mcp");
|
|
1040
|
+
expect(result.outputPath).toBe("/tmp/results.json");
|
|
1041
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1042
|
+
});
|
|
1043
|
+
it("should work with --http and --conformance", () => {
|
|
1044
|
+
const result = parseArgs([
|
|
1045
|
+
"test-server",
|
|
1046
|
+
"--http",
|
|
1047
|
+
"http://localhost:10900/mcp",
|
|
1048
|
+
"--conformance",
|
|
1049
|
+
]);
|
|
1050
|
+
expect(result.httpUrl).toBe("http://localhost:10900/mcp");
|
|
1051
|
+
expect(result.conformanceEnabled).toBe(true);
|
|
1052
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1053
|
+
});
|
|
1054
|
+
it("should work with --sse and --conformance", () => {
|
|
1055
|
+
const result = parseArgs([
|
|
1056
|
+
"test-server",
|
|
1057
|
+
"--sse",
|
|
1058
|
+
"http://localhost:9002/sse",
|
|
1059
|
+
"--conformance",
|
|
1060
|
+
]);
|
|
1061
|
+
expect(result.sseUrl).toBe("http://localhost:9002/sse");
|
|
1062
|
+
expect(result.conformanceEnabled).toBe(true);
|
|
1063
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1064
|
+
});
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
/**
|
|
1068
|
+
* Module Flag Tests (--module, -m)
|
|
1069
|
+
*
|
|
1070
|
+
* Tests for the single module execution flag that bypasses orchestrator.
|
|
1071
|
+
* Issue #184: Single module runner for focused testing without orchestration overhead.
|
|
1072
|
+
*/
|
|
1073
|
+
describe("Module Flag (--module, -m)", () => {
|
|
1074
|
+
let processExitSpy;
|
|
1075
|
+
let consoleErrorSpy;
|
|
1076
|
+
beforeEach(() => {
|
|
1077
|
+
jest.useFakeTimers();
|
|
1078
|
+
processExitSpy = jest
|
|
1079
|
+
.spyOn(process, "exit")
|
|
1080
|
+
.mockImplementation((() => { }));
|
|
1081
|
+
consoleErrorSpy = jest.spyOn(console, "error").mockImplementation(() => { });
|
|
1082
|
+
});
|
|
1083
|
+
afterEach(() => {
|
|
1084
|
+
jest.runAllTimers();
|
|
1085
|
+
jest.useRealTimers();
|
|
1086
|
+
processExitSpy?.mockRestore();
|
|
1087
|
+
consoleErrorSpy?.mockRestore();
|
|
1088
|
+
});
|
|
1089
|
+
describe("valid module names", () => {
|
|
1090
|
+
it("should accept valid module name with long flag", () => {
|
|
1091
|
+
const result = parseArgs([
|
|
1092
|
+
"test-server",
|
|
1093
|
+
"--config",
|
|
1094
|
+
"config.json",
|
|
1095
|
+
"--module",
|
|
1096
|
+
"toolAnnotations",
|
|
1097
|
+
]);
|
|
1098
|
+
expect(result.singleModule).toBe("toolAnnotations");
|
|
1099
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1100
|
+
});
|
|
1101
|
+
it("should accept valid module name with short flag", () => {
|
|
1102
|
+
const result = parseArgs([
|
|
1103
|
+
"test-server",
|
|
1104
|
+
"--config",
|
|
1105
|
+
"config.json",
|
|
1106
|
+
"-m",
|
|
1107
|
+
"security",
|
|
1108
|
+
]);
|
|
1109
|
+
expect(result.singleModule).toBe("security");
|
|
1110
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1111
|
+
});
|
|
1112
|
+
it("should accept all valid core module names", () => {
|
|
1113
|
+
const coreModules = [
|
|
1114
|
+
"functionality",
|
|
1115
|
+
"security",
|
|
1116
|
+
"documentation",
|
|
1117
|
+
"errorHandling",
|
|
1118
|
+
"usability",
|
|
1119
|
+
"mcpSpecCompliance",
|
|
1120
|
+
"aupCompliance",
|
|
1121
|
+
"toolAnnotations",
|
|
1122
|
+
"prohibitedLibraries",
|
|
1123
|
+
"externalAPIScanner",
|
|
1124
|
+
"authentication",
|
|
1125
|
+
"temporal",
|
|
1126
|
+
"resources",
|
|
1127
|
+
"prompts",
|
|
1128
|
+
"crossCapability",
|
|
1129
|
+
"protocolConformance",
|
|
1130
|
+
];
|
|
1131
|
+
for (const moduleName of coreModules) {
|
|
1132
|
+
consoleErrorSpy.mockClear();
|
|
1133
|
+
const result = parseArgs([
|
|
1134
|
+
"test-server",
|
|
1135
|
+
"--config",
|
|
1136
|
+
"config.json",
|
|
1137
|
+
"--module",
|
|
1138
|
+
moduleName,
|
|
1139
|
+
]);
|
|
1140
|
+
expect(result.singleModule).toBe(moduleName);
|
|
1141
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
it("should accept optional module names", () => {
|
|
1145
|
+
const optionalModules = ["manifestValidation", "portability"];
|
|
1146
|
+
for (const moduleName of optionalModules) {
|
|
1147
|
+
consoleErrorSpy.mockClear();
|
|
1148
|
+
const result = parseArgs([
|
|
1149
|
+
"test-server",
|
|
1150
|
+
"--config",
|
|
1151
|
+
"config.json",
|
|
1152
|
+
"--module",
|
|
1153
|
+
moduleName,
|
|
1154
|
+
]);
|
|
1155
|
+
expect(result.singleModule).toBe(moduleName);
|
|
1156
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
});
|
|
1160
|
+
describe("invalid module names", () => {
|
|
1161
|
+
it("should reject invalid module name", () => {
|
|
1162
|
+
const result = parseArgs([
|
|
1163
|
+
"test-server",
|
|
1164
|
+
"--config",
|
|
1165
|
+
"config.json",
|
|
1166
|
+
"--module",
|
|
1167
|
+
"invalidModule",
|
|
1168
|
+
]);
|
|
1169
|
+
expect(result.helpRequested).toBe(true);
|
|
1170
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid module name"));
|
|
1171
|
+
});
|
|
1172
|
+
it("should reject missing module argument", () => {
|
|
1173
|
+
const result = parseArgs([
|
|
1174
|
+
"test-server",
|
|
1175
|
+
"--config",
|
|
1176
|
+
"config.json",
|
|
1177
|
+
"--module",
|
|
1178
|
+
]);
|
|
1179
|
+
expect(result.helpRequested).toBe(true);
|
|
1180
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module requires a module name"));
|
|
1181
|
+
});
|
|
1182
|
+
it("should reject when next argument is another flag", () => {
|
|
1183
|
+
const result = parseArgs([
|
|
1184
|
+
"test-server",
|
|
1185
|
+
"--config",
|
|
1186
|
+
"config.json",
|
|
1187
|
+
"--module",
|
|
1188
|
+
"--verbose",
|
|
1189
|
+
]);
|
|
1190
|
+
expect(result.helpRequested).toBe(true);
|
|
1191
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module requires a module name"));
|
|
1192
|
+
});
|
|
1193
|
+
it("should reject case-sensitive mismatch", () => {
|
|
1194
|
+
const result = parseArgs([
|
|
1195
|
+
"test-server",
|
|
1196
|
+
"--config",
|
|
1197
|
+
"config.json",
|
|
1198
|
+
"--module",
|
|
1199
|
+
"SECURITY",
|
|
1200
|
+
]);
|
|
1201
|
+
expect(result.helpRequested).toBe(true);
|
|
1202
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid module name"));
|
|
1203
|
+
});
|
|
1204
|
+
it("should reject module name with typo", () => {
|
|
1205
|
+
const result = parseArgs([
|
|
1206
|
+
"test-server",
|
|
1207
|
+
"--config",
|
|
1208
|
+
"config.json",
|
|
1209
|
+
"--module",
|
|
1210
|
+
"functionalaty",
|
|
1211
|
+
]);
|
|
1212
|
+
expect(result.helpRequested).toBe(true);
|
|
1213
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid module name"));
|
|
1214
|
+
});
|
|
1215
|
+
});
|
|
1216
|
+
describe("mutual exclusivity with orchestrator flags", () => {
|
|
1217
|
+
it("should reject --module with --profile", () => {
|
|
1218
|
+
const result = parseArgs([
|
|
1219
|
+
"test-server",
|
|
1220
|
+
"--config",
|
|
1221
|
+
"config.json",
|
|
1222
|
+
"--module",
|
|
1223
|
+
"security",
|
|
1224
|
+
"--profile",
|
|
1225
|
+
"quick",
|
|
1226
|
+
]);
|
|
1227
|
+
expect(result.helpRequested).toBe(true);
|
|
1228
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module cannot be used with --skip-modules, --only-modules, or --profile"));
|
|
1229
|
+
});
|
|
1230
|
+
it("should reject --module with --skip-modules", () => {
|
|
1231
|
+
const result = parseArgs([
|
|
1232
|
+
"test-server",
|
|
1233
|
+
"--config",
|
|
1234
|
+
"config.json",
|
|
1235
|
+
"--module",
|
|
1236
|
+
"security",
|
|
1237
|
+
"--skip-modules",
|
|
1238
|
+
"temporal",
|
|
1239
|
+
]);
|
|
1240
|
+
expect(result.helpRequested).toBe(true);
|
|
1241
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module cannot be used with"));
|
|
1242
|
+
});
|
|
1243
|
+
it("should reject --module with --only-modules", () => {
|
|
1244
|
+
const result = parseArgs([
|
|
1245
|
+
"test-server",
|
|
1246
|
+
"--config",
|
|
1247
|
+
"config.json",
|
|
1248
|
+
"--module",
|
|
1249
|
+
"security",
|
|
1250
|
+
"--only-modules",
|
|
1251
|
+
"functionality",
|
|
1252
|
+
]);
|
|
1253
|
+
expect(result.helpRequested).toBe(true);
|
|
1254
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module cannot be used with"));
|
|
1255
|
+
});
|
|
1256
|
+
it("should reject --profile with --module (order reversed)", () => {
|
|
1257
|
+
const result = parseArgs([
|
|
1258
|
+
"test-server",
|
|
1259
|
+
"--config",
|
|
1260
|
+
"config.json",
|
|
1261
|
+
"--profile",
|
|
1262
|
+
"quick",
|
|
1263
|
+
"--module",
|
|
1264
|
+
"security",
|
|
1265
|
+
]);
|
|
1266
|
+
expect(result.helpRequested).toBe(true);
|
|
1267
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module cannot be used with"));
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
describe("combined with transport flags", () => {
|
|
1271
|
+
it("should work with --http", () => {
|
|
1272
|
+
const result = parseArgs([
|
|
1273
|
+
"test-server",
|
|
1274
|
+
"--http",
|
|
1275
|
+
"http://localhost:10900/mcp",
|
|
1276
|
+
"--module",
|
|
1277
|
+
"security",
|
|
1278
|
+
]);
|
|
1279
|
+
expect(result.httpUrl).toBe("http://localhost:10900/mcp");
|
|
1280
|
+
expect(result.singleModule).toBe("security");
|
|
1281
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1282
|
+
});
|
|
1283
|
+
it("should work with --sse", () => {
|
|
1284
|
+
const result = parseArgs([
|
|
1285
|
+
"test-server",
|
|
1286
|
+
"--sse",
|
|
1287
|
+
"http://localhost:9002/sse",
|
|
1288
|
+
"--module",
|
|
1289
|
+
"functionality",
|
|
1290
|
+
]);
|
|
1291
|
+
expect(result.sseUrl).toBe("http://localhost:9002/sse");
|
|
1292
|
+
expect(result.singleModule).toBe("functionality");
|
|
1293
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1294
|
+
});
|
|
1295
|
+
it("should work with --config", () => {
|
|
1296
|
+
const result = parseArgs([
|
|
1297
|
+
"test-server",
|
|
1298
|
+
"--config",
|
|
1299
|
+
"config.json",
|
|
1300
|
+
"--module",
|
|
1301
|
+
"temporal",
|
|
1302
|
+
]);
|
|
1303
|
+
expect(result.serverConfigPath).toBe("config.json");
|
|
1304
|
+
expect(result.singleModule).toBe("temporal");
|
|
1305
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1306
|
+
});
|
|
1307
|
+
});
|
|
1308
|
+
describe("combined with other compatible flags", () => {
|
|
1309
|
+
it("should work with --output", () => {
|
|
1310
|
+
const result = parseArgs([
|
|
1311
|
+
"test-server",
|
|
1312
|
+
"--config",
|
|
1313
|
+
"config.json",
|
|
1314
|
+
"--module",
|
|
1315
|
+
"security",
|
|
1316
|
+
"--output",
|
|
1317
|
+
"/tmp/results.json",
|
|
1318
|
+
]);
|
|
1319
|
+
expect(result.singleModule).toBe("security");
|
|
1320
|
+
expect(result.outputPath).toBe("/tmp/results.json");
|
|
1321
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1322
|
+
});
|
|
1323
|
+
it("should work with --verbose", () => {
|
|
1324
|
+
const result = parseArgs([
|
|
1325
|
+
"test-server",
|
|
1326
|
+
"--config",
|
|
1327
|
+
"config.json",
|
|
1328
|
+
"--module",
|
|
1329
|
+
"toolAnnotations",
|
|
1330
|
+
"--verbose",
|
|
1331
|
+
]);
|
|
1332
|
+
expect(result.singleModule).toBe("toolAnnotations");
|
|
1333
|
+
expect(result.verbose).toBe(true);
|
|
1334
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1335
|
+
});
|
|
1336
|
+
it("should work with --log-level", () => {
|
|
1337
|
+
const result = parseArgs([
|
|
1338
|
+
"test-server",
|
|
1339
|
+
"--config",
|
|
1340
|
+
"config.json",
|
|
1341
|
+
"--module",
|
|
1342
|
+
"errorHandling",
|
|
1343
|
+
"--log-level",
|
|
1344
|
+
"debug",
|
|
1345
|
+
]);
|
|
1346
|
+
expect(result.singleModule).toBe("errorHandling");
|
|
1347
|
+
expect(result.logLevel).toBe("debug");
|
|
1348
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1349
|
+
});
|
|
1350
|
+
it("should work with --temporal-invocations", () => {
|
|
1351
|
+
const result = parseArgs([
|
|
1352
|
+
"test-server",
|
|
1353
|
+
"--config",
|
|
1354
|
+
"config.json",
|
|
1355
|
+
"--module",
|
|
1356
|
+
"temporal",
|
|
1357
|
+
"--temporal-invocations",
|
|
1358
|
+
"10",
|
|
1359
|
+
]);
|
|
1360
|
+
expect(result.singleModule).toBe("temporal");
|
|
1361
|
+
expect(result.temporalInvocations).toBe(10);
|
|
1362
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1363
|
+
});
|
|
1364
|
+
it("should work with --conformance", () => {
|
|
1365
|
+
const result = parseArgs([
|
|
1366
|
+
"test-server",
|
|
1367
|
+
"--http",
|
|
1368
|
+
"http://localhost:10900/mcp",
|
|
1369
|
+
"--module",
|
|
1370
|
+
"protocolConformance",
|
|
1371
|
+
"--conformance",
|
|
1372
|
+
]);
|
|
1373
|
+
expect(result.singleModule).toBe("protocolConformance");
|
|
1374
|
+
expect(result.conformanceEnabled).toBe(true);
|
|
1375
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1376
|
+
});
|
|
1377
|
+
it("should work with --format", () => {
|
|
1378
|
+
const result = parseArgs([
|
|
1379
|
+
"test-server",
|
|
1380
|
+
"--config",
|
|
1381
|
+
"config.json",
|
|
1382
|
+
"--module",
|
|
1383
|
+
"security",
|
|
1384
|
+
"--format",
|
|
1385
|
+
"markdown",
|
|
1386
|
+
]);
|
|
1387
|
+
expect(result.singleModule).toBe("security");
|
|
1388
|
+
expect(result.format).toBe("markdown");
|
|
1389
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1390
|
+
});
|
|
1391
|
+
});
|
|
1392
|
+
describe("short flag behavior", () => {
|
|
1393
|
+
it("should accept -m with all transport types", () => {
|
|
1394
|
+
// Test with --http
|
|
1395
|
+
let result = parseArgs([
|
|
1396
|
+
"test-server",
|
|
1397
|
+
"--http",
|
|
1398
|
+
"http://localhost:10900/mcp",
|
|
1399
|
+
"-m",
|
|
1400
|
+
"security",
|
|
1401
|
+
]);
|
|
1402
|
+
expect(result.singleModule).toBe("security");
|
|
1403
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1404
|
+
// Test with --sse
|
|
1405
|
+
consoleErrorSpy.mockClear();
|
|
1406
|
+
result = parseArgs([
|
|
1407
|
+
"test-server",
|
|
1408
|
+
"--sse",
|
|
1409
|
+
"http://localhost:9002/sse",
|
|
1410
|
+
"-m",
|
|
1411
|
+
"functionality",
|
|
1412
|
+
]);
|
|
1413
|
+
expect(result.singleModule).toBe("functionality");
|
|
1414
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1415
|
+
// Test with --config
|
|
1416
|
+
consoleErrorSpy.mockClear();
|
|
1417
|
+
result = parseArgs([
|
|
1418
|
+
"test-server",
|
|
1419
|
+
"--config",
|
|
1420
|
+
"config.json",
|
|
1421
|
+
"-m",
|
|
1422
|
+
"temporal",
|
|
1423
|
+
]);
|
|
1424
|
+
expect(result.singleModule).toBe("temporal");
|
|
1425
|
+
expect(result.helpRequested).toBeFalsy();
|
|
1426
|
+
});
|
|
1427
|
+
it("should reject -m with missing argument", () => {
|
|
1428
|
+
const result = parseArgs([
|
|
1429
|
+
"test-server",
|
|
1430
|
+
"--config",
|
|
1431
|
+
"config.json",
|
|
1432
|
+
"-m",
|
|
1433
|
+
]);
|
|
1434
|
+
expect(result.helpRequested).toBe(true);
|
|
1435
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("--module requires a module name"));
|
|
1436
|
+
});
|
|
1437
|
+
it("should reject -m with invalid module", () => {
|
|
1438
|
+
const result = parseArgs([
|
|
1439
|
+
"test-server",
|
|
1440
|
+
"--config",
|
|
1441
|
+
"config.json",
|
|
1442
|
+
"-m",
|
|
1443
|
+
"notAModule",
|
|
1444
|
+
]);
|
|
1445
|
+
expect(result.helpRequested).toBe(true);
|
|
1446
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining("Invalid module name"));
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1449
|
+
});
|
package/build/assess-full.js
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
import { ScopedListenerConfig } from "./lib/event-config.js";
|
|
13
13
|
// Import from extracted modules
|
|
14
14
|
import { parseArgs } from "./lib/cli-parser.js";
|
|
15
|
-
import { runFullAssessment } from "./lib/assessment-runner.js";
|
|
16
|
-
import { saveResults, saveTieredResults, saveSummaryOnly, displaySummary, } from "./lib/result-output.js";
|
|
15
|
+
import { runFullAssessment, runSingleModule } from "./lib/assessment-runner.js";
|
|
16
|
+
import { saveResults, saveTieredResults, saveSummaryOnly, displaySummary, saveSingleModuleResults, displaySingleModuleSummary, } from "./lib/result-output.js";
|
|
17
17
|
import { handleComparison, displayComparisonSummary, } from "./lib/comparison-handler.js";
|
|
18
18
|
import { shouldAutoTier, formatTokenEstimate, } from "../../client/lib/lib/assessment/summarizer/index.js";
|
|
19
19
|
// ============================================================================
|
|
@@ -35,6 +35,23 @@ async function main() {
|
|
|
35
35
|
}
|
|
36
36
|
// Apply scoped listener configuration for assessment
|
|
37
37
|
listenerConfig.apply();
|
|
38
|
+
// Single module mode - bypass orchestrator for lightweight execution (Issue #184)
|
|
39
|
+
if (options.singleModule) {
|
|
40
|
+
const result = await runSingleModule(options.singleModule, options);
|
|
41
|
+
if (!options.jsonOnly) {
|
|
42
|
+
displaySingleModuleSummary(result);
|
|
43
|
+
}
|
|
44
|
+
const outputPath = saveSingleModuleResults(options.serverName, options.singleModule, result, options);
|
|
45
|
+
if (options.jsonOnly) {
|
|
46
|
+
console.log(outputPath);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
console.log(`\n📄 Results saved to: ${outputPath}\n`);
|
|
50
|
+
}
|
|
51
|
+
const exitCode = result.status === "FAIL" ? 1 : 0;
|
|
52
|
+
setTimeout(() => process.exit(exitCode), 10);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
38
55
|
const results = await runFullAssessment(options);
|
|
39
56
|
// Pre-flight mode handles its own output and exit
|
|
40
57
|
if (options.preflightOnly) {
|
|
@@ -43,9 +43,34 @@ export async function runFullAssessment(options) {
|
|
|
43
43
|
if (!options.jsonOnly) {
|
|
44
44
|
console.log(`\n🔍 Starting full assessment for: ${options.serverName}`);
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
// Determine server config: --http/--sse flags take precedence over --config
|
|
47
|
+
let serverConfig;
|
|
48
|
+
if (options.httpUrl) {
|
|
49
|
+
// Direct HTTP URL provided via --http flag (no config file needed)
|
|
50
|
+
serverConfig = {
|
|
51
|
+
transport: "http",
|
|
52
|
+
url: options.httpUrl,
|
|
53
|
+
};
|
|
54
|
+
if (!options.jsonOnly) {
|
|
55
|
+
console.log(`✅ Using HTTP transport: ${options.httpUrl}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else if (options.sseUrl) {
|
|
59
|
+
// Direct SSE URL provided via --sse flag (no config file needed)
|
|
60
|
+
serverConfig = {
|
|
61
|
+
transport: "sse",
|
|
62
|
+
url: options.sseUrl,
|
|
63
|
+
};
|
|
64
|
+
if (!options.jsonOnly) {
|
|
65
|
+
console.log(`✅ Using SSE transport: ${options.sseUrl}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Load from config file (existing behavior)
|
|
70
|
+
serverConfig = loadServerConfig(options.serverName, options.serverConfigPath);
|
|
71
|
+
if (!options.jsonOnly) {
|
|
72
|
+
console.log("✅ Server config loaded");
|
|
73
|
+
}
|
|
49
74
|
}
|
|
50
75
|
// Phase 1: Discovery
|
|
51
76
|
const discoveryStart = Date.now();
|
|
@@ -20,3 +20,5 @@ export { createCallToolWrapper } from "./tool-wrapper.js";
|
|
|
20
20
|
export { buildConfig } from "./config-builder.js";
|
|
21
21
|
// Assessment Execution
|
|
22
22
|
export { runFullAssessment } from "./assessment-executor.js";
|
|
23
|
+
// Single Module Execution (Issue #184)
|
|
24
|
+
export { runSingleModule, getValidModuleNames, } from "./single-module-runner.js";
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single Module Runner
|
|
3
|
+
*
|
|
4
|
+
* Lightweight execution for individual assessment modules via --module flag.
|
|
5
|
+
* Bypasses full orchestration for faster, targeted assessment.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/lib/assessment-runner/single-module-runner
|
|
8
|
+
* @see GitHub Issue #184
|
|
9
|
+
*/
|
|
10
|
+
import { ASSESSOR_DEFINITION_MAP, ASSESSOR_DEFINITIONS, } from "../../../../client/lib/services/assessment/registry/AssessorDefinitions.js";
|
|
11
|
+
import { DEFAULT_CONTEXT_REQUIREMENTS } from "../../../../client/lib/services/assessment/registry/types.js";
|
|
12
|
+
import { loadServerConfig } from "./server-config.js";
|
|
13
|
+
import { loadSourceFiles } from "./source-loader.js";
|
|
14
|
+
import { resolveSourcePath } from "./path-resolver.js";
|
|
15
|
+
import { connectToServer } from "./server-connection.js";
|
|
16
|
+
import { createCallToolWrapper } from "./tool-wrapper.js";
|
|
17
|
+
import { buildConfig } from "./config-builder.js";
|
|
18
|
+
import { getToolsWithPreservedHints } from "./tools-with-hints.js";
|
|
19
|
+
/**
|
|
20
|
+
* Get all valid module names for --module validation.
|
|
21
|
+
*/
|
|
22
|
+
export function getValidModuleNames() {
|
|
23
|
+
return ASSESSOR_DEFINITIONS.map((def) => def.id);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Run a single assessment module directly without orchestration.
|
|
27
|
+
*
|
|
28
|
+
* This provides lightweight execution for targeted validation:
|
|
29
|
+
* - Builds only the context required by the specific module
|
|
30
|
+
* - Skips orchestrator phase ordering
|
|
31
|
+
* - Returns focused output
|
|
32
|
+
*
|
|
33
|
+
* @param moduleName - The module ID to run (e.g., 'toolAnnotations', 'functionality')
|
|
34
|
+
* @param options - CLI assessment options
|
|
35
|
+
* @returns Single module result
|
|
36
|
+
*/
|
|
37
|
+
export async function runSingleModule(moduleName, options) {
|
|
38
|
+
const definition = ASSESSOR_DEFINITION_MAP.get(moduleName);
|
|
39
|
+
if (!definition) {
|
|
40
|
+
const validModules = getValidModuleNames().join(", ");
|
|
41
|
+
throw new Error(`Unknown module: ${moduleName}\nValid modules: ${validModules}`);
|
|
42
|
+
}
|
|
43
|
+
if (!options.jsonOnly) {
|
|
44
|
+
console.log(`\n🎯 Running single module: ${definition.displayName}`);
|
|
45
|
+
}
|
|
46
|
+
// Build server config (respects --http/--sse from Issue #183)
|
|
47
|
+
let serverConfig;
|
|
48
|
+
if (options.httpUrl) {
|
|
49
|
+
serverConfig = { transport: "http", url: options.httpUrl };
|
|
50
|
+
if (!options.jsonOnly) {
|
|
51
|
+
console.log(`✅ Using HTTP transport: ${options.httpUrl}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (options.sseUrl) {
|
|
55
|
+
serverConfig = { transport: "sse", url: options.sseUrl };
|
|
56
|
+
if (!options.jsonOnly) {
|
|
57
|
+
console.log(`✅ Using SSE transport: ${options.sseUrl}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
serverConfig = loadServerConfig(options.serverName, options.serverConfigPath);
|
|
62
|
+
if (!options.jsonOnly) {
|
|
63
|
+
console.log("✅ Server config loaded");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const client = await connectToServer(serverConfig);
|
|
67
|
+
try {
|
|
68
|
+
if (!options.jsonOnly) {
|
|
69
|
+
console.log("✅ Connected to MCP server");
|
|
70
|
+
}
|
|
71
|
+
// Get context requirements (use default if not specified)
|
|
72
|
+
const requirements = definition.contextRequirements || DEFAULT_CONTEXT_REQUIREMENTS;
|
|
73
|
+
// Build minimal context based on module requirements
|
|
74
|
+
const context = await buildMinimalContext(client, definition, requirements, options, serverConfig);
|
|
75
|
+
// Instantiate and run the assessor
|
|
76
|
+
const config = buildConfig(options);
|
|
77
|
+
const assessor = new definition.assessorClass(config);
|
|
78
|
+
// Apply custom setup if defined (e.g., ToolAnnotationAssessor pattern loading)
|
|
79
|
+
if (definition.customSetup) {
|
|
80
|
+
const { createLogger } = await import("../../../../client/lib/services/assessment/lib/logger.js");
|
|
81
|
+
const logger = createLogger(config.logging?.level || "info");
|
|
82
|
+
definition.customSetup(assessor, config, logger);
|
|
83
|
+
}
|
|
84
|
+
if (!options.jsonOnly) {
|
|
85
|
+
console.log(`\n🏃 Running ${definition.displayName} assessment...`);
|
|
86
|
+
}
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
const result = await assessor.assess(context);
|
|
89
|
+
const executionTime = Date.now() - startTime;
|
|
90
|
+
// Estimate test count using the definition's estimator
|
|
91
|
+
const estimatedTestCount = definition.estimateTests(context, config);
|
|
92
|
+
const singleResult = {
|
|
93
|
+
timestamp: new Date().toISOString(),
|
|
94
|
+
serverName: options.serverName,
|
|
95
|
+
module: moduleName,
|
|
96
|
+
displayName: definition.displayName,
|
|
97
|
+
result,
|
|
98
|
+
status: extractStatus(result),
|
|
99
|
+
executionTime,
|
|
100
|
+
estimatedTestCount,
|
|
101
|
+
phase: definition.phase,
|
|
102
|
+
};
|
|
103
|
+
return singleResult;
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
await client.close();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Build minimal AssessmentContext based on module requirements.
|
|
111
|
+
* Only fetches/prepares what the specific module needs.
|
|
112
|
+
*/
|
|
113
|
+
async function buildMinimalContext(client, definition, requirements, options, serverConfig) {
|
|
114
|
+
const config = buildConfig(options);
|
|
115
|
+
// Start with minimal context
|
|
116
|
+
const context = {
|
|
117
|
+
serverName: options.serverName,
|
|
118
|
+
config,
|
|
119
|
+
};
|
|
120
|
+
// Fetch tools if needed
|
|
121
|
+
if (requirements.needsTools) {
|
|
122
|
+
context.tools = await getToolsWithPreservedHints(client);
|
|
123
|
+
if (!options.jsonOnly) {
|
|
124
|
+
console.log(`🔧 Found ${context.tools.length} tool${context.tools.length !== 1 ? "s" : ""}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
context.tools = [];
|
|
129
|
+
}
|
|
130
|
+
// Setup callTool wrapper if needed
|
|
131
|
+
if (requirements.needsCallTool) {
|
|
132
|
+
context.callTool = createCallToolWrapper(client);
|
|
133
|
+
}
|
|
134
|
+
// Setup listTools function if needed (for TemporalAssessor baseline)
|
|
135
|
+
if (requirements.needsListTools) {
|
|
136
|
+
context.listTools = async () => {
|
|
137
|
+
return getToolsWithPreservedHints(client);
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Fetch resources if needed
|
|
141
|
+
if (requirements.needsResources) {
|
|
142
|
+
try {
|
|
143
|
+
const resourcesResponse = await client.listResources();
|
|
144
|
+
context.resources = (resourcesResponse.resources || []).map((r) => ({
|
|
145
|
+
uri: r.uri,
|
|
146
|
+
name: r.name,
|
|
147
|
+
description: r.description,
|
|
148
|
+
mimeType: r.mimeType,
|
|
149
|
+
}));
|
|
150
|
+
// Also get resource templates
|
|
151
|
+
try {
|
|
152
|
+
const templatesResponse = await client.listResourceTemplates();
|
|
153
|
+
context.resourceTemplates = (templatesResponse.resourceTemplates || []).map((rt) => ({
|
|
154
|
+
uriTemplate: rt.uriTemplate,
|
|
155
|
+
name: rt.name,
|
|
156
|
+
description: rt.description,
|
|
157
|
+
mimeType: rt.mimeType,
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
context.resourceTemplates = [];
|
|
162
|
+
}
|
|
163
|
+
// Setup readResource wrapper
|
|
164
|
+
context.readResource = async (uri) => {
|
|
165
|
+
const response = await client.readResource({ uri });
|
|
166
|
+
if (response.contents && response.contents.length > 0) {
|
|
167
|
+
const content = response.contents[0];
|
|
168
|
+
if ("text" in content && content.text) {
|
|
169
|
+
return content.text;
|
|
170
|
+
}
|
|
171
|
+
if ("blob" in content && content.blob) {
|
|
172
|
+
return content.blob;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return "";
|
|
176
|
+
};
|
|
177
|
+
if (!options.jsonOnly && context.resources.length > 0) {
|
|
178
|
+
console.log(`📦 Found ${context.resources.length} resource(s) and ${context.resourceTemplates?.length || 0} template(s)`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
context.resources = [];
|
|
183
|
+
context.resourceTemplates = [];
|
|
184
|
+
if (!options.jsonOnly) {
|
|
185
|
+
console.log("📦 Resources not supported by server");
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Fetch prompts if needed
|
|
190
|
+
if (requirements.needsPrompts) {
|
|
191
|
+
try {
|
|
192
|
+
const promptsResponse = await client.listPrompts();
|
|
193
|
+
context.prompts = (promptsResponse.prompts || []).map((p) => ({
|
|
194
|
+
name: p.name,
|
|
195
|
+
description: p.description,
|
|
196
|
+
arguments: p.arguments?.map((a) => ({
|
|
197
|
+
name: a.name,
|
|
198
|
+
description: a.description,
|
|
199
|
+
required: a.required,
|
|
200
|
+
})),
|
|
201
|
+
}));
|
|
202
|
+
// Setup getPrompt wrapper
|
|
203
|
+
context.getPrompt = async (name, args) => {
|
|
204
|
+
const response = await client.getPrompt({ name, arguments: args });
|
|
205
|
+
return {
|
|
206
|
+
messages: (response.messages || []).map((m) => ({
|
|
207
|
+
role: m.role,
|
|
208
|
+
content: typeof m.content === "string"
|
|
209
|
+
? m.content
|
|
210
|
+
: JSON.stringify(m.content),
|
|
211
|
+
})),
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
if (!options.jsonOnly && context.prompts.length > 0) {
|
|
215
|
+
console.log(`💬 Found ${context.prompts.length} prompt(s)`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
context.prompts = [];
|
|
220
|
+
if (!options.jsonOnly) {
|
|
221
|
+
console.log("💬 Prompts not supported by server");
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Load source code if needed and path provided
|
|
226
|
+
if (requirements.needsSourceCode && options.sourceCodePath) {
|
|
227
|
+
const resolvedSourcePath = resolveSourcePath(options.sourceCodePath);
|
|
228
|
+
const { existsSync } = await import("fs");
|
|
229
|
+
if (existsSync(resolvedSourcePath)) {
|
|
230
|
+
const sourceFiles = loadSourceFiles(resolvedSourcePath, options.debugSource);
|
|
231
|
+
context.sourceCodeFiles = sourceFiles.sourceCodeFiles;
|
|
232
|
+
context.sourceCodePath = options.sourceCodePath;
|
|
233
|
+
context.manifestJson = sourceFiles.manifestJson;
|
|
234
|
+
context.manifestRaw = sourceFiles.manifestRaw;
|
|
235
|
+
context.packageJson = sourceFiles.packageJson;
|
|
236
|
+
context.readmeContent = sourceFiles.readmeContent;
|
|
237
|
+
if (!options.jsonOnly) {
|
|
238
|
+
console.log(`📁 Loaded source files from: ${resolvedSourcePath}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (!options.jsonOnly) {
|
|
242
|
+
console.log(`⚠️ Source path not found: ${options.sourceCodePath} (module may have reduced coverage)`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Load manifest specifically if needed (for ManifestValidationAssessor)
|
|
246
|
+
if (requirements.needsManifest && options.sourceCodePath) {
|
|
247
|
+
const resolvedSourcePath = resolveSourcePath(options.sourceCodePath);
|
|
248
|
+
const { existsSync } = await import("fs");
|
|
249
|
+
if (existsSync(resolvedSourcePath)) {
|
|
250
|
+
const sourceFiles = loadSourceFiles(resolvedSourcePath, options.debugSource);
|
|
251
|
+
context.manifestJson = sourceFiles.manifestJson;
|
|
252
|
+
context.manifestRaw = sourceFiles.manifestRaw;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Get server info if needed (for ProtocolComplianceAssessor)
|
|
256
|
+
if (requirements.needsServerInfo) {
|
|
257
|
+
const rawServerInfo = client.getServerVersion();
|
|
258
|
+
const rawServerCapabilities = client.getServerCapabilities();
|
|
259
|
+
context.serverInfo = rawServerInfo
|
|
260
|
+
? {
|
|
261
|
+
name: rawServerInfo.name || "unknown",
|
|
262
|
+
version: rawServerInfo.version,
|
|
263
|
+
metadata: rawServerInfo.metadata,
|
|
264
|
+
}
|
|
265
|
+
: undefined;
|
|
266
|
+
context.serverCapabilities =
|
|
267
|
+
rawServerCapabilities ??
|
|
268
|
+
undefined;
|
|
269
|
+
// Set serverUrl for conformance tests when HTTP/SSE transport
|
|
270
|
+
if (serverConfig.url && !config.serverUrl) {
|
|
271
|
+
config.serverUrl = serverConfig.url;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return context;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Extract status from assessment result.
|
|
278
|
+
* Handles various result structures from different assessors.
|
|
279
|
+
*/
|
|
280
|
+
function extractStatus(result) {
|
|
281
|
+
if (!result || typeof result !== "object") {
|
|
282
|
+
return "UNKNOWN";
|
|
283
|
+
}
|
|
284
|
+
const r = result;
|
|
285
|
+
// Check for direct status field
|
|
286
|
+
if (typeof r.status === "string") {
|
|
287
|
+
return r.status;
|
|
288
|
+
}
|
|
289
|
+
// Check for overallStatus field
|
|
290
|
+
if (typeof r.overallStatus === "string") {
|
|
291
|
+
return r.overallStatus;
|
|
292
|
+
}
|
|
293
|
+
// Check for overall field with status
|
|
294
|
+
if (r.overall && typeof r.overall === "object") {
|
|
295
|
+
const overall = r.overall;
|
|
296
|
+
if (typeof overall.status === "string") {
|
|
297
|
+
return overall.status;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Derive from vulnerabilities/issues count
|
|
301
|
+
if (Array.isArray(r.vulnerabilities) && r.vulnerabilities.length > 0) {
|
|
302
|
+
return "FAIL";
|
|
303
|
+
}
|
|
304
|
+
if (Array.isArray(r.issues) && r.issues.length > 0) {
|
|
305
|
+
return "FAIL";
|
|
306
|
+
}
|
|
307
|
+
// Check for pass/fail counts
|
|
308
|
+
if (typeof r.passCount === "number" && typeof r.failCount === "number") {
|
|
309
|
+
if (r.failCount > 0)
|
|
310
|
+
return "FAIL";
|
|
311
|
+
if (r.passCount > 0)
|
|
312
|
+
return "PASS";
|
|
313
|
+
}
|
|
314
|
+
return "UNKNOWN";
|
|
315
|
+
}
|
package/build/lib/cli-parser.js
CHANGED
|
@@ -200,6 +200,89 @@ export function parseArgs(argv) {
|
|
|
200
200
|
case "--skip-temporal":
|
|
201
201
|
options.skipTemporal = true;
|
|
202
202
|
break;
|
|
203
|
+
case "--http": {
|
|
204
|
+
const httpUrlValue = args[++i];
|
|
205
|
+
if (!httpUrlValue || httpUrlValue.startsWith("-")) {
|
|
206
|
+
console.error("Error: --http requires a URL argument");
|
|
207
|
+
console.error(" Example: --http http://localhost:10900/mcp");
|
|
208
|
+
setTimeout(() => process.exit(1), 10);
|
|
209
|
+
options.helpRequested = true;
|
|
210
|
+
return options;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const parsedHttpUrl = new URL(httpUrlValue);
|
|
214
|
+
// Validate protocol is HTTP or HTTPS (reject file://, ftp://, etc.)
|
|
215
|
+
if (parsedHttpUrl.protocol !== "http:" &&
|
|
216
|
+
parsedHttpUrl.protocol !== "https:") {
|
|
217
|
+
console.error(`Error: --http requires HTTP or HTTPS URL, got: ${parsedHttpUrl.protocol}`);
|
|
218
|
+
console.error(" Expected format: http://hostname:port/path or https://hostname:port/path");
|
|
219
|
+
setTimeout(() => process.exit(1), 10);
|
|
220
|
+
options.helpRequested = true;
|
|
221
|
+
return options;
|
|
222
|
+
}
|
|
223
|
+
options.httpUrl = httpUrlValue;
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
console.error(`Error: Invalid URL for --http: ${httpUrlValue}`);
|
|
227
|
+
console.error(" Expected format: http://hostname:port/path or https://hostname:port/path");
|
|
228
|
+
setTimeout(() => process.exit(1), 10);
|
|
229
|
+
options.helpRequested = true;
|
|
230
|
+
return options;
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
case "--sse": {
|
|
235
|
+
const sseUrlValue = args[++i];
|
|
236
|
+
if (!sseUrlValue || sseUrlValue.startsWith("-")) {
|
|
237
|
+
console.error("Error: --sse requires a URL argument");
|
|
238
|
+
console.error(" Example: --sse http://localhost:9002/sse");
|
|
239
|
+
setTimeout(() => process.exit(1), 10);
|
|
240
|
+
options.helpRequested = true;
|
|
241
|
+
return options;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const parsedSseUrl = new URL(sseUrlValue);
|
|
245
|
+
// Validate protocol is HTTP or HTTPS (reject file://, ftp://, etc.)
|
|
246
|
+
if (parsedSseUrl.protocol !== "http:" &&
|
|
247
|
+
parsedSseUrl.protocol !== "https:") {
|
|
248
|
+
console.error(`Error: --sse requires HTTP or HTTPS URL, got: ${parsedSseUrl.protocol}`);
|
|
249
|
+
console.error(" Expected format: http://hostname:port/path or https://hostname:port/path");
|
|
250
|
+
setTimeout(() => process.exit(1), 10);
|
|
251
|
+
options.helpRequested = true;
|
|
252
|
+
return options;
|
|
253
|
+
}
|
|
254
|
+
options.sseUrl = sseUrlValue;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
console.error(`Error: Invalid URL for --sse: ${sseUrlValue}`);
|
|
258
|
+
console.error(" Expected format: http://hostname:port/path or https://hostname:port/path");
|
|
259
|
+
setTimeout(() => process.exit(1), 10);
|
|
260
|
+
options.helpRequested = true;
|
|
261
|
+
return options;
|
|
262
|
+
}
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case "--module":
|
|
266
|
+
case "-m": {
|
|
267
|
+
// Issue #184: Single module execution (bypasses orchestrator)
|
|
268
|
+
const moduleValue = args[++i];
|
|
269
|
+
if (!moduleValue || moduleValue.startsWith("-")) {
|
|
270
|
+
console.error("Error: --module requires a module name");
|
|
271
|
+
console.error(` Valid modules: ${VALID_MODULE_NAMES.join(", ")}`);
|
|
272
|
+
setTimeout(() => process.exit(1), 10);
|
|
273
|
+
options.helpRequested = true;
|
|
274
|
+
return options;
|
|
275
|
+
}
|
|
276
|
+
// Validate the module name
|
|
277
|
+
const validated = validateModuleNames(moduleValue, "--module");
|
|
278
|
+
if (validated.length === 0) {
|
|
279
|
+
options.helpRequested = true;
|
|
280
|
+
return options;
|
|
281
|
+
}
|
|
282
|
+
// Only accept a single module (first one if multiple provided)
|
|
283
|
+
options.singleModule = validated[0];
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
203
286
|
case "--conformance":
|
|
204
287
|
// Enable official MCP conformance tests (requires HTTP/SSE transport with serverUrl)
|
|
205
288
|
options.conformanceEnabled = true;
|
|
@@ -326,6 +409,32 @@ export function parseArgs(argv) {
|
|
|
326
409
|
options.helpRequested = true;
|
|
327
410
|
return options;
|
|
328
411
|
}
|
|
412
|
+
// Validate mutual exclusivity of --module with orchestrator options (Issue #184)
|
|
413
|
+
if (options.singleModule &&
|
|
414
|
+
(options.skipModules?.length ||
|
|
415
|
+
options.onlyModules?.length ||
|
|
416
|
+
options.profile)) {
|
|
417
|
+
console.error("Error: --module cannot be used with --skip-modules, --only-modules, or --profile");
|
|
418
|
+
console.error(" Use --module for single-module runs, or the other flags for orchestrated runs");
|
|
419
|
+
setTimeout(() => process.exit(1), 10);
|
|
420
|
+
options.helpRequested = true;
|
|
421
|
+
return options;
|
|
422
|
+
}
|
|
423
|
+
// Validate mutual exclusivity of --http, --sse, and --config
|
|
424
|
+
if ((options.httpUrl || options.sseUrl) && options.serverConfigPath) {
|
|
425
|
+
console.error("Error: --http/--sse cannot be used with --config (they are mutually exclusive)");
|
|
426
|
+
console.error(" Use --http or --sse for direct URL, or --config for JSON file");
|
|
427
|
+
setTimeout(() => process.exit(1), 10);
|
|
428
|
+
options.helpRequested = true;
|
|
429
|
+
return options;
|
|
430
|
+
}
|
|
431
|
+
if (options.httpUrl && options.sseUrl) {
|
|
432
|
+
console.error("Error: --http and --sse are mutually exclusive");
|
|
433
|
+
console.error(" Use --http for HTTP transport or --sse for SSE transport");
|
|
434
|
+
setTimeout(() => process.exit(1), 10);
|
|
435
|
+
options.helpRequested = true;
|
|
436
|
+
return options;
|
|
437
|
+
}
|
|
329
438
|
if (!options.serverName) {
|
|
330
439
|
console.error("Error: --server is required");
|
|
331
440
|
printHelp();
|
|
@@ -379,6 +488,8 @@ Run comprehensive MCP server assessment with 16 assessor modules organized in 4
|
|
|
379
488
|
Options:
|
|
380
489
|
--server, -s <name> Server name (required, or pass as first positional arg)
|
|
381
490
|
--config, -c <path> Path to server config JSON
|
|
491
|
+
--http <url> Use HTTP transport with specified URL (no config file needed)
|
|
492
|
+
--sse <url> Use SSE transport with specified URL (no config file needed)
|
|
382
493
|
--output, -o <path> Output path (default: /tmp/inspector-full-assessment-<server>.<ext>)
|
|
383
494
|
--source <path> Source code path for deep analysis (AUP, portability, etc.)
|
|
384
495
|
--debug-source Enable debug logging for source file loading (Issue #151)
|
|
@@ -398,7 +509,7 @@ Options:
|
|
|
398
509
|
--mcp-auditor-url <url> mcp-auditor URL for HTTP transport (default: http://localhost:8085)
|
|
399
510
|
--full Enable all assessment modules (default)
|
|
400
511
|
--profile <name> Use predefined module profile (quick, security, compliance, full)
|
|
401
|
-
--temporal-invocations <n> Number of invocations per tool for rug pull detection (default:
|
|
512
|
+
--temporal-invocations <n> Number of invocations per tool for rug pull detection (default: 3)
|
|
402
513
|
--skip-temporal Skip temporal/rug pull testing (faster assessment)
|
|
403
514
|
--conformance Enable official MCP conformance tests (experimental, requires HTTP/SSE transport)
|
|
404
515
|
--output-format <fmt> Output format: full (default), tiered, summary-only
|
|
@@ -409,6 +520,8 @@ Options:
|
|
|
409
520
|
--stage-b-verbose Enable Stage B enrichment for Claude semantic analysis
|
|
410
521
|
Adds evidence samples, payload correlations, and confidence
|
|
411
522
|
breakdowns to tiered output (Tier 2 + Tier 3)
|
|
523
|
+
--module, -m <name> Run single module directly (bypasses orchestrator for faster execution)
|
|
524
|
+
Mutually exclusive with --skip-modules, --only-modules, --profile
|
|
412
525
|
--skip-modules <list> Skip specific modules (comma-separated)
|
|
413
526
|
--only-modules <list> Run only specific modules (comma-separated)
|
|
414
527
|
--json Output only JSON path (no console summary)
|
|
@@ -426,7 +539,8 @@ Environment Variables:
|
|
|
426
539
|
|
|
427
540
|
${getProfileHelpText()}
|
|
428
541
|
Module Selection:
|
|
429
|
-
--profile, --skip-modules, and --only-modules are mutually exclusive.
|
|
542
|
+
--module, --profile, --skip-modules, and --only-modules are mutually exclusive.
|
|
543
|
+
Use --module for single-module runs (fastest, bypasses orchestrator).
|
|
430
544
|
Use --profile for common assessment scenarios.
|
|
431
545
|
Use --skip-modules for custom runs by disabling expensive modules.
|
|
432
546
|
Use --only-modules to focus on specific areas (e.g., tool annotation PRs).
|
|
@@ -468,13 +582,27 @@ Module Tiers (16 total):
|
|
|
468
582
|
• Portability - Cross-platform compatibility
|
|
469
583
|
• External API - External service detection
|
|
470
584
|
|
|
585
|
+
Transport Options:
|
|
586
|
+
--config, --http, and --sse are mutually exclusive.
|
|
587
|
+
Use --http or --sse for quick testing without a config file.
|
|
588
|
+
Use --config for complex setups (STDIO, env vars, etc.).
|
|
589
|
+
|
|
471
590
|
Examples:
|
|
591
|
+
# Quick HTTP/SSE testing (no config file needed):
|
|
592
|
+
mcp-assess-full my-server --http http://localhost:10900/mcp
|
|
593
|
+
mcp-assess-full my-server --sse http://localhost:9002/sse
|
|
594
|
+
|
|
472
595
|
# Profile-based (recommended):
|
|
473
596
|
mcp-assess-full my-server --profile quick # CI/CD fast check (~30s)
|
|
474
597
|
mcp-assess-full my-server --profile security # Security audit (~2-3min)
|
|
475
598
|
mcp-assess-full my-server --profile compliance # Directory submission (~5min)
|
|
476
599
|
mcp-assess-full my-server --profile full # Comprehensive audit (~10-15min)
|
|
477
600
|
|
|
601
|
+
# Single module (fastest - bypasses orchestrator):
|
|
602
|
+
mcp-assess-full my-server --http http://localhost:10900/mcp --module toolAnnotations
|
|
603
|
+
mcp-assess-full my-server --http http://localhost:10900/mcp --module functionality
|
|
604
|
+
mcp-assess-full my-server --http http://localhost:10900/mcp --module security
|
|
605
|
+
|
|
478
606
|
# Custom module selection:
|
|
479
607
|
mcp-assess-full my-server --skip-modules temporal,resources # Skip expensive modules
|
|
480
608
|
mcp-assess-full my-server --only-modules functionality,toolAnnotations # Annotation PR review
|
|
@@ -305,3 +305,133 @@ export function displaySummary(results) {
|
|
|
305
305
|
}
|
|
306
306
|
console.log("\n" + "=".repeat(70));
|
|
307
307
|
}
|
|
308
|
+
// ============================================================================
|
|
309
|
+
// Single Module Output (Issue #184)
|
|
310
|
+
// ============================================================================
|
|
311
|
+
/**
|
|
312
|
+
* Save single module results to file.
|
|
313
|
+
*
|
|
314
|
+
* @param serverName - Server name
|
|
315
|
+
* @param moduleName - Module ID that was executed
|
|
316
|
+
* @param result - Single module result
|
|
317
|
+
* @param options - Assessment options
|
|
318
|
+
* @returns Path to output file
|
|
319
|
+
*/
|
|
320
|
+
export function saveSingleModuleResults(serverName, moduleName, result, options) {
|
|
321
|
+
const defaultPath = `/tmp/inspector-${moduleName}-${serverName}.json`;
|
|
322
|
+
const finalPath = options.outputPath || defaultPath;
|
|
323
|
+
fs.writeFileSync(finalPath, JSON.stringify(result, null, 2));
|
|
324
|
+
return finalPath;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Display single module summary to console.
|
|
328
|
+
*
|
|
329
|
+
* @param result - Single module result
|
|
330
|
+
*/
|
|
331
|
+
export function displaySingleModuleSummary(result) {
|
|
332
|
+
const statusIcon = result.status === "PASS"
|
|
333
|
+
? "✅"
|
|
334
|
+
: result.status === "FAIL"
|
|
335
|
+
? "❌"
|
|
336
|
+
: result.status === "PARTIAL"
|
|
337
|
+
? "⚠️"
|
|
338
|
+
: "❓";
|
|
339
|
+
console.log("\n" + "=".repeat(60));
|
|
340
|
+
console.log(`MODULE: ${result.displayName.toUpperCase()}`);
|
|
341
|
+
console.log("=".repeat(60));
|
|
342
|
+
console.log(`Server: ${result.serverName}`);
|
|
343
|
+
console.log(`Status: ${statusIcon} ${result.status}`);
|
|
344
|
+
console.log(`Estimated Tests: ${result.estimatedTestCount}`);
|
|
345
|
+
console.log(`Execution Time: ${result.executionTime}ms`);
|
|
346
|
+
console.log("=".repeat(60));
|
|
347
|
+
// Display module-specific highlights based on result type
|
|
348
|
+
displayModuleHighlights(result);
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Display module-specific highlights from the result.
|
|
352
|
+
*/
|
|
353
|
+
function displayModuleHighlights(result) {
|
|
354
|
+
const r = result.result;
|
|
355
|
+
if (!r)
|
|
356
|
+
return;
|
|
357
|
+
// Security module
|
|
358
|
+
if (result.module === "security") {
|
|
359
|
+
const vulnCount = Array.isArray(r.vulnerabilities)
|
|
360
|
+
? r.vulnerabilities.length
|
|
361
|
+
: 0;
|
|
362
|
+
if (vulnCount > 0) {
|
|
363
|
+
console.log(`\n🔒 VULNERABILITIES FOUND: ${vulnCount}`);
|
|
364
|
+
const vulns = r.vulnerabilities;
|
|
365
|
+
for (const vuln of vulns.slice(0, 5)) {
|
|
366
|
+
console.log(` • ${vuln.toolName || "unknown"}: ${vuln.testName || "unknown"} (${vuln.riskLevel || "unknown"})`);
|
|
367
|
+
}
|
|
368
|
+
if (vulnCount > 5) {
|
|
369
|
+
console.log(` ... and ${vulnCount - 5} more`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
else {
|
|
373
|
+
console.log("\n✅ No vulnerabilities detected");
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Functionality module
|
|
377
|
+
if (result.module === "functionality") {
|
|
378
|
+
const working = r.workingTools;
|
|
379
|
+
const broken = r.brokenTools;
|
|
380
|
+
if (typeof working === "number" || typeof broken === "number") {
|
|
381
|
+
console.log(`\n📊 TOOL STATUS: ${working || 0} working, ${Array.isArray(broken) ? broken.length : 0} broken`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Tool Annotations module
|
|
385
|
+
if (result.module === "toolAnnotations") {
|
|
386
|
+
const missing = r.missingAnnotationsCount;
|
|
387
|
+
const misaligned = r.misalignedAnnotationsCount;
|
|
388
|
+
const annotated = r.annotatedCount;
|
|
389
|
+
console.log(`\n🏷️ ANNOTATION STATUS:`);
|
|
390
|
+
if (typeof annotated === "number") {
|
|
391
|
+
console.log(` Annotated tools: ${annotated}`);
|
|
392
|
+
}
|
|
393
|
+
if (typeof missing === "number" && missing > 0) {
|
|
394
|
+
console.log(` ⚠️ Missing annotations: ${missing}`);
|
|
395
|
+
}
|
|
396
|
+
if (typeof misaligned === "number" && misaligned > 0) {
|
|
397
|
+
console.log(` ⚠️ Misaligned annotations: ${misaligned}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// Error Handling module
|
|
401
|
+
if (result.module === "errorHandling") {
|
|
402
|
+
const metrics = r.metrics;
|
|
403
|
+
if (metrics) {
|
|
404
|
+
console.log(`\n🛡️ ERROR HANDLING METRICS:`);
|
|
405
|
+
if (typeof metrics.invalidParamHandledCorrectly === "number") {
|
|
406
|
+
console.log(` Invalid param handling: ${metrics.invalidParamHandledCorrectly}%`);
|
|
407
|
+
}
|
|
408
|
+
if (typeof metrics.missingParamHandledCorrectly === "number") {
|
|
409
|
+
console.log(` Missing param handling: ${metrics.missingParamHandledCorrectly}%`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// AUP Compliance module
|
|
414
|
+
if (result.module === "aupCompliance") {
|
|
415
|
+
const violations = r.violations;
|
|
416
|
+
if (Array.isArray(violations) && violations.length > 0) {
|
|
417
|
+
const critical = violations.filter((v) => v.severity === "CRITICAL");
|
|
418
|
+
console.log(`\n⚖️ AUP FINDINGS:`);
|
|
419
|
+
console.log(` Total flagged: ${violations.length}`);
|
|
420
|
+
if (critical.length > 0) {
|
|
421
|
+
console.log(` 🚨 CRITICAL violations: ${critical.length}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
// Temporal module
|
|
426
|
+
if (result.module === "temporal") {
|
|
427
|
+
const overallRisk = r.overallRisk;
|
|
428
|
+
const detectedChanges = r.detectedChanges;
|
|
429
|
+
console.log(`\n⏱️ TEMPORAL ANALYSIS:`);
|
|
430
|
+
if (typeof overallRisk === "string") {
|
|
431
|
+
console.log(` Overall Risk: ${overallRisk}`);
|
|
432
|
+
}
|
|
433
|
+
if (typeof detectedChanges === "number") {
|
|
434
|
+
console.log(` Detected Changes: ${detectedChanges}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
package/package.json
CHANGED