Refactor project components
* Remove bin folder, leaving cli, server, and client * This fixes #315 * In .gitignore, - add .idea - remove bin/build * Remove bin and bin/cli.js * Remove bin/scripts/copy-cli.js * Refactor/move bin/scripts to cli/scripts * Refactor/move bin/src/index.ts to cli/src/cli.ts * Refactor/renamed client/bin/cli.js to client/bin/client.js * In .github/workflows/main.yml, - add run of cli tests * In cli/pacakge.json - change main and bin/mcp-inspector-cli properties to build/cli.js * In client/package.json, - change bin/mcp-inspector-client properties to build/start.js * In pacakge.json - change bin/mcp-inspector property to ./cli/build/cli.js - removed bin and cli/bin from files list - removed @modelcontextprotocol/inspector-bin dependency - rearranged and corrected scripts
This commit is contained in:
parent
f7272d8d8c
commit
6a99feaf33
15 changed files with 639 additions and 3399 deletions
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
|
|
@ -30,6 +30,10 @@ jobs:
|
||||||
working-directory: ./client
|
working-directory: ./client
|
||||||
run: npm test
|
run: npm test
|
||||||
|
|
||||||
|
- name: Run cli tests
|
||||||
|
working-directory: ./cli
|
||||||
|
run: npm test
|
||||||
|
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
|
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,11 +1,11 @@
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
node_modules/
|
node_modules/
|
||||||
*-workspace/
|
*-workspace/
|
||||||
server/build
|
server/build
|
||||||
client/dist
|
client/dist
|
||||||
client/tsconfig.app.tsbuildinfo
|
client/tsconfig.app.tsbuildinfo
|
||||||
client/tsconfig.node.tsbuildinfo
|
client/tsconfig.node.tsbuildinfo
|
||||||
.vscode
|
|
||||||
bin/build
|
|
||||||
cli/build
|
cli/build
|
||||||
test-output
|
test-output
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@modelcontextprotocol/inspector-bin",
|
|
||||||
"version": "0.9.0",
|
|
||||||
"description": "Model Context Protocol inspector",
|
|
||||||
"license": "MIT",
|
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
|
||||||
"homepage": "https://modelcontextprotocol.io",
|
|
||||||
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
|
||||||
"type": "module",
|
|
||||||
"bin": {
|
|
||||||
"mcp-inspector": "./cli.js"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"cli.js"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"postbuild": "node scripts/make-executable.js && node scripts/copy-cli.js",
|
|
||||||
"test": "node scripts/cli-tests.js"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"commander": "^13.1.0",
|
|
||||||
"spawn-rx": "^5.1.2"
|
|
||||||
},
|
|
||||||
"devDependencies": {}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
/**
|
|
||||||
* Cross-platform script to copy the built file to cli.js
|
|
||||||
*/
|
|
||||||
import { promises as fs } from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const SOURCE_FILE = path.resolve("build/index.js");
|
|
||||||
const TARGET_FILE = path.resolve("cli.js");
|
|
||||||
|
|
||||||
async function copyFile() {
|
|
||||||
try {
|
|
||||||
await fs.copyFile(SOURCE_FILE, TARGET_FILE);
|
|
||||||
console.log(`Successfully copied ${SOURCE_FILE} to ${TARGET_FILE}`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error copying file:", error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
copyFile();
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "Node16",
|
|
||||||
"moduleResolution": "Node16",
|
|
||||||
"outDir": "./build",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules", "packages", "**/*.spec.ts"]
|
|
||||||
}
|
|
||||||
|
|
@ -3,18 +3,21 @@
|
||||||
"version": "0.9.0",
|
"version": "0.9.0",
|
||||||
"description": "CLI for the Model Context Protocol inspector",
|
"description": "CLI for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Nicolas Barraud",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
"homepage": "https://github.com/nbarraud",
|
"homepage": "https://modelcontextprotocol.io",
|
||||||
"main": "build/index.js",
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
|
"main": "build/cli.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mcp-inspector-cli": "build/index.js"
|
"mcp-inspector-cli": "build/cli.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"build"
|
"build"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc"
|
"build": "tsc",
|
||||||
|
"postbuild": "node scripts/make-executable.js",
|
||||||
|
"test": "node scripts/cli-tests.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
633
cli/scripts/cli-tests.js
Executable file
633
cli/scripts/cli-tests.js
Executable file
|
|
@ -0,0 +1,633 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
// Colors for output
|
||||||
|
const colors = {
|
||||||
|
GREEN: "\x1b[32m",
|
||||||
|
YELLOW: "\x1b[33m",
|
||||||
|
RED: "\x1b[31m",
|
||||||
|
BLUE: "\x1b[34m",
|
||||||
|
ORANGE: "\x1b[33m",
|
||||||
|
NC: "\x1b[0m", // No Color
|
||||||
|
};
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { execSync, spawn } from "child_process";
|
||||||
|
import os from "os";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
// Get directory paths with ESM compatibility
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Track test results
|
||||||
|
let PASSED_TESTS = 0;
|
||||||
|
let FAILED_TESTS = 0;
|
||||||
|
let SKIPPED_TESTS = 0;
|
||||||
|
let TOTAL_TESTS = 0;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${colors.YELLOW}=== MCP Inspector CLI Test Script ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}This script tests the MCP Inspector CLI's ability to handle various command line options:${colors.NC}`,
|
||||||
|
);
|
||||||
|
console.log(`${colors.BLUE}- Basic CLI mode${colors.NC}`);
|
||||||
|
console.log(`${colors.BLUE}- Environment variables (-e)${colors.NC}`);
|
||||||
|
console.log(`${colors.BLUE}- Config file (--config)${colors.NC}`);
|
||||||
|
console.log(`${colors.BLUE}- Server selection (--server)${colors.NC}`);
|
||||||
|
console.log(`${colors.BLUE}- Method selection (--method)${colors.NC}`);
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}- Tool-related options (--tool-name, --tool-arg)${colors.NC}`,
|
||||||
|
);
|
||||||
|
console.log(`${colors.BLUE}- Resource-related options (--uri)${colors.NC}`);
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}- Prompt-related options (--prompt-name, --prompt-args)${colors.NC}`,
|
||||||
|
);
|
||||||
|
console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}\n`);
|
||||||
|
|
||||||
|
// Get directory paths
|
||||||
|
const SCRIPTS_DIR = __dirname;
|
||||||
|
const PROJECT_ROOT = path.join(SCRIPTS_DIR, "../../");
|
||||||
|
const BUILD_DIR = path.resolve(SCRIPTS_DIR, "../build");
|
||||||
|
|
||||||
|
// Define the test server command using npx
|
||||||
|
const TEST_CMD = "npx";
|
||||||
|
const TEST_ARGS = ["@modelcontextprotocol/server-everything"];
|
||||||
|
|
||||||
|
// Create output directory for test results
|
||||||
|
const OUTPUT_DIR = path.join(SCRIPTS_DIR, "test-output");
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a temporary directory for test files
|
||||||
|
const TEMP_DIR = fs.mkdirSync(path.join(os.tmpdir(), "mcp-inspector-tests"), {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("exit", () => {
|
||||||
|
try {
|
||||||
|
fs.rmSync(TEMP_DIR, { recursive: true, force: true });
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
`${colors.RED}Failed to remove temp directory: ${err.message}${colors.NC}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use the existing sample config file
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}Using existing sample config file: ${PROJECT_ROOT}/sample-config.json${colors.NC}`,
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const sampleConfig = fs.readFileSync(
|
||||||
|
path.join(PROJECT_ROOT, "sample-config.json"),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
console.log(sampleConfig);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`${colors.RED}Error reading sample config: ${error.message}${colors.NC}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an invalid config file for testing
|
||||||
|
const invalidConfigPath = path.join(TEMP_DIR, "invalid-config.json");
|
||||||
|
fs.writeFileSync(invalidConfigPath, '{\n "mcpServers": {\n "invalid": {');
|
||||||
|
|
||||||
|
// Function to run a basic test
|
||||||
|
async function runBasicTest(testName, ...args) {
|
||||||
|
const outputFile = path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
`${testName.replace(/\//g, "_")}.log`,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`\n${colors.YELLOW}Testing: ${testName}${colors.NC}`);
|
||||||
|
TOTAL_TESTS++;
|
||||||
|
|
||||||
|
// Run the command and capture output
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}Command: node ${BUILD_DIR}/cli.js ${args.join(" ")}${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a write stream for the output file
|
||||||
|
const outputStream = fs.createWriteStream(outputFile);
|
||||||
|
|
||||||
|
// Spawn the process
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const child = spawn("node", [path.join(BUILD_DIR, "cli.js"), ...args], {
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pipe stdout and stderr to the output file
|
||||||
|
child.stdout.pipe(outputStream);
|
||||||
|
child.stderr.pipe(outputStream);
|
||||||
|
|
||||||
|
// Also capture output for display
|
||||||
|
let output = "";
|
||||||
|
child.stdout.on("data", (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
child.stderr.on("data", (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", (code) => {
|
||||||
|
outputStream.end();
|
||||||
|
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`${colors.GREEN}✓ Test passed: ${testName}${colors.NC}`);
|
||||||
|
console.log(`${colors.BLUE}First few lines of output:${colors.NC}`);
|
||||||
|
const firstFewLines = output
|
||||||
|
.split("\n")
|
||||||
|
.slice(0, 5)
|
||||||
|
.map((line) => ` ${line}`)
|
||||||
|
.join("\n");
|
||||||
|
console.log(firstFewLines);
|
||||||
|
PASSED_TESTS++;
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
console.log(`${colors.RED}✗ Test failed: ${testName}${colors.NC}`);
|
||||||
|
console.log(`${colors.RED}Error output:${colors.NC}`);
|
||||||
|
console.log(
|
||||||
|
output
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => ` ${line}`)
|
||||||
|
.join("\n"),
|
||||||
|
);
|
||||||
|
FAILED_TESTS++;
|
||||||
|
|
||||||
|
// Stop after any error is encountered
|
||||||
|
console.log(
|
||||||
|
`${colors.YELLOW}Stopping tests due to error. Please validate and fix before continuing.${colors.NC}`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`${colors.RED}Error running test: ${error.message}${colors.NC}`,
|
||||||
|
);
|
||||||
|
FAILED_TESTS++;
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to run an error test (expected to fail)
|
||||||
|
async function runErrorTest(testName, ...args) {
|
||||||
|
const outputFile = path.join(
|
||||||
|
OUTPUT_DIR,
|
||||||
|
`${testName.replace(/\//g, "_")}.log`,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`\n${colors.YELLOW}Testing error case: ${testName}${colors.NC}`);
|
||||||
|
TOTAL_TESTS++;
|
||||||
|
|
||||||
|
// Run the command and capture output
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}Command: node ${BUILD_DIR}/cli.js ${args.join(" ")}${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a write stream for the output file
|
||||||
|
const outputStream = fs.createWriteStream(outputFile);
|
||||||
|
|
||||||
|
// Spawn the process
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const child = spawn("node", [path.join(BUILD_DIR, "cli.js"), ...args], {
|
||||||
|
stdio: ["ignore", "pipe", "pipe"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pipe stdout and stderr to the output file
|
||||||
|
child.stdout.pipe(outputStream);
|
||||||
|
child.stderr.pipe(outputStream);
|
||||||
|
|
||||||
|
// Also capture output for display
|
||||||
|
let output = "";
|
||||||
|
child.stdout.on("data", (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
child.stderr.on("data", (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on("close", (code) => {
|
||||||
|
outputStream.end();
|
||||||
|
|
||||||
|
// For error tests, we expect a non-zero exit code
|
||||||
|
if (code !== 0) {
|
||||||
|
console.log(
|
||||||
|
`${colors.GREEN}✓ Error test passed: ${testName}${colors.NC}`,
|
||||||
|
);
|
||||||
|
console.log(`${colors.BLUE}Error output (expected):${colors.NC}`);
|
||||||
|
const firstFewLines = output
|
||||||
|
.split("\n")
|
||||||
|
.slice(0, 5)
|
||||||
|
.map((line) => ` ${line}`)
|
||||||
|
.join("\n");
|
||||||
|
console.log(firstFewLines);
|
||||||
|
PASSED_TESTS++;
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`${colors.RED}✗ Error test failed: ${testName} (expected error but got success)${colors.NC}`,
|
||||||
|
);
|
||||||
|
console.log(`${colors.RED}Output:${colors.NC}`);
|
||||||
|
console.log(
|
||||||
|
output
|
||||||
|
.split("\n")
|
||||||
|
.map((line) => ` ${line}`)
|
||||||
|
.join("\n"),
|
||||||
|
);
|
||||||
|
FAILED_TESTS++;
|
||||||
|
|
||||||
|
// Stop after any error is encountered
|
||||||
|
console.log(
|
||||||
|
`${colors.YELLOW}Stopping tests due to error. Please validate and fix before continuing.${colors.NC}`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`${colors.RED}Error running test: ${error.message}${colors.NC}`,
|
||||||
|
);
|
||||||
|
FAILED_TESTS++;
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
async function runTests() {
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Basic CLI Mode Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 1: Basic CLI mode with method
|
||||||
|
await runBasicTest(
|
||||||
|
"basic_cli_mode",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 2: CLI mode with non-existent method (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"nonexistent_method",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"nonexistent/method",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 3: CLI mode without method (should fail)
|
||||||
|
await runErrorTest("missing_method", TEST_CMD, ...TEST_ARGS, "--cli");
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Environment Variable Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 4: CLI mode with environment variables
|
||||||
|
await runBasicTest(
|
||||||
|
"env_variables",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"-e",
|
||||||
|
"KEY1=value1",
|
||||||
|
"-e",
|
||||||
|
"KEY2=value2",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 5: CLI mode with invalid environment variable format (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"invalid_env_format",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"-e",
|
||||||
|
"INVALID_FORMAT",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 5b: CLI mode with environment variable containing equals sign in value
|
||||||
|
await runBasicTest(
|
||||||
|
"env_variable_with_equals",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"-e",
|
||||||
|
"API_KEY=abc123=xyz789==",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 5c: CLI mode with environment variable containing base64-encoded value
|
||||||
|
await runBasicTest(
|
||||||
|
"env_variable_with_base64",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"-e",
|
||||||
|
"JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Config File Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 6: Using config file with CLI mode
|
||||||
|
await runBasicTest(
|
||||||
|
"config_file",
|
||||||
|
"--config",
|
||||||
|
path.join(PROJECT_ROOT, "sample-config.json"),
|
||||||
|
"--server",
|
||||||
|
"everything",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 7: Using config file without server name (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"config_without_server",
|
||||||
|
"--config",
|
||||||
|
path.join(PROJECT_ROOT, "sample-config.json"),
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 8: Using server name without config file (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"server_without_config",
|
||||||
|
"--server",
|
||||||
|
"everything",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 9: Using non-existent config file (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"nonexistent_config",
|
||||||
|
"--config",
|
||||||
|
"./nonexistent-config.json",
|
||||||
|
"--server",
|
||||||
|
"everything",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 10: Using invalid config file format (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"invalid_config",
|
||||||
|
"--config",
|
||||||
|
invalidConfigPath,
|
||||||
|
"--server",
|
||||||
|
"everything",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 11: Using config file with non-existent server (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"nonexistent_server",
|
||||||
|
"--config",
|
||||||
|
path.join(PROJECT_ROOT, "sample-config.json"),
|
||||||
|
"--server",
|
||||||
|
"nonexistent",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Tool-Related Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 12: CLI mode with tool call
|
||||||
|
await runBasicTest(
|
||||||
|
"tool_call",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/call",
|
||||||
|
"--tool-name",
|
||||||
|
"echo",
|
||||||
|
"--tool-arg",
|
||||||
|
"message=Hello",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 13: CLI mode with tool call but missing tool name (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"missing_tool_name",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/call",
|
||||||
|
"--tool-arg",
|
||||||
|
"message=Hello",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 14: CLI mode with tool call but invalid tool args format (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"invalid_tool_args",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/call",
|
||||||
|
"--tool-name",
|
||||||
|
"echo",
|
||||||
|
"--tool-arg",
|
||||||
|
"invalid_format",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 15: CLI mode with multiple tool args
|
||||||
|
await runBasicTest(
|
||||||
|
"multiple_tool_args",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/call",
|
||||||
|
"--tool-name",
|
||||||
|
"add",
|
||||||
|
"--tool-arg",
|
||||||
|
"a=1",
|
||||||
|
"b=2",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Resource-Related Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 16: CLI mode with resource read
|
||||||
|
await runBasicTest(
|
||||||
|
"resource_read",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"resources/read",
|
||||||
|
"--uri",
|
||||||
|
"test://static/resource/1",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 17: CLI mode with resource read but missing URI (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"missing_uri",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"resources/read",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Prompt-Related Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 18: CLI mode with prompt get
|
||||||
|
await runBasicTest(
|
||||||
|
"prompt_get",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"prompts/get",
|
||||||
|
"--prompt-name",
|
||||||
|
"simple_prompt",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 19: CLI mode with prompt get and args
|
||||||
|
await runBasicTest(
|
||||||
|
"prompt_get_with_args",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"prompts/get",
|
||||||
|
"--prompt-name",
|
||||||
|
"complex_prompt",
|
||||||
|
"--prompt-args",
|
||||||
|
"temperature=0.7",
|
||||||
|
"style=concise",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 20: CLI mode with prompt get but missing prompt name (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"missing_prompt_name",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"prompts/get",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`\n${colors.YELLOW}=== Running Logging Tests ===${colors.NC}`);
|
||||||
|
|
||||||
|
// Test 21: CLI mode with log level
|
||||||
|
await runBasicTest(
|
||||||
|
"log_level",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"logging/setLevel",
|
||||||
|
"--log-level",
|
||||||
|
"debug",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 22: CLI mode with invalid log level (should fail)
|
||||||
|
await runErrorTest(
|
||||||
|
"invalid_log_level",
|
||||||
|
TEST_CMD,
|
||||||
|
...TEST_ARGS,
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"logging/setLevel",
|
||||||
|
"--log-level",
|
||||||
|
"invalid",
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`\n${colors.YELLOW}=== Running Combined Option Tests ===${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Note about the combined options issue
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}Testing combined options with environment variables and config file.${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 23: CLI mode with config file, environment variables, and tool call
|
||||||
|
await runBasicTest(
|
||||||
|
"combined_options",
|
||||||
|
"--config",
|
||||||
|
path.join(PROJECT_ROOT, "sample-config.json"),
|
||||||
|
"--server",
|
||||||
|
"everything",
|
||||||
|
"-e",
|
||||||
|
"CLI_ENV_VAR=cli_value",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/list",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test 24: CLI mode with all possible options (that make sense together)
|
||||||
|
await runBasicTest(
|
||||||
|
"all_options",
|
||||||
|
"--config",
|
||||||
|
path.join(PROJECT_ROOT, "sample-config.json"),
|
||||||
|
"--server",
|
||||||
|
"everything",
|
||||||
|
"-e",
|
||||||
|
"CLI_ENV_VAR=cli_value",
|
||||||
|
"--cli",
|
||||||
|
"--method",
|
||||||
|
"tools/call",
|
||||||
|
"--tool-name",
|
||||||
|
"echo",
|
||||||
|
"--tool-arg",
|
||||||
|
"message=Hello",
|
||||||
|
"--log-level",
|
||||||
|
"debug",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Print test summary
|
||||||
|
console.log(`\n${colors.YELLOW}=== Test Summary ===${colors.NC}`);
|
||||||
|
console.log(`${colors.GREEN}Passed: ${PASSED_TESTS}${colors.NC}`);
|
||||||
|
console.log(`${colors.RED}Failed: ${FAILED_TESTS}${colors.NC}`);
|
||||||
|
console.log(`${colors.ORANGE}Skipped: ${SKIPPED_TESTS}${colors.NC}`);
|
||||||
|
console.log(`Total: ${TOTAL_TESTS}`);
|
||||||
|
console.log(
|
||||||
|
`${colors.BLUE}Detailed logs saved to: ${OUTPUT_DIR}${colors.NC}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`\n${colors.GREEN}All tests completed!${colors.NC}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run all tests
|
||||||
|
runTests().catch((error) => {
|
||||||
|
console.error(
|
||||||
|
`${colors.RED}Tests failed with error: ${error.message}${colors.NC}`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
29
cli/scripts/make-executable.js
Executable file
29
cli/scripts/make-executable.js
Executable file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Cross-platform script to make a file executable
|
||||||
|
*/
|
||||||
|
import { promises as fs } from "fs";
|
||||||
|
import { platform } from "os";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const TARGET_FILE = path.resolve("build/cli.js");
|
||||||
|
|
||||||
|
async function makeExecutable() {
|
||||||
|
try {
|
||||||
|
// On Unix-like systems (Linux, macOS), use chmod
|
||||||
|
if (platform() !== "win32") {
|
||||||
|
execSync(`chmod +x "${TARGET_FILE}"`);
|
||||||
|
console.log("Made file executable with chmod");
|
||||||
|
} else {
|
||||||
|
// On Windows, no need to make files "executable" in the Unix sense
|
||||||
|
// Just ensure the file exists
|
||||||
|
await fs.access(TARGET_FILE);
|
||||||
|
console.log("File exists and is accessible on Windows");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error making file executable:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
makeExecutable();
|
||||||
114
bin/cli.js → cli/src/cli.ts
Executable file → Normal file
114
bin/cli.js → cli/src/cli.ts
Executable file → Normal file
|
|
@ -1,13 +1,37 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { dirname, resolve } from "path";
|
import { dirname, resolve } from "path";
|
||||||
import { spawnPromise } from "spawn-rx";
|
import { spawnPromise } from "spawn-rx";
|
||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
function handleError(error) {
|
|
||||||
let message;
|
type Args = {
|
||||||
|
command: string;
|
||||||
|
args: string[];
|
||||||
|
envArgs: Record<string, string>;
|
||||||
|
cli: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CliOptions = {
|
||||||
|
e?: Record<string, string>;
|
||||||
|
config?: string;
|
||||||
|
server?: string;
|
||||||
|
cli?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ServerConfig = {
|
||||||
|
command: string;
|
||||||
|
args?: string[];
|
||||||
|
env?: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleError(error: unknown): never {
|
||||||
|
let message: string;
|
||||||
|
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
message = error.message;
|
message = error.message;
|
||||||
} else if (typeof error === "string") {
|
} else if (typeof error === "string") {
|
||||||
|
|
@ -15,39 +39,49 @@ function handleError(error) {
|
||||||
} else {
|
} else {
|
||||||
message = "Unknown error";
|
message = "Unknown error";
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(message);
|
console.error(message);
|
||||||
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
function delay(ms) {
|
|
||||||
|
function delay(ms: number): Promise<void> {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms, true));
|
return new Promise((resolve) => setTimeout(resolve, ms, true));
|
||||||
}
|
}
|
||||||
async function runWebClient(args) {
|
|
||||||
|
async function runWebClient(args: Args): Promise<void> {
|
||||||
const inspectorServerPath = resolve(
|
const inspectorServerPath = resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
"..",
|
"../../",
|
||||||
"server",
|
"server",
|
||||||
"build",
|
"build",
|
||||||
"index.js",
|
"index.js",
|
||||||
);
|
);
|
||||||
|
|
||||||
// Path to the client entry point
|
// Path to the client entry point
|
||||||
const inspectorClientPath = resolve(
|
const inspectorClientPath = resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
"..",
|
"../../",
|
||||||
"client",
|
"client",
|
||||||
"bin",
|
"bin",
|
||||||
"cli.js",
|
"client.js",
|
||||||
);
|
);
|
||||||
const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
|
|
||||||
const SERVER_PORT = process.env.SERVER_PORT ?? "6277";
|
const CLIENT_PORT: string = process.env.CLIENT_PORT ?? "6274";
|
||||||
|
const SERVER_PORT: string = process.env.SERVER_PORT ?? "6277";
|
||||||
|
|
||||||
console.log("Starting MCP inspector...");
|
console.log("Starting MCP inspector...");
|
||||||
|
|
||||||
const abort = new AbortController();
|
const abort = new AbortController();
|
||||||
let cancelled = false;
|
let cancelled: boolean = false;
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
abort.abort();
|
abort.abort();
|
||||||
});
|
});
|
||||||
let server;
|
|
||||||
let serverOk;
|
let server: ReturnType<typeof spawnPromise>;
|
||||||
|
let serverOk: unknown;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
server = spawnPromise(
|
server = spawnPromise(
|
||||||
"node",
|
"node",
|
||||||
|
|
@ -66,9 +100,11 @@ async function runWebClient(args) {
|
||||||
echoOutput: true,
|
echoOutput: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Make sure server started before starting client
|
// Make sure server started before starting client
|
||||||
serverOk = await Promise.race([server, delay(2 * 1000)]);
|
serverOk = await Promise.race([server, delay(2 * 1000)]);
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
||||||
if (serverOk) {
|
if (serverOk) {
|
||||||
try {
|
try {
|
||||||
await spawnPromise("node", [inspectorClientPath], {
|
await spawnPromise("node", [inspectorClientPath], {
|
||||||
|
|
@ -81,15 +117,20 @@ async function runWebClient(args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function runCli(args) {
|
|
||||||
|
async function runCli(args: Args): Promise<void> {
|
||||||
const projectRoot = resolve(__dirname, "..");
|
const projectRoot = resolve(__dirname, "..");
|
||||||
const cliPath = resolve(projectRoot, "cli", "build", "index.js");
|
const cliPath = resolve(projectRoot, "build", "index.js");
|
||||||
|
|
||||||
const abort = new AbortController();
|
const abort = new AbortController();
|
||||||
|
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
|
|
||||||
process.on("SIGINT", () => {
|
process.on("SIGINT", () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
abort.abort();
|
abort.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await spawnPromise("node", [cliPath, args.command, ...args.args], {
|
await spawnPromise("node", [cliPath, args.command, ...args.args], {
|
||||||
env: { ...process.env, ...args.envArgs },
|
env: { ...process.env, ...args.envArgs },
|
||||||
|
|
@ -102,16 +143,20 @@ async function runCli(args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function loadConfigFile(configPath, serverName) {
|
|
||||||
|
function loadConfigFile(configPath: string, serverName: string): ServerConfig {
|
||||||
try {
|
try {
|
||||||
const resolvedConfigPath = path.isAbsolute(configPath)
|
const resolvedConfigPath = path.isAbsolute(configPath)
|
||||||
? configPath
|
? configPath
|
||||||
: path.resolve(process.cwd(), configPath);
|
: path.resolve(process.cwd(), configPath);
|
||||||
|
|
||||||
if (!fs.existsSync(resolvedConfigPath)) {
|
if (!fs.existsSync(resolvedConfigPath)) {
|
||||||
throw new Error(`Config file not found: ${resolvedConfigPath}`);
|
throw new Error(`Config file not found: ${resolvedConfigPath}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
|
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
|
||||||
const parsedConfig = JSON.parse(configContent);
|
const parsedConfig = JSON.parse(configContent);
|
||||||
|
|
||||||
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
|
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
|
||||||
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(
|
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(
|
||||||
", ",
|
", ",
|
||||||
|
|
@ -120,35 +165,48 @@ function loadConfigFile(configPath, serverName) {
|
||||||
`Server '${serverName}' not found in config file. Available servers: ${availableServers}`,
|
`Server '${serverName}' not found in config file. Available servers: ${availableServers}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverConfig = parsedConfig.mcpServers[serverName];
|
const serverConfig = parsedConfig.mcpServers[serverName];
|
||||||
|
|
||||||
return serverConfig;
|
return serverConfig;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err instanceof SyntaxError) {
|
if (err instanceof SyntaxError) {
|
||||||
throw new Error(`Invalid JSON in config file: ${err.message}`);
|
throw new Error(`Invalid JSON in config file: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function parseKeyValuePair(value, previous = {}) {
|
|
||||||
|
function parseKeyValuePair(
|
||||||
|
value: string,
|
||||||
|
previous: Record<string, string> = {},
|
||||||
|
): Record<string, string> {
|
||||||
const parts = value.split("=");
|
const parts = value.split("=");
|
||||||
const key = parts[0];
|
const key = parts[0];
|
||||||
const val = parts.slice(1).join("=");
|
const val = parts.slice(1).join("=");
|
||||||
|
|
||||||
if (val === undefined || val === "") {
|
if (val === undefined || val === "") {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid parameter format: ${value}. Use key=value format.`,
|
`Invalid parameter format: ${value}. Use key=value format.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return { ...previous, [key]: val };
|
|
||||||
|
return { ...previous, [key as string]: val };
|
||||||
}
|
}
|
||||||
function parseArgs() {
|
|
||||||
|
function parseArgs(): Args {
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
const argSeparatorIndex = process.argv.indexOf("--");
|
const argSeparatorIndex = process.argv.indexOf("--");
|
||||||
let preArgs = process.argv;
|
let preArgs = process.argv;
|
||||||
let postArgs = [];
|
let postArgs: string[] = [];
|
||||||
|
|
||||||
if (argSeparatorIndex !== -1) {
|
if (argSeparatorIndex !== -1) {
|
||||||
preArgs = process.argv.slice(0, argSeparatorIndex);
|
preArgs = process.argv.slice(0, argSeparatorIndex);
|
||||||
postArgs = process.argv.slice(argSeparatorIndex + 1);
|
postArgs = process.argv.slice(argSeparatorIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
program
|
program
|
||||||
.name("inspector-bin")
|
.name("inspector-bin")
|
||||||
.allowExcessArguments()
|
.allowExcessArguments()
|
||||||
|
|
@ -162,12 +220,16 @@ function parseArgs() {
|
||||||
.option("--config <path>", "config file path")
|
.option("--config <path>", "config file path")
|
||||||
.option("--server <n>", "server name from config file")
|
.option("--server <n>", "server name from config file")
|
||||||
.option("--cli", "enable CLI mode");
|
.option("--cli", "enable CLI mode");
|
||||||
|
|
||||||
// Parse only the arguments before --
|
// Parse only the arguments before --
|
||||||
program.parse(preArgs);
|
program.parse(preArgs);
|
||||||
const options = program.opts();
|
|
||||||
|
const options = program.opts() as CliOptions;
|
||||||
const remainingArgs = program.args;
|
const remainingArgs = program.args;
|
||||||
|
|
||||||
// Add back any arguments that came after --
|
// Add back any arguments that came after --
|
||||||
const finalArgs = [...remainingArgs, ...postArgs];
|
const finalArgs = [...remainingArgs, ...postArgs];
|
||||||
|
|
||||||
// Validate that config and server are provided together
|
// Validate that config and server are provided together
|
||||||
if (
|
if (
|
||||||
(options.config && !options.server) ||
|
(options.config && !options.server) ||
|
||||||
|
|
@ -177,11 +239,13 @@ function parseArgs() {
|
||||||
"Both --config and --server must be provided together. If you specify one, you must specify the other.",
|
"Both --config and --server must be provided together. If you specify one, you must specify the other.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If config file is specified, load and use the options from the file. We must merge the args
|
// If config file is specified, load and use the options from the file. We must merge the args
|
||||||
// from the command line and the file together, or we will miss the method options (--method,
|
// from the command line and the file together, or we will miss the method options (--method,
|
||||||
// etc.)
|
// etc.)
|
||||||
if (options.config && options.server) {
|
if (options.config && options.server) {
|
||||||
const config = loadConfigFile(options.config, options.server);
|
const config = loadConfigFile(options.config, options.server);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
command: config.command,
|
command: config.command,
|
||||||
args: [...(config.args || []), ...finalArgs],
|
args: [...(config.args || []), ...finalArgs],
|
||||||
|
|
@ -189,9 +253,11 @@ function parseArgs() {
|
||||||
cli: options.cli || false,
|
cli: options.cli || false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise use command line arguments
|
// Otherwise use command line arguments
|
||||||
const command = finalArgs[0] || "";
|
const command = finalArgs[0] || "";
|
||||||
const args = finalArgs.slice(1);
|
const args = finalArgs.slice(1);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
command,
|
command,
|
||||||
args,
|
args,
|
||||||
|
|
@ -199,12 +265,15 @@ function parseArgs() {
|
||||||
cli: options.cli || false,
|
cli: options.cli || false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async function main() {
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const args = parseArgs();
|
const args = parseArgs();
|
||||||
|
|
||||||
if (args.cli) {
|
if (args.cli) {
|
||||||
runCli(args);
|
runCli(args);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -214,4 +283,5 @@ async function main() {
|
||||||
handleError(error);
|
handleError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
33
client/bin/client.js
Executable file
33
client/bin/client.js
Executable file
|
|
@ -0,0 +1,33 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import handler from "serve-handler";
|
||||||
|
import http from "http";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const distPath = join(__dirname, "../dist");
|
||||||
|
|
||||||
|
const server = http.createServer((request, response) => {
|
||||||
|
return handler(request, response, {
|
||||||
|
public: distPath,
|
||||||
|
rewrites: [{ source: "/**", destination: "/index.html" }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const port = process.env.PORT || 6274;
|
||||||
|
server.on("listening", () => {
|
||||||
|
console.log(
|
||||||
|
`🔍 MCP Inspector is up and running at http://127.0.0.1:${port} 🚀`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
server.on("error", (err) => {
|
||||||
|
if (err.message.includes(`EADDRINUSE`)) {
|
||||||
|
console.error(
|
||||||
|
`❌ MCP Inspector PORT IS IN USE at http://127.0.0.1:${port} ❌ `,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.listen(port);
|
||||||
120
client/bin/start.js
Executable file
120
client/bin/start.js
Executable file
|
|
@ -0,0 +1,120 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { resolve, dirname } from "path";
|
||||||
|
import { spawnPromise } from "spawn-rx";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
function delay(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
// Parse command line arguments
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const envVars = {};
|
||||||
|
const mcpServerArgs = [];
|
||||||
|
let command = null;
|
||||||
|
let parsingFlags = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
const arg = args[i];
|
||||||
|
|
||||||
|
if (parsingFlags && arg === "--") {
|
||||||
|
parsingFlags = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsingFlags && arg === "-e" && i + 1 < args.length) {
|
||||||
|
const envVar = args[++i];
|
||||||
|
const equalsIndex = envVar.indexOf("=");
|
||||||
|
|
||||||
|
if (equalsIndex !== -1) {
|
||||||
|
const key = envVar.substring(0, equalsIndex);
|
||||||
|
const value = envVar.substring(equalsIndex + 1);
|
||||||
|
envVars[key] = value;
|
||||||
|
} else {
|
||||||
|
envVars[envVar] = "";
|
||||||
|
}
|
||||||
|
} else if (!command) {
|
||||||
|
command = arg;
|
||||||
|
} else {
|
||||||
|
mcpServerArgs.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const inspectorServerPath = resolve(
|
||||||
|
__dirname,
|
||||||
|
"../..",
|
||||||
|
"server",
|
||||||
|
"build",
|
||||||
|
"index.js",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Path to the client entry point
|
||||||
|
const inspectorClientPath = resolve(
|
||||||
|
__dirname,
|
||||||
|
"../..",
|
||||||
|
"client",
|
||||||
|
"bin",
|
||||||
|
"client.js",
|
||||||
|
);
|
||||||
|
|
||||||
|
const CLIENT_PORT = process.env.CLIENT_PORT ?? "6274";
|
||||||
|
const SERVER_PORT = process.env.SERVER_PORT ?? "6277";
|
||||||
|
|
||||||
|
console.log("Starting MCP inspector...");
|
||||||
|
|
||||||
|
const abort = new AbortController();
|
||||||
|
|
||||||
|
let cancelled = false;
|
||||||
|
process.on("SIGINT", () => {
|
||||||
|
cancelled = true;
|
||||||
|
abort.abort();
|
||||||
|
});
|
||||||
|
let server, serverOk;
|
||||||
|
try {
|
||||||
|
server = spawnPromise(
|
||||||
|
"node",
|
||||||
|
[
|
||||||
|
inspectorServerPath,
|
||||||
|
...(command ? [`--env`, command] : []),
|
||||||
|
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
|
||||||
|
],
|
||||||
|
{
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PORT: SERVER_PORT,
|
||||||
|
MCP_ENV_VARS: JSON.stringify(envVars),
|
||||||
|
},
|
||||||
|
signal: abort.signal,
|
||||||
|
echoOutput: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure server started before starting client
|
||||||
|
serverOk = await Promise.race([server, delay(2 * 1000)]);
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
|
if (serverOk) {
|
||||||
|
try {
|
||||||
|
await spawnPromise("node", [inspectorClientPath], {
|
||||||
|
env: { ...process.env, PORT: CLIENT_PORT },
|
||||||
|
signal: abort.signal,
|
||||||
|
echoOutput: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (!cancelled || process.env.DEBUG) throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then((_) => process.exit(0))
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mcp-inspector-client": "./bin/cli.js"
|
"mcp-inspector-client": "./bin/start.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
"bin",
|
||||||
|
|
|
||||||
2593
package-lock.json
generated
2593
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
|
@ -8,42 +8,36 @@
|
||||||
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
"bugs": "https://github.com/modelcontextprotocol/inspector/issues",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"mcp-inspector": "./bin/cli.js"
|
"mcp-inspector": "./cli/build/cli.js"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"bin",
|
|
||||||
"client/bin",
|
"client/bin",
|
||||||
"client/dist",
|
"client/dist",
|
||||||
"server/build",
|
"server/build",
|
||||||
"cli/bin",
|
|
||||||
"cli/build"
|
"cli/build"
|
||||||
],
|
],
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"client",
|
"client",
|
||||||
"server",
|
"server",
|
||||||
"cli",
|
"cli"
|
||||||
"bin"
|
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"",
|
"build": "npm run build-server && npm run build-client && npm run build-cli",
|
||||||
"dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"",
|
|
||||||
"test": "npm run prettier-check && cd client && npm test",
|
|
||||||
"test-cli": "cd bin && npm run test",
|
|
||||||
"build-bin": "cd bin && npm run build",
|
|
||||||
"build-server": "cd server && npm run build",
|
"build-server": "cd server && npm run build",
|
||||||
"build-client": "cd client && npm run build",
|
"build-client": "cd client && npm run build",
|
||||||
"build-cli": "cd cli && npm run build",
|
"build-cli": "cd cli && npm run build",
|
||||||
"build": "npm run build-bin && npm run build-server && npm run build-client && npm run build-cli",
|
"dev": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev\"",
|
||||||
|
"dev:windows": "concurrently \"cd client && npm run dev\" \"cd server && npm run dev:windows\"",
|
||||||
|
"start": "node client/bin/start.js",
|
||||||
"start-server": "cd server && npm run start",
|
"start-server": "cd server && npm run start",
|
||||||
"start-client": "cd client && npm run preview",
|
"start-client": "cd client && npm run preview",
|
||||||
"start": "node ./bin/cli.js",
|
"test": "npm run prettier-check && cd client && npm test",
|
||||||
"prepare": "npm run build",
|
"test-cli": "cd cli && npm run test",
|
||||||
"prettier-fix": "prettier --write .",
|
"prettier-fix": "prettier --write .",
|
||||||
"prettier-check": "prettier --check .",
|
"prettier-check": "prettier --check .",
|
||||||
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
"publish-all": "npm publish --workspaces --access public && npm publish --access public"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-bin": "^0.9.0",
|
|
||||||
"@modelcontextprotocol/inspector-cli": "^0.9.0",
|
"@modelcontextprotocol/inspector-cli": "^0.9.0",
|
||||||
"@modelcontextprotocol/inspector-client": "^0.9.0",
|
"@modelcontextprotocol/inspector-client": "^0.9.0",
|
||||||
"@modelcontextprotocol/inspector-server": "^0.9.0",
|
"@modelcontextprotocol/inspector-server": "^0.9.0",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue