Revert "First pass at pulling the v1.5 CLI, TUI, and Launcher clients into v2."

This reverts commit 467b01edd6.
This commit is contained in:
Bob Dickinson 2026-06-09 21:56:38 -07:00
commit 62d442b073
9 changed files with 203 additions and 24043 deletions

View file

@ -1,44 +0,0 @@
# CLI Tests
## Running Tests
```bash
# Run all tests
npm test
# Run in watch mode (useful for test file changes; won't work on CLI source changes without rebuild)
npm run test:watch
# Run specific test file
npm run test:cli # cli.test.ts
npm run test:cli-tools # tools.test.ts
npm run test:cli-headers # headers.test.ts
npm run test:cli-metadata # metadata.test.ts
```
## Test Files
- `cli.test.ts` - Basic CLI functionality: CLI mode, environment variables, config files, resources, prompts, logging, transport types
- `tools.test.ts` - Tool-related tests: Tool discovery, JSON argument parsing, error handling, prompts
- `headers.test.ts` - Header parsing and validation
- `metadata.test.ts` - Metadata functionality: General metadata, tool-specific metadata, parsing, merging, validation
## Helpers
The `helpers/` directory contains shared utilities:
- `cli-runner.ts` - Spawns CLI as subprocess and captures output
- `test-mcp-server.ts` - Standalone stdio MCP server script for stdio transport testing
- `instrumented-server.ts` - In-process MCP test server for HTTP/SSE transports with request recording
- `assertions.ts` - Custom assertion helpers for CLI output validation
- `fixtures.ts` - Test config file generators and temporary directory management
## Notes
- Tests run in parallel across files (Vitest default)
- Tests within a file run sequentially (we have isolated config files and ports, so we could get more aggressive if desired)
- Config files use `crypto.randomUUID()` for uniqueness in parallel execution
- HTTP/SSE servers use dynamic port allocation to avoid conflicts
- Coverage is not used because much of the code that we want to measure is run by a spawned process, so it can't be tracked by Vitest
- /sample-config.json is no longer used by tests - not clear if this file serves some other purpose so leaving it for now
- All tests now use built-in MCP test servers, there are no external dependencies on servers from a registry

View file

