diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx
index a41e4bfc..4691c99c 100644
--- a/client/src/components/Sidebar.tsx
+++ b/client/src/components/Sidebar.tsx
@@ -268,6 +268,7 @@ const Sidebar = ({
placeholder="Command"
value={command}
onChange={(e) => setCommand(e.target.value)}
+ onBlur={(e) => setCommand(e.target.value.trim())}
className="font-mono"
/>
diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx
index e892a7f8..8f693717 100644
--- a/client/src/components/__tests__/Sidebar.test.tsx
+++ b/client/src/components/__tests__/Sidebar.test.tsx
@@ -31,7 +31,7 @@ Object.defineProperty(navigator, "clipboard", {
// Setup fake timers
jest.useFakeTimers();
-describe("Sidebar Environment Variables", () => {
+describe("Sidebar", () => {
const defaultProps = {
connectionStatus: "disconnected" as const,
transportType: "stdio" as const,
@@ -69,595 +69,329 @@ describe("Sidebar Environment Variables", () => {
);
};
- const openEnvVarsSection = () => {
- const button = screen.getByTestId("env-vars-button");
- fireEvent.click(button);
- };
-
beforeEach(() => {
jest.clearAllMocks();
jest.clearAllTimers();
});
- describe("Basic Operations", () => {
- it("should add a new environment variable", () => {
- const setEnv = jest.fn();
- renderSidebar({ env: {}, setEnv });
+ describe("Command and arguments", () => {
+ it("should trim whitespace from command input on blur", () => {
+ const setCommand = jest.fn();
+ renderSidebar({ command: " node server.js ", setCommand });
- openEnvVarsSection();
+ const commandInput = screen.getByLabelText("Command");
- const addButton = screen.getByText("Add Environment Variable");
- fireEvent.click(addButton);
-
- expect(setEnv).toHaveBeenCalledWith({ "": "" });
+ fireEvent.blur(commandInput);
+ expect(setCommand).toHaveBeenLastCalledWith("node server.js");
});
- it("should remove an environment variable", () => {
- const setEnv = jest.fn();
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv, setEnv });
+ it("should handle whitespace-only command input on blur", () => {
+ const setCommand = jest.fn();
+ renderSidebar({ command: " ", setCommand });
- openEnvVarsSection();
+ const commandInput = screen.getByLabelText("Command");
- const removeButton = screen.getByRole("button", { name: "×" });
- fireEvent.click(removeButton);
-
- expect(setEnv).toHaveBeenCalledWith({});
+ fireEvent.blur(commandInput);
+ expect(setCommand).toHaveBeenLastCalledWith("");
});
- it("should update environment variable value", () => {
- const setEnv = jest.fn();
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv, setEnv });
+ it("should not affect command without surrounding whitespace", () => {
+ const setCommand = jest.fn();
+ renderSidebar({ command: "node", setCommand });
- openEnvVarsSection();
+ const commandInput = screen.getByLabelText("Command");
- const valueInput = screen.getByDisplayValue("test_value");
- fireEvent.change(valueInput, { target: { value: "new_value" } });
-
- expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: "new_value" });
- });
-
- it("should toggle value visibility", () => {
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv });
-
- openEnvVarsSection();
-
- const valueInput = screen.getByDisplayValue("test_value");
- expect(valueInput).toHaveProperty("type", "password");
-
- const toggleButton = screen.getByRole("button", { name: /show value/i });
- fireEvent.click(toggleButton);
-
- expect(valueInput).toHaveProperty("type", "text");
+ fireEvent.blur(commandInput);
+ expect(setCommand).toHaveBeenLastCalledWith("node");
});
});
- describe("Authentication", () => {
- const openAuthSection = () => {
- const button = screen.getByTestId("auth-button");
+ describe("Environment Variables", () => {
+ const openEnvVarsSection = () => {
+ const button = screen.getByTestId("env-vars-button");
fireEvent.click(button);
};
- it("should update bearer token", () => {
- const setBearerToken = jest.fn();
- renderSidebar({
- bearerToken: "",
- setBearerToken,
- transportType: "sse", // Set transport type to SSE
+ describe("Basic Operations", () => {
+ it("should add a new environment variable", () => {
+ const setEnv = jest.fn();
+ renderSidebar({ env: {}, setEnv });
+
+ openEnvVarsSection();
+
+ const addButton = screen.getByText("Add Environment Variable");
+ fireEvent.click(addButton);
+
+ expect(setEnv).toHaveBeenCalledWith({ "": "" });
});
- openAuthSection();
+ it("should remove an environment variable", () => {
+ const setEnv = jest.fn();
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv, setEnv });
- const tokenInput = screen.getByTestId("bearer-token-input");
- fireEvent.change(tokenInput, { target: { value: "new_token" } });
+ openEnvVarsSection();
- expect(setBearerToken).toHaveBeenCalledWith("new_token");
- });
+ const removeButton = screen.getByRole("button", { name: "×" });
+ fireEvent.click(removeButton);
- it("should update header name", () => {
- const setHeaderName = jest.fn();
- renderSidebar({
- headerName: "Authorization",
- setHeaderName,
- transportType: "sse",
+ expect(setEnv).toHaveBeenCalledWith({});
});
- openAuthSection();
+ it("should update environment variable value", () => {
+ const setEnv = jest.fn();
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv, setEnv });
- const headerInput = screen.getByTestId("header-input");
- fireEvent.change(headerInput, { target: { value: "X-Custom-Auth" } });
+ openEnvVarsSection();
- expect(setHeaderName).toHaveBeenCalledWith("X-Custom-Auth");
- });
+ const valueInput = screen.getByDisplayValue("test_value");
+ fireEvent.change(valueInput, { target: { value: "new_value" } });
- it("should clear bearer token", () => {
- const setBearerToken = jest.fn();
- renderSidebar({
- bearerToken: "existing_token",
- setBearerToken,
- transportType: "sse", // Set transport type to SSE
+ expect(setEnv).toHaveBeenCalledWith({ TEST_KEY: "new_value" });
});
- openAuthSection();
+ it("should toggle value visibility", () => {
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv });
- const tokenInput = screen.getByTestId("bearer-token-input");
- fireEvent.change(tokenInput, { target: { value: "" } });
+ openEnvVarsSection();
- expect(setBearerToken).toHaveBeenCalledWith("");
- });
+ const valueInput = screen.getByDisplayValue("test_value");
+ expect(valueInput).toHaveProperty("type", "password");
- it("should properly render bearer token input", () => {
- const { rerender } = renderSidebar({
- bearerToken: "existing_token",
- transportType: "sse", // Set transport type to SSE
- });
+ const toggleButton = screen.getByRole("button", {
+ name: /show value/i,
+ });
+ fireEvent.click(toggleButton);
- openAuthSection();
-
- // Token input should be a password field
- const tokenInput = screen.getByTestId("bearer-token-input");
- expect(tokenInput).toHaveProperty("type", "password");
-
- // Update the token
- fireEvent.change(tokenInput, { target: { value: "new_token" } });
-
- // Rerender with updated token
- rerender(
-
-
- ,
- );
-
- // Token input should still exist after update
- expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
- });
-
- it("should maintain token visibility state after update", () => {
- const { rerender } = renderSidebar({
- bearerToken: "existing_token",
- transportType: "sse", // Set transport type to SSE
- });
-
- openAuthSection();
-
- // Token input should be a password field
- const tokenInput = screen.getByTestId("bearer-token-input");
- expect(tokenInput).toHaveProperty("type", "password");
-
- // Update the token
- fireEvent.change(tokenInput, { target: { value: "new_token" } });
-
- // Rerender with updated token
- rerender(
-
-
- ,
- );
-
- // Token input should still exist after update
- expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
- });
-
- it("should maintain header name when toggling auth section", () => {
- renderSidebar({
- headerName: "X-API-Key",
- transportType: "sse",
- });
-
- // Open auth section
- openAuthSection();
-
- // Verify header name is displayed
- const headerInput = screen.getByTestId("header-input");
- expect(headerInput).toHaveValue("X-API-Key");
-
- // Close auth section
- const authButton = screen.getByTestId("auth-button");
- fireEvent.click(authButton);
-
- // Reopen auth section
- fireEvent.click(authButton);
-
- // Verify header name is still preserved
- expect(screen.getByTestId("header-input")).toHaveValue("X-API-Key");
- });
-
- it("should display default header name when not specified", () => {
- renderSidebar({
- headerName: undefined,
- transportType: "sse",
- });
-
- openAuthSection();
-
- const headerInput = screen.getByTestId("header-input");
- expect(headerInput).toHaveAttribute("placeholder", "Authorization");
- });
- });
-
- describe("Key Editing", () => {
- it("should maintain order when editing first key", () => {
- const setEnv = jest.fn();
- const initialEnv = {
- FIRST_KEY: "first_value",
- SECOND_KEY: "second_value",
- THIRD_KEY: "third_value",
- };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- const firstKeyInput = screen.getByDisplayValue("FIRST_KEY");
- fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } });
-
- expect(setEnv).toHaveBeenCalledWith({
- NEW_FIRST_KEY: "first_value",
- SECOND_KEY: "second_value",
- THIRD_KEY: "third_value",
+ expect(valueInput).toHaveProperty("type", "text");
});
});
- it("should maintain order when editing middle key", () => {
- const setEnv = jest.fn();
- const initialEnv = {
- FIRST_KEY: "first_value",
- SECOND_KEY: "second_value",
- THIRD_KEY: "third_value",
- };
- renderSidebar({ env: initialEnv, setEnv });
+ describe("Key Editing", () => {
+ it("should maintain order when editing first key", () => {
+ const setEnv = jest.fn();
+ const initialEnv = {
+ FIRST_KEY: "first_value",
+ SECOND_KEY: "second_value",
+ THIRD_KEY: "third_value",
+ };
+ renderSidebar({ env: initialEnv, setEnv });
- openEnvVarsSection();
+ openEnvVarsSection();
- const middleKeyInput = screen.getByDisplayValue("SECOND_KEY");
- fireEvent.change(middleKeyInput, { target: { value: "NEW_SECOND_KEY" } });
+ const firstKeyInput = screen.getByDisplayValue("FIRST_KEY");
+ fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } });
- expect(setEnv).toHaveBeenCalledWith({
- FIRST_KEY: "first_value",
- NEW_SECOND_KEY: "second_value",
- THIRD_KEY: "third_value",
- });
- });
-
- it("should maintain order when editing last key", () => {
- const setEnv = jest.fn();
- const initialEnv = {
- FIRST_KEY: "first_value",
- SECOND_KEY: "second_value",
- THIRD_KEY: "third_value",
- };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- const lastKeyInput = screen.getByDisplayValue("THIRD_KEY");
- fireEvent.change(lastKeyInput, { target: { value: "NEW_THIRD_KEY" } });
-
- expect(setEnv).toHaveBeenCalledWith({
- FIRST_KEY: "first_value",
- SECOND_KEY: "second_value",
- NEW_THIRD_KEY: "third_value",
- });
- });
-
- it("should maintain order during key editing", () => {
- const setEnv = jest.fn();
- const initialEnv = {
- KEY1: "value1",
- KEY2: "value2",
- };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- // Type "NEW_" one character at a time
- const key1Input = screen.getByDisplayValue("KEY1");
- "NEW_".split("").forEach((char) => {
- fireEvent.change(key1Input, {
- target: { value: char + "KEY1".slice(1) },
+ expect(setEnv).toHaveBeenCalledWith({
+ NEW_FIRST_KEY: "first_value",
+ SECOND_KEY: "second_value",
+ THIRD_KEY: "third_value",
});
});
- // Verify the last setEnv call maintains the order
- const lastCall = setEnv.mock.calls[
- setEnv.mock.calls.length - 1
- ][0] as Record;
- const entries = Object.entries(lastCall);
+ it("should maintain order when editing middle key", () => {
+ const setEnv = jest.fn();
+ const initialEnv = {
+ FIRST_KEY: "first_value",
+ SECOND_KEY: "second_value",
+ THIRD_KEY: "third_value",
+ };
+ renderSidebar({ env: initialEnv, setEnv });
- // The values should stay with their original keys
- expect(entries[0][1]).toBe("value1"); // First entry should still have value1
- expect(entries[1][1]).toBe("value2"); // Second entry should still have value2
- });
- });
+ openEnvVarsSection();
- describe("Multiple Operations", () => {
- it("should maintain state after multiple key edits", () => {
- const setEnv = jest.fn();
- const initialEnv = {
- FIRST_KEY: "first_value",
- SECOND_KEY: "second_value",
- };
- const { rerender } = renderSidebar({ env: initialEnv, setEnv });
+ const middleKeyInput = screen.getByDisplayValue("SECOND_KEY");
+ fireEvent.change(middleKeyInput, {
+ target: { value: "NEW_SECOND_KEY" },
+ });
- openEnvVarsSection();
+ expect(setEnv).toHaveBeenCalledWith({
+ FIRST_KEY: "first_value",
+ NEW_SECOND_KEY: "second_value",
+ THIRD_KEY: "third_value",
+ });
+ });
- // First key edit
- const firstKeyInput = screen.getByDisplayValue("FIRST_KEY");
- fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } });
+ it("should maintain order when editing last key", () => {
+ const setEnv = jest.fn();
+ const initialEnv = {
+ FIRST_KEY: "first_value",
+ SECOND_KEY: "second_value",
+ THIRD_KEY: "third_value",
+ };
+ renderSidebar({ env: initialEnv, setEnv });
- // Get the updated env from the first setEnv call
- const updatedEnv = setEnv.mock.calls[0][0] as Record;
+ openEnvVarsSection();
- // Rerender with the updated env
- rerender(
-
-
- ,
- );
+ const lastKeyInput = screen.getByDisplayValue("THIRD_KEY");
+ fireEvent.change(lastKeyInput, { target: { value: "NEW_THIRD_KEY" } });
- // Second key edit
- const secondKeyInput = screen.getByDisplayValue("SECOND_KEY");
- fireEvent.change(secondKeyInput, { target: { value: "NEW_SECOND_KEY" } });
+ expect(setEnv).toHaveBeenCalledWith({
+ FIRST_KEY: "first_value",
+ SECOND_KEY: "second_value",
+ NEW_THIRD_KEY: "third_value",
+ });
+ });
- // Verify the final state matches what we expect
- expect(setEnv).toHaveBeenLastCalledWith({
- NEW_FIRST_KEY: "first_value",
- NEW_SECOND_KEY: "second_value",
+ it("should maintain order during key editing", () => {
+ const setEnv = jest.fn();
+ const initialEnv = {
+ KEY1: "value1",
+ KEY2: "value2",
+ };
+ renderSidebar({ env: initialEnv, setEnv });
+
+ openEnvVarsSection();
+
+ // Type "NEW_" one character at a time
+ const key1Input = screen.getByDisplayValue("KEY1");
+ "NEW_".split("").forEach((char) => {
+ fireEvent.change(key1Input, {
+ target: { value: char + "KEY1".slice(1) },
+ });
+ });
+
+ // Verify the last setEnv call maintains the order
+ const lastCall = setEnv.mock.calls[
+ setEnv.mock.calls.length - 1
+ ][0] as Record;
+ const entries = Object.entries(lastCall);
+
+ // The values should stay with their original keys
+ expect(entries[0][1]).toBe("value1"); // First entry should still have value1
+ expect(entries[1][1]).toBe("value2"); // Second entry should still have value2
});
});
- it("should maintain visibility state after key edit", () => {
- const initialEnv = { TEST_KEY: "test_value" };
- const { rerender } = renderSidebar({ env: initialEnv });
+ describe("Multiple Operations", () => {
+ it("should maintain state after multiple key edits", () => {
+ const setEnv = jest.fn();
+ const initialEnv = {
+ FIRST_KEY: "first_value",
+ SECOND_KEY: "second_value",
+ };
+ const { rerender } = renderSidebar({ env: initialEnv, setEnv });
- openEnvVarsSection();
+ openEnvVarsSection();
- // Show the value
- const toggleButton = screen.getByRole("button", { name: /show value/i });
- fireEvent.click(toggleButton);
+ // First key edit
+ const firstKeyInput = screen.getByDisplayValue("FIRST_KEY");
+ fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } });
- const valueInput = screen.getByDisplayValue("test_value");
- expect(valueInput).toHaveProperty("type", "text");
+ // Get the updated env from the first setEnv call
+ const updatedEnv = setEnv.mock.calls[0][0] as Record;
- // Edit the key
- const keyInput = screen.getByDisplayValue("TEST_KEY");
- fireEvent.change(keyInput, { target: { value: "NEW_KEY" } });
+ // Rerender with the updated env
+ rerender(
+
+
+ ,
+ );
- // Rerender with updated env
- rerender(
-
-
- ,
- );
+ // Second key edit
+ const secondKeyInput = screen.getByDisplayValue("SECOND_KEY");
+ fireEvent.change(secondKeyInput, {
+ target: { value: "NEW_SECOND_KEY" },
+ });
- // Value should still be visible
- const updatedValueInput = screen.getByDisplayValue("test_value");
- expect(updatedValueInput).toHaveProperty("type", "text");
+ // Verify the final state matches what we expect
+ expect(setEnv).toHaveBeenLastCalledWith({
+ NEW_FIRST_KEY: "first_value",
+ NEW_SECOND_KEY: "second_value",
+ });
+ });
+
+ it("should maintain visibility state after key edit", () => {
+ const initialEnv = { TEST_KEY: "test_value" };
+ const { rerender } = renderSidebar({ env: initialEnv });
+
+ openEnvVarsSection();
+
+ // Show the value
+ const toggleButton = screen.getByRole("button", {
+ name: /show value/i,
+ });
+ fireEvent.click(toggleButton);
+
+ const valueInput = screen.getByDisplayValue("test_value");
+ expect(valueInput).toHaveProperty("type", "text");
+
+ // Edit the key
+ const keyInput = screen.getByDisplayValue("TEST_KEY");
+ fireEvent.change(keyInput, { target: { value: "NEW_KEY" } });
+
+ // Rerender with updated env
+ rerender(
+
+
+ ,
+ );
+
+ // Value should still be visible
+ const updatedValueInput = screen.getByDisplayValue("test_value");
+ expect(updatedValueInput).toHaveProperty("type", "text");
+ });
+ });
+
+ describe("Edge Cases", () => {
+ it("should handle empty key", () => {
+ const setEnv = jest.fn();
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv, setEnv });
+
+ openEnvVarsSection();
+
+ const keyInput = screen.getByDisplayValue("TEST_KEY");
+ fireEvent.change(keyInput, { target: { value: "" } });
+
+ expect(setEnv).toHaveBeenCalledWith({ "": "test_value" });
+ });
+
+ it("should handle special characters in key", () => {
+ const setEnv = jest.fn();
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv, setEnv });
+
+ openEnvVarsSection();
+
+ const keyInput = screen.getByDisplayValue("TEST_KEY");
+ fireEvent.change(keyInput, { target: { value: "TEST-KEY@123" } });
+
+ expect(setEnv).toHaveBeenCalledWith({ "TEST-KEY@123": "test_value" });
+ });
+
+ it("should handle unicode characters in key", () => {
+ const setEnv = jest.fn();
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv, setEnv });
+
+ openEnvVarsSection();
+
+ const keyInput = screen.getByDisplayValue("TEST_KEY");
+ fireEvent.change(keyInput, { target: { value: "TEST_🔑" } });
+
+ expect(setEnv).toHaveBeenCalledWith({ "TEST_🔑": "test_value" });
+ });
+
+ it("should handle a very long key name", () => {
+ const setEnv = jest.fn();
+ const initialEnv = { TEST_KEY: "test_value" };
+ renderSidebar({ env: initialEnv, setEnv });
+
+ openEnvVarsSection();
+
+ const keyInput = screen.getByDisplayValue("TEST_KEY");
+ const longKey = "A".repeat(100);
+ fireEvent.change(keyInput, { target: { value: longKey } });
+
+ expect(setEnv).toHaveBeenCalledWith({ [longKey]: "test_value" });
+ });
});
});
- describe("Edge Cases", () => {
- it("should handle empty key", () => {
- const setEnv = jest.fn();
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- const keyInput = screen.getByDisplayValue("TEST_KEY");
- fireEvent.change(keyInput, { target: { value: "" } });
-
- expect(setEnv).toHaveBeenCalledWith({ "": "test_value" });
- });
-
- it("should handle special characters in key", () => {
- const setEnv = jest.fn();
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- const keyInput = screen.getByDisplayValue("TEST_KEY");
- fireEvent.change(keyInput, { target: { value: "TEST-KEY@123" } });
-
- expect(setEnv).toHaveBeenCalledWith({ "TEST-KEY@123": "test_value" });
- });
-
- it("should handle unicode characters", () => {
- const setEnv = jest.fn();
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- const keyInput = screen.getByDisplayValue("TEST_KEY");
- fireEvent.change(keyInput, { target: { value: "TEST_🔑" } });
-
- expect(setEnv).toHaveBeenCalledWith({ "TEST_🔑": "test_value" });
- });
-
- it("should handle very long key names", () => {
- const setEnv = jest.fn();
- const initialEnv = { TEST_KEY: "test_value" };
- renderSidebar({ env: initialEnv, setEnv });
-
- openEnvVarsSection();
-
- const keyInput = screen.getByDisplayValue("TEST_KEY");
- const longKey = "A".repeat(100);
- fireEvent.change(keyInput, { target: { value: longKey } });
-
- expect(setEnv).toHaveBeenCalledWith({ [longKey]: "test_value" });
- });
- });
-
- describe("Configuration Operations", () => {
- const openConfigSection = () => {
- const button = screen.getByTestId("config-button");
- fireEvent.click(button);
- };
-
- it("should update MCP server request timeout", () => {
- const setConfig = jest.fn();
- renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
-
- openConfigSection();
-
- const timeoutInput = screen.getByTestId(
- "MCP_SERVER_REQUEST_TIMEOUT-input",
- );
- fireEvent.change(timeoutInput, { target: { value: "5000" } });
-
- expect(setConfig).toHaveBeenCalledWith(
- expect.objectContaining({
- MCP_SERVER_REQUEST_TIMEOUT: {
- label: "Request Timeout",
- description: "Timeout for requests to the MCP server (ms)",
- value: 5000,
- is_session_item: false,
- },
- }),
- );
- });
-
- it("should update MCP server proxy address", () => {
- const setConfig = jest.fn();
- renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
-
- openConfigSection();
-
- const proxyAddressInput = screen.getByTestId(
- "MCP_PROXY_FULL_ADDRESS-input",
- );
- fireEvent.change(proxyAddressInput, {
- target: { value: "http://localhost:8080" },
- });
-
- expect(setConfig).toHaveBeenCalledWith(
- expect.objectContaining({
- MCP_PROXY_FULL_ADDRESS: {
- label: "Inspector Proxy Address",
- description:
- "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
- value: "http://localhost:8080",
- is_session_item: false,
- },
- }),
- );
- });
-
- it("should update max total timeout", () => {
- const setConfig = jest.fn();
- renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
-
- openConfigSection();
-
- const maxTotalTimeoutInput = screen.getByTestId(
- "MCP_REQUEST_MAX_TOTAL_TIMEOUT-input",
- );
- fireEvent.change(maxTotalTimeoutInput, {
- target: { value: "10000" },
- });
-
- expect(setConfig).toHaveBeenCalledWith(
- expect.objectContaining({
- MCP_REQUEST_MAX_TOTAL_TIMEOUT: {
- label: "Maximum Total Timeout",
- description:
- "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)",
- value: 10000,
- is_session_item: false,
- },
- }),
- );
- });
-
- it("should handle invalid timeout values entered by user", () => {
- const setConfig = jest.fn();
- renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
-
- openConfigSection();
-
- const timeoutInput = screen.getByTestId(
- "MCP_SERVER_REQUEST_TIMEOUT-input",
- );
- fireEvent.change(timeoutInput, { target: { value: "abc1" } });
-
- expect(setConfig).toHaveBeenCalledWith(
- expect.objectContaining({
- MCP_SERVER_REQUEST_TIMEOUT: {
- label: "Request Timeout",
- description: "Timeout for requests to the MCP server (ms)",
- value: 0,
- is_session_item: false,
- },
- }),
- );
- });
-
- it("should maintain configuration state after multiple updates", () => {
- const setConfig = jest.fn();
- const { rerender } = renderSidebar({
- config: DEFAULT_INSPECTOR_CONFIG,
- setConfig,
- });
-
- openConfigSection();
- // First update
- const timeoutInput = screen.getByTestId(
- "MCP_SERVER_REQUEST_TIMEOUT-input",
- );
- fireEvent.change(timeoutInput, { target: { value: "5000" } });
-
- // Get the updated config from the first setConfig call
- const updatedConfig = setConfig.mock.calls[0][0] as InspectorConfig;
-
- // Rerender with the updated config
- rerender(
-
-
- ,
- );
-
- // Second update
- const updatedTimeoutInput = screen.getByTestId(
- "MCP_SERVER_REQUEST_TIMEOUT-input",
- );
- fireEvent.change(updatedTimeoutInput, { target: { value: "3000" } });
-
- // Verify the final state matches what we expect
- expect(setConfig).toHaveBeenLastCalledWith(
- expect.objectContaining({
- MCP_SERVER_REQUEST_TIMEOUT: {
- label: "Request Timeout",
- description: "Timeout for requests to the MCP server (ms)",
- value: 3000,
- is_session_item: false,
- },
- }),
- );
- });
- });
-
- describe("Copy Configuration Features", () => {
- beforeEach(() => {
- jest.clearAllMocks();
- jest.clearAllTimers();
- });
-
+ describe("Copy Server Features", () => {
const getCopyButtons = () => {
return {
serverEntry: screen.getByRole("button", { name: /server entry/i }),
@@ -878,4 +612,307 @@ describe("Sidebar Environment Variables", () => {
expect(mockClipboardWrite).toHaveBeenCalledWith(expectedConfig);
});
});
+
+ describe("Authentication", () => {
+ const openAuthSection = () => {
+ const button = screen.getByTestId("auth-button");
+ fireEvent.click(button);
+ };
+
+ it("should update bearer token", () => {
+ const setBearerToken = jest.fn();
+ renderSidebar({
+ bearerToken: "",
+ setBearerToken,
+ transportType: "sse", // Set transport type to SSE
+ });
+
+ openAuthSection();
+
+ const tokenInput = screen.getByTestId("bearer-token-input");
+ fireEvent.change(tokenInput, { target: { value: "new_token" } });
+
+ expect(setBearerToken).toHaveBeenCalledWith("new_token");
+ });
+
+ it("should update header name", () => {
+ const setHeaderName = jest.fn();
+ renderSidebar({
+ headerName: "Authorization",
+ setHeaderName,
+ transportType: "sse",
+ });
+
+ openAuthSection();
+
+ const headerInput = screen.getByTestId("header-input");
+ fireEvent.change(headerInput, { target: { value: "X-Custom-Auth" } });
+
+ expect(setHeaderName).toHaveBeenCalledWith("X-Custom-Auth");
+ });
+
+ it("should clear bearer token", () => {
+ const setBearerToken = jest.fn();
+ renderSidebar({
+ bearerToken: "existing_token",
+ setBearerToken,
+ transportType: "sse", // Set transport type to SSE
+ });
+
+ openAuthSection();
+
+ const tokenInput = screen.getByTestId("bearer-token-input");
+ fireEvent.change(tokenInput, { target: { value: "" } });
+
+ expect(setBearerToken).toHaveBeenCalledWith("");
+ });
+
+ it("should properly render bearer token input", () => {
+ const { rerender } = renderSidebar({
+ bearerToken: "existing_token",
+ transportType: "sse", // Set transport type to SSE
+ });
+
+ openAuthSection();
+
+ // Token input should be a password field
+ const tokenInput = screen.getByTestId("bearer-token-input");
+ expect(tokenInput).toHaveProperty("type", "password");
+
+ // Update the token
+ fireEvent.change(tokenInput, { target: { value: "new_token" } });
+
+ // Rerender with updated token
+ rerender(
+
+
+ ,
+ );
+
+ // Token input should still exist after update
+ expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
+ });
+
+ it("should maintain token visibility state after update", () => {
+ const { rerender } = renderSidebar({
+ bearerToken: "existing_token",
+ transportType: "sse", // Set transport type to SSE
+ });
+
+ openAuthSection();
+
+ // Token input should be a password field
+ const tokenInput = screen.getByTestId("bearer-token-input");
+ expect(tokenInput).toHaveProperty("type", "password");
+
+ // Update the token
+ fireEvent.change(tokenInput, { target: { value: "new_token" } });
+
+ // Rerender with updated token
+ rerender(
+
+
+ ,
+ );
+
+ // Token input should still exist after update
+ expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
+ });
+
+ it("should maintain header name when toggling auth section", () => {
+ renderSidebar({
+ headerName: "X-API-Key",
+ transportType: "sse",
+ });
+
+ // Open auth section
+ openAuthSection();
+
+ // Verify header name is displayed
+ const headerInput = screen.getByTestId("header-input");
+ expect(headerInput).toHaveValue("X-API-Key");
+
+ // Close auth section
+ const authButton = screen.getByTestId("auth-button");
+ fireEvent.click(authButton);
+
+ // Reopen auth section
+ fireEvent.click(authButton);
+
+ // Verify header name is still preserved
+ expect(screen.getByTestId("header-input")).toHaveValue("X-API-Key");
+ });
+
+ it("should display default header name when not specified", () => {
+ renderSidebar({
+ headerName: undefined,
+ transportType: "sse",
+ });
+
+ openAuthSection();
+
+ const headerInput = screen.getByTestId("header-input");
+ expect(headerInput).toHaveAttribute("placeholder", "Authorization");
+ });
+ });
+
+ describe("Configuration", () => {
+ const openConfigSection = () => {
+ const button = screen.getByTestId("config-button");
+ fireEvent.click(button);
+ };
+
+ it("should update MCP server request timeout", () => {
+ const setConfig = jest.fn();
+ renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
+
+ openConfigSection();
+
+ const timeoutInput = screen.getByTestId(
+ "MCP_SERVER_REQUEST_TIMEOUT-input",
+ );
+ fireEvent.change(timeoutInput, { target: { value: "5000" } });
+
+ expect(setConfig).toHaveBeenCalledWith(
+ expect.objectContaining({
+ MCP_SERVER_REQUEST_TIMEOUT: {
+ label: "Request Timeout",
+ description: "Timeout for requests to the MCP server (ms)",
+ value: 5000,
+ is_session_item: false,
+ },
+ }),
+ );
+ });
+
+ it("should update MCP server proxy address", () => {
+ const setConfig = jest.fn();
+ renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
+
+ openConfigSection();
+
+ const proxyAddressInput = screen.getByTestId(
+ "MCP_PROXY_FULL_ADDRESS-input",
+ );
+ fireEvent.change(proxyAddressInput, {
+ target: { value: "http://localhost:8080" },
+ });
+
+ expect(setConfig).toHaveBeenCalledWith(
+ expect.objectContaining({
+ MCP_PROXY_FULL_ADDRESS: {
+ label: "Inspector Proxy Address",
+ description:
+ "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
+ value: "http://localhost:8080",
+ is_session_item: false,
+ },
+ }),
+ );
+ });
+
+ it("should update max total timeout", () => {
+ const setConfig = jest.fn();
+ renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
+
+ openConfigSection();
+
+ const maxTotalTimeoutInput = screen.getByTestId(
+ "MCP_REQUEST_MAX_TOTAL_TIMEOUT-input",
+ );
+ fireEvent.change(maxTotalTimeoutInput, {
+ target: { value: "10000" },
+ });
+
+ expect(setConfig).toHaveBeenCalledWith(
+ expect.objectContaining({
+ MCP_REQUEST_MAX_TOTAL_TIMEOUT: {
+ label: "Maximum Total Timeout",
+ description:
+ "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)",
+ value: 10000,
+ is_session_item: false,
+ },
+ }),
+ );
+ });
+
+ it("should handle invalid timeout values entered by user", () => {
+ const setConfig = jest.fn();
+ renderSidebar({ config: DEFAULT_INSPECTOR_CONFIG, setConfig });
+
+ openConfigSection();
+
+ const timeoutInput = screen.getByTestId(
+ "MCP_SERVER_REQUEST_TIMEOUT-input",
+ );
+ fireEvent.change(timeoutInput, { target: { value: "abc1" } });
+
+ expect(setConfig).toHaveBeenCalledWith(
+ expect.objectContaining({
+ MCP_SERVER_REQUEST_TIMEOUT: {
+ label: "Request Timeout",
+ description: "Timeout for requests to the MCP server (ms)",
+ value: 0,
+ is_session_item: false,
+ },
+ }),
+ );
+ });
+
+ it("should maintain configuration state after multiple updates", () => {
+ const setConfig = jest.fn();
+ const { rerender } = renderSidebar({
+ config: DEFAULT_INSPECTOR_CONFIG,
+ setConfig,
+ });
+
+ openConfigSection();
+ // First update
+ const timeoutInput = screen.getByTestId(
+ "MCP_SERVER_REQUEST_TIMEOUT-input",
+ );
+ fireEvent.change(timeoutInput, { target: { value: "5000" } });
+
+ // Get the updated config from the first setConfig call
+ const updatedConfig = setConfig.mock.calls[0][0] as InspectorConfig;
+
+ // Rerender with the updated config
+ rerender(
+
+
+ ,
+ );
+
+ // Second update
+ const updatedTimeoutInput = screen.getByTestId(
+ "MCP_SERVER_REQUEST_TIMEOUT-input",
+ );
+ fireEvent.change(updatedTimeoutInput, { target: { value: "3000" } });
+
+ // Verify the final state matches what we expect
+ expect(setConfig).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ MCP_SERVER_REQUEST_TIMEOUT: {
+ label: "Request Timeout",
+ description: "Timeout for requests to the MCP server (ms)",
+ value: 3000,
+ is_session_item: false,
+ },
+ }),
+ );
+ });
+ });
});
diff --git a/server/src/index.ts b/server/src/index.ts
index 0a0f7bcc..f35c9275 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -181,7 +181,7 @@ const createTransport = async (req: express.Request): Promise => {
const transportType = query.transportType as string;
if (transportType === "stdio") {
- const command = query.command as string;
+ const command = (query.command as string).trim();
const origArgs = shellParseArgs(query.args as string) as string[];
const queryEnv = query.env ? JSON.parse(query.env as string) : {};
const env = { ...defaultEnvironment, ...process.env, ...queryEnv };