@bryan-thompson/inspector-assessment-cli 1.26.5 → 1.26.7
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__/assessment-runner/assessment-executor.test.js +248 -0
- package/build/__tests__/assessment-runner/config-builder.test.js +289 -0
- package/build/__tests__/assessment-runner/index.test.js +41 -0
- package/build/__tests__/assessment-runner/server-config.test.js +249 -0
- package/build/__tests__/assessment-runner/server-connection.test.js +221 -0
- package/build/__tests__/assessment-runner/source-loader.test.js +341 -0
- package/build/__tests__/assessment-runner/tool-wrapper.test.js +114 -0
- package/build/__tests__/assessment-runner-facade.test.js +118 -0
- package/build/assess-full.js +26 -1254
- package/build/lib/assessment-runner/assessment-executor.js +323 -0
- package/build/lib/assessment-runner/config-builder.js +127 -0
- package/build/lib/assessment-runner/index.js +20 -0
- package/build/lib/assessment-runner/server-config.js +78 -0
- package/build/lib/assessment-runner/server-connection.js +80 -0
- package/build/lib/assessment-runner/source-loader.js +139 -0
- package/build/lib/assessment-runner/tool-wrapper.js +40 -0
- package/build/lib/assessment-runner/types.js +8 -0
- package/build/lib/assessment-runner.js +12 -0
- package/build/lib/cli-parser.js +419 -0
- package/build/lib/comparison-handler.js +84 -0
- package/build/lib/result-output.js +154 -0
- package/package.json +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Source File Loading
|
|
3
|
+
*
|
|
4
|
+
* Handles recursive source file discovery with gitignore support.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/lib/assessment-runner/source-loader
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from "fs";
|
|
9
|
+
import * as path from "path";
|
|
10
|
+
/** Maximum file size (in characters) to include in source code analysis */
|
|
11
|
+
const MAX_SOURCE_FILE_SIZE = 100_000;
|
|
12
|
+
/**
|
|
13
|
+
* Load optional files from source code path
|
|
14
|
+
*
|
|
15
|
+
* @param sourcePath - Path to source code directory
|
|
16
|
+
* @returns Object containing loaded source files
|
|
17
|
+
*/
|
|
18
|
+
export function loadSourceFiles(sourcePath) {
|
|
19
|
+
const result = {};
|
|
20
|
+
// Search for README in source directory and parent directories (up to 3 levels)
|
|
21
|
+
// This handles cases where --source points to a subdirectory but README is at repo root
|
|
22
|
+
const readmePaths = ["README.md", "readme.md", "Readme.md"];
|
|
23
|
+
let readmeFound = false;
|
|
24
|
+
// First try the source directory itself
|
|
25
|
+
for (const readmePath of readmePaths) {
|
|
26
|
+
const fullPath = path.join(sourcePath, readmePath);
|
|
27
|
+
if (fs.existsSync(fullPath)) {
|
|
28
|
+
result.readmeContent = fs.readFileSync(fullPath, "utf-8");
|
|
29
|
+
readmeFound = true;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// If not found, search parent directories (up to 3 levels)
|
|
34
|
+
if (!readmeFound) {
|
|
35
|
+
let currentDir = sourcePath;
|
|
36
|
+
for (let i = 0; i < 3; i++) {
|
|
37
|
+
const parentDir = path.dirname(currentDir);
|
|
38
|
+
if (parentDir === currentDir)
|
|
39
|
+
break; // Reached filesystem root
|
|
40
|
+
for (const readmePath of readmePaths) {
|
|
41
|
+
const fullPath = path.join(parentDir, readmePath);
|
|
42
|
+
if (fs.existsSync(fullPath)) {
|
|
43
|
+
result.readmeContent = fs.readFileSync(fullPath, "utf-8");
|
|
44
|
+
readmeFound = true;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (readmeFound)
|
|
49
|
+
break;
|
|
50
|
+
currentDir = parentDir;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const packagePath = path.join(sourcePath, "package.json");
|
|
54
|
+
if (fs.existsSync(packagePath)) {
|
|
55
|
+
result.packageJson = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
56
|
+
}
|
|
57
|
+
const manifestPath = path.join(sourcePath, "manifest.json");
|
|
58
|
+
if (fs.existsSync(manifestPath)) {
|
|
59
|
+
result.manifestRaw = fs.readFileSync(manifestPath, "utf-8");
|
|
60
|
+
try {
|
|
61
|
+
result.manifestJson = JSON.parse(result.manifestRaw);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
console.warn("[Assessment] Failed to parse manifest.json");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
result.sourceCodeFiles = new Map();
|
|
68
|
+
// Include config files for portability analysis
|
|
69
|
+
const sourceExtensions = [
|
|
70
|
+
".ts",
|
|
71
|
+
".js",
|
|
72
|
+
".py",
|
|
73
|
+
".go",
|
|
74
|
+
".rs",
|
|
75
|
+
".json",
|
|
76
|
+
".sh",
|
|
77
|
+
".yaml",
|
|
78
|
+
".yml",
|
|
79
|
+
];
|
|
80
|
+
// Parse .gitignore patterns
|
|
81
|
+
const gitignorePatterns = [];
|
|
82
|
+
const gitignorePath = path.join(sourcePath, ".gitignore");
|
|
83
|
+
if (fs.existsSync(gitignorePath)) {
|
|
84
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf-8");
|
|
85
|
+
for (const line of gitignoreContent.split("\n")) {
|
|
86
|
+
const trimmed = line.trim();
|
|
87
|
+
if (!trimmed || trimmed.startsWith("#"))
|
|
88
|
+
continue;
|
|
89
|
+
// Convert gitignore pattern to regex
|
|
90
|
+
const pattern = trimmed
|
|
91
|
+
.replace(/\./g, "\\.")
|
|
92
|
+
.replace(/\*\*/g, ".*")
|
|
93
|
+
.replace(/\*/g, "[^/]*")
|
|
94
|
+
.replace(/\?/g, ".");
|
|
95
|
+
try {
|
|
96
|
+
gitignorePatterns.push(new RegExp(pattern));
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Skip invalid patterns
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const isGitignored = (relativePath) => {
|
|
104
|
+
return gitignorePatterns.some((pattern) => pattern.test(relativePath));
|
|
105
|
+
};
|
|
106
|
+
const loadSourceDir = (dir, prefix = "") => {
|
|
107
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
110
|
+
continue;
|
|
111
|
+
const fullPath = path.join(dir, entry.name);
|
|
112
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
113
|
+
// Skip gitignored files
|
|
114
|
+
if (isGitignored(relativePath))
|
|
115
|
+
continue;
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
loadSourceDir(fullPath, relativePath);
|
|
118
|
+
}
|
|
119
|
+
else if (sourceExtensions.some((ext) => entry.name.endsWith(ext))) {
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
122
|
+
if (content.length < MAX_SOURCE_FILE_SIZE) {
|
|
123
|
+
result.sourceCodeFiles.set(relativePath, content);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// Skip unreadable files
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
try {
|
|
133
|
+
loadSourceDir(sourcePath);
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
console.warn("[Assessment] Could not load source files:", e);
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Call Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Creates a wrapper around MCP client.callTool() for assessment context.
|
|
5
|
+
*
|
|
6
|
+
* @module cli/lib/assessment-runner/tool-wrapper
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Create callTool wrapper for assessment context
|
|
10
|
+
*
|
|
11
|
+
* @param client - Connected MCP client
|
|
12
|
+
* @returns Wrapped callTool function
|
|
13
|
+
*/
|
|
14
|
+
export function createCallToolWrapper(client) {
|
|
15
|
+
return async (name, params) => {
|
|
16
|
+
try {
|
|
17
|
+
const response = await client.callTool({
|
|
18
|
+
name,
|
|
19
|
+
arguments: params,
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
content: response.content,
|
|
23
|
+
isError: response.isError || false,
|
|
24
|
+
structuredContent: response
|
|
25
|
+
.structuredContent,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
isError: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Assessment Runner Module (Facade)
|
|
3
|
+
*
|
|
4
|
+
* This file provides backward-compatible exports by re-exporting from the
|
|
5
|
+
* modular assessment-runner/ directory structure.
|
|
6
|
+
*
|
|
7
|
+
* Refactored as part of Issue #94.
|
|
8
|
+
*
|
|
9
|
+
* @module cli/lib/assessment-runner
|
|
10
|
+
*/
|
|
11
|
+
// Re-export all public APIs from the modular structure
|
|
12
|
+
export * from "./assessment-runner/index.js";
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Parser Module
|
|
3
|
+
*
|
|
4
|
+
* Handles command-line argument parsing, validation, and help text for
|
|
5
|
+
* the mcp-assess-full CLI tool.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from assess-full.ts as part of Issue #90 modularization.
|
|
8
|
+
*
|
|
9
|
+
* @module cli/lib/cli-parser
|
|
10
|
+
*/
|
|
11
|
+
import { ASSESSMENT_CATEGORY_METADATA, } from "../../../client/lib/lib/assessmentTypes.js";
|
|
12
|
+
import { ASSESSMENT_PROFILES, isValidProfileName, getProfileHelpText, } from "../profiles.js";
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// Constants
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Valid module names derived from ASSESSMENT_CATEGORY_METADATA
|
|
17
|
+
const VALID_MODULE_NAMES = Object.keys(ASSESSMENT_CATEGORY_METADATA);
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Validation Functions
|
|
20
|
+
// ============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Validate module names from CLI input
|
|
23
|
+
*
|
|
24
|
+
* @param input - Comma-separated module names
|
|
25
|
+
* @param flagName - Flag name for error messages (e.g., "--skip-modules")
|
|
26
|
+
* @returns Array of validated module names, or empty array if invalid
|
|
27
|
+
*/
|
|
28
|
+
export function validateModuleNames(input, flagName) {
|
|
29
|
+
const names = input
|
|
30
|
+
.split(",")
|
|
31
|
+
.map((n) => n.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
const invalid = names.filter((n) => !VALID_MODULE_NAMES.includes(n));
|
|
34
|
+
if (invalid.length > 0) {
|
|
35
|
+
console.error(`Error: Invalid module name(s) for ${flagName}: ${invalid.join(", ")}`);
|
|
36
|
+
console.error(`Valid modules: ${VALID_MODULE_NAMES.join(", ")}`);
|
|
37
|
+
setTimeout(() => process.exit(1), 10);
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
return names;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate parsed options for consistency and requirements
|
|
44
|
+
*
|
|
45
|
+
* @param options - Partial assessment options to validate
|
|
46
|
+
* @returns Validation result with any errors
|
|
47
|
+
*/
|
|
48
|
+
export function validateArgs(options) {
|
|
49
|
+
const errors = [];
|
|
50
|
+
// Server name is required
|
|
51
|
+
if (!options.serverName) {
|
|
52
|
+
errors.push("--server is required");
|
|
53
|
+
}
|
|
54
|
+
// Validate mutual exclusivity of --profile, --skip-modules, and --only-modules
|
|
55
|
+
if (options.profile &&
|
|
56
|
+
(options.skipModules?.length || options.onlyModules?.length)) {
|
|
57
|
+
errors.push("--profile cannot be used with --skip-modules or --only-modules");
|
|
58
|
+
}
|
|
59
|
+
if (options.skipModules?.length && options.onlyModules?.length) {
|
|
60
|
+
errors.push("--skip-modules and --only-modules are mutually exclusive");
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
valid: errors.length === 0,
|
|
64
|
+
errors,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Argument Parsing
|
|
69
|
+
// ============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Parse command-line arguments
|
|
72
|
+
*
|
|
73
|
+
* @param argv - Command-line arguments (defaults to process.argv.slice(2))
|
|
74
|
+
* @returns Parsed assessment options
|
|
75
|
+
*/
|
|
76
|
+
export function parseArgs(argv) {
|
|
77
|
+
const args = argv ?? process.argv.slice(2);
|
|
78
|
+
const options = {};
|
|
79
|
+
for (let i = 0; i < args.length; i++) {
|
|
80
|
+
const arg = args[i];
|
|
81
|
+
if (!arg)
|
|
82
|
+
continue;
|
|
83
|
+
switch (arg) {
|
|
84
|
+
case "--server":
|
|
85
|
+
case "-s":
|
|
86
|
+
options.serverName = args[++i];
|
|
87
|
+
break;
|
|
88
|
+
case "--config":
|
|
89
|
+
case "-c":
|
|
90
|
+
options.serverConfigPath = args[++i];
|
|
91
|
+
break;
|
|
92
|
+
case "--output":
|
|
93
|
+
case "-o":
|
|
94
|
+
options.outputPath = args[++i];
|
|
95
|
+
break;
|
|
96
|
+
case "--source":
|
|
97
|
+
options.sourceCodePath = args[++i];
|
|
98
|
+
break;
|
|
99
|
+
case "--pattern-config":
|
|
100
|
+
case "-p":
|
|
101
|
+
options.patternConfigPath = args[++i];
|
|
102
|
+
break;
|
|
103
|
+
case "--performance-config":
|
|
104
|
+
options.performanceConfigPath = args[++i];
|
|
105
|
+
break;
|
|
106
|
+
case "--claude-enabled":
|
|
107
|
+
options.claudeEnabled = true;
|
|
108
|
+
break;
|
|
109
|
+
case "--claude-http":
|
|
110
|
+
// Enable Claude Bridge with HTTP transport (connects to mcp-auditor)
|
|
111
|
+
options.claudeEnabled = true;
|
|
112
|
+
options.claudeHttp = true;
|
|
113
|
+
break;
|
|
114
|
+
case "--mcp-auditor-url": {
|
|
115
|
+
const urlValue = args[++i];
|
|
116
|
+
if (!urlValue || urlValue.startsWith("-")) {
|
|
117
|
+
console.error("Error: --mcp-auditor-url requires a URL argument");
|
|
118
|
+
setTimeout(() => process.exit(1), 10);
|
|
119
|
+
options.helpRequested = true;
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
new URL(urlValue); // Validate URL format
|
|
124
|
+
options.mcpAuditorUrl = urlValue;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
console.error(`Error: Invalid URL for --mcp-auditor-url: ${urlValue}`);
|
|
128
|
+
console.error(" Expected format: http://hostname:port or https://hostname:port");
|
|
129
|
+
setTimeout(() => process.exit(1), 10);
|
|
130
|
+
options.helpRequested = true;
|
|
131
|
+
return options;
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
case "--full":
|
|
136
|
+
options.fullAssessment = true;
|
|
137
|
+
break;
|
|
138
|
+
case "--verbose":
|
|
139
|
+
case "-v":
|
|
140
|
+
options.verbose = true;
|
|
141
|
+
options.logLevel = "debug";
|
|
142
|
+
break;
|
|
143
|
+
case "--silent":
|
|
144
|
+
options.logLevel = "silent";
|
|
145
|
+
break;
|
|
146
|
+
case "--log-level": {
|
|
147
|
+
const levelValue = args[++i];
|
|
148
|
+
const validLevels = [
|
|
149
|
+
"silent",
|
|
150
|
+
"error",
|
|
151
|
+
"warn",
|
|
152
|
+
"info",
|
|
153
|
+
"debug",
|
|
154
|
+
];
|
|
155
|
+
if (!validLevels.includes(levelValue)) {
|
|
156
|
+
console.error(`Invalid log level: ${levelValue}. Valid options: ${validLevels.join(", ")}`);
|
|
157
|
+
setTimeout(() => process.exit(1), 10);
|
|
158
|
+
options.helpRequested = true;
|
|
159
|
+
return options;
|
|
160
|
+
}
|
|
161
|
+
options.logLevel = levelValue;
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
case "--json":
|
|
165
|
+
options.jsonOnly = true;
|
|
166
|
+
break;
|
|
167
|
+
case "--format":
|
|
168
|
+
case "-f": {
|
|
169
|
+
const formatValue = args[++i];
|
|
170
|
+
if (formatValue !== "json" && formatValue !== "markdown") {
|
|
171
|
+
console.error(`Invalid format: ${formatValue}. Valid options: json, markdown`);
|
|
172
|
+
setTimeout(() => process.exit(1), 10);
|
|
173
|
+
options.helpRequested = true;
|
|
174
|
+
return options;
|
|
175
|
+
}
|
|
176
|
+
options.format = formatValue;
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
case "--include-policy":
|
|
180
|
+
options.includePolicy = true;
|
|
181
|
+
break;
|
|
182
|
+
case "--preflight":
|
|
183
|
+
options.preflightOnly = true;
|
|
184
|
+
break;
|
|
185
|
+
case "--compare":
|
|
186
|
+
options.comparePath = args[++i];
|
|
187
|
+
break;
|
|
188
|
+
case "--diff-only":
|
|
189
|
+
options.diffOnly = true;
|
|
190
|
+
break;
|
|
191
|
+
case "--resume":
|
|
192
|
+
options.resume = true;
|
|
193
|
+
break;
|
|
194
|
+
case "--no-resume":
|
|
195
|
+
options.noResume = true;
|
|
196
|
+
break;
|
|
197
|
+
case "--temporal-invocations":
|
|
198
|
+
options.temporalInvocations = parseInt(args[++i], 10);
|
|
199
|
+
break;
|
|
200
|
+
case "--skip-temporal":
|
|
201
|
+
options.skipTemporal = true;
|
|
202
|
+
break;
|
|
203
|
+
case "--profile": {
|
|
204
|
+
const profileValue = args[++i];
|
|
205
|
+
if (!profileValue) {
|
|
206
|
+
console.error("Error: --profile requires a profile name");
|
|
207
|
+
console.error(`Valid profiles: ${Object.keys(ASSESSMENT_PROFILES).join(", ")}`);
|
|
208
|
+
setTimeout(() => process.exit(1), 10);
|
|
209
|
+
options.helpRequested = true;
|
|
210
|
+
return options;
|
|
211
|
+
}
|
|
212
|
+
if (!isValidProfileName(profileValue)) {
|
|
213
|
+
console.error(`Error: Invalid profile name: ${profileValue}`);
|
|
214
|
+
console.error(`Valid profiles: ${Object.keys(ASSESSMENT_PROFILES).join(", ")}`);
|
|
215
|
+
setTimeout(() => process.exit(1), 10);
|
|
216
|
+
options.helpRequested = true;
|
|
217
|
+
return options;
|
|
218
|
+
}
|
|
219
|
+
options.profile = profileValue;
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "--skip-modules": {
|
|
223
|
+
const skipValue = args[++i];
|
|
224
|
+
if (!skipValue) {
|
|
225
|
+
console.error("Error: --skip-modules requires a comma-separated list");
|
|
226
|
+
setTimeout(() => process.exit(1), 10);
|
|
227
|
+
options.helpRequested = true;
|
|
228
|
+
return options;
|
|
229
|
+
}
|
|
230
|
+
options.skipModules = validateModuleNames(skipValue, "--skip-modules");
|
|
231
|
+
if (options.skipModules.length === 0 && skipValue) {
|
|
232
|
+
options.helpRequested = true;
|
|
233
|
+
return options;
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "--only-modules": {
|
|
238
|
+
const onlyValue = args[++i];
|
|
239
|
+
if (!onlyValue) {
|
|
240
|
+
console.error("Error: --only-modules requires a comma-separated list");
|
|
241
|
+
setTimeout(() => process.exit(1), 10);
|
|
242
|
+
options.helpRequested = true;
|
|
243
|
+
return options;
|
|
244
|
+
}
|
|
245
|
+
options.onlyModules = validateModuleNames(onlyValue, "--only-modules");
|
|
246
|
+
if (options.onlyModules.length === 0 && onlyValue) {
|
|
247
|
+
options.helpRequested = true;
|
|
248
|
+
return options;
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
case "--help":
|
|
253
|
+
case "-h":
|
|
254
|
+
printHelp();
|
|
255
|
+
options.helpRequested = true;
|
|
256
|
+
return options;
|
|
257
|
+
default:
|
|
258
|
+
if (!arg.startsWith("-")) {
|
|
259
|
+
if (!options.serverName) {
|
|
260
|
+
options.serverName = arg;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
console.error(`Unknown argument: ${arg}`);
|
|
265
|
+
printHelp();
|
|
266
|
+
setTimeout(() => process.exit(1), 10);
|
|
267
|
+
options.helpRequested = true;
|
|
268
|
+
return options;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
// Validate mutual exclusivity of --profile, --skip-modules, and --only-modules
|
|
273
|
+
if (options.profile &&
|
|
274
|
+
(options.skipModules?.length || options.onlyModules?.length)) {
|
|
275
|
+
console.error("Error: --profile cannot be used with --skip-modules or --only-modules");
|
|
276
|
+
setTimeout(() => process.exit(1), 10);
|
|
277
|
+
options.helpRequested = true;
|
|
278
|
+
return options;
|
|
279
|
+
}
|
|
280
|
+
if (options.skipModules?.length && options.onlyModules?.length) {
|
|
281
|
+
console.error("Error: --skip-modules and --only-modules are mutually exclusive");
|
|
282
|
+
setTimeout(() => process.exit(1), 10);
|
|
283
|
+
options.helpRequested = true;
|
|
284
|
+
return options;
|
|
285
|
+
}
|
|
286
|
+
if (!options.serverName) {
|
|
287
|
+
console.error("Error: --server is required");
|
|
288
|
+
printHelp();
|
|
289
|
+
setTimeout(() => process.exit(1), 10);
|
|
290
|
+
options.helpRequested = true;
|
|
291
|
+
return options;
|
|
292
|
+
}
|
|
293
|
+
// Environment variable fallbacks (matches run-security-assessment.ts behavior)
|
|
294
|
+
// INSPECTOR_CLAUDE=true enables Claude with HTTP transport
|
|
295
|
+
if (process.env.INSPECTOR_CLAUDE === "true" && !options.claudeEnabled) {
|
|
296
|
+
options.claudeEnabled = true;
|
|
297
|
+
options.claudeHttp = true; // HTTP transport when enabled via env var
|
|
298
|
+
}
|
|
299
|
+
// INSPECTOR_MCP_AUDITOR_URL overrides default URL (only if not set via CLI)
|
|
300
|
+
if (process.env.INSPECTOR_MCP_AUDITOR_URL && !options.mcpAuditorUrl) {
|
|
301
|
+
const envUrl = process.env.INSPECTOR_MCP_AUDITOR_URL;
|
|
302
|
+
try {
|
|
303
|
+
new URL(envUrl);
|
|
304
|
+
options.mcpAuditorUrl = envUrl;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
console.warn(`Warning: Invalid INSPECTOR_MCP_AUDITOR_URL: ${envUrl}, using default`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return options;
|
|
311
|
+
}
|
|
312
|
+
// ============================================================================
|
|
313
|
+
// Help Text
|
|
314
|
+
// ============================================================================
|
|
315
|
+
/**
|
|
316
|
+
* Print help message to console
|
|
317
|
+
*/
|
|
318
|
+
export function printHelp() {
|
|
319
|
+
console.log(`
|
|
320
|
+
Usage: mcp-assess-full [options] [server-name]
|
|
321
|
+
|
|
322
|
+
Run comprehensive MCP server assessment with 16 assessor modules organized in 4 tiers.
|
|
323
|
+
|
|
324
|
+
Options:
|
|
325
|
+
--server, -s <name> Server name (required, or pass as first positional arg)
|
|
326
|
+
--config, -c <path> Path to server config JSON
|
|
327
|
+
--output, -o <path> Output path (default: /tmp/inspector-full-assessment-<server>.<ext>)
|
|
328
|
+
--source <path> Source code path for deep analysis (AUP, portability, etc.)
|
|
329
|
+
--pattern-config, -p <path> Path to custom annotation pattern JSON
|
|
330
|
+
--performance-config <path> Path to performance tuning JSON (batch sizes, timeouts, etc.)
|
|
331
|
+
--format, -f <type> Output format: json (default) or markdown
|
|
332
|
+
--include-policy Include policy compliance mapping in report (30 requirements)
|
|
333
|
+
--preflight Run quick validation only (tools exist, manifest valid, server responds)
|
|
334
|
+
--compare <path> Compare current assessment against baseline JSON file
|
|
335
|
+
--diff-only Output only the comparison diff (requires --compare)
|
|
336
|
+
--resume Resume from previous interrupted assessment
|
|
337
|
+
--no-resume Force fresh start, clear any existing state
|
|
338
|
+
--claude-enabled Enable Claude Code integration (CLI transport: requires 'claude' binary)
|
|
339
|
+
--claude-http Enable Claude Code via HTTP transport (connects to mcp-auditor proxy)
|
|
340
|
+
--mcp-auditor-url <url> mcp-auditor URL for HTTP transport (default: http://localhost:8085)
|
|
341
|
+
--full Enable all assessment modules (default)
|
|
342
|
+
--profile <name> Use predefined module profile (quick, security, compliance, full)
|
|
343
|
+
--temporal-invocations <n> Number of invocations per tool for rug pull detection (default: 25)
|
|
344
|
+
--skip-temporal Skip temporal/rug pull testing (faster assessment)
|
|
345
|
+
--skip-modules <list> Skip specific modules (comma-separated)
|
|
346
|
+
--only-modules <list> Run only specific modules (comma-separated)
|
|
347
|
+
--json Output only JSON path (no console summary)
|
|
348
|
+
--verbose, -v Enable verbose logging (same as --log-level debug)
|
|
349
|
+
--silent Suppress all diagnostic logging
|
|
350
|
+
--log-level <level> Set log level: silent, error, warn, info (default), debug
|
|
351
|
+
Also supports LOG_LEVEL environment variable
|
|
352
|
+
--help, -h Show this help message
|
|
353
|
+
|
|
354
|
+
Environment Variables:
|
|
355
|
+
INSPECTOR_CLAUDE=true Enable Claude with HTTP transport (same as --claude-http)
|
|
356
|
+
INSPECTOR_MCP_AUDITOR_URL Override default mcp-auditor URL (default: http://localhost:8085)
|
|
357
|
+
LOG_LEVEL Set log level (overridden by --log-level flag)
|
|
358
|
+
|
|
359
|
+
${getProfileHelpText()}
|
|
360
|
+
Module Selection:
|
|
361
|
+
--profile, --skip-modules, and --only-modules are mutually exclusive.
|
|
362
|
+
Use --profile for common assessment scenarios.
|
|
363
|
+
Use --skip-modules for custom runs by disabling expensive modules.
|
|
364
|
+
Use --only-modules to focus on specific areas (e.g., tool annotation PRs).
|
|
365
|
+
|
|
366
|
+
Valid module names (new naming):
|
|
367
|
+
functionality, security, errorHandling, protocolCompliance, aupCompliance,
|
|
368
|
+
toolAnnotations, prohibitedLibraries, manifestValidation, authentication,
|
|
369
|
+
temporal, resources, prompts, crossCapability, developerExperience,
|
|
370
|
+
portability, externalAPIScanner
|
|
371
|
+
|
|
372
|
+
Legacy module names (deprecated, will map to new names):
|
|
373
|
+
documentation -> developerExperience
|
|
374
|
+
usability -> developerExperience
|
|
375
|
+
mcpSpecCompliance -> protocolCompliance
|
|
376
|
+
protocolConformance -> protocolCompliance
|
|
377
|
+
|
|
378
|
+
Module Tiers (16 total):
|
|
379
|
+
Tier 1 - Core Security (Always Run):
|
|
380
|
+
• Functionality - Tests all tools work correctly
|
|
381
|
+
• Security - Prompt injection & vulnerability testing
|
|
382
|
+
• Error Handling - Validates error responses
|
|
383
|
+
• Protocol Compliance - MCP protocol + JSON-RPC validation
|
|
384
|
+
• AUP Compliance - Acceptable Use Policy checks
|
|
385
|
+
• Temporal - Rug pull/temporal behavior change detection
|
|
386
|
+
|
|
387
|
+
Tier 2 - Compliance (MCP Directory):
|
|
388
|
+
• Tool Annotations - readOnlyHint/destructiveHint validation
|
|
389
|
+
• Prohibited Libs - Dependency security checks
|
|
390
|
+
• Manifest - MCPB manifest.json validation
|
|
391
|
+
• Authentication - OAuth/auth evaluation
|
|
392
|
+
|
|
393
|
+
Tier 3 - Capability-Based (Conditional):
|
|
394
|
+
• Resources - Resource capability assessment
|
|
395
|
+
• Prompts - Prompt capability assessment
|
|
396
|
+
• Cross-Capability - Chained vulnerability detection
|
|
397
|
+
|
|
398
|
+
Tier 4 - Extended (Optional):
|
|
399
|
+
• Developer Experience - Documentation + usability assessment
|
|
400
|
+
• Portability - Cross-platform compatibility
|
|
401
|
+
• External API - External service detection
|
|
402
|
+
|
|
403
|
+
Examples:
|
|
404
|
+
# Profile-based (recommended):
|
|
405
|
+
mcp-assess-full my-server --profile quick # CI/CD fast check (~30s)
|
|
406
|
+
mcp-assess-full my-server --profile security # Security audit (~2-3min)
|
|
407
|
+
mcp-assess-full my-server --profile compliance # Directory submission (~5min)
|
|
408
|
+
mcp-assess-full my-server --profile full # Comprehensive audit (~10-15min)
|
|
409
|
+
|
|
410
|
+
# Custom module selection:
|
|
411
|
+
mcp-assess-full my-server --skip-modules temporal,resources # Skip expensive modules
|
|
412
|
+
mcp-assess-full my-server --only-modules functionality,toolAnnotations # Annotation PR review
|
|
413
|
+
|
|
414
|
+
# Advanced options:
|
|
415
|
+
mcp-assess-full --server my-server --source ./my-server --output ./results.json
|
|
416
|
+
mcp-assess-full --server my-server --format markdown --include-policy
|
|
417
|
+
mcp-assess-full --server my-server --compare ./baseline.json --diff-only
|
|
418
|
+
`);
|
|
419
|
+
}
|