@ -1,859 +0,0 @@
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliSuccess,
expectCliFailure,
expectValidJson,
} from "./helpers/assertions.js";
import {
NO_SERVER_SENTINEL,
createSampleTestConfig,
createTestConfig,
createInvalidConfig,
deleteConfigFile,
} from "./helpers/fixtures.js";
import {
getTestMcpServerCommand,
createTestServerHttp,
createEchoTool,
createTestServerInfo,
} from "@modelcontextprotocol/inspector-test-server";
describe("CLI Tests", () => {
describe("Basic CLI Mode", () => {
it("should execute tools/list successfully", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
// Validate expected tools from test-mcp-server
const toolNames = json.tools.map((tool: { name: string }) => tool.name);
expect(toolNames).toContain("echo");
expect(toolNames).toContain("get_sum");
expect(toolNames).toContain("get_annotated_message");
});
it("should fail with nonexistent method", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"--cli",
"--method",
"nonexistent/method",
]);
expectCliFailure(result);
});
it("should fail without method", async () => {
const result = await runCli([NO_SERVER_SENTINEL, "--cli"]);
expectCliFailure(result);
});
});
describe("Environment Variables", () => {
it("should accept environment variables", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"-e",
"KEY1=value1",
"-e",
"KEY2=value2",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
expect(Array.isArray(json.contents)).toBe(true);
expect(json.contents.length).toBeGreaterThan(0);
// Parse the env vars from the resource
const envVars = JSON.parse(json.contents[0].text);
expect(envVars.KEY1).toBe("value1");
expect(envVars.KEY2).toBe("value2");
});
it("should reject invalid environment variable format", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"-e",
"INVALID_FORMAT",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should handle environment variable with equals sign in value", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"-e",
"API_KEY=abc123=xyz789==",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
const envVars = JSON.parse(json.contents[0].text);
expect(envVars.API_KEY).toBe("abc123=xyz789==");
});
it("should handle environment variable with base64-encoded value", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"-e",
"JWT_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
const envVars = JSON.parse(json.contents[0].text);
expect(envVars.JWT_TOKEN).toBe(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=",
);
});
});
describe("Config File", () => {
it("should use config file with CLI mode", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
deleteConfigFile(configPath);
}
});
it("should fail when using config file without server name", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should fail when using server name without config file", async () => {
const result = await runCli([
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should fail with nonexistent config file", async () => {
const result = await runCli([
"--config",
"./nonexistent-config.json",
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should fail with invalid config file format", async () => {
// Create invalid config temporarily
const invalidConfigPath = createInvalidConfig();
try {
const result = await runCli([
"--config",
invalidConfigPath,
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(invalidConfigPath);
}
});
it("should fail with nonexistent server in config", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"nonexistent",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
});
describe("Resource Options", () => {
it("should read resource with URI", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"resources/read",
"--uri",
"demo://resource/static/document/architecture.md",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
expect(Array.isArray(json.contents)).toBe(true);
expect(json.contents.length).toBeGreaterThan(0);
expect(json.contents[0]).toHaveProperty(
"uri",
"demo://resource/static/document/architecture.md",
);
expect(json.contents[0]).toHaveProperty("mimeType", "text/markdown");
expect(json.contents[0]).toHaveProperty("text");
expect(json.contents[0].text).toContain("Architecture Documentation");
});
it("should fail when reading resource without URI", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"resources/read",
]);
expectCliFailure(result);
});
});
describe("Prompt Options", () => {
it("should get prompt by name", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"simple_prompt",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("role", "user");
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
expect(json.messages[0].content.text).toBe(
"This is a simple prompt for testing purposes.",
);
});
it("should get prompt with arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"args_prompt",
"--prompt-args",
"city=New York",
"state=NY",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("role", "user");
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
// Verify that the arguments were actually used in the response
expect(json.messages[0].content.text).toContain("city=New York");
expect(json.messages[0].content.text).toContain("state=NY");
});
it("should fail when getting prompt without name", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
]);
expectCliFailure(result);
});
});
describe("Logging Options", () => {
it("should set log level", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
logging: true,
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"logging/setLevel",
"--log-level",
"debug",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate the response - logging/setLevel should return an empty result
const json = expectValidJson(result);
expect(json).toEqual({});
// Validate that the server actually received and recorded the log level
expect(server.getCurrentLogLevel()).toBe("debug");
} finally {
await server.stop();
}
});
it("should reject invalid log level", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"logging/setLevel",
"--log-level",
"invalid",
]);
expectCliFailure(result);
});
});
describe("Combined Options", () => {
it("should handle config file with environment variables", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"-e",
"CLI_ENV_VAR=cli_value",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
expect(Array.isArray(json.contents)).toBe(true);
expect(json.contents.length).toBeGreaterThan(0);
// Parse the env vars from the resource
const envVars = JSON.parse(json.contents[0].text);
expect(envVars).toHaveProperty("CLI_ENV_VAR");
expect(envVars.CLI_ENV_VAR).toBe("cli_value");
} finally {
deleteConfigFile(configPath);
}
});
it("should handle all options together", async () => {
const configPath = createSampleTestConfig();
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"-e",
"CLI_ENV_VAR=cli_value",
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=Hello",
"--log-level",
"debug",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
expect(json.content[0].text).toBe("Echo: Hello");
} finally {
deleteConfigFile(configPath);
}
});
});
describe("Config Transport Types", () => {
it("should work with stdio transport type", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"test-stdio": {
type: "stdio",
command,
args,
env: {
TEST_ENV: "test-value",
},
},
},
});
try {
// First validate tools/list works
const toolsResult = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(toolsResult);
const toolsJson = expectValidJson(toolsResult);
expect(toolsJson).toHaveProperty("tools");
expect(Array.isArray(toolsJson.tools)).toBe(true);
expect(toolsJson.tools.length).toBeGreaterThan(0);
// Then validate env vars from config are passed to server
const envResult = await runCli([
"--config",
configPath,
"--server",
"test-stdio",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(envResult);
const envJson = expectValidJson(envResult);
const envVars = JSON.parse(envJson.contents[0].text);
expect(envVars).toHaveProperty("TEST_ENV");
expect(envVars.TEST_ENV).toBe("test-value");
} finally {
deleteConfigFile(configPath);
}
});
it("should fail with SSE transport type in CLI mode (connection error)", async () => {
const configPath = createTestConfig({
mcpServers: {
"test-sse": {
type: "sse",
url: "http://localhost:3000/sse",
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-sse",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should fail with HTTP transport type in CLI mode (connection error)", async () => {
const configPath = createTestConfig({
mcpServers: {
"test-http": {
type: "streamable-http",
url: "http://localhost:3001/mcp",
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--server",
"test-http",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should work with legacy config without type field", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"test-legacy": {
command,
args,
env: {
LEGACY_ENV: "legacy-value",
},
},
},
});
try {
// First validate tools/list works
const toolsResult = await runCli([
"--config",
configPath,
"--server",
"test-legacy",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(toolsResult);
const toolsJson = expectValidJson(toolsResult);
expect(toolsJson).toHaveProperty("tools");
expect(Array.isArray(toolsJson.tools)).toBe(true);
expect(toolsJson.tools.length).toBeGreaterThan(0);
// Then validate env vars from config are passed to server
const envResult = await runCli([
"--config",
configPath,
"--server",
"test-legacy",
"--cli",
"--method",
"resources/read",
"--uri",
"test://env",
]);
expectCliSuccess(envResult);
const envJson = expectValidJson(envResult);
const envVars = JSON.parse(envJson.contents[0].text);
expect(envVars).toHaveProperty("LEGACY_ENV");
expect(envVars.LEGACY_ENV).toBe("legacy-value");
} finally {
deleteConfigFile(configPath);
}
});
});
describe("Default Server Selection", () => {
it("should auto-select single server", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"only-server": {
command,
args,
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
deleteConfigFile(configPath);
}
});
it("should require explicit server selection even with default-server key (multiple servers)", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
"default-server": {
command,
args,
},
"other-server": {
command: "node",
args: ["other.js"],
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
it("should require explicit server selection with multiple servers", async () => {
const { command, args } = getTestMcpServerCommand();
const configPath = createTestConfig({
mcpServers: {
server1: {
command,
args,
},
server2: {
command: "node",
args: ["other.js"],
},
},
});
try {
const result = await runCli([
"--config",
configPath,
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
deleteConfigFile(configPath);
}
});
});
describe("HTTP Transport", () => {
it("should infer HTTP transport from URL ending with /mcp", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
await server.stop();
}
});
it("should work with explicit --transport http flag", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--transport",
"http",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
await server.stop();
}
});
it("should work with explicit transport flag and URL suffix", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--transport",
"http",
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
} finally {
await server.stop();
}
});
it("should fail when SSE transport is given to HTTP server", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--transport",
"sse",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
} finally {
await server.stop();
}
});
it("should fail when HTTP transport is specified without URL", async () => {
const result = await runCli([
"--transport",
"http",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
it("should fail when SSE transport is specified without URL", async () => {
const result = await runCli([
"--transport",
"sse",
"--cli",
"--method",
"tools/list",
]);
expectCliFailure(result);
});
});
});

View file

@ -1,202 +0,0 @@
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliFailure,
expectOutputContains,
expectCliSuccess,
} from "./helpers/assertions.js";
import {
createTestServerHttp,
createEchoTool,
createTestServerInfo,
} from "@modelcontextprotocol/inspector-test-server";
describe("Header Parsing and Validation", () => {
describe("Valid Headers", () => {
it("should parse valid single header and send it to server", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"Authorization: Bearer token123",
]);
expectCliSuccess(result);
// Check that the server received the request with the correct headers
const recordedRequests = server.getRecordedRequests();
expect(recordedRequests.length).toBeGreaterThan(0);
// Find the tools/list request (should be the last one)
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest.method).toBe("tools/list");
// Express normalizes headers to lowercase
expect(toolsListRequest.headers).toHaveProperty("authorization");
expect(toolsListRequest.headers?.authorization).toBe("Bearer token123");
} finally {
await server.stop();
}
});
it("should parse multiple headers", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"Authorization: Bearer token123",
"--header",
"X-API-Key: secret123",
]);
expectCliSuccess(result);
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest.method).toBe("tools/list");
expect(toolsListRequest.headers?.authorization).toBe("Bearer token123");
expect(toolsListRequest.headers?.["x-api-key"]).toBe("secret123");
} finally {
await server.stop();
}
});
it("should handle header with colons in value", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"X-Time: 2023:12:25:10:30:45",
]);
expectCliSuccess(result);
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest.method).toBe("tools/list");
expect(toolsListRequest.headers?.["x-time"]).toBe(
"2023:12:25:10:30:45",
);
} finally {
await server.stop();
}
});
it("should handle whitespace in headers", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
" X-Header : value with spaces ",
]);
expectCliSuccess(result);
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests[recordedRequests.length - 1];
expect(toolsListRequest.method).toBe("tools/list");
// Header values should be trimmed by the CLI parser
expect(toolsListRequest.headers?.["x-header"]).toBe(
"value with spaces",
);
} finally {
await server.stop();
}
});
});
describe("Invalid Header Formats", () => {
it("should reject header format without colon", async () => {
const result = await runCli([
"https://example.com",
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"InvalidHeader",
]);
expectCliFailure(result);
expectOutputContains(result, "Invalid header format");
});
it("should reject header format with empty name", async () => {
const result = await runCli([
"https://example.com",
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
": value",
]);
expectCliFailure(result);
expectOutputContains(result, "Invalid header format");
});
it("should reject header format with empty value", async () => {
const result = await runCli([
"https://example.com",
"--cli",
"--method",
"tools/list",
"--transport",
"http",
"--header",
"Header:",
]);
expectCliFailure(result);
expectOutputContains(result, "Invalid header format");
});
});
});

