From 1aaf6419b9741a761993d17c2ec43717e5a6a461 Mon Sep 17 00:00:00 2001 From: KavyapriyaJG Date: Sun, 4 May 2025 01:45:27 +0530 Subject: [PATCH 01/10] feat - incorporated mcp server request configs for cli mode --- cli/src/client/connection.ts | 4 ++- cli/src/index.ts | 48 +++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/cli/src/client/connection.ts b/cli/src/client/connection.ts index 931f803da..919bcb347 100644 --- a/cli/src/client/connection.ts +++ b/cli/src/client/connection.ts @@ -1,6 +1,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { McpResponse } from "./types.js"; +import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js"; export const validLogLevels = [ "trace", @@ -15,9 +16,10 @@ export type LogLevel = (typeof validLogLevels)[number]; export async function connect( client: Client, transport: Transport, + options: RequestOptions, ): Promise { try { - await client.connect(transport); + await client.connect(transport, options); } catch (error) { throw new Error( `Failed to connect to MCP server: ${error instanceof Error ? error.message : String(error)}`, diff --git a/cli/src/index.ts b/cli/src/index.ts index bda1e8f73..a36f37c3e 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -19,6 +19,7 @@ import { } from "./client/index.js"; import { handleError } from "./error-handler.js"; import { createTransport, TransportOptions } from "./transport.js"; +import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js"; type Args = { target: string[]; @@ -29,6 +30,9 @@ type Args = { logLevel?: LogLevel; toolName?: string; toolArg?: Record; + requestTimeout?: number; + resetTimeoutOnProgress?: boolean; + maxTotalTimeout?: number; }; function createTransportOptions(target: string[]): TransportOptions { @@ -67,7 +71,13 @@ async function callMethod(args: Args): Promise { }); try { - await connect(client, transport); + const mcpRequestOptions: RequestOptions = { + timeout: args?.requestTimeout, + resetTimeoutOnProgress: args?.resetTimeoutOnProgress, + maxTotalTimeout: args?.maxTotalTimeout, + }; + + await connect(client, transport, mcpRequestOptions); let result: McpResponse; @@ -151,6 +161,24 @@ function parseKeyValuePair( return { ...previous, [key as string]: val }; } +function parseStringToNumber(value: string): number { + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new Error(`Invalid parameter format: ${value}. Use a number format.`); + } + return parsedValue; +} + +function parseStringToBoolean(value: string): boolean { + if (value === "true") { + return true; + } else if (value === "false") { + return false; + } else { + throw new Error(`Invalid parameter format: ${value}. Use true or false.`); + } +} + function parseArgs(): Args { const program = new Command(); @@ -214,6 +242,24 @@ function parseArgs(): Args { return value as LogLevel; }, + ) + .option( + "--request-timeout ", + "Timeout for requests to the MCP server (ms)", + parseStringToNumber, + 10000, + ) + .option( + "--reset-timeout-on-progress ", + "Reset timeout on progress notifications", + parseStringToBoolean, + true, + ) + .option( + "--max-total-timeout ", + "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", + parseStringToNumber, + 60000, ); // Parse only the arguments before -- From d8a7534d701853c15e1069ff2fede9d3d9b8d50f Mon Sep 17 00:00:00 2001 From: KavyapriyaJG Date: Sun, 15 Jun 2025 23:35:41 +0530 Subject: [PATCH 02/10] feat - Added test scripts for the CLI server configs --- cli/package.json | 3 +- cli/scripts/timeout-tests.js | 580 +++++++++++++++++++++++++++++++++++ 2 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 cli/scripts/timeout-tests.js diff --git a/cli/package.json b/cli/package.json index 8e895a556..91a8e98d2 100644 --- a/cli/package.json +++ b/cli/package.json @@ -17,7 +17,8 @@ "scripts": { "build": "tsc", "postbuild": "node scripts/make-executable.js", - "test": "node scripts/cli-tests.js" + "test": "node scripts/cli-tests.js", + "test:timeouts": "node scripts/timeout-tests.js" }, "devDependencies": {}, "dependencies": { diff --git a/cli/scripts/timeout-tests.js b/cli/scripts/timeout-tests.js new file mode 100644 index 000000000..626d204ef --- /dev/null +++ b/cli/scripts/timeout-tests.js @@ -0,0 +1,580 @@ +#!/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 { 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 Timeout Configuration Tests ===${colors.NC}`, +); +console.log( + `${colors.BLUE}This script tests the MCP Inspector CLI's timeout configuration options:${colors.NC}`, +); +console.log(`${colors.BLUE}- Request timeout (--request-timeout)${colors.NC}`); +console.log( + `${colors.BLUE}- Reset timeout on progress (--reset-timeout-on-progress)${colors.NC}`, +); +console.log( + `${colors.BLUE}- Maximum total timeout (--max-total-timeout)${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-timeout-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}`, + ); + } +}); + +// Helper function to analyze output for timeout behavior verification +function analyzeTimeoutBehavior(output, testName, expectedBehavior) { + const lines = output.split("\n"); + + console.log( + `${colors.BLUE}Analyzing timeout behavior for ${testName}...${colors.NC}`, + ); + + // Extract key timing information + const progressUpdates = lines.filter((line) => + line.includes("Progress update received"), + ); + const timeoutMessages = lines.filter( + (line) => line.includes("timeout") || line.includes("timed out"), + ); + const errorMessages = lines.filter( + (line) => line.includes("Error") || line.includes("Failed"), + ); + + console.log( + `${colors.BLUE}Found ${progressUpdates.length} progress updates${colors.NC}`, + ); + + if (progressUpdates.length > 0) { + console.log( + `${colors.BLUE}First progress update: ${progressUpdates[0]}${colors.NC}`, + ); + if (progressUpdates.length > 1) { + console.log( + `${colors.BLUE}Last progress update: ${progressUpdates[progressUpdates.length - 1]}${colors.NC}`, + ); + } + } + + if (timeoutMessages.length > 0) { + console.log( + `${colors.BLUE}Timeout messages: ${timeoutMessages.join("\n")}${colors.NC}`, + ); + } + + if (errorMessages.length > 0) { + console.log( + `${colors.BLUE}Error messages: ${errorMessages.join("\n")}${colors.NC}`, + ); + } + + switch (expectedBehavior) { + case "should_reset_timeout": + // For tests where resetTimeoutOnProgress is true, we expect: + // 1. Multiple progress updates (more than 2) + // 2. No timeout errors before completion + // 3. Successful completion + return ( + progressUpdates.length >= 3 && + !output.includes("Request timed out") && + !output.includes("Maximum total timeout exceeded") + ); + + case "should_timeout_quickly": + // For tests where resetTimeoutOnProgress is false, we expect: + // 1. Few progress updates (less than 3) + // 2. A timeout error + return ( + progressUpdates.length < 3 && + (output.includes("Request timed out") || output.includes("timed out")) + ); + + case "should_hit_max_timeout": + // For tests where maxTotalTimeout should be hit, we expect: + // 1. Error mentioning maximum total timeout + return output.includes("Maximum total timeout exceeded"); + + default: + return null; // No specific expectation + } +} + +// Function to run a basic test with timeout behavior verification +async function runBasicTest(testName, expectedBehavior, ...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(); + + // For specific timeout behavior tests, validate the behavior + if (expectedBehavior) { + const behaviorCorrect = analyzeTimeoutBehavior( + output, + testName, + expectedBehavior, + ); + + if (behaviorCorrect === true) { + console.log( + `${colors.GREEN}✓ Timeout behavior test passed: ${testName}${colors.NC}`, + ); + PASSED_TESTS++; + resolve(true); + return; + } else if (behaviorCorrect === false) { + console.log( + `${colors.RED}✗ Timeout behavior test failed: ${testName}${colors.NC}`, + ); + console.log( + `${colors.RED}Expected: ${expectedBehavior}${colors.NC}`, + ); + console.log( + `${colors.RED}Output did not match expected behavior${colors.NC}`, + ); + FAILED_TESTS++; + process.exit(1); + } + } + + 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) with timeout behavior verification +async function runErrorTest(testName, expectedBehavior, ...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 specific timeout behavior tests, validate the behavior + if (expectedBehavior) { + const behaviorCorrect = analyzeTimeoutBehavior( + output, + testName, + expectedBehavior, + ); + + if (behaviorCorrect === true) { + console.log( + `${colors.GREEN}✓ Timeout behavior test passed: ${testName}${colors.NC}`, + ); + PASSED_TESTS++; + resolve(true); + return; + } else if (behaviorCorrect === false) { + console.log( + `${colors.RED}✗ Timeout behavior test failed: ${testName}${colors.NC}`, + ); + console.log( + `${colors.RED}Expected: ${expectedBehavior}${colors.NC}`, + ); + console.log( + `${colors.RED}Output did not match expected behavior${colors.NC}`, + ); + FAILED_TESTS++; + process.exit(1); + } + } + + // For error tests, we expect a non-zero exit code + if (code !== 0) { + // Look for specific timeout errors + if ( + testName.includes("timeout") && + (output.includes("timeout") || output.includes("timed out")) + ) { + console.log( + `${colors.GREEN}✓ Timeout error test passed: ${testName}${colors.NC}`, + ); + } else { + 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 Timeout Tests ===${colors.NC}`, + ); + + // Test 1: Default timeout values + await runBasicTest( + "default_timeouts", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + ); + + // Test 2: Custom request timeout + await runBasicTest( + "custom_request_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=5", + "steps=5", + "--request-timeout", + "15000", + ); + + // Test 3: Request timeout too short (should fail) + await runErrorTest( + "short_request_timeout", + "should_timeout_quickly", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=5", + "steps=5", + "--request-timeout", + "100", + ); + + console.log( + `\n${colors.YELLOW}=== Running Progress-Related Timeout Tests ===${colors.NC}`, + ); + + // Test 4: Reset timeout on progress enabled - should complete successfully + await runBasicTest( + "reset_timeout_on_progress", + "should_reset_timeout", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=15", // 15 second operation + "steps=5", // 5 steps = progress every 3 seconds + "--request-timeout", + "2000", // 2 second timeout per interval + "--reset-timeout-on-progress", + "true", + "--max-total-timeout", + "30000", + ); + + // Test 5: Reset timeout on progress disabled - should fail with timeout + await runErrorTest( + "reset_timeout_disabled", + "should_timeout_quickly", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=15", // Same configuration as above + "steps=5", + "--request-timeout", + "2000", + "--reset-timeout-on-progress", + "false", // Only difference is here + "--max-total-timeout", + "30000", + ); + + console.log( + `\n${colors.YELLOW}=== Running Max Total Timeout Tests ===${colors.NC}`, + ); + + // Test 6: Max total timeout exceeded (should fail) + await runErrorTest( + "max_total_timeout_exceeded", + "should_hit_max_timeout", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=10", + "steps=10", + "--request-timeout", + "2000", + "--reset-timeout-on-progress", + "true", + "--max-total-timeout", + "3000", // 3 second total timeout + ); + + console.log( + `\n${colors.YELLOW}=== Running Input Validation Tests ===${colors.NC}`, + ); + + // Test 7: Invalid request timeout value + await runErrorTest( + "invalid_request_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--request-timeout", + "invalid", + ); + + // Test 8: Invalid reset-timeout-on-progress value + await runErrorTest( + "invalid_reset_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--reset-timeout-on-progress", + "not-a-boolean", + ); + + // Test 9: Invalid max total timeout value + await runErrorTest( + "invalid_max_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--max-total-timeout", + "invalid", + ); + + // 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 timeout configuration 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); +}); From cecbc7709f74c479368a89af3d14905bcfe5a4f5 Mon Sep 17 00:00:00 2001 From: KavyapriyaJG Date: Wed, 18 Jun 2025 23:55:02 +0530 Subject: [PATCH 03/10] feat - Moved timeout tests to cli tests --- cli/package.json | 3 +- cli/scripts/cli-tests.js | 367 +++++++++++++++++++++- cli/scripts/timeout-tests.js | 580 ----------------------------------- 3 files changed, 362 insertions(+), 588 deletions(-) delete mode 100644 cli/scripts/timeout-tests.js diff --git a/cli/package.json b/cli/package.json index 5da33b84a..f1ff1c339 100644 --- a/cli/package.json +++ b/cli/package.json @@ -17,8 +17,7 @@ "scripts": { "build": "tsc", "postbuild": "node scripts/make-executable.js", - "test": "node scripts/cli-tests.js", - "test:timeouts": "node scripts/timeout-tests.js" + "test": "node scripts/cli-tests.js" }, "devDependencies": {}, "dependencies": { diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 6e6b56f9c..6db385a3d 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -96,8 +96,85 @@ try { const invalidConfigPath = path.join(TEMP_DIR, "invalid-config.json"); fs.writeFileSync(invalidConfigPath, '{\n "mcpServers": {\n "invalid": {'); +// Helper function to analyze output for timeout behavior verification +function analyzeTimeoutBehavior(output, testName, expectedBehavior) { + const lines = output.split("\n"); + + console.log( + `${colors.BLUE}Analyzing timeout behavior for ${testName}...${colors.NC}`, + ); + + // Extract key timing information + const progressUpdates = lines.filter((line) => + line.includes("Progress update received"), + ); + const timeoutMessages = lines.filter( + (line) => line.includes("timeout") || line.includes("timed out"), + ); + const errorMessages = lines.filter( + (line) => line.includes("Error") || line.includes("Failed"), + ); + + console.log( + `${colors.BLUE}Found ${progressUpdates.length} progress updates${colors.NC}`, + ); + + if (progressUpdates.length > 0) { + console.log( + `${colors.BLUE}First progress update: ${progressUpdates[0]}${colors.NC}`, + ); + if (progressUpdates.length > 1) { + console.log( + `${colors.BLUE}Last progress update: ${progressUpdates[progressUpdates.length - 1]}${colors.NC}`, + ); + } + } + + if (timeoutMessages.length > 0) { + console.log( + `${colors.BLUE}Timeout messages: ${timeoutMessages.join("\n")}${colors.NC}`, + ); + } + + if (errorMessages.length > 0) { + console.log( + `${colors.BLUE}Error messages: ${errorMessages.join("\n")}${colors.NC}`, + ); + } + + switch (expectedBehavior) { + case "should_reset_timeout": + // For tests where resetTimeoutOnProgress is true, we expect: + // 1. Multiple progress updates (more than 2) + // 2. No timeout errors before completion + // 3. Successful completion + return ( + progressUpdates.length >= 3 && + !output.includes("Request timed out") && + !output.includes("Maximum total timeout exceeded") + ); + + case "should_timeout_quickly": + // For tests where resetTimeoutOnProgress is false, we expect: + // 1. Few progress updates (less than 3) + // 2. A timeout error + return ( + progressUpdates.length < 3 && + (output.includes("Request timed out") || output.includes("timed out")) + ); + + case "should_hit_max_timeout": + // For tests where maxTotalTimeout should be hit, we expect: + // 1. Error mentioning maximum total timeout + return output.includes("Maximum total timeout exceeded"); + + default: + return null; // No specific expectation + } +} + // Function to run a basic test -async function runBasicTest(testName, ...args) { +async function runBasicTest(testName, expectedBehavior, ...args) { const outputFile = path.join( OUTPUT_DIR, `${testName.replace(/\//g, "_")}.log`, @@ -105,6 +182,7 @@ async function runBasicTest(testName, ...args) { console.log(`\n${colors.YELLOW}Testing: ${testName}${colors.NC}`); TOTAL_TESTS++; + console.log("basic test kavya " + TOTAL_TESTS); // Run the command and capture output console.log( @@ -137,6 +215,36 @@ async function runBasicTest(testName, ...args) { child.on("close", (code) => { outputStream.end(); + // For specific timeout behavior tests, validate the behavior + if (expectedBehavior) { + const behaviorCorrect = analyzeTimeoutBehavior( + output, + testName, + expectedBehavior, + ); + + if (behaviorCorrect === true) { + console.log( + `${colors.GREEN}✓ Timeout behavior test passed: ${testName}${colors.NC}`, + ); + PASSED_TESTS++; + resolve(true); + return; + } else if (behaviorCorrect === false) { + console.log( + `${colors.RED}✗ Timeout behavior test failed: ${testName}${colors.NC}`, + ); + console.log( + `${colors.RED}Expected: ${expectedBehavior}${colors.NC}`, + ); + console.log( + `${colors.RED}Output did not match expected behavior${colors.NC}`, + ); + FAILED_TESTS++; + process.exit(1); + } + } + if (code === 0) { console.log(`${colors.GREEN}✓ Test passed: ${testName}${colors.NC}`); console.log(`${colors.BLUE}First few lines of output:${colors.NC}`); @@ -177,7 +285,7 @@ async function runBasicTest(testName, ...args) { } // Function to run an error test (expected to fail) -async function runErrorTest(testName, ...args) { +async function runErrorTest(testName, expectedBehavior, ...args) { const outputFile = path.join( OUTPUT_DIR, `${testName.replace(/\//g, "_")}.log`, @@ -185,6 +293,7 @@ async function runErrorTest(testName, ...args) { console.log(`\n${colors.YELLOW}Testing error case: ${testName}${colors.NC}`); TOTAL_TESTS++; + console.log("error test kavya " + TOTAL_TESTS); // Run the command and capture output console.log( @@ -217,11 +326,52 @@ async function runErrorTest(testName, ...args) { child.on("close", (code) => { outputStream.end(); + // For specific timeout behavior tests, validate the behavior + if (expectedBehavior) { + const behaviorCorrect = analyzeTimeoutBehavior( + output, + testName, + expectedBehavior, + ); + + if (behaviorCorrect === true) { + console.log( + `${colors.GREEN}✓ Timeout behavior test passed: ${testName}${colors.NC}`, + ); + PASSED_TESTS++; + resolve(true); + return; + } else if (behaviorCorrect === false) { + console.log( + `${colors.RED}✗ Timeout behavior test failed: ${testName}${colors.NC}`, + ); + console.log( + `${colors.RED}Expected: ${expectedBehavior}${colors.NC}`, + ); + console.log( + `${colors.RED}Output did not match expected behavior${colors.NC}`, + ); + FAILED_TESTS++; + process.exit(1); + } + } + // For error tests, we expect a non-zero exit code if (code !== 0) { - console.log( - `${colors.GREEN}✓ Error test passed: ${testName}${colors.NC}`, - ); + // Look for specific timeout errors + if ( + testName.includes("timeout") && + (output.includes("timeout") || output.includes("timed out")) + ) { + console.log( + `${colors.GREEN}✓ Timeout error test passed: ${testName}${colors.NC}`, + ); + } else { + console.log( + `${colors.GREEN}✓ Error test passed: ${testName}${colors.NC}`, + ); + } + console.log(`${colors.BLUE}Error output (expected):${colors.NC}`); const firstFewLines = output .split("\n") @@ -270,6 +420,7 @@ async function runTests() { // Test 1: Basic CLI mode with method await runBasicTest( "basic_cli_mode", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -280,6 +431,7 @@ async function runTests() { // Test 2: CLI mode with non-existent method (should fail) await runErrorTest( "nonexistent_method", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -288,7 +440,7 @@ async function runTests() { ); // Test 3: CLI mode without method (should fail) - await runErrorTest("missing_method", TEST_CMD, ...TEST_ARGS, "--cli"); + await runErrorTest("missing_method", null, TEST_CMD, ...TEST_ARGS, "--cli"); console.log( `\n${colors.YELLOW}=== Running Environment Variable Tests ===${colors.NC}`, @@ -297,6 +449,7 @@ async function runTests() { // Test 4: CLI mode with environment variables await runBasicTest( "env_variables", + null, TEST_CMD, ...TEST_ARGS, "-e", @@ -311,6 +464,7 @@ async function runTests() { // Test 5: CLI mode with invalid environment variable format (should fail) await runErrorTest( "invalid_env_format", + null, TEST_CMD, ...TEST_ARGS, "-e", @@ -323,6 +477,7 @@ async function runTests() { // Test 5b: CLI mode with environment variable containing equals sign in value await runBasicTest( "env_variable_with_equals", + null, TEST_CMD, ...TEST_ARGS, "-e", @@ -335,6 +490,7 @@ async function runTests() { // Test 5c: CLI mode with environment variable containing base64-encoded value await runBasicTest( "env_variable_with_base64", + null, TEST_CMD, ...TEST_ARGS, "-e", @@ -351,6 +507,7 @@ async function runTests() { // Test 6: Using config file with CLI mode await runBasicTest( "config_file", + null, "--config", path.join(PROJECT_ROOT, "sample-config.json"), "--server", @@ -363,6 +520,7 @@ async function runTests() { // Test 7: Using config file without server name (should fail) await runErrorTest( "config_without_server", + null, "--config", path.join(PROJECT_ROOT, "sample-config.json"), "--cli", @@ -373,6 +531,7 @@ async function runTests() { // Test 8: Using server name without config file (should fail) await runErrorTest( "server_without_config", + null, "--server", "everything", "--cli", @@ -383,6 +542,7 @@ async function runTests() { // Test 9: Using non-existent config file (should fail) await runErrorTest( "nonexistent_config", + null, "--config", "./nonexistent-config.json", "--server", @@ -395,6 +555,7 @@ async function runTests() { // Test 10: Using invalid config file format (should fail) await runErrorTest( "invalid_config", + null, "--config", invalidConfigPath, "--server", @@ -407,6 +568,7 @@ async function runTests() { // Test 11: Using config file with non-existent server (should fail) await runErrorTest( "nonexistent_server", + null, "--config", path.join(PROJECT_ROOT, "sample-config.json"), "--server", @@ -423,6 +585,7 @@ async function runTests() { // Test 12: CLI mode with tool call await runBasicTest( "tool_call", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -437,6 +600,7 @@ async function runTests() { // Test 13: CLI mode with tool call but missing tool name (should fail) await runErrorTest( "missing_tool_name", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -449,6 +613,7 @@ async function runTests() { // Test 14: CLI mode with tool call but invalid tool args format (should fail) await runErrorTest( "invalid_tool_args", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -463,6 +628,7 @@ async function runTests() { // Test 15: CLI mode with multiple tool args await runBasicTest( "multiple_tool_args", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -482,6 +648,7 @@ async function runTests() { // Test 16: CLI mode with resource read await runBasicTest( "resource_read", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -494,6 +661,7 @@ async function runTests() { // Test 17: CLI mode with resource read but missing URI (should fail) await runErrorTest( "missing_uri", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -508,6 +676,7 @@ async function runTests() { // Test 18: CLI mode with prompt get await runBasicTest( "prompt_get", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -520,6 +689,7 @@ async function runTests() { // Test 19: CLI mode with prompt get and args await runBasicTest( "prompt_get_with_args", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -535,6 +705,7 @@ async function runTests() { // Test 20: CLI mode with prompt get but missing prompt name (should fail) await runErrorTest( "missing_prompt_name", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -547,6 +718,7 @@ async function runTests() { // Test 21: CLI mode with log level await runBasicTest( "log_level", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -559,6 +731,7 @@ async function runTests() { // Test 22: CLI mode with invalid log level (should fail) await runErrorTest( "invalid_log_level", + null, TEST_CMD, ...TEST_ARGS, "--cli", @@ -580,6 +753,7 @@ async function runTests() { // Test 23: CLI mode with config file, environment variables, and tool call await runBasicTest( "combined_options", + null, "--config", path.join(PROJECT_ROOT, "sample-config.json"), "--server", @@ -594,6 +768,7 @@ async function runTests() { // Test 24: CLI mode with all possible options (that make sense together) await runBasicTest( "all_options", + null, "--config", path.join(PROJECT_ROOT, "sample-config.json"), "--server", @@ -611,6 +786,186 @@ async function runTests() { "debug", ); + console.log( + `${colors.YELLOW}=== MCP Inspector CLI Timeout Configuration Tests ===${colors.NC}`, + ); + console.log( + `${colors.BLUE}This script tests the MCP Inspector CLI's timeout configuration options:${colors.NC}`, + ); + console.log( + `${colors.BLUE}- Request timeout (--request-timeout)${colors.NC}`, + ); + console.log( + `${colors.BLUE}- Reset timeout on progress (--reset-timeout-on-progress)${colors.NC}`, + ); + console.log( + `${colors.BLUE}- Maximum total timeout (--max-total-timeout)${colors.NC}\n`, + ); + + // Test 25: Default timeout values + await runBasicTest( + "default_timeouts", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + ); + + // Test 26: Custom request timeout + await runBasicTest( + "custom_request_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=5", + "steps=5", + "--request-timeout", + "15000", + ); + + // Test 27: Request timeout too short (should fail) + await runErrorTest( + "short_request_timeout", + "should_timeout_quickly", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=5", + "steps=5", + "--request-timeout", + "100", + ); + + console.log( + `\n${colors.YELLOW}=== Running Progress-Related Timeout Tests ===${colors.NC}`, + ); + + // Test 28: Reset timeout on progress enabled - should complete successfully + // await runBasicTest( + // "reset_timeout_on_progress", + // "should_reset_timeout", + // TEST_CMD, + // ...TEST_ARGS, + // "--cli", + // "--method", + // "tools/call", + // "--tool-name", + // "longRunningOperation", + // "--tool-arg", + // "duration=15", // 15 second operation + // "steps=5", // 5 steps = progress every 3 seconds + // "--request-timeout", + // "2000", // 2 second timeout per interval + // "--reset-timeout-on-progress", + // "true", + // "--max-total-timeout", + // "30000", + // ); + + // Test 29: Reset timeout on progress disabled - should fail with timeout + await runErrorTest( + "reset_timeout_disabled", + "should_timeout_quickly", + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=15", // Same configuration as above + "steps=5", + "--request-timeout", + "2000", + "--reset-timeout-on-progress", + "false", // Only difference is here + "--max-total-timeout", + "30000", + ); + + // console.log( + // `\n${colors.YELLOW}=== Running Max Total Timeout Tests ===${colors.NC}`, + // ); + + // Test 30: Max total timeout exceeded (should fail) + // await runErrorTest( + // "max_total_timeout_exceeded", + // "should_hit_max_timeout", + // TEST_CMD, + // ...TEST_ARGS, + // "--cli", + // "--method", + // "tools/call", + // "--tool-name", + // "longRunningOperation", + // "--tool-arg", + // "duration=10", + // "steps=10", + // "--request-timeout", + // "2000", + // "--reset-timeout-on-progress", + // "true", + // "--max-total-timeout", + // "3000", // 3 second total timeout + // ); + + console.log( + `\n${colors.YELLOW}=== Running Input Validation Tests ===${colors.NC}`, + ); + + // Test 31: Invalid request timeout value + await runErrorTest( + "invalid_request_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--request-timeout", + "invalid", + ); + + // Test 32: Invalid reset-timeout-on-progress value + await runErrorTest( + "invalid_reset_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--reset-timeout-on-progress", + "not-a-boolean", + ); + + // Test 33: Invalid max total timeout value + await runErrorTest( + "invalid_max_timeout", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/list", + "--max-total-timeout", + "invalid", + ); + // Print test summary console.log(`\n${colors.YELLOW}=== Test Summary ===${colors.NC}`); console.log(`${colors.GREEN}Passed: ${PASSED_TESTS}${colors.NC}`); diff --git a/cli/scripts/timeout-tests.js b/cli/scripts/timeout-tests.js deleted file mode 100644 index 626d204ef..000000000 --- a/cli/scripts/timeout-tests.js +++ /dev/null @@ -1,580 +0,0 @@ -#!/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 { 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 Timeout Configuration Tests ===${colors.NC}`, -); -console.log( - `${colors.BLUE}This script tests the MCP Inspector CLI's timeout configuration options:${colors.NC}`, -); -console.log(`${colors.BLUE}- Request timeout (--request-timeout)${colors.NC}`); -console.log( - `${colors.BLUE}- Reset timeout on progress (--reset-timeout-on-progress)${colors.NC}`, -); -console.log( - `${colors.BLUE}- Maximum total timeout (--max-total-timeout)${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-timeout-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}`, - ); - } -}); - -// Helper function to analyze output for timeout behavior verification -function analyzeTimeoutBehavior(output, testName, expectedBehavior) { - const lines = output.split("\n"); - - console.log( - `${colors.BLUE}Analyzing timeout behavior for ${testName}...${colors.NC}`, - ); - - // Extract key timing information - const progressUpdates = lines.filter((line) => - line.includes("Progress update received"), - ); - const timeoutMessages = lines.filter( - (line) => line.includes("timeout") || line.includes("timed out"), - ); - const errorMessages = lines.filter( - (line) => line.includes("Error") || line.includes("Failed"), - ); - - console.log( - `${colors.BLUE}Found ${progressUpdates.length} progress updates${colors.NC}`, - ); - - if (progressUpdates.length > 0) { - console.log( - `${colors.BLUE}First progress update: ${progressUpdates[0]}${colors.NC}`, - ); - if (progressUpdates.length > 1) { - console.log( - `${colors.BLUE}Last progress update: ${progressUpdates[progressUpdates.length - 1]}${colors.NC}`, - ); - } - } - - if (timeoutMessages.length > 0) { - console.log( - `${colors.BLUE}Timeout messages: ${timeoutMessages.join("\n")}${colors.NC}`, - ); - } - - if (errorMessages.length > 0) { - console.log( - `${colors.BLUE}Error messages: ${errorMessages.join("\n")}${colors.NC}`, - ); - } - - switch (expectedBehavior) { - case "should_reset_timeout": - // For tests where resetTimeoutOnProgress is true, we expect: - // 1. Multiple progress updates (more than 2) - // 2. No timeout errors before completion - // 3. Successful completion - return ( - progressUpdates.length >= 3 && - !output.includes("Request timed out") && - !output.includes("Maximum total timeout exceeded") - ); - - case "should_timeout_quickly": - // For tests where resetTimeoutOnProgress is false, we expect: - // 1. Few progress updates (less than 3) - // 2. A timeout error - return ( - progressUpdates.length < 3 && - (output.includes("Request timed out") || output.includes("timed out")) - ); - - case "should_hit_max_timeout": - // For tests where maxTotalTimeout should be hit, we expect: - // 1. Error mentioning maximum total timeout - return output.includes("Maximum total timeout exceeded"); - - default: - return null; // No specific expectation - } -} - -// Function to run a basic test with timeout behavior verification -async function runBasicTest(testName, expectedBehavior, ...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(); - - // For specific timeout behavior tests, validate the behavior - if (expectedBehavior) { - const behaviorCorrect = analyzeTimeoutBehavior( - output, - testName, - expectedBehavior, - ); - - if (behaviorCorrect === true) { - console.log( - `${colors.GREEN}✓ Timeout behavior test passed: ${testName}${colors.NC}`, - ); - PASSED_TESTS++; - resolve(true); - return; - } else if (behaviorCorrect === false) { - console.log( - `${colors.RED}✗ Timeout behavior test failed: ${testName}${colors.NC}`, - ); - console.log( - `${colors.RED}Expected: ${expectedBehavior}${colors.NC}`, - ); - console.log( - `${colors.RED}Output did not match expected behavior${colors.NC}`, - ); - FAILED_TESTS++; - process.exit(1); - } - } - - 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) with timeout behavior verification -async function runErrorTest(testName, expectedBehavior, ...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 specific timeout behavior tests, validate the behavior - if (expectedBehavior) { - const behaviorCorrect = analyzeTimeoutBehavior( - output, - testName, - expectedBehavior, - ); - - if (behaviorCorrect === true) { - console.log( - `${colors.GREEN}✓ Timeout behavior test passed: ${testName}${colors.NC}`, - ); - PASSED_TESTS++; - resolve(true); - return; - } else if (behaviorCorrect === false) { - console.log( - `${colors.RED}✗ Timeout behavior test failed: ${testName}${colors.NC}`, - ); - console.log( - `${colors.RED}Expected: ${expectedBehavior}${colors.NC}`, - ); - console.log( - `${colors.RED}Output did not match expected behavior${colors.NC}`, - ); - FAILED_TESTS++; - process.exit(1); - } - } - - // For error tests, we expect a non-zero exit code - if (code !== 0) { - // Look for specific timeout errors - if ( - testName.includes("timeout") && - (output.includes("timeout") || output.includes("timed out")) - ) { - console.log( - `${colors.GREEN}✓ Timeout error test passed: ${testName}${colors.NC}`, - ); - } else { - 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 Timeout Tests ===${colors.NC}`, - ); - - // Test 1: Default timeout values - await runBasicTest( - "default_timeouts", - null, - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/list", - ); - - // Test 2: Custom request timeout - await runBasicTest( - "custom_request_timeout", - null, - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/call", - "--tool-name", - "longRunningOperation", - "--tool-arg", - "duration=5", - "steps=5", - "--request-timeout", - "15000", - ); - - // Test 3: Request timeout too short (should fail) - await runErrorTest( - "short_request_timeout", - "should_timeout_quickly", - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/call", - "--tool-name", - "longRunningOperation", - "--tool-arg", - "duration=5", - "steps=5", - "--request-timeout", - "100", - ); - - console.log( - `\n${colors.YELLOW}=== Running Progress-Related Timeout Tests ===${colors.NC}`, - ); - - // Test 4: Reset timeout on progress enabled - should complete successfully - await runBasicTest( - "reset_timeout_on_progress", - "should_reset_timeout", - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/call", - "--tool-name", - "longRunningOperation", - "--tool-arg", - "duration=15", // 15 second operation - "steps=5", // 5 steps = progress every 3 seconds - "--request-timeout", - "2000", // 2 second timeout per interval - "--reset-timeout-on-progress", - "true", - "--max-total-timeout", - "30000", - ); - - // Test 5: Reset timeout on progress disabled - should fail with timeout - await runErrorTest( - "reset_timeout_disabled", - "should_timeout_quickly", - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/call", - "--tool-name", - "longRunningOperation", - "--tool-arg", - "duration=15", // Same configuration as above - "steps=5", - "--request-timeout", - "2000", - "--reset-timeout-on-progress", - "false", // Only difference is here - "--max-total-timeout", - "30000", - ); - - console.log( - `\n${colors.YELLOW}=== Running Max Total Timeout Tests ===${colors.NC}`, - ); - - // Test 6: Max total timeout exceeded (should fail) - await runErrorTest( - "max_total_timeout_exceeded", - "should_hit_max_timeout", - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/call", - "--tool-name", - "longRunningOperation", - "--tool-arg", - "duration=10", - "steps=10", - "--request-timeout", - "2000", - "--reset-timeout-on-progress", - "true", - "--max-total-timeout", - "3000", // 3 second total timeout - ); - - console.log( - `\n${colors.YELLOW}=== Running Input Validation Tests ===${colors.NC}`, - ); - - // Test 7: Invalid request timeout value - await runErrorTest( - "invalid_request_timeout", - null, - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/list", - "--request-timeout", - "invalid", - ); - - // Test 8: Invalid reset-timeout-on-progress value - await runErrorTest( - "invalid_reset_timeout", - null, - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/list", - "--reset-timeout-on-progress", - "not-a-boolean", - ); - - // Test 9: Invalid max total timeout value - await runErrorTest( - "invalid_max_timeout", - null, - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/list", - "--max-total-timeout", - "invalid", - ); - - // 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 timeout configuration 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); -}); From cbe58f9c00cffe87c8acc43b564a334af6ab1bd2 Mon Sep 17 00:00:00 2001 From: KavyapriyaJG Date: Sat, 21 Jun 2025 17:36:04 +0530 Subject: [PATCH 04/10] fix - removed test logs --- cli/scripts/cli-tests.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 6db385a3d..1a9bc1fe5 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -182,7 +182,6 @@ async function runBasicTest(testName, expectedBehavior, ...args) { console.log(`\n${colors.YELLOW}Testing: ${testName}${colors.NC}`); TOTAL_TESTS++; - console.log("basic test kavya " + TOTAL_TESTS); // Run the command and capture output console.log( @@ -293,7 +292,6 @@ async function runErrorTest(testName, expectedBehavior, ...args) { console.log(`\n${colors.YELLOW}Testing error case: ${testName}${colors.NC}`); TOTAL_TESTS++; - console.log("error test kavya " + TOTAL_TESTS); // Run the command and capture output console.log( From 2ef0c0f62f3a2fd5db8725f0c9c1c62d0b182b52 Mon Sep 17 00:00:00 2001 From: olaservo Date: Sat, 28 Jun 2025 20:46:18 -0700 Subject: [PATCH 05/10] Merge from main --- .github/workflows/e2e_tests.yml | 8 +- README.md | 5 +- cli/scripts/cli-tests.js | 208 ++++++++++++++++++++----------- cli/src/cli.ts | 2 +- cli/src/index.ts | 47 ++++++- cli/src/transport.ts | 20 ++- client/package.json | 2 +- package-lock.json | 210 ++++++++++++++++---------------- 8 files changed, 317 insertions(+), 185 deletions(-) diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index f40dd5825..2630a4955 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -8,7 +8,8 @@ on: jobs: test: - timeout-minutes: 5 + # Installing Playright dependencies can take quite awhile, and also depends on GitHub CI load. + timeout-minutes: 15 runs-on: ubuntu-latest steps: @@ -44,11 +45,12 @@ jobs: if: steps.cache-playwright.outputs.cache-hit != 'true' - name: Run Playwright tests + id: playwright-tests run: npm run test:e2e - name: Upload Playwright Report and Screenshots uses: actions/upload-artifact@v4 - if: always() + if: steps.playwright-tests.conclusion != 'skipped' with: name: playwright-report path: | @@ -59,7 +61,7 @@ jobs: - name: Publish Playwright Test Summary uses: daun/playwright-report-summary@v3 - if: always() + if: steps.playwright-tests.conclusion != 'skipped' with: create-comment: ${{ github.event.pull_request.head.repo.full_name == github.repository }} report-file: client/results.json diff --git a/README.md b/README.md index 94efe83cd..0c20df147 100644 --- a/README.md +++ b/README.md @@ -299,9 +299,12 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method resources # List available prompts npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list -# Connect to a remote MCP server +# Connect to a remote MCP server (default is SSE transport) npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com +# Connect to a remote MCP server (with Streamable HTTP transport) +npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http + # Call a tool on a remote server npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 1a9bc1fe5..39d4f60a8 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -44,7 +44,17 @@ 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`); +console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}`); +console.log( + `${colors.BLUE}- Transport types (--transport http/sse/stdio)${colors.NC}`, +); +console.log( + `${colors.BLUE}- Transport inference from URL suffixes (/mcp, /sse)${colors.NC}`, +); +console.log( + `${colors.BLUE}- Request timeout configuration options${colors.NC}`, +); +console.log(`\n`); // Get directory paths const SCRIPTS_DIR = __dirname; @@ -52,8 +62,8 @@ 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"]; +const TEST_CMD = "node"; +const TEST_ARGS = ["C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js"]; // Create output directory for test results const OUTPUT_DIR = path.join(SCRIPTS_DIR, "test-output"); @@ -62,9 +72,11 @@ if (!fs.existsSync(OUTPUT_DIR)) { } // Create a temporary directory for test files -const TEMP_DIR = fs.mkdirSync(path.join(os.tmpdir(), "mcp-inspector-tests"), { - recursive: true, -}); +const TEMP_DIR = path.join(os.tmpdir(), "mcp-inspector-tests"); +fs.mkdirSync(TEMP_DIR, { recursive: true }); + +// Track servers for cleanup +let runningServers = []; process.on("exit", () => { try { @@ -74,6 +86,21 @@ process.on("exit", () => { `${colors.RED}Failed to remove temp directory: ${err.message}${colors.NC}`, ); } + + runningServers.forEach((server) => { + try { + process.kill(-server.pid); + } catch (e) {} + }); +}); + +process.on("SIGINT", () => { + runningServers.forEach((server) => { + try { + process.kill(-server.pid); + } catch (e) {} + }); + process.exit(1); }); // Use the existing sample config file @@ -198,6 +225,11 @@ async function runBasicTest(testName, expectedBehavior, ...args) { stdio: ["ignore", "pipe", "pipe"], }); + const timeout = setTimeout(() => { + console.log(`${colors.YELLOW}Test timed out: ${testName}${colors.NC}`); + child.kill(); + }, 10000); + // Pipe stdout and stderr to the output file child.stdout.pipe(outputStream); child.stderr.pipe(outputStream); @@ -212,6 +244,7 @@ async function runBasicTest(testName, expectedBehavior, ...args) { }); child.on("close", (code) => { + clearTimeout(timeout); outputStream.end(); // For specific timeout behavior tests, validate the behavior @@ -308,6 +341,13 @@ async function runErrorTest(testName, expectedBehavior, ...args) { stdio: ["ignore", "pipe", "pipe"], }); + const timeout = setTimeout(() => { + console.log( + `${colors.YELLOW}Error test timed out: ${testName}${colors.NC}`, + ); + child.kill(); + }, 10000); + // Pipe stdout and stderr to the output file child.stdout.pipe(outputStream); child.stderr.pipe(outputStream); @@ -322,6 +362,7 @@ async function runErrorTest(testName, expectedBehavior, ...args) { }); child.on("close", (code) => { + clearTimeout(timeout); outputStream.end(); // For specific timeout behavior tests, validate the behavior @@ -785,7 +826,84 @@ async function runTests() { ); console.log( - `${colors.YELLOW}=== MCP Inspector CLI Timeout Configuration Tests ===${colors.NC}`, + `\n${colors.YELLOW}=== Running HTTP Transport Tests ===${colors.NC}`, + ); + + console.log( + `${colors.BLUE}Starting server-everything in streamableHttp mode.${colors.NC}`, + ); + const httpServer = spawn( + "node", + ["C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js", "streamableHttp"], + { + detached: true, + stdio: "ignore", + }, + ); + runningServers.push(httpServer); + + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // Test 25: HTTP transport inferred from URL ending with /mcp + await runBasicTest( + "http_transport_inferred", + null, + "http://127.0.0.1:3001/mcp", + "--cli", + "--method", + "tools/list", + ); + + // Test 26: HTTP transport with explicit --transport http flag + await runBasicTest( + "http_transport_with_explicit_flag", + null, + "http://127.0.0.1:3001", + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ); + + // Test 27: HTTP transport with suffix and --transport http flag + await runBasicTest( + "http_transport_with_explicit_flag_and_suffix", + null, + "http://127.0.0.1:3001/mcp", + "--transport", + "http", + "--cli", + "--method", + "tools/list", + ); + + // Test 28: SSE transport given to HTTP server (should fail) + await runErrorTest( + "sse_transport_given_to_http_server", + null, + "http://127.0.0.1:3001", + "--transport", + "sse", + "--cli", + "--method", + "tools/list", + ); + + // Kill HTTP server + try { + process.kill(-httpServer.pid); + console.log( + `${colors.BLUE}HTTP server killed, waiting for port to be released...${colors.NC}`, + ); + } catch (e) { + console.log( + `${colors.RED}Error killing HTTP server: ${e.message}${colors.NC}`, + ); + } + + console.log( + `\n${colors.YELLOW}=== MCP Inspector CLI Timeout Configuration Tests ===${colors.NC}`, ); console.log( `${colors.BLUE}This script tests the MCP Inspector CLI's timeout configuration options:${colors.NC}`, @@ -800,7 +918,7 @@ async function runTests() { `${colors.BLUE}- Maximum total timeout (--max-total-timeout)${colors.NC}\n`, ); - // Test 25: Default timeout values + // Test 29: Default timeout values await runBasicTest( "default_timeouts", null, @@ -811,7 +929,7 @@ async function runTests() { "tools/list", ); - // Test 26: Custom request timeout + // Test 30: Custom request timeout await runBasicTest( "custom_request_timeout", null, @@ -829,7 +947,7 @@ async function runTests() { "15000", ); - // Test 27: Request timeout too short (should fail) + // Test 31: Request timeout too short (should fail) await runErrorTest( "short_request_timeout", "should_timeout_quickly", @@ -847,62 +965,14 @@ async function runTests() { "100", ); - console.log( - `\n${colors.YELLOW}=== Running Progress-Related Timeout Tests ===${colors.NC}`, - ); - - // Test 28: Reset timeout on progress enabled - should complete successfully - // await runBasicTest( - // "reset_timeout_on_progress", - // "should_reset_timeout", - // TEST_CMD, - // ...TEST_ARGS, - // "--cli", - // "--method", - // "tools/call", - // "--tool-name", - // "longRunningOperation", - // "--tool-arg", - // "duration=15", // 15 second operation - // "steps=5", // 5 steps = progress every 3 seconds - // "--request-timeout", - // "2000", // 2 second timeout per interval - // "--reset-timeout-on-progress", - // "true", - // "--max-total-timeout", - // "30000", - // ); - - // Test 29: Reset timeout on progress disabled - should fail with timeout - await runErrorTest( - "reset_timeout_disabled", - "should_timeout_quickly", - TEST_CMD, - ...TEST_ARGS, - "--cli", - "--method", - "tools/call", - "--tool-name", - "longRunningOperation", - "--tool-arg", - "duration=15", // Same configuration as above - "steps=5", - "--request-timeout", - "2000", - "--reset-timeout-on-progress", - "false", // Only difference is here - "--max-total-timeout", - "30000", - ); - // console.log( - // `\n${colors.YELLOW}=== Running Max Total Timeout Tests ===${colors.NC}`, + // `\n${colors.YELLOW}=== Running Progress-Related Timeout Tests ===${colors.NC}`, // ); - // Test 30: Max total timeout exceeded (should fail) + // // Test 32: Reset timeout on progress disabled - should fail with timeout // await runErrorTest( - // "max_total_timeout_exceeded", - // "should_hit_max_timeout", + // "reset_timeout_disabled", + // "should_timeout_quickly", // TEST_CMD, // ...TEST_ARGS, // "--cli", @@ -911,21 +981,21 @@ async function runTests() { // "--tool-name", // "longRunningOperation", // "--tool-arg", - // "duration=10", - // "steps=10", + // "duration=15", // Same configuration as above + // "steps=5", // "--request-timeout", // "2000", // "--reset-timeout-on-progress", - // "true", + // "false", // Only difference is here // "--max-total-timeout", - // "3000", // 3 second total timeout + // "30000", // ); console.log( `\n${colors.YELLOW}=== Running Input Validation Tests ===${colors.NC}`, ); - // Test 31: Invalid request timeout value + // Test 33: Invalid request timeout value await runErrorTest( "invalid_request_timeout", null, @@ -938,7 +1008,7 @@ async function runTests() { "invalid", ); - // Test 32: Invalid reset-timeout-on-progress value + // Test 34: Invalid reset-timeout-on-progress value await runErrorTest( "invalid_reset_timeout", null, @@ -951,7 +1021,7 @@ async function runTests() { "not-a-boolean", ); - // Test 33: Invalid max total timeout value + // Test 35: Invalid max total timeout value await runErrorTest( "invalid_max_timeout", null, diff --git a/cli/src/cli.ts b/cli/src/cli.ts index 1f87b5163..9107b3c3b 100644 --- a/cli/src/cli.ts +++ b/cli/src/cli.ts @@ -275,7 +275,7 @@ async function main(): Promise { const args = parseArgs(); if (args.cli) { - runCli(args); + await runCli(args); } else { await runWebClient(args); } diff --git a/cli/src/index.ts b/cli/src/index.ts index a36f37c3e..bf8bab06d 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -33,9 +33,13 @@ type Args = { requestTimeout?: number; resetTimeoutOnProgress?: boolean; maxTotalTimeout?: number; + transport?: "sse" | "stdio" | "http"; }; -function createTransportOptions(target: string[]): TransportOptions { +function createTransportOptions( + target: string[], + transport?: "sse" | "stdio" | "http", +): TransportOptions { if (target.length === 0) { throw new Error( "Target is required. Specify a URL or a command to execute.", @@ -54,8 +58,30 @@ function createTransportOptions(target: string[]): TransportOptions { throw new Error("Arguments cannot be passed to a URL-based MCP server."); } + let transportType: "sse" | "stdio" | "http"; + if (transport) { + if (!isUrl && transport !== "stdio") { + throw new Error("Only stdio transport can be used with local commands."); + } + if (isUrl && transport === "stdio") { + throw new Error("stdio transport cannot be used with URLs."); + } + transportType = transport; + } else if (isUrl) { + const url = new URL(command); + if (url.pathname.endsWith("/mcp")) { + transportType = "http"; + } else if (url.pathname.endsWith("/sse")) { + transportType = "sse"; + } else { + transportType = "sse"; + } + } else { + transportType = "stdio"; + } + return { - transportType: isUrl ? "sse" : "stdio", + transportType, command: isUrl ? undefined : command, args: isUrl ? undefined : commandArgs, url: isUrl ? command : undefined, @@ -63,7 +89,7 @@ function createTransportOptions(target: string[]): TransportOptions { } async function callMethod(args: Args): Promise { - const transportOptions = createTransportOptions(args.target); + const transportOptions = createTransportOptions(args.target, args.transport); const transport = createTransport(transportOptions); const client = new Client({ name: "inspector-cli", @@ -260,6 +286,19 @@ function parseArgs(): Args { "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", parseStringToNumber, 60000, + ) + .option( + "--transport ", + "Transport type (sse, http, or stdio). Auto-detected from URL: /mcp → http, /sse → sse, commands → stdio", + (value: string) => { + const validTransports = ["sse", "http", "stdio"]; + if (!validTransports.includes(value)) { + throw new Error( + `Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`, + ); + } + return value as "sse" | "http" | "stdio"; + }, ); // Parse only the arguments before -- @@ -291,6 +330,8 @@ async function main(): Promise { try { const args = parseArgs(); await callMethod(args); + // Explicitly exit the process to prevent hanging + process.exit(0); } catch (error) { handleError(error); } diff --git a/cli/src/transport.ts b/cli/src/transport.ts index e693f2460..e0d67b4ec 100644 --- a/cli/src/transport.ts +++ b/cli/src/transport.ts @@ -3,11 +3,12 @@ import { getDefaultEnvironment, StdioClientTransport, } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { findActualExecutable } from "spawn-rx"; export type TransportOptions = { - transportType: "sse" | "stdio"; + transportType: "sse" | "stdio" | "http"; command?: string; args?: string[]; url?: string; @@ -15,11 +16,22 @@ export type TransportOptions = { function createSSETransport(options: TransportOptions): Transport { const baseUrl = new URL(options.url ?? ""); - const sseUrl = new URL("/sse", baseUrl); + const sseUrl = baseUrl.pathname.endsWith("/sse") + ? baseUrl + : new URL("/sse", baseUrl); return new SSEClientTransport(sseUrl); } +function createHTTPTransport(options: TransportOptions): Transport { + const baseUrl = new URL(options.url ?? ""); + const mcpUrl = baseUrl.pathname.endsWith("/mcp") + ? baseUrl + : new URL("/mcp", baseUrl); + + return new StreamableHTTPClientTransport(mcpUrl); +} + function createStdioTransport(options: TransportOptions): Transport { let args: string[] = []; @@ -67,6 +79,10 @@ export function createTransport(options: TransportOptions): Transport { return createSSETransport(options); } + if (transportType === "http") { + return createHTTPTransport(options); + } + throw new Error(`Unsupported transport type: ${transportType}`); } catch (error) { throw new Error( diff --git a/client/package.json b/client/package.json index 12438aeb6..3c87560a0 100644 --- a/client/package.json +++ b/client/package.json @@ -40,7 +40,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.4", - "lucide-react": "^0.447.0", + "lucide-react": "^0.523.0", "pkce-challenge": "^4.1.0", "prismjs": "^1.30.0", "react": "^18.3.1", diff --git a/package-lock.json b/package-lock.json index 0e74be500..4bfb7a9db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.4", - "lucide-react": "^0.447.0", + "lucide-react": "^0.523.0", "pkce-challenge": "^4.1.0", "prismjs": "^1.30.0", "react": "^18.3.1", @@ -2100,12 +2100,12 @@ "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.4.tgz", - "integrity": "sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.0" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -2123,16 +2123,16 @@ } }, "node_modules/@radix-ui/react-checkbox": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.2.3.tgz", - "integrity": "sha512-pHVzDYsnaDmBlAuwim45y3soIN8H4R7KbkSVirGhXO+R/kO2OLCe0eucUEbddaTcdMHHdzcIGHtZSMSQlA+apw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz", + "integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" @@ -2153,15 +2153,15 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.4.tgz", - "integrity": "sha512-cv4vSf7HttqXilDnAnvINd53OTl1/bjUYVZrkFnA7nwmY9Ob2POUy0WY0sfqBAe1s5FyKsyceQlqiEGPYNTadg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-slot": "1.2.0" + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2209,22 +2209,22 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.11.tgz", - "integrity": "sha512-yI7S1ipkP5/+99qhSI6nthfo/tR6bL6Zgxi/+1UO6qPa6UeM6nlafWcQ65vB4rU2XjgjMfMhI3k9Y5MztA62VQ==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.14.tgz", + "integrity": "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" @@ -2260,14 +2260,14 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.7.tgz", - "integrity": "sha512-j5+WBUdhccJsmH5/H0K6RncjDtoALSEr6jbkaZu+bjw6hOPOhHycr6vEUujl+HBK8kjUfWcoCJXxP6e4lUlMZw==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.10.tgz", + "integrity": "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, @@ -2302,13 +2302,13 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.4.tgz", - "integrity": "sha512-r2annK27lIW5w9Ho5NyQgqs0MmgZSTIKXWpVCJaLC1q2kZrZkcqnmHkCHMEmv8XLvsLlurKMPT+kbKkRkm/xVA==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { @@ -2354,12 +2354,12 @@ } }, "node_modules/@radix-ui/react-label": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.4.tgz", - "integrity": "sha512-wy3dqizZnZVV4ja0FNnUhIWNwWdoldXrneEyUcVtLYDAt8ovGS4ridtMAOGgXBBIfggL4BOveVWsjXDORdGEQg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz", + "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.0" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -2377,23 +2377,23 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.11.tgz", - "integrity": "sha512-yFMfZkVA5G3GJnBgb2PxrrcLKm1ZLWXrbYVgdyTl//0TYEIHS9LJbnyz7WWcZ0qCq7hIlJZpRtxeSeIG5T5oJw==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz", + "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.4", - "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" @@ -2414,16 +2414,16 @@ } }, "node_modules/@radix-ui/react-popper": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.4.tgz", - "integrity": "sha512-3p2Rgm/a1cK0r/UVkx5F/K9v/EplfjAeIFCGOPYPO4lZ0jtg4iSQXt/YGTSLWaf4x7NG6Z4+uKFcylcTZjeqDA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz", + "integrity": "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.4", + "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", @@ -2446,12 +2446,12 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.6.tgz", - "integrity": "sha512-XmsIl2z1n/TsYFLIdYam2rmFwf9OC/Sh2avkbmVMDuBZIe7hSpM0cYnWPAo7nHOVx8zTuwDZGByfcqLdnzp3Vw==", + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { @@ -2494,12 +2494,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.0.tgz", - "integrity": "sha512-/J/FhLdK0zVcILOwt5g+dH4KnkonCtkVJsa2G6JmvbbtZfBEI1gMsO3QMjseL4F/SwfAMt1Vc/0XKYKq+xJ1sw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.0" + "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2517,18 +2517,18 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.7.tgz", - "integrity": "sha512-C6oAg451/fQT3EGbWHbCQjYTtbyjNO1uzQgMzwyivcHT3GKNEmu1q3UuREhN+HzHAVtv3ivMVK08QlC+PkYw9Q==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.10.tgz", + "integrity": "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-collection": "1.1.4", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, @@ -2548,30 +2548,30 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.2.tgz", - "integrity": "sha512-HjkVHtBkuq+r3zUAZ/CvNWUGKPfuicGDbgtZgiQuFmNcV5F+Tgy24ep2nsAW2nFgvhGPJVqeBZa6KyVN0EyrBA==", + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.5.tgz", + "integrity": "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA==", "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-collection": "1.1.4", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", - "@radix-ui/react-focus-scope": "1.1.4", + "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.4", - "@radix-ui/react-portal": "1.1.6", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.0", + "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, @@ -2591,9 +2591,9 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.0.tgz", - "integrity": "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -2609,9 +2609,9 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.9.tgz", - "integrity": "sha512-KIjtwciYvquiW/wAFkELZCVnaNLBsYNhTNcvl+zfMAbMhRkcvNuCLXDDd22L0j7tagpzVh/QwbFpwAATg7ILPw==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz", + "integrity": "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", @@ -2619,8 +2619,8 @@ "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-roving-focus": "1.1.7", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { @@ -2639,23 +2639,23 @@ } }, "node_modules/@radix-ui/react-toast": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.11.tgz", - "integrity": "sha512-Ed2mlOmT+tktOsu2NZBK1bCSHh/uqULu1vWOkpQTVq53EoOuZUZw7FInQoDB3uil5wZc2oe0XN9a7uVZB7/6AQ==", + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.14.tgz", + "integrity": "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", - "@radix-ui/react-collection": "1.1.4", + "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.7", - "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-dismissable-layer": "1.1.10", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.0", + "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-visually-hidden": "1.2.0" + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2673,23 +2673,23 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.4.tgz", - "integrity": "sha512-DyW8VVeeMSSLFvAmnVnCwvI3H+1tpJFHT50r+tdOoMse9XqYDBCcyux8u3G2y+LOpt7fPQ6KKH0mhs+ce1+Z5w==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.7.tgz", + "integrity": "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.7", + "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.4", - "@radix-ui/react-portal": "1.1.6", + "@radix-ui/react-popper": "1.2.7", + "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", - "@radix-ui/react-primitive": "2.1.0", - "@radix-ui/react-slot": "1.2.0", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-visually-hidden": "1.2.0" + "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", @@ -2843,12 +2843,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.0.tgz", - "integrity": "sha512-rQj0aAWOpCdCMRbI6pLQm8r7S2BM3YhTa0SzOYD55k+hJA8oo9J+H+9wLM9oMlZWOX/wJWPTzfDfmZkf7LvCfg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.0" + "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", @@ -7860,12 +7860,12 @@ } }, "node_modules/lucide-react": { - "version": "0.447.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.447.0.tgz", - "integrity": "sha512-SZ//hQmvi+kDKrNepArVkYK7/jfeZ5uFNEnYmd45RKZcbGD78KLnrcNXmgeg6m+xNHFvTG+CblszXCy4n6DN4w==", + "version": "0.523.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.523.0.tgz", + "integrity": "sha512-rUjQoy7egZT9XYVXBK1je9ckBnNp7qzRZOhLQx5RcEp2dCGlXo+mv6vf7Am4LimEcFBJIIZzSGfgTqc9QCrPSw==", "license": "ISC", "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/lz-string": { From ff846b68d000d8387d18227267097447bda79d3a Mon Sep 17 00:00:00 2001 From: olaservo Date: Sat, 28 Jun 2025 20:49:37 -0700 Subject: [PATCH 06/10] Fix formatting --- cli/scripts/cli-tests.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 39d4f60a8..f714848fc 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -63,7 +63,9 @@ const BUILD_DIR = path.resolve(SCRIPTS_DIR, "../build"); // Define the test server command using npx const TEST_CMD = "node"; -const TEST_ARGS = ["C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js"]; +const TEST_ARGS = [ + "C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js", +]; // Create output directory for test results const OUTPUT_DIR = path.join(SCRIPTS_DIR, "test-output"); @@ -834,7 +836,10 @@ async function runTests() { ); const httpServer = spawn( "node", - ["C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js", "streamableHttp"], + [ + "C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js", + "streamableHttp", + ], { detached: true, stdio: "ignore", From 302257b5d32161520b3cf396fa607008c1cdde92 Mon Sep 17 00:00:00 2001 From: Ola Hungerford Date: Sat, 28 Jun 2025 20:50:58 -0700 Subject: [PATCH 07/10] Update cli-tests.js --- cli/scripts/cli-tests.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 39d4f60a8..6aaeb25a3 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -62,8 +62,8 @@ 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 = "node"; -const TEST_ARGS = ["C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js"]; +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"); @@ -833,8 +833,8 @@ async function runTests() { `${colors.BLUE}Starting server-everything in streamableHttp mode.${colors.NC}`, ); const httpServer = spawn( - "node", - ["C:\\Users\\johnn\\OneDrive\\Documents\\GitHub\\olaservo\\servers\\src\\everything\\dist\\index.js", "streamableHttp"], + "npx", + ["@modelcontextprotocol/server-everything", "streamableHttp"], { detached: true, stdio: "ignore", From 29947ea4def8bd6c206fcdc3ef5f4b6da01e76fd Mon Sep 17 00:00:00 2001 From: KavyapriyaJG Date: Mon, 14 Jul 2025 23:06:51 +0530 Subject: [PATCH 08/10] feat: updated documentation for CLI req timeout configs --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 6a671f1c4..b24dad96e 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,15 @@ npx @modelcontextprotocol/inspector --cli node build/index.js # With config file npx @modelcontextprotocol/inspector --cli --config path/to/config.json --server myserver +# With request timeout configs (in milliseconds) - request timeout +npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name longRunningOperation --tool-arg duration=5 steps=5 --request-timeout 15000 + +# With request timeout configs (in milliseconds) - reset timeout on progress +npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name longRunningOperation --tool-arg duration=15 steps=5 --reset-timeout-on-progress true + +# With request timeout configs (in milliseconds) - max total timeout +npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name longRunningOperation --tool-arg duration=15 steps=5 --max-total-timeout 30000 + # List available tools npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list From c509065bd20b5bb5f3166e9a6f86db510c01a838 Mon Sep 17 00:00:00 2001 From: olaservo Date: Sun, 19 Oct 2025 11:39:14 -0700 Subject: [PATCH 09/10] Fix cli-tests: add missing null expectedBehavior parameter to test calls --- cli/scripts/cli-tests.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index 7130140a1..b6556edbc 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -908,6 +908,7 @@ async function runTests() { // Test 25: Config with stdio transport type await runBasicTest( "config_stdio_type", + null, "--config", stdioConfigPath, "--server", @@ -920,6 +921,7 @@ async function runTests() { // Test 26: Config with SSE transport type (CLI mode) - expects connection error await runErrorTest( "config_sse_type_cli", + null, "--config", sseConfigPath, "--server", @@ -932,6 +934,7 @@ async function runTests() { // Test 27: Config with streamable-http transport type (CLI mode) - expects connection error await runErrorTest( "config_http_type_cli", + null, "--config", httpConfigPath, "--server", @@ -944,6 +947,7 @@ async function runTests() { // Test 28: Legacy config without type field (backward compatibility) await runBasicTest( "config_legacy_no_type", + null, "--config", legacyConfigPath, "--server", @@ -1028,6 +1032,7 @@ async function runTests() { // Test 29: Config with single server auto-selection await runBasicTest( "single_server_auto_select", + null, "--config", singleServerConfigPath, "--cli", @@ -1038,6 +1043,7 @@ async function runTests() { // Test 30: Config with default-server should now require explicit selection (multiple servers) await runErrorTest( "default_server_requires_explicit_selection", + null, "--config", defaultServerConfigPath, "--cli", @@ -1048,6 +1054,7 @@ async function runTests() { // Test 31: Config with multiple servers and no default (should fail) await runErrorTest( "multi_server_no_default", + null, "--config", multiServerConfigPath, "--cli", From d6d90b50465ff509350f9c7e597d5f6c4add94fa Mon Sep 17 00:00:00 2001 From: olaservo Date: Sun, 19 Oct 2025 11:49:04 -0700 Subject: [PATCH 10/10] Address cliffhall's review: update docs and add combined timeout test - Update README example to show --max-total-timeout used with --request-timeout and --reset-timeout-on-progress - Add Test 32: combined_timeout_options that demonstrates all three flags working together - Update test numbers (34-36) to account for new test - Add explanatory comment about progress notification limitation in CLI mode - Fix Windows npx spawn issue by adding shell: true - Adjust test timeouts to work within 10-second test timeout limit - Document that progress-based timeout resets don't work in CLI mode yet --- README.md | 4 ++-- cli/scripts/cli-tests.js | 41 ++++++++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ae4a9b229..7cbf40c38 100644 --- a/README.md +++ b/README.md @@ -419,8 +419,8 @@ npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call # With request timeout configs (in milliseconds) - reset timeout on progress npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name longRunningOperation --tool-arg duration=15 steps=5 --reset-timeout-on-progress true -# With request timeout configs (in milliseconds) - max total timeout -npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name longRunningOperation --tool-arg duration=15 steps=5 --max-total-timeout 30000 +# With request timeout configs (in milliseconds) - max total timeout (used with request timeout and reset timeout on progress) +npx modelcontextprotocol/inspector --cli node build/index.js --method tools/call --tool-name longRunningOperation --tool-arg duration=35 steps=5 --reset-timeout-on-progress true --request-timeout 7000 --max-total-timeout 35000 # List available tools npx @modelcontextprotocol/inspector --cli node build/index.js --method tools/list diff --git a/cli/scripts/cli-tests.js b/cli/scripts/cli-tests.js index b6556edbc..fa6a7439f 100755 --- a/cli/scripts/cli-tests.js +++ b/cli/scripts/cli-tests.js @@ -1075,6 +1075,7 @@ async function runTests() { { detached: true, stdio: "ignore", + shell: true, }, ); runningServers.push(httpServer); @@ -1222,11 +1223,39 @@ async function runTests() { "100", ); + // Test 32: Combined timeout options for long-running operations + // This tests request-timeout + reset-timeout-on-progress + max-total-timeout working together + // Note: We cannot fully validate progress-based timeout resets in CLI mode since progress + // notifications are not yet captured/handled, so the timeout won't actually reset on progress. + // We use a request-timeout long enough for the operation to complete without relying on resets. + await runBasicTest( + "combined_timeout_options", + null, + TEST_CMD, + ...TEST_ARGS, + "--cli", + "--method", + "tools/call", + "--tool-name", + "longRunningOperation", + "--tool-arg", + "duration=2", + "steps=2", + "--request-timeout", + "5000", + "--reset-timeout-on-progress", + "true", + "--max-total-timeout", + "10000", + ); + // console.log( // `\n${colors.YELLOW}=== Running Progress-Related Timeout Tests ===${colors.NC}`, // ); - // // Test 32: Reset timeout on progress disabled - should fail with timeout + // // Test 33: Reset timeout on progress disabled - should fail with timeout + // // This test is commented out because we cannot yet capture progress notifications + // // in CLI mode to validate that timeouts are NOT being reset on progress // await runErrorTest( // "reset_timeout_disabled", // "should_timeout_quickly", @@ -1238,12 +1267,12 @@ async function runTests() { // "--tool-name", // "longRunningOperation", // "--tool-arg", - // "duration=15", // Same configuration as above + // "duration=15", // "steps=5", // "--request-timeout", // "2000", // "--reset-timeout-on-progress", - // "false", // Only difference is here + // "false", // "--max-total-timeout", // "30000", // ); @@ -1252,7 +1281,7 @@ async function runTests() { `\n${colors.YELLOW}=== Running Input Validation Tests ===${colors.NC}`, ); - // Test 33: Invalid request timeout value + // Test 34: Invalid request timeout value await runErrorTest( "invalid_request_timeout", null, @@ -1265,7 +1294,7 @@ async function runTests() { "invalid", ); - // Test 34: Invalid reset-timeout-on-progress value + // Test 35: Invalid reset-timeout-on-progress value await runErrorTest( "invalid_reset_timeout", null, @@ -1278,7 +1307,7 @@ async function runTests() { "not-a-boolean", ); - // Test 35: Invalid max total timeout value + // Test 36: Invalid max total timeout value await runErrorTest( "invalid_max_timeout", null,