Add CLI and config file support
This commit is contained in:
211
bin/cli.js
211
bin/cli.js
@@ -1,43 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { resolve, dirname } from "path";
|
||||
import { Command } from "commander";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { dirname, resolve } from "path";
|
||||
import { spawnPromise } from "spawn-rx";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function handleError(error) {
|
||||
let message;
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
} else if (typeof error === "string") {
|
||||
message = error;
|
||||
} else {
|
||||
message = "Unknown error";
|
||||
}
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
function delay(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
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 [key, value] = args[++i].split("=");
|
||||
if (key && value) {
|
||||
envVars[key] = value;
|
||||
}
|
||||
} else if (!command) {
|
||||
command = arg;
|
||||
} else {
|
||||
mcpServerArgs.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
async function runWebClient(args) {
|
||||
const inspectorServerPath = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
@@ -45,7 +29,6 @@ async function main() {
|
||||
"build",
|
||||
"index.js",
|
||||
);
|
||||
|
||||
// Path to the client entry point
|
||||
const inspectorClientPath = resolve(
|
||||
__dirname,
|
||||
@@ -54,63 +37,183 @@ async function main() {
|
||||
"bin",
|
||||
"cli.js",
|
||||
);
|
||||
|
||||
const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173";
|
||||
const SERVER_PORT = process.env.SERVER_PORT ?? "3000";
|
||||
|
||||
console.log("Starting MCP inspector...");
|
||||
|
||||
const abort = new AbortController();
|
||||
|
||||
let cancelled = false;
|
||||
process.on("SIGINT", () => {
|
||||
cancelled = true;
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
const server = spawnPromise(
|
||||
"node",
|
||||
[
|
||||
inspectorServerPath,
|
||||
...(command ? [`--env`, command] : []),
|
||||
...(mcpServerArgs ? [`--args=${mcpServerArgs.join(" ")}`] : []),
|
||||
...(args.command ? [`--env`, args.command] : []),
|
||||
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: SERVER_PORT,
|
||||
MCP_ENV_VARS: JSON.stringify(envVars),
|
||||
MCP_ENV_VARS: JSON.stringify(args.envArgs),
|
||||
},
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
},
|
||||
);
|
||||
|
||||
const client = spawnPromise("node", [inspectorClientPath], {
|
||||
env: { ...process.env, PORT: CLIENT_PORT },
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
});
|
||||
|
||||
// Make sure our server/client didn't immediately fail
|
||||
await Promise.any([server, client, delay(2 * 1000)]);
|
||||
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
|
||||
console.log(
|
||||
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.any([server, client]);
|
||||
} catch (e) {
|
||||
if (!cancelled || process.env.DEBUG) throw e;
|
||||
if (!cancelled || process.env.DEBUG) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
main()
|
||||
.then((_) => process.exit(0))
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
async function runCli(args) {
|
||||
const projectRoot = resolve(__dirname, "..");
|
||||
const cliPath = resolve(projectRoot, "cli", "build", "index.js");
|
||||
const abort = new AbortController();
|
||||
let cancelled = false;
|
||||
process.on("SIGINT", () => {
|
||||
cancelled = true;
|
||||
abort.abort();
|
||||
});
|
||||
try {
|
||||
await spawnPromise("node", [cliPath, args.command, ...args.args], {
|
||||
env: { ...process.env, ...args.envArgs },
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!cancelled || process.env.DEBUG) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
function loadConfigFile(configPath, serverName) {
|
||||
try {
|
||||
const resolvedConfigPath = path.isAbsolute(configPath)
|
||||
? configPath
|
||||
: path.resolve(process.cwd(), configPath);
|
||||
if (!fs.existsSync(resolvedConfigPath)) {
|
||||
throw new Error(`Config file not found: ${resolvedConfigPath}`);
|
||||
}
|
||||
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
|
||||
const parsedConfig = JSON.parse(configContent);
|
||||
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
|
||||
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(
|
||||
", ",
|
||||
);
|
||||
throw new Error(
|
||||
`Server '${serverName}' not found in config file. Available servers: ${availableServers}`,
|
||||
);
|
||||
}
|
||||
const serverConfig = parsedConfig.mcpServers[serverName];
|
||||
return serverConfig;
|
||||
} catch (err) {
|
||||
if (err instanceof SyntaxError) {
|
||||
throw new Error(`Invalid JSON in config file: ${err.message}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
function parseKeyValuePair(value, previous = {}) {
|
||||
const parts = value.split("=");
|
||||
const key = parts[0];
|
||||
const val = parts.slice(1).join("=");
|
||||
if (val === undefined || val === "") {
|
||||
throw new Error(
|
||||
`Invalid parameter format: ${value}. Use key=value format.`,
|
||||
);
|
||||
}
|
||||
return { ...previous, [key]: val };
|
||||
}
|
||||
function parseArgs() {
|
||||
const program = new Command();
|
||||
const argSeparatorIndex = process.argv.indexOf("--");
|
||||
let preArgs = process.argv;
|
||||
let postArgs = [];
|
||||
if (argSeparatorIndex !== -1) {
|
||||
preArgs = process.argv.slice(0, argSeparatorIndex);
|
||||
postArgs = process.argv.slice(argSeparatorIndex + 1);
|
||||
}
|
||||
program
|
||||
.name("inspector-bin")
|
||||
.allowExcessArguments()
|
||||
.allowUnknownOption()
|
||||
.option(
|
||||
"-e <env>",
|
||||
"environment variables in KEY=VALUE format",
|
||||
parseKeyValuePair,
|
||||
{},
|
||||
)
|
||||
.option("--config <path>", "config file path")
|
||||
.option("--server <n>", "server name from config file")
|
||||
.option("--cli", "enable CLI mode");
|
||||
// Parse only the arguments before --
|
||||
program.parse(preArgs);
|
||||
const options = program.opts();
|
||||
const remainingArgs = program.args;
|
||||
// Add back any arguments that came after --
|
||||
const finalArgs = [...remainingArgs, ...postArgs];
|
||||
// Validate that config and server are provided together
|
||||
if (
|
||||
(options.config && !options.server) ||
|
||||
(!options.config && options.server)
|
||||
) {
|
||||
throw new Error(
|
||||
"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
|
||||
// from the command line and the file together, or we will miss the method options (--method,
|
||||
// etc.)
|
||||
if (options.config && options.server) {
|
||||
const config = loadConfigFile(options.config, options.server);
|
||||
return {
|
||||
command: config.command,
|
||||
args: [...(config.args || []), ...finalArgs],
|
||||
envArgs: { ...(config.env || {}), ...(options.e || {}) },
|
||||
cli: options.cli || false,
|
||||
};
|
||||
}
|
||||
// Otherwise use command line arguments
|
||||
const command = finalArgs[0] || "";
|
||||
const args = finalArgs.slice(1);
|
||||
return {
|
||||
command,
|
||||
args,
|
||||
envArgs: options.e || {},
|
||||
cli: options.cli || false,
|
||||
};
|
||||
}
|
||||
async function main() {
|
||||
process.on("uncaughtException", (error) => {
|
||||
handleError(error);
|
||||
});
|
||||
try {
|
||||
const args = parseArgs();
|
||||
console.log(args);
|
||||
if (args.cli) {
|
||||
runCli(args);
|
||||
} else {
|
||||
await runWebClient(args);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
main();
|
||||
|
||||
23
bin/package.json
Normal file
23
bin/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@modelcontextprotocol/inspector-bin",
|
||||
"version": "0.5.1",
|
||||
"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": "chmod +x build/index.js && cp build/index.js cli.js",
|
||||
"test": "./tests/cli-tests.sh"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {}
|
||||
}
|
||||
289
bin/src/index.ts
Normal file
289
bin/src/index.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from "commander";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { dirname, resolve } from "path";
|
||||
import { spawnPromise } from "spawn-rx";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
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) {
|
||||
message = error.message;
|
||||
} else if (typeof error === "string") {
|
||||
message = error;
|
||||
} else {
|
||||
message = "Unknown error";
|
||||
}
|
||||
|
||||
console.error(message);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function runWebClient(args: Args): Promise<void> {
|
||||
const inspectorServerPath = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"server",
|
||||
"build",
|
||||
"index.js",
|
||||
);
|
||||
|
||||
// Path to the client entry point
|
||||
const inspectorClientPath = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"client",
|
||||
"bin",
|
||||
"cli.js",
|
||||
);
|
||||
|
||||
const CLIENT_PORT = process.env.CLIENT_PORT ?? "5173";
|
||||
const SERVER_PORT = process.env.SERVER_PORT ?? "3000";
|
||||
|
||||
console.log("Starting MCP inspector...");
|
||||
|
||||
const abort = new AbortController();
|
||||
|
||||
let cancelled = false;
|
||||
process.on("SIGINT", () => {
|
||||
cancelled = true;
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
const server = spawnPromise(
|
||||
"node",
|
||||
[
|
||||
inspectorServerPath,
|
||||
...(args.command ? [`--env`, args.command] : []),
|
||||
...(args.args ? [`--args=${args.args.join(" ")}`] : []),
|
||||
],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: SERVER_PORT,
|
||||
MCP_ENV_VARS: JSON.stringify(args.envArgs),
|
||||
},
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
},
|
||||
);
|
||||
|
||||
const client = spawnPromise("node", [inspectorClientPath], {
|
||||
env: { ...process.env, PORT: CLIENT_PORT },
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
});
|
||||
|
||||
// Make sure our server/client didn't immediately fail
|
||||
await Promise.any([server, client, delay(2 * 1000)]);
|
||||
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
|
||||
console.log(
|
||||
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
|
||||
);
|
||||
|
||||
try {
|
||||
await Promise.any([server, client]);
|
||||
} catch (e) {
|
||||
if (!cancelled || process.env.DEBUG) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runCli(args: Args): Promise<void> {
|
||||
const projectRoot = resolve(__dirname, "..");
|
||||
const cliPath = resolve(projectRoot, "cli", "build", "index.js");
|
||||
|
||||
const abort = new AbortController();
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
process.on("SIGINT", () => {
|
||||
cancelled = true;
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
try {
|
||||
await spawnPromise("node", [cliPath, args.command, ...args.args], {
|
||||
env: { ...process.env, ...args.envArgs },
|
||||
signal: abort.signal,
|
||||
echoOutput: true,
|
||||
});
|
||||
} catch (e) {
|
||||
if (!cancelled || process.env.DEBUG) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadConfigFile(configPath: string, serverName: string): ServerConfig {
|
||||
try {
|
||||
const resolvedConfigPath = path.isAbsolute(configPath)
|
||||
? configPath
|
||||
: path.resolve(process.cwd(), configPath);
|
||||
|
||||
if (!fs.existsSync(resolvedConfigPath)) {
|
||||
throw new Error(`Config file not found: ${resolvedConfigPath}`);
|
||||
}
|
||||
|
||||
const configContent = fs.readFileSync(resolvedConfigPath, "utf8");
|
||||
const parsedConfig = JSON.parse(configContent);
|
||||
|
||||
if (!parsedConfig.mcpServers || !parsedConfig.mcpServers[serverName]) {
|
||||
const availableServers = Object.keys(parsedConfig.mcpServers || {}).join(
|
||||
", ",
|
||||
);
|
||||
throw new Error(
|
||||
`Server '${serverName}' not found in config file. Available servers: ${availableServers}`,
|
||||
);
|
||||
}
|
||||
|
||||
const serverConfig = parsedConfig.mcpServers[serverName];
|
||||
|
||||
return serverConfig;
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof SyntaxError) {
|
||||
throw new Error(`Invalid JSON in config file: ${err.message}`);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function parseKeyValuePair(
|
||||
value: string,
|
||||
previous: Record<string, string> = {},
|
||||
): Record<string, string> {
|
||||
const parts = value.split("=");
|
||||
const key = parts[0];
|
||||
const val = parts.slice(1).join("=");
|
||||
|
||||
if (val === undefined || val === "") {
|
||||
throw new Error(
|
||||
`Invalid parameter format: ${value}. Use key=value format.`,
|
||||
);
|
||||
}
|
||||
|
||||
return { ...previous, [key as string]: val };
|
||||
}
|
||||
|
||||
function parseArgs(): Args {
|
||||
const program = new Command();
|
||||
|
||||
const argSeparatorIndex = process.argv.indexOf("--");
|
||||
let preArgs = process.argv;
|
||||
let postArgs: string[] = [];
|
||||
|
||||
if (argSeparatorIndex !== -1) {
|
||||
preArgs = process.argv.slice(0, argSeparatorIndex);
|
||||
postArgs = process.argv.slice(argSeparatorIndex + 1);
|
||||
}
|
||||
|
||||
program
|
||||
.name("inspector-bin")
|
||||
.allowExcessArguments()
|
||||
.allowUnknownOption()
|
||||
.option(
|
||||
"-e <env>",
|
||||
"environment variables in KEY=VALUE format",
|
||||
parseKeyValuePair,
|
||||
{},
|
||||
)
|
||||
.option("--config <path>", "config file path")
|
||||
.option("--server <n>", "server name from config file")
|
||||
.option("--cli", "enable CLI mode");
|
||||
|
||||
// Parse only the arguments before --
|
||||
program.parse(preArgs);
|
||||
|
||||
const options = program.opts() as CliOptions;
|
||||
const remainingArgs = program.args;
|
||||
|
||||
// Add back any arguments that came after --
|
||||
const finalArgs = [...remainingArgs, ...postArgs];
|
||||
|
||||
// Validate that config and server are provided together
|
||||
if (
|
||||
(options.config && !options.server) ||
|
||||
(!options.config && options.server)
|
||||
) {
|
||||
throw new Error(
|
||||
"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
|
||||
// from the command line and the file together, or we will miss the method options (--method,
|
||||
// etc.)
|
||||
if (options.config && options.server) {
|
||||
const config = loadConfigFile(options.config, options.server);
|
||||
|
||||
return {
|
||||
command: config.command,
|
||||
args: [...(config.args || []), ...finalArgs],
|
||||
envArgs: { ...(config.env || {}), ...(options.e || {}) },
|
||||
cli: options.cli || false,
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise use command line arguments
|
||||
const command = finalArgs[0] || "";
|
||||
const args = finalArgs.slice(1);
|
||||
|
||||
return {
|
||||
command,
|
||||
args,
|
||||
envArgs: options.e || {},
|
||||
cli: options.cli || false,
|
||||
};
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
process.on("uncaughtException", (error) => {
|
||||
handleError(error);
|
||||
});
|
||||
|
||||
try {
|
||||
const args = parseArgs();
|
||||
|
||||
if (args.cli) {
|
||||
runCli(args);
|
||||
} else {
|
||||
await runWebClient(args);
|
||||
}
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
231
bin/tests/cli-tests.sh
Executable file
231
bin/tests/cli-tests.sh
Executable file
@@ -0,0 +1,231 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
ORANGE='\033[0;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Track test results
|
||||
PASSED_TESTS=0
|
||||
FAILED_TESTS=0
|
||||
SKIPPED_TESTS=0
|
||||
TOTAL_TESTS=0
|
||||
|
||||
echo -e "${YELLOW}=== MCP Inspector CLI Test Script ===${NC}"
|
||||
echo -e "${BLUE}This script tests the MCP Inspector CLI's ability to handle various command line options:${NC}"
|
||||
echo -e "${BLUE}- Basic CLI mode${NC}"
|
||||
echo -e "${BLUE}- Environment variables (-e)${NC}"
|
||||
echo -e "${BLUE}- Config file (--config)${NC}"
|
||||
echo -e "${BLUE}- Server selection (--server)${NC}"
|
||||
echo -e "${BLUE}- Method selection (--method)${NC}"
|
||||
echo -e "${BLUE}- Tool-related options (--tool-name, --tool-args)${NC}"
|
||||
echo -e "${BLUE}- Resource-related options (--uri)${NC}"
|
||||
echo -e "${BLUE}- Prompt-related options (--prompt-name, --prompt-args)${NC}"
|
||||
echo -e "${BLUE}- Logging options (--log-level)${NC}"
|
||||
echo ""
|
||||
|
||||
# Change to the bin directory
|
||||
cd "$(dirname "$0")/.."
|
||||
BIN_DIR="$(pwd)"
|
||||
PROJECT_ROOT="$(dirname "$BIN_DIR")"
|
||||
|
||||
# Compile bin and cli projects
|
||||
echo -e "${YELLOW}Compiling MCP Inspector bin and cli...${NC}"
|
||||
cd "$BIN_DIR"
|
||||
npm run build
|
||||
cd "$PROJECT_ROOT/cli"
|
||||
npm run build
|
||||
cd "$BIN_DIR"
|
||||
|
||||
# Create a symbolic link to handle path resolution
|
||||
echo -e "${YELLOW}Setting up environment for tests...${NC}"
|
||||
PARENT_DIR="$(dirname "$PROJECT_ROOT")"
|
||||
|
||||
# Define the test server command using npx
|
||||
TEST_CMD="npx"
|
||||
TEST_ARGS=("@modelcontextprotocol/server-everything")
|
||||
|
||||
# Create output directory for test results
|
||||
OUTPUT_DIR="$BIN_DIR/tests/output"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# Create a temporary directory for test files
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
trap 'rm -rf "$TEMP_DIR"' EXIT INT TERM
|
||||
|
||||
# Use the existing sample config file
|
||||
echo -e "${BLUE}Using existing sample config file: $PROJECT_ROOT/sample-config.json${NC}"
|
||||
cat "$PROJECT_ROOT/sample-config.json"
|
||||
|
||||
# Create an invalid config file for testing
|
||||
echo '{
|
||||
"mcpServers": {
|
||||
"invalid": {' > "$TEMP_DIR/invalid-config.json"
|
||||
|
||||
# Function to run a basic test
|
||||
run_basic_test() {
|
||||
local test_name=$1
|
||||
local output_file="$OUTPUT_DIR/${test_name//\//_}.log"
|
||||
shift
|
||||
|
||||
echo -e "\n${YELLOW}Testing: ${test_name}${NC}"
|
||||
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||||
|
||||
# Run the command and capture output
|
||||
echo -e "${BLUE}Command: node ${BIN_DIR}/cli.js $*${NC}"
|
||||
node "$BIN_DIR/cli.js" "$@" > "$output_file" 2>&1
|
||||
local exit_code=$?
|
||||
|
||||
# Check if the test passed or failed
|
||||
if [ $exit_code -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Test passed: ${test_name}${NC}"
|
||||
echo -e "${BLUE}First few lines of output:${NC}"
|
||||
head -n 5 "$output_file" | sed 's/^/ /'
|
||||
PASSED_TESTS=$((PASSED_TESTS + 1))
|
||||
else
|
||||
echo -e "${RED}✗ Test failed: ${test_name}${NC}"
|
||||
echo -e "${RED}Error output:${NC}"
|
||||
cat "$output_file" | sed 's/^/ /'
|
||||
FAILED_TESTS=$((FAILED_TESTS + 1))
|
||||
|
||||
# Stop after any error is encountered
|
||||
echo -e "${YELLOW}Stopping tests due to error. Please validate and fix before continuing.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run an error test (expected to fail)
|
||||
run_error_test() {
|
||||
local test_name=$1
|
||||
local output_file="$OUTPUT_DIR/${test_name//\//_}.log"
|
||||
shift
|
||||
|
||||
echo -e "\n${YELLOW}Testing error case: ${test_name}${NC}"
|
||||
TOTAL_TESTS=$((TOTAL_TESTS + 1))
|
||||
|
||||
# Run the command and capture output
|
||||
echo -e "${BLUE}Command: node ${BIN_DIR}/cli.js $*${NC}"
|
||||
node "$BIN_DIR/cli.js" "$@" > "$output_file" 2>&1
|
||||
local exit_code=$?
|
||||
|
||||
# For error tests, we expect a non-zero exit code
|
||||
if [ $exit_code -ne 0 ]; then
|
||||
echo -e "${GREEN}✓ Error test passed: ${test_name}${NC}"
|
||||
echo -e "${BLUE}Error output (expected):${NC}"
|
||||
head -n 5 "$output_file" | sed 's/^/ /'
|
||||
PASSED_TESTS=$((PASSED_TESTS + 1))
|
||||
else
|
||||
echo -e "${RED}✗ Error test failed: ${test_name} (expected error but got success)${NC}"
|
||||
echo -e "${RED}Output:${NC}"
|
||||
cat "$output_file" | sed 's/^/ /'
|
||||
FAILED_TESTS=$((FAILED_TESTS + 1))
|
||||
|
||||
# Stop after any error is encountered
|
||||
echo -e "${YELLOW}Stopping tests due to error. Please validate and fix before continuing.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Basic CLI Mode Tests ===${NC}"
|
||||
|
||||
# Test 1: Basic CLI mode with method
|
||||
run_basic_test "basic_cli_mode" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "tools/list"
|
||||
|
||||
# Test 2: CLI mode with non-existent method (should fail)
|
||||
run_error_test "nonexistent_method" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "nonexistent/method"
|
||||
|
||||
# Test 3: CLI mode without method (should fail)
|
||||
run_error_test "missing_method" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Environment Variable Tests ===${NC}"
|
||||
|
||||
# Test 4: CLI mode with environment variables
|
||||
run_basic_test "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)
|
||||
run_error_test "invalid_env_format" "${TEST_CMD}" "${TEST_ARGS[@]}" "-e" "INVALID_FORMAT" "--cli" "--method" "tools/list"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Config File Tests ===${NC}"
|
||||
|
||||
# Test 6: Using config file with CLI mode
|
||||
run_basic_test "config_file" "--config" "$PROJECT_ROOT/sample-config.json" "--server" "everything" "--cli" "--method" "tools/list"
|
||||
|
||||
# Test 7: Using config file without server name (should fail)
|
||||
run_error_test "config_without_server" "--config" "$PROJECT_ROOT/sample-config.json" "--cli" "--method" "tools/list"
|
||||
|
||||
# Test 8: Using server name without config file (should fail)
|
||||
run_error_test "server_without_config" "--server" "everything" "--cli" "--method" "tools/list"
|
||||
|
||||
# Test 9: Using non-existent config file (should fail)
|
||||
run_error_test "nonexistent_config" "--config" "./nonexistent-config.json" "--server" "everything" "--cli" "--method" "tools/list"
|
||||
|
||||
# Test 10: Using invalid config file format (should fail)
|
||||
run_error_test "invalid_config" "--config" "$TEMP_DIR/invalid-config.json" "--server" "everything" "--cli" "--method" "tools/list"
|
||||
|
||||
# Test 11: Using config file with non-existent server (should fail)
|
||||
run_error_test "nonexistent_server" "--config" "$PROJECT_ROOT/sample-config.json" "--server" "nonexistent" "--cli" "--method" "tools/list"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Tool-Related Tests ===${NC}"
|
||||
|
||||
# Test 12: CLI mode with tool call
|
||||
run_basic_test "tool_call" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "tools/call" "--tool-name" "echo" "--tool-args" "message=Hello"
|
||||
|
||||
# Test 13: CLI mode with tool call but missing tool name (should fail)
|
||||
run_error_test "missing_tool_name" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "tools/call" "--tool-args" "message=Hello"
|
||||
|
||||
# Test 14: CLI mode with tool call but invalid tool args format (should fail)
|
||||
run_error_test "invalid_tool_args" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "tools/call" "--tool-name" "echo" "--tool-args" "invalid_format"
|
||||
|
||||
# Test 15: CLI mode with multiple tool args
|
||||
run_basic_test "multiple_tool_args" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "tools/call" "--tool-name" "add" "--tool-args" "a=1" "b=2"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Resource-Related Tests ===${NC}"
|
||||
|
||||
# Test 16: CLI mode with resource read
|
||||
run_basic_test "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)
|
||||
run_error_test "missing_uri" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "resources/read"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Prompt-Related Tests ===${NC}"
|
||||
|
||||
# Test 18: CLI mode with prompt get
|
||||
run_basic_test "prompt_get" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "prompts/get" "--prompt-name" "simple_prompt"
|
||||
|
||||
# Test 19: CLI mode with prompt get and args
|
||||
run_basic_test "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)
|
||||
run_error_test "missing_prompt_name" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "prompts/get"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Logging Tests ===${NC}"
|
||||
|
||||
# Test 21: CLI mode with log level
|
||||
run_basic_test "log_level" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "logging/setLevel" "--log-level" "debug"
|
||||
|
||||
# Test 22: CLI mode with invalid log level (should fail)
|
||||
run_error_test "invalid_log_level" "${TEST_CMD}" "${TEST_ARGS[@]}" "--cli" "--method" "logging/setLevel" "--log-level" "invalid"
|
||||
|
||||
echo -e "\n${YELLOW}=== Running Combined Option Tests ===${NC}"
|
||||
|
||||
# Note about the combined options issue
|
||||
echo -e "${BLUE}Testing combined options with environment variables and config file.${NC}"
|
||||
|
||||
# Test 23: CLI mode with config file, environment variables, and tool call
|
||||
run_basic_test "combined_options" "--config" "$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)
|
||||
run_basic_test "all_options" "--config" "$PROJECT_ROOT/sample-config.json" "--server" "everything" "-e" "CLI_ENV_VAR=cli_value" "--cli" "--method" "tools/call" "--tool-name" "echo" "--tool-args" "message=Hello" "--log-level" "debug"
|
||||
|
||||
# Print test summary
|
||||
echo -e "\n${YELLOW}=== Test Summary ===${NC}"
|
||||
echo -e "${GREEN}Passed: $PASSED_TESTS${NC}"
|
||||
echo -e "${RED}Failed: $FAILED_TESTS${NC}"
|
||||
echo -e "${ORANGE}Skipped: $SKIPPED_TESTS${NC}"
|
||||
echo -e "Total: $TOTAL_TESTS"
|
||||
echo -e "${BLUE}Detailed logs saved to: $OUTPUT_DIR${NC}"
|
||||
|
||||
echo -e "\n${GREEN}All tests completed!${NC}"
|
||||
16
bin/tsconfig.json
Normal file
16
bin/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
Reference in New Issue
Block a user