View file

@ -1,42 +0,0 @@
import { describe, it, expect } from "vitest";
import { expectCliSuccess, expectCliFailure } from "./assertions.js";
import type { CliResult } from "./cli-runner.js";
function assertionMessage(fn: () => void): string {
try {
fn();
return "";
} catch (error) {
return error instanceof Error ? error.message : String(error);
}
}
describe("CLI assertion helpers", () => {
it("expectCliSuccess includes stdout and stderr on failure", () => {
const result: CliResult = {
exitCode: 1,
stdout: "tool output",
stderr: "connect error",
output: "tool outputconnect error",
};
const message = assertionMessage(() => expectCliSuccess(result));
expect(message).toContain("CLI exited with code 1");
expect(message).toContain("stdout: tool output");
expect(message).toContain("stderr: connect error");
});
it("expectCliFailure includes stdout and stderr on unexpected success", () => {
const result: CliResult = {
exitCode: 0,
stdout: "ok",
stderr: "",
output: "ok",
};
const message = assertionMessage(() => expectCliFailure(result));
expect(message).toContain("CLI unexpectedly exited with code 0");
expect(message).toContain("stdout: ok");
expect(message).toContain("stderr: (empty)");
});
});

View file

@ -1,64 +0,0 @@
import { expect } from "vitest";
import type { CliResult } from "./cli-runner.js";
function formatCliOutput(result: CliResult): string {
const out = result.stdout?.trim() || "(empty)";
const err = result.stderr?.trim() || "(empty)";
return `stdout: ${out}\nstderr: ${err}`;
}
/**
* Assert that CLI command succeeded (exit code 0)
*/
export function expectCliSuccess(result: CliResult) {
expect(
result.exitCode,
`CLI exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
).toBe(0);
}
/**
* Assert that CLI command failed (non-zero exit code)
*/
export function expectCliFailure(result: CliResult) {
expect(
result.exitCode,
`CLI unexpectedly exited with code ${result.exitCode}. ${formatCliOutput(result)}`,
).not.toBe(0);
}
/**
* Assert that output contains expected text
*/
export function expectOutputContains(result: CliResult, text: string) {
expect(result.output).toContain(text);
}
/**
* Assert that output contains valid JSON
* Uses stdout (not stderr) since JSON is written to stdout and warnings go to stderr
*/
export function expectValidJson(result: CliResult) {
expect(() => JSON.parse(result.stdout)).not.toThrow();
return JSON.parse(result.stdout);
}
/**
* Assert that output contains JSON with error flag
*/
export function expectJsonError(result: CliResult) {
const json = expectValidJson(result);
expect(json.isError).toBe(true);
return json;
}
/**
* Assert that output contains expected JSON structure
*/
export function expectJsonStructure(result: CliResult, expectedKeys: string[]) {
const json = expectValidJson(result);
expectedKeys.forEach((key) => {
expect(json).toHaveProperty(key);
});
return json;
}

View file

@ -1,97 +0,0 @@
import { spawn } from "child_process";
import { resolve } from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
const __dirname = dirname(fileURLToPath(import.meta.url));
const CLI_PATH = resolve(__dirname, "../../build/index.js");
export interface CliResult {
exitCode: number | null;
stdout: string;
stderr: string;
output: string; // Combined stdout + stderr
}
export interface CliOptions {
timeout?: number;
cwd?: string;
env?: Record<string, string>;
signal?: AbortSignal;
}
/**
* Run the CLI with given arguments and capture output
*/
export async function runCli(
args: string[],
options: CliOptions = {},
): Promise<CliResult> {
return new Promise((resolve, reject) => {
const child = spawn("node", [CLI_PATH, ...args], {
stdio: ["pipe", "pipe", "pipe"],
cwd: options.cwd,
env: { ...process.env, ...options.env },
signal: options.signal,
// Own process group on Unix so timeout can kill CLI + stdio grandchildren.
detached: process.platform !== "win32",
});
let stdout = "";
let stderr = "";
let resolved = false;
// Default timeout of 10 seconds (less than vitest's 15s)
const timeoutMs = options.timeout ?? 10000;
const timeout = setTimeout(() => {
if (!resolved) {
resolved = true;
// Kill the process and all its children
try {
if (process.platform !== "win32" && child.pid != null) {
process.kill(-child.pid, "SIGTERM");
} else {
child.kill("SIGTERM");
}
} catch {
// Process might already be dead, try direct kill
try {
child.kill("SIGKILL");
} catch {
// Process is definitely dead
}
}
reject(new Error(`CLI command timed out after ${timeoutMs}ms`));
}
}, timeoutMs);
child.stdout.on("data", (data) => {
stdout += data.toString();
});
child.stderr.on("data", (data) => {
stderr += data.toString();
});
child.on("close", (code) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
resolve({
exitCode: code,
stdout,
stderr,
output: stdout + stderr,
});
}
});
child.on("error", (error) => {
if (!resolved) {
resolved = true;
clearTimeout(timeout);
reject(error);
}
});
});
}

View file

@ -1,90 +0,0 @@
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import * as crypto from "crypto";
import { getTestMcpServerCommand } from "@modelcontextprotocol/inspector-test-server";
import type { MCPServerConfig } from "@modelcontextprotocol/inspector-core/mcp/index.js";
/**
* Sentinel value for tests that don't need a real server
* (tests that expect failure before connecting)
*/
export const NO_SERVER_SENTINEL = "invalid-command-that-does-not-exist";
/**
* Create a sample test config with test-stdio and test-http servers
* Returns a temporary config file path that should be cleaned up with deleteConfigFile()
* @param httpUrl - Optional full URL (including /mcp path) for test-http server.
* If not provided, uses a placeholder URL. The test-http server exists
* to test server selection logic and may not actually be used.
*/
export function createSampleTestConfig(httpUrl?: string): string {
const { command, args } = getTestMcpServerCommand();
return createTestConfig({
mcpServers: {
"test-stdio": {
type: "stdio",
command,
args,
env: {
HELLO: "Hello MCP!",
},
},
"test-http": {
type: "streamable-http",
url: httpUrl || "http://localhost:3001/mcp",
},
},
});
}
/**
* Create a temporary directory for test files
* Uses crypto.randomUUID() to ensure uniqueness even when called in parallel
*/
function createTempDir(prefix: string = "mcp-inspector-test-"): string {
const uniqueId = crypto.randomUUID();
const tempDir = path.join(os.tmpdir(), `${prefix}${uniqueId}`);
fs.mkdirSync(tempDir, { recursive: true });
return tempDir;
}
/**
* Clean up temporary directory
*/
function cleanupTempDir(dir: string) {
try {
fs.rmSync(dir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
}
/**
* Create a test config file
*/
export function createTestConfig(config: {
mcpServers: Record<string, MCPServerConfig>;
}): string {
const tempDir = createTempDir("mcp-inspector-config-");
const configPath = path.join(tempDir, "config.json");
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
return configPath;
}
/**
* Create an invalid config file (malformed JSON)
*/
export function createInvalidConfig(): string {
const tempDir = createTempDir("mcp-inspector-config-");
const configPath = path.join(tempDir, "invalid-config.json");
fs.writeFileSync(configPath, '{\n "mcpServers": {\n "invalid": {');
return configPath;
}
/**
* Delete a config file and its containing directory
*/
export function deleteConfigFile(configPath: string): void {
cleanupTempDir(path.dirname(configPath));
}

View file

@ -1,934 +0,0 @@
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliSuccess,
expectCliFailure,
expectValidJson,
} from "./helpers/assertions.js";
import {
createTestServerHttp,
createEchoTool,
createAddTool,
createTestServerInfo,
} from "@modelcontextprotocol/inspector-test-server";
import { NO_SERVER_SENTINEL } from "./helpers/fixtures.js";
describe("Metadata Tests", () => {
describe("General Metadata", () => {
it("should work with tools/list", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
it("should work with resources/list", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
resources: [
{
uri: "test://resource",
name: "test-resource",
text: "test content",
},
],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"resources/list",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("resources");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const resourcesListRequest = recordedRequests.find(
(r) => r.method === "resources/list",
);
expect(resourcesListRequest).toBeDefined();
expect(resourcesListRequest?.metadata).toEqual({
client: "test-client",
});
} finally {
await server.stop();
}
});
it("should work with prompts/list", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
prompts: [
{
name: "test-prompt",
description: "A test prompt",
promptString: "test prompt",
},
],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"prompts/list",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("prompts");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const promptsListRequest = recordedRequests.find(
(r) => r.method === "prompts/list",
);
expect(promptsListRequest).toBeDefined();
expect(promptsListRequest?.metadata).toEqual({
client: "test-client",
});
} finally {
await server.stop();
}
});
it("should work with resources/read", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
resources: [
{
uri: "test://resource",
name: "test-resource",
text: "test content",
},
],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"resources/read",
"--uri",
"test://resource",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("contents");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const readRequest = recordedRequests.find(
(r) => r.method === "resources/read",
);
expect(readRequest).toBeDefined();
expect(readRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
it("should work with prompts/get", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
prompts: [
{
name: "test-prompt",
description: "A test prompt",
promptString: "test prompt",
},
],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"test-prompt",
"--metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const getPromptRequest = recordedRequests.find(
(r) => r.method === "prompts/get",
);
expect(getPromptRequest).toBeDefined();
expect(getPromptRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
});
describe("Tool-Specific Metadata", () => {
it("should work with tools/call", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello world",
"--tool-metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
it("should work with complex tool", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createAddTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"add",
"--tool-arg",
"a=10",
"b=20",
"--tool-metadata",
"client=test-client",
"--transport",
"http",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
});
describe("Metadata Merging", () => {
it("should merge general and tool-specific metadata (tool-specific overrides)", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello world",
"--metadata",
"client=general-client",
"shared_key=shared_value",
"--tool-metadata",
"client=tool-specific-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was merged correctly (tool-specific overrides general)
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
client: "tool-specific-client", // Tool-specific overrides general
shared_key: "shared_value", // General metadata is preserved
});
} finally {
await server.stop();
}
});
});
describe("Metadata Parsing", () => {
it("should handle numeric values", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"integer_value=42",
"decimal_value=3.14159",
"negative_value=-10",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata values are sent as strings
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
integer_value: "42",
decimal_value: "3.14159",
negative_value: "-10",
});
} finally {
await server.stop();
}
});
it("should handle JSON values", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
'json_object="{\\"key\\":\\"value\\"}"',
'json_array="[1,2,3]"',
'json_string="\\"quoted\\""',
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate JSON values are sent as strings
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
json_object: '{"key":"value"}',
json_array: "[1,2,3]",
json_string: '"quoted"',
});
} finally {
await server.stop();
}
});
it("should handle special characters", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"unicode=🚀🎉✨",
"special_chars=!@#$%^&*()",
"spaces=hello world with spaces",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate special characters are preserved
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
unicode: "🚀🎉✨",
special_chars: "!@#$%^&*()",
spaces: "hello world with spaces",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Edge Cases", () => {
it("should handle single metadata entry", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"single_key=single_value",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate single metadata entry
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
single_key: "single_value",
});
} finally {
await server.stop();
}
});
it("should handle many metadata entries", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"key1=value1",
"key2=value2",
"key3=value3",
"key4=value4",
"key5=value5",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate all metadata entries
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
key1: "value1",
key2: "value2",
key3: "value3",
key4: "value4",
key5: "value5",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Error Cases", () => {
it("should fail with invalid metadata format (missing equals)", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"--cli",
"--method",
"tools/list",
"--metadata",
"invalid_format_no_equals",
]);
expectCliFailure(result);
});
it("should fail with invalid tool-metadata format (missing equals)", async () => {
const result = await runCli([
NO_SERVER_SENTINEL,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=test",
"--tool-metadata",
"invalid_format_no_equals",
]);
expectCliFailure(result);
});
});
describe("Metadata Impact", () => {
it("should handle tool-specific metadata precedence over general", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=precedence test",
"--metadata",
"client=general-client",
"--tool-metadata",
"client=tool-specific-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate tool-specific metadata overrides general
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
client: "tool-specific-client",
});
} finally {
await server.stop();
}
});
it("should work with resources methods", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
resources: [
{
uri: "test://resource",
name: "test-resource",
text: "test content",
},
],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"resources/list",
"--metadata",
"resource_client=test-resource-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const resourcesListRequest = recordedRequests.find(
(r) => r.method === "resources/list",
);
expect(resourcesListRequest).toBeDefined();
expect(resourcesListRequest?.metadata).toEqual({
resource_client: "test-resource-client",
});
} finally {
await server.stop();
}
});
it("should work with prompts methods", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
prompts: [
{
name: "test-prompt",
description: "A test prompt",
promptString: "test prompt",
},
],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"test-prompt",
"--metadata",
"prompt_client=test-prompt-client",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const getPromptRequest = recordedRequests.find(
(r) => r.method === "prompts/get",
);
expect(getPromptRequest).toBeDefined();
expect(getPromptRequest?.metadata).toEqual({
prompt_client: "test-prompt-client",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Validation", () => {
it("should handle special characters in keys", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=special keys test",
"--metadata",
"key-with-dashes=value1",
"key_with_underscores=value2",
"key.with.dots=value3",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate special characters in keys are preserved
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
"key-with-dashes": "value1",
key_with_underscores: "value2",
"key.with.dots": "value3",
});
} finally {
await server.stop();
}
});
});
describe("Metadata Integration", () => {
it("should work with all MCP methods", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"integration_test=true",
"test_phase=all_methods",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({
integration_test: "true",
test_phase: "all_methods",
});
} finally {
await server.stop();
}
});
it("should handle complex metadata scenario", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=complex test",
"--metadata",
"session_id=12345",
"user_id=67890",
"timestamp=2024-01-01T00:00:00Z",
"request_id=req-abc-123",
"--tool-metadata",
"tool_session=session-xyz-789",
"execution_context=test",
"priority=high",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate complex metadata merging
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
session_id: "12345",
user_id: "67890",
timestamp: "2024-01-01T00:00:00Z",
request_id: "req-abc-123",
tool_session: "session-xyz-789",
execution_context: "test",
priority: "high",
});
} finally {
await server.stop();
}
});
it("should handle metadata parsing validation", async () => {
const server = createTestServerHttp({
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=parsing validation test",
"--metadata",
"valid_key=valid_value",
"numeric_key=123",
"boolean_key=true",
'json_key=\'{"test":"value"}\'',
"special_key=!@#$%^&*()",
"unicode_key=🚀🎉✨",
"--transport",
"http",
]);
expectCliSuccess(result);
// Validate all value types are sent as strings
// Note: The CLI parses metadata values, so single-quoted JSON strings
// are preserved with their quotes
const recordedRequests = server.getRecordedRequests();
const toolCallRequest = recordedRequests.find(
(r) => r.method === "tools/call",
);
expect(toolCallRequest).toBeDefined();
expect(toolCallRequest?.metadata).toEqual({
valid_key: "valid_value",
numeric_key: "123",
boolean_key: "true",
json_key: '\'{"test":"value"}\'', // Single quotes are preserved
special_key: "!@#$%^&*()",
unicode_key: "🚀🎉✨",
});
} finally {
await server.stop();
}
});
});
describe("SSE Transport Tests", () => {
it("should work with tools/list using SSE transport", async () => {
const server = createTestServerHttp({
serverType: "sse",
serverInfo: createTestServerInfo(),
tools: [createEchoTool()],
});
try {
await server.start();
const result = await runCli([
server.url,
"--cli",
"--method",
"tools/list",
"--metadata",
"client=test-client",
"--transport",
"sse",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
// Validate metadata was sent
const recordedRequests = server.getRecordedRequests();
const toolsListRequest = recordedRequests.find(
(r) => r.method === "tools/list",
);
expect(toolsListRequest).toBeDefined();
expect(toolsListRequest?.metadata).toEqual({ client: "test-client" });
} finally {
await server.stop();
}
});
});
});

View file

@ -1,527 +0,0 @@
import { describe, it, expect } from "vitest";
import { runCli } from "./helpers/cli-runner.js";
import {
expectCliSuccess,
expectCliFailure,
expectValidJson,
expectJsonError,
} from "./helpers/assertions.js";
import { getTestMcpServerCommand } from "@modelcontextprotocol/inspector-test-server";
describe("Tool Tests", () => {
describe("Tool Discovery", () => {
it("should list available tools", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/list",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("tools");
expect(Array.isArray(json.tools)).toBe(true);
expect(json.tools.length).toBeGreaterThan(0);
// Validate that tools have required properties
expect(json.tools[0]).toHaveProperty("name");
expect(json.tools[0]).toHaveProperty("description");
// Validate expected tools from test-mcp-server
const toolNames = json.tools.map((tool: { name: string }) => tool.name);
expect(toolNames).toContain("echo");
expect(toolNames).toContain("get_sum");
expect(toolNames).toContain("get_annotated_message");
});
});
describe("JSON Argument Parsing", () => {
it("should handle string arguments (backward compatibility)", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello world",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
expect(json.content[0].text).toBe("Echo: hello world");
});
it("should handle integer number arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get_sum",
"--tool-arg",
"a=42",
"b=58",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBe(100);
});
it("should handle decimal number arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get_sum",
"--tool-arg",
"a=19.99",
"b=20.01",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBeCloseTo(40.0, 2);
});
it("should handle boolean arguments - true", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get_annotated_message",
"--tool-arg",
"messageType=success",
"includeImage=true",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
// Should have both text and image content
expect(json.content.length).toBeGreaterThan(1);
const hasImage = json.content.some(
(item: { type?: string }) => item.type === "image",
);
expect(hasImage).toBe(true);
});
it("should handle boolean arguments - false", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get_annotated_message",
"--tool-arg",
"messageType=error",
"includeImage=false",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
// Should only have text content, no image
const hasImage = json.content.some(
(item: { type?: string }) => item.type === "image",
);
expect(hasImage).toBe(false);
// test-mcp-server returns "This is a {messageType} message"
expect(json.content[0].text.toLowerCase()).toContain("error");
});
it("should handle null arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message="null"',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// The string "null" should be passed through
expect(json.content[0].text).toBe("Echo: null");
});
it("should handle multiple arguments with mixed types", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get_sum",
"--tool-arg",
"a=42.5",
"b=57.5",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBeCloseTo(100.0, 1);
});
});
describe("JSON Parsing Edge Cases", () => {
it("should fall back to string for invalid JSON", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message={invalid json}",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Should treat invalid JSON as a string
expect(json.content[0].text).toBe("Echo: {invalid json}");
});
it("should handle empty string value", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message=""',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Empty string should be preserved
expect(json.content[0].text).toBe("Echo: ");
});
it("should handle special characters in strings", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message="C:\\\\Users\\\\test"',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Special characters should be preserved
expect(json.content[0].text).toContain("C:");
expect(json.content[0].text).toContain("Users");
expect(json.content[0].text).toContain("test");
});
it("should handle unicode characters", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
'message="🚀🎉✨"',
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Unicode characters should be preserved
expect(json.content[0].text).toContain("🚀");
expect(json.content[0].text).toContain("🎉");
expect(json.content[0].text).toContain("✨");
});
it("should handle arguments with equals signs in values", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=2+2=4",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Equals signs in values should be preserved
expect(json.content[0].text).toBe("Echo: 2+2=4");
});
it("should handle base64-like strings", async () => {
const { command, args } = getTestMcpServerCommand();
const base64String =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0=";
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
`message=${base64String}`,
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
// Base64-like strings should be preserved
expect(json.content[0].text).toBe(`Echo: ${base64String}`);
});
});
describe("Tool Error Handling", () => {
it("should fail with nonexistent tool", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"nonexistent_tool",
"--tool-arg",
"message=test",
]);
// CLI returns exit code 0 but includes isError: true in JSON (server returns error)
expectJsonError(result);
});
it("should fail when tool name is missing", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-arg",
"message=test",
]);
expectCliFailure(result);
});
it("should fail with invalid tool argument format", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"invalid_format_no_equals",
]);
expectCliFailure(result);
});
});
describe("Prompt JSON Arguments", () => {
it("should handle prompt with JSON arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"args_prompt",
"--prompt-args",
"city=New York",
"state=NY",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
// Validate that the arguments were actually used in the response
// test-mcp-server formats it as "This is a prompt with arguments: city={city}, state={state}"
expect(json.messages[0].content.text).toContain("city=New York");
expect(json.messages[0].content.text).toContain("state=NY");
});
it("should handle prompt with simple arguments", async () => {
// Note: simple_prompt doesn't accept arguments, but the CLI should still
// accept the command and the server should ignore the arguments
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"prompts/get",
"--prompt-name",
"simple_prompt",
"--prompt-args",
"name=test",
"count=5",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("messages");
expect(Array.isArray(json.messages)).toBe(true);
expect(json.messages.length).toBeGreaterThan(0);
expect(json.messages[0]).toHaveProperty("content");
expect(json.messages[0].content).toHaveProperty("type", "text");
// test-mcp-server's simple_prompt returns standard message (ignoring args)
expect(json.messages[0].content.text).toBe(
"This is a simple prompt for testing purposes.",
);
});
});
describe("Backward Compatibility", () => {
it("should support existing string-only usage", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"echo",
"--tool-arg",
"message=hello",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content[0]).toHaveProperty("type", "text");
expect(json.content[0].text).toBe("Echo: hello");
});
it("should support multiple string arguments", async () => {
const { command, args } = getTestMcpServerCommand();
const result = await runCli([
command,
...args,
"--cli",
"--method",
"tools/call",
"--tool-name",
"get_sum",
"--tool-arg",
"a=10",
"b=20",
]);
expectCliSuccess(result);
const json = expectValidJson(result);
expect(json).toHaveProperty("content");
expect(Array.isArray(json.content)).toBe(true);
expect(json.content.length).toBeGreaterThan(0);
expect(json.content[0]).toHaveProperty("type", "text");
// test-mcp-server returns JSON with {result: a+b}
const resultData = JSON.parse(json.content[0].text);
expect(resultData.result).toBe(30);
});
});
});