Ports the CLI, TUI, and launcher from v1.5 into v2 behind a single `mcp-inspector` entrypoint (launcher → web/cli/tui). Includes the `mcp-inspector --web --dev` launcher fix (Vite alias resolution + prod-server double-start), the root `postinstall` install cascade with simplified CI, and spec/AGENTS docs (no-workspaces rationale, Development setup). Closes #1246. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
527 lines
16 KiB
TypeScript
527 lines
16 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|