Skip to content

Commit f846c15

Browse files
authored
Merge branch 'main' into perf_useTheme
2 parents 3ac0059 + 4d4bb91 commit f846c15

File tree

15 files changed

+787
-28
lines changed

15 files changed

+787
-28
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Thanks for your interest in contributing! This guide explains how to get involve
77
1. Fork the repository and clone it locally
88
2. Install dependencies with `npm install`
99
3. Run `npm run dev` to start both client and server in development mode
10-
4. Use the web UI at http://localhost:5173 to interact with the inspector
10+
4. Use the web UI at http://127.0.0.1:5173 to interact with the inspector
1111

1212
## Development Process & Pull Requests
1313

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ For more details on ways to use the inspector, see the [Inspector section of the
4242

4343
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.
4444

45+
### Security Considerations
46+
47+
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.
48+
4549
### From this repository
4650

4751
If you're working on the inspector itself:

bin/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async function main() {
102102
await Promise.any([server, client, delay(2 * 1000)]);
103103
const portParam = SERVER_PORT === "3000" ? "" : `?proxyPort=${SERVER_PORT}`;
104104
console.log(
105-
`\n🔍 MCP Inspector is up and running at http://localhost:${CLIENT_PORT}${portParam} 🚀`,
105+
`\n🔍 MCP Inspector is up and running at http://127.0.0.1:${CLIENT_PORT}${portParam} 🚀`,
106106
);
107107

108108
try {

client/jest.config.cjs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,12 @@ module.exports = {
33
testEnvironment: "jsdom",
44
moduleNameMapper: {
55
"^@/(.*)$": "<rootDir>/src/$1",
6-
"^../components/DynamicJsonForm$":
7-
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
8-
"^../../components/DynamicJsonForm$":
9-
"<rootDir>/src/utils/__mocks__/DynamicJsonForm.ts",
6+
"\\.css$": "<rootDir>/src/__mocks__/styleMock.js",
107
},
118
transform: {
129
"^.+\\.tsx?$": [
1310
"ts-jest",
1411
{
15-
useESM: true,
1612
jsx: "react-jsx",
1713
tsconfig: "tsconfig.jest.json",
1814
},

client/package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-client",
3-
"version": "0.6.0",
3+
"version": "0.7.0",
44
"description": "Client-side application for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -24,8 +24,8 @@
2424
},
2525
"dependencies": {
2626
"@modelcontextprotocol/sdk": "^1.6.1",
27-
"@radix-ui/react-dialog": "^1.1.3",
2827
"@radix-ui/react-checkbox": "^1.1.4",
28+
"@radix-ui/react-dialog": "^1.1.3",
2929
"@radix-ui/react-icons": "^1.3.0",
3030
"@radix-ui/react-label": "^2.1.0",
3131
"@radix-ui/react-popover": "^1.1.3",
@@ -38,7 +38,7 @@
3838
"cmdk": "^1.0.4",
3939
"lucide-react": "^0.447.0",
4040
"pkce-challenge": "^4.1.0",
41-
"prismjs": "^1.29.0",
41+
"prismjs": "^1.30.0",
4242
"react": "^18.3.1",
4343
"react-dom": "^18.3.1",
4444
"react-simple-code-editor": "^0.14.1",
@@ -50,6 +50,8 @@
5050
},
5151
"devDependencies": {
5252
"@eslint/js": "^9.11.1",
53+
"@testing-library/jest-dom": "^6.6.3",
54+
"@testing-library/react": "^16.2.0",
5355
"@types/jest": "^29.5.14",
5456
"@types/node": "^22.7.5",
5557
"@types/react": "^18.3.10",

client/src/__mocks__/styleMock.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = {};

client/src/components/Sidebar.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,17 @@ const Sidebar = ({
187187
value={key}
188188
onChange={(e) => {
189189
const newKey = e.target.value;
190-
const newEnv = { ...env };
191-
delete newEnv[key];
192-
newEnv[newKey] = value;
190+
const newEnv = Object.entries(env).reduce(
191+
(acc, [k, v]) => {
192+
if (k === key) {
193+
acc[newKey] = value;
194+
} else {
195+
acc[k] = v;
196+
}
197+
return acc;
198+
},
199+
{} as Record<string, string>,
200+
);
193201
setEnv(newEnv);
194202
setShownEnvVars((prev) => {
195203
const next = new Set(prev);

client/src/components/ToolsTab.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ const ToolsTab = ({
233233
id={key}
234234
name={key}
235235
placeholder={prop.description}
236+
value={(params[key] as string) ?? ""}
236237
onChange={(e) =>
237238
setParams({
238239
...params,
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { render, screen, fireEvent } from "@testing-library/react";
2+
import { describe, it, expect, jest } from "@jest/globals";
3+
import DynamicJsonForm from "../DynamicJsonForm";
4+
import type { JsonSchemaType } from "../DynamicJsonForm";
5+
6+
describe("DynamicJsonForm String Fields", () => {
7+
const renderForm = (props = {}) => {
8+
const defaultProps = {
9+
schema: {
10+
type: "string" as const,
11+
description: "Test string field",
12+
} satisfies JsonSchemaType,
13+
value: undefined,
14+
onChange: jest.fn(),
15+
};
16+
return render(<DynamicJsonForm {...defaultProps} {...props} />);
17+
};
18+
19+
describe("Type Validation", () => {
20+
it("should handle numeric input as string type", () => {
21+
const onChange = jest.fn();
22+
renderForm({ onChange });
23+
24+
const input = screen.getByRole("textbox");
25+
fireEvent.change(input, { target: { value: "123321" } });
26+
27+
expect(onChange).toHaveBeenCalledWith("123321");
28+
// Verify the value is a string, not a number
29+
expect(typeof onChange.mock.calls[0][0]).toBe("string");
30+
});
31+
32+
it("should render as text input, not number input", () => {
33+
renderForm();
34+
const input = screen.getByRole("textbox");
35+
expect(input).toHaveProperty("type", "text");
36+
});
37+
});
38+
});
39+
40+
describe("DynamicJsonForm Integer Fields", () => {
41+
const renderForm = (props = {}) => {
42+
const defaultProps = {
43+
schema: {
44+
type: "integer" as const,
45+
description: "Test integer field",
46+
} satisfies JsonSchemaType,
47+
value: undefined,
48+
onChange: jest.fn(),
49+
};
50+
return render(<DynamicJsonForm {...defaultProps} {...props} />);
51+
};
52+
53+
describe("Basic Operations", () => {
54+
it("should render number input with step=1", () => {
55+
renderForm();
56+
const input = screen.getByRole("spinbutton");
57+
expect(input).toHaveProperty("type", "number");
58+
expect(input).toHaveProperty("step", "1");
59+
});
60+
61+
it("should pass integer values to onChange", () => {
62+
const onChange = jest.fn();
63+
renderForm({ onChange });
64+
65+
const input = screen.getByRole("spinbutton");
66+
fireEvent.change(input, { target: { value: "42" } });
67+
68+
expect(onChange).toHaveBeenCalledWith(42);
69+
// Verify the value is a number, not a string
70+
expect(typeof onChange.mock.calls[0][0]).toBe("number");
71+
});
72+
73+
it("should not pass string values to onChange", () => {
74+
const onChange = jest.fn();
75+
renderForm({ onChange });
76+
77+
const input = screen.getByRole("spinbutton");
78+
fireEvent.change(input, { target: { value: "abc" } });
79+
80+
expect(onChange).not.toHaveBeenCalled();
81+
});
82+
});
83+
84+
describe("Edge Cases", () => {
85+
it("should handle non-numeric input by not calling onChange", () => {
86+
const onChange = jest.fn();
87+
renderForm({ onChange });
88+
89+
const input = screen.getByRole("spinbutton");
90+
fireEvent.change(input, { target: { value: "abc" } });
91+
92+
expect(onChange).not.toHaveBeenCalled();
93+
});
94+
});
95+
});

0 commit comments

Comments
 (0)