diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ad5699..b225713 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve 1. Fork the repository and clone it locally 2. Install dependencies with `npm install` 3. Run `npm run dev` to start both client and server in development mode -4. Use the web UI at http://localhost:5173 to interact with the inspector +4. Use the web UI at http://127.0.0.1:5173 to interact with the inspector ## Development Process & Pull Requests diff --git a/README.md b/README.md index a6ab6d4..f1bd97c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ For more details on ways to use the inspector, see the [Inspector section of the The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header. +### Security Considerations + +The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server. + ### From this repository If you're working on the inspector itself: diff --git a/bin/cli.js b/bin/cli.js index 8e0318d..1b744ce 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -102,7 +102,7 @@ async function main() { await Promise.any([server, client, delay(2 * 1000)]); const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`; console.log( - `\nšŸ” MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} šŸš€`, + `\nšŸ” MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} šŸš€`, ); try { diff --git a/client/jest.config.cjs b/client/jest.config.cjs index 3830e79..c360e72 100644 --- a/client/jest.config.cjs +++ b/client/jest.config.cjs @@ -3,16 +3,12 @@ module.exports = { testEnvironment: "jsdom", moduleNameMapper: { "^@/(.*)$": "/src/$1", - "^../components/DynamicJsonForm$": - "/src/utils/__mocks__/DynamicJsonForm.ts", - "^../../components/DynamicJsonForm$": - "/src/utils/__mocks__/DynamicJsonForm.ts", + "\\.css$": "/src/__mocks__/styleMock.js", }, transform: { "^.+\\.tsx?$": [ "ts-jest", { - useESM: true, jsx: "react-jsx", tsconfig: "tsconfig.jest.json", }, diff --git a/client/package.json b/client/package.json index 0471765..9eff88c 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.6.0", + "version": "0.7.0", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -24,8 +24,8 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", - "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-dialog": "^1.1.3", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.3", @@ -38,7 +38,7 @@ "cmdk": "^1.0.4", "lucide-react": "^0.447.0", "pkce-challenge": "^4.1.0", - "prismjs": "^1.29.0", + "prismjs": "^1.30.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", @@ -50,6 +50,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", diff --git a/client/src/__mocks__/styleMock.js b/client/src/__mocks__/styleMock.js new file mode 100644 index 0000000..f053ebf --- /dev/null +++ b/client/src/__mocks__/styleMock.js @@ -0,0 +1 @@ +module.exports = {}; diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx index aa930e5..4f60a77 100644 --- a/client/src/components/Sidebar.tsx +++ b/client/src/components/Sidebar.tsx @@ -187,9 +187,17 @@ const Sidebar = ({ value={key} onChange={(e) => { const newKey = e.target.value; - const newEnv = { ...env }; - delete newEnv[key]; - newEnv[newKey] = value; + const newEnv = Object.entries(env).reduce( + (acc, [k, v]) => { + if (k === key) { + acc[newKey] = value; + } else { + acc[k] = v; + } + return acc; + }, + {} as Record, + ); setEnv(newEnv); setShownEnvVars((prev) => { const next = new Set(prev); diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 82ebdb0..7008f09 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -233,6 +233,7 @@ const ToolsTab = ({ id={key} name={key} placeholder={prop.description} + value={(params[key] as string) ?? ""} onChange={(e) => setParams({ ...params, diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx new file mode 100644 index 0000000..fce6014 --- /dev/null +++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx @@ -0,0 +1,95 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, jest } from "@jest/globals"; +import DynamicJsonForm from "../DynamicJsonForm"; +import type { JsonSchemaType } from "../DynamicJsonForm"; + +describe("DynamicJsonForm String Fields", () => { + const renderForm = (props = {}) => { + const defaultProps = { + schema: { + type: "string" as const, + description: "Test string field", + } satisfies JsonSchemaType, + value: undefined, + onChange: jest.fn(), + }; + return render(); + }; + + describe("Type Validation", () => { + it("should handle numeric input as string type", () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole("textbox"); + fireEvent.change(input, { target: { value: "123321" } }); + + expect(onChange).toHaveBeenCalledWith("123321"); + // Verify the value is a string, not a number + expect(typeof onChange.mock.calls[0][0]).toBe("string"); + }); + + it("should render as text input, not number input", () => { + renderForm(); + const input = screen.getByRole("textbox"); + expect(input).toHaveProperty("type", "text"); + }); + }); +}); + +describe("DynamicJsonForm Integer Fields", () => { + const renderForm = (props = {}) => { + const defaultProps = { + schema: { + type: "integer" as const, + description: "Test integer field", + } satisfies JsonSchemaType, + value: undefined, + onChange: jest.fn(), + }; + return render(); + }; + + describe("Basic Operations", () => { + it("should render number input with step=1", () => { + renderForm(); + const input = screen.getByRole("spinbutton"); + expect(input).toHaveProperty("type", "number"); + expect(input).toHaveProperty("step", "1"); + }); + + it("should pass integer values to onChange", () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "42" } }); + + expect(onChange).toHaveBeenCalledWith(42); + // Verify the value is a number, not a string + expect(typeof onChange.mock.calls[0][0]).toBe("number"); + }); + + it("should not pass string values to onChange", () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "abc" } }); + + expect(onChange).not.toHaveBeenCalled(); + }); + }); + + describe("Edge Cases", () => { + it("should handle non-numeric input by not calling onChange", () => { + const onChange = jest.fn(); + renderForm({ onChange }); + + const input = screen.getByRole("spinbutton"); + fireEvent.change(input, { target: { value: "abc" } }); + + expect(onChange).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx new file mode 100644 index 0000000..710c80d --- /dev/null +++ b/client/src/components/__tests__/Sidebar.test.tsx @@ -0,0 +1,307 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, beforeEach, jest } from "@jest/globals"; +import Sidebar from "../Sidebar"; + +// Mock theme hook +jest.mock("../../lib/useTheme", () => ({ + __esModule: true, + default: () => ["light", jest.fn()], +})); + +describe("Sidebar Environment Variables", () => { + const defaultProps = { + connectionStatus: "disconnected" as const, + transportType: "stdio" as const, + setTransportType: jest.fn(), + command: "", + setCommand: jest.fn(), + args: "", + setArgs: jest.fn(), + sseUrl: "", + setSseUrl: jest.fn(), + env: {}, + setEnv: jest.fn(), + bearerToken: "", + setBearerToken: jest.fn(), + onConnect: jest.fn(), + stdErrNotifications: [], + logLevel: "info" as const, + sendLogLevelRequest: jest.fn(), + loggingSupported: true, + }; + + const renderSidebar = (props = {}) => { + return render(); + }; + + const openEnvVarsSection = () => { + const button = screen.getByText("Environment Variables"); + fireEvent.click(button); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + 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({ "": "" }); + }); + + it("should remove an environment variable", () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: "test_value" }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + const removeButton = screen.getByRole("button", { name: "Ɨ" }); + fireEvent.click(removeButton); + + expect(setEnv).toHaveBeenCalledWith({}); + }); + + it("should update environment variable value", () => { + const setEnv = jest.fn(); + const initialEnv = { TEST_KEY: "test_value" }; + renderSidebar({ env: initialEnv, setEnv }); + + openEnvVarsSection(); + + 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"); + }); + }); + + 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", + }); + }); + + 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 }); + + openEnvVarsSection(); + + const middleKeyInput = screen.getByDisplayValue("SECOND_KEY"); + fireEvent.change(middleKeyInput, { target: { value: "NEW_SECOND_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) }, + }); + }); + + // 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 + }); + }); + + 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(); + + // First key edit + const firstKeyInput = screen.getByDisplayValue("FIRST_KEY"); + fireEvent.change(firstKeyInput, { target: { value: "NEW_FIRST_KEY" } }); + + // Get the updated env from the first setEnv call + const updatedEnv = setEnv.mock.calls[0][0] as Record; + + // Rerender with the updated env + rerender(); + + // Second key edit + const secondKeyInput = screen.getByDisplayValue("SECOND_KEY"); + fireEvent.change(secondKeyInput, { target: { value: "NEW_SECOND_KEY" } }); + + // 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", () => { + 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" }); + }); + }); +}); diff --git a/client/src/components/__tests__/ToolsTab.test.tsx b/client/src/components/__tests__/ToolsTab.test.tsx new file mode 100644 index 0000000..2a45065 --- /dev/null +++ b/client/src/components/__tests__/ToolsTab.test.tsx @@ -0,0 +1,72 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, it, expect, jest } from "@jest/globals"; +import ToolsTab from "../ToolsTab"; +import { Tool } from "@modelcontextprotocol/sdk/types.js"; +import { Tabs } from "@/components/ui/tabs"; + +describe("ToolsTab", () => { + const mockTools: Tool[] = [ + { + name: "tool1", + description: "First tool", + inputSchema: { + type: "object" as const, + properties: { + num: { type: "number" as const }, + }, + }, + }, + { + name: "tool2", + description: "Second tool", + inputSchema: { + type: "object" as const, + properties: { + num: { type: "number" as const }, + }, + }, + }, + ]; + + const defaultProps = { + tools: mockTools, + listTools: jest.fn(), + clearTools: jest.fn(), + callTool: jest.fn(), + selectedTool: null, + setSelectedTool: jest.fn(), + toolResult: null, + nextCursor: "", + error: null, + }; + + const renderToolsTab = (props = {}) => { + return render( + + + , + ); + }; + + it("should reset input values when switching tools", () => { + const { rerender } = renderToolsTab({ + selectedTool: mockTools[0], + }); + + // Enter a value in the first tool's input + const input = screen.getByRole("spinbutton") as HTMLInputElement; + fireEvent.change(input, { target: { value: "42" } }); + expect(input.value).toBe("42"); + + // Switch to second tool + rerender( + + + , + ); + + // Verify input is reset + const newInput = screen.getByRole("spinbutton") as HTMLInputElement; + expect(newInput.value).toBe(""); + }); +}); diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json index 980c215..e6620f7 100644 --- a/client/tsconfig.app.json +++ b/client/tsconfig.app.json @@ -24,7 +24,8 @@ "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "types": ["jest", "@testing-library/jest-dom", "node"] }, "include": ["src"] } diff --git a/package-lock.json b/package-lock.json index 68929c5..448edd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,20 +1,20 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "workspaces": [ "client", "server" ], "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.6.0", - "@modelcontextprotocol/inspector-server": "^0.6.0", + "@modelcontextprotocol/inspector-client": "^0.7.0", + "@modelcontextprotocol/inspector-server": "^0.7.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", @@ -32,7 +32,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", @@ -50,7 +50,7 @@ "cmdk": "^1.0.4", "lucide-react": "^0.447.0", "pkce-challenge": "^4.1.0", - "prismjs": "^1.29.0", + "prismjs": "^1.30.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-simple-code-editor": "^0.14.1", @@ -65,6 +65,8 @@ }, "devDependencies": { "@eslint/js": "^9.11.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.2.0", "@types/jest": "^29.5.14", "@types/node": "^22.7.5", "@types/react": "^18.3.10", @@ -87,6 +89,13 @@ "vite": "^5.4.8" } }, + "node_modules/@adobe/css-tools": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", + "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "dev": true, + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -576,6 +585,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -3786,6 +3808,148 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@testing-library/dom/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@testing-library/dom/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "chalk": "^3.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/jest-dom/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@testing-library/react": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.2.0.tgz", + "integrity": "sha512-2cSskAvA1QNtKc8Y9VJQRv0tm3hLVgxRGDB+KYhIaPQJ1I+RHbhIXcM+zClKXzMes/wshsMVzf4B9vS4IZpqDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -3820,6 +3984,14 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "license": "MIT" }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4585,6 +4757,16 @@ "node": ">=10" } }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -5331,6 +5513,13 @@ "node": ">= 8" } }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5467,6 +5656,16 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -5524,6 +5723,14 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/domexception": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", @@ -6796,6 +7003,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7984,6 +8201,17 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -8137,6 +8365,16 @@ "node": ">=6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8842,9 +9080,9 @@ } }, "node_modules/prismjs": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", - "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", "license": "MIT", "engines": { "node": ">=6" @@ -9151,6 +9389,27 @@ "node": ">=8.10.0" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9837,6 +10096,19 @@ "node": ">=6" } }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -11299,7 +11571,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", diff --git a/package.json b/package.json index e3bce92..66cc1a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.6.0", + "version": "0.7.0", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -34,8 +34,8 @@ "publish-all": "npm publish --workspaces --access public && npm publish --access public" }, "dependencies": { - "@modelcontextprotocol/inspector-client": "^0.6.0", - "@modelcontextprotocol/inspector-server": "^0.6.0", + "@modelcontextprotocol/inspector-client": "^0.7.0", + "@modelcontextprotocol/inspector-server": "^0.7.0", "concurrently": "^9.0.1", "shell-quote": "^1.8.2", "spawn-rx": "^5.1.2", diff --git a/server/package.json b/server/package.json index 5d8839f..732993f 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.6.0", + "version": "0.7.0", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",