Skip to content

Commit 3075cbb

Browse files
authored
Merge branch 'main' into alejandro.borbolla/copy-input
2 parents 2fd781a + 2352993 commit 3075cbb

26 files changed

+815
-188
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
name: Bug report
3+
about: Create a report to help us improve
4+
title: ""
5+
labels: ""
6+
assignees: ""
7+
---
8+
9+
**Inspector Version**
10+
11+
- [e.g. 0.16.5)
12+
13+
**Describe the bug**
14+
A clear and concise description of what the bug is.
15+
16+
**To Reproduce**
17+
Steps to reproduce the behavior:
18+
19+
1. Go to '...'
20+
2. Click on '....'
21+
3. Scroll down to '....'
22+
4. See error
23+
24+
**Expected behavior**
25+
A clear and concise description of what you expected to happen.
26+
27+
**Screenshots**
28+
If applicable, add screenshots to help explain your problem.
29+
30+
**Environment (please complete the following information):**
31+
32+
- OS: [e.g. iOS]
33+
- Browser [e.g. chrome, safari]
34+
35+
**Additional context**
36+
Add any other context about the problem here.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ client/results.json
1717
client/test-results/
1818
client/e2e/test-results/
1919
mcp.json
20+
.claude/settings.local.json

README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,16 @@ The MCP Inspector provides convenient buttons to export server launch configurat
9898
}
9999
```
100100

101+
**Streamable HTTP transport example:**
102+
103+
```json
104+
{
105+
"type": "streamable-http",
106+
"url": "http://localhost:3000/mcp",
107+
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
108+
}
109+
```
110+
101111
- **Servers File** - Copies a complete MCP configuration file structure to your clipboard, with your current server configuration added as `default-server`. This can be saved directly as `mcp.json`.
102112

103113
**STDIO transport example:**
@@ -131,9 +141,23 @@ The MCP Inspector provides convenient buttons to export server launch configurat
131141
}
132142
```
133143

144+
**Streamable HTTP transport example:**
145+
146+
```json
147+
{
148+
"mcpServers": {
149+
"default-server": {
150+
"type": "streamable-http",
151+
"url": "http://localhost:3000/mcp",
152+
"note": "For Streamable HTTP connections, add this URL directly in your MCP Client"
153+
}
154+
}
155+
}
156+
```
157+
134158
These buttons appear in the Inspector UI after you've configured your server settings, making it easy to save and reuse your configurations.
135159

136-
For SSE transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the SSE URL configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the SSE URL for direct use in clients.
160+
For SSE and Streamable HTTP transport connections, the Inspector provides similar functionality for both buttons. The "Server Entry" button copies the configuration that can be added to your existing configuration file, while the "Servers File" button creates a complete configuration file containing the URL for direct use in clients.
137161

138162
You can paste the Server Entry into your existing `mcp.json` file under your chosen server name, or use the complete Servers File payload to create a new configuration file.
139163

cli/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-cli",
3-
"version": "0.16.5",
3+
"version": "0.16.7",
44
"description": "CLI for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -17,13 +17,14 @@
1717
"scripts": {
1818
"build": "tsc",
1919
"postbuild": "node scripts/make-executable.js",
20-
"test": "node scripts/cli-tests.js && node scripts/cli-tool-tests.js",
20+
"test": "node scripts/cli-tests.js && node scripts/cli-tool-tests.js && node scripts/cli-header-tests.js",
2121
"test:cli": "node scripts/cli-tests.js",
22-
"test:cli-tools": "node scripts/cli-tool-tests.js"
22+
"test:cli-tools": "node scripts/cli-tool-tests.js",
23+
"test:cli-headers": "node scripts/cli-header-tests.js"
2324
},
2425
"devDependencies": {},
2526
"dependencies": {
26-
"@modelcontextprotocol/sdk": "^1.17.3",
27+
"@modelcontextprotocol/sdk": "^1.18.0",
2728
"commander": "^13.1.0",
2829
"spawn-rx": "^5.1.2"
2930
}

cli/scripts/cli-header-tests.js

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Integration tests for header functionality
5+
* Tests the CLI header parsing end-to-end
6+
*/
7+
8+
import { spawn } from "node:child_process";
9+
import { resolve, dirname } from "node:path";
10+
import { fileURLToPath } from "node:url";
11+
12+
const __dirname = dirname(fileURLToPath(import.meta.url));
13+
const CLI_PATH = resolve(__dirname, "..", "build", "index.js");
14+
15+
// ANSI colors for output
16+
const colors = {
17+
GREEN: "\x1b[32m",
18+
RED: "\x1b[31m",
19+
YELLOW: "\x1b[33m",
20+
BLUE: "\x1b[34m",
21+
NC: "\x1b[0m", // No Color
22+
};
23+
24+
let testsPassed = 0;
25+
let testsFailed = 0;
26+
27+
/**
28+
* Run a CLI test with given arguments and check for expected behavior
29+
*/
30+
function runHeaderTest(
31+
testName,
32+
args,
33+
expectSuccess = false,
34+
expectedInOutput = null,
35+
) {
36+
return new Promise((resolve) => {
37+
console.log(`\n${colors.BLUE}Testing: ${testName}${colors.NC}`);
38+
console.log(
39+
`${colors.BLUE}Command: node ${CLI_PATH} ${args.join(" ")}${colors.NC}`,
40+
);
41+
42+
const child = spawn("node", [CLI_PATH, ...args], {
43+
stdio: ["pipe", "pipe", "pipe"],
44+
timeout: 10000,
45+
});
46+
47+
let stdout = "";
48+
let stderr = "";
49+
50+
child.stdout.on("data", (data) => {
51+
stdout += data.toString();
52+
});
53+
54+
child.stderr.on("data", (data) => {
55+
stderr += data.toString();
56+
});
57+
58+
child.on("close", (code) => {
59+
const output = stdout + stderr;
60+
let passed = true;
61+
let reason = "";
62+
63+
// Check exit code expectation
64+
if (expectSuccess && code !== 0) {
65+
passed = false;
66+
reason = `Expected success (exit code 0) but got ${code}`;
67+
} else if (!expectSuccess && code === 0) {
68+
passed = false;
69+
reason = `Expected failure (non-zero exit code) but got success`;
70+
}
71+
72+
// Check expected output
73+
if (passed && expectedInOutput && !output.includes(expectedInOutput)) {
74+
passed = false;
75+
reason = `Expected output to contain "${expectedInOutput}"`;
76+
}
77+
78+
if (passed) {
79+
console.log(`${colors.GREEN}PASS: ${testName}${colors.NC}`);
80+
testsPassed++;
81+
} else {
82+
console.log(`${colors.RED}FAIL: ${testName}${colors.NC}`);
83+
console.log(`${colors.RED}Reason: ${reason}${colors.NC}`);
84+
console.log(`${colors.RED}Exit code: ${code}${colors.NC}`);
85+
console.log(`${colors.RED}Output: ${output}${colors.NC}`);
86+
testsFailed++;
87+
}
88+
89+
resolve();
90+
});
91+
92+
child.on("error", (error) => {
93+
console.log(
94+
`${colors.RED}ERROR: ${testName} - ${error.message}${colors.NC}`,
95+
);
96+
testsFailed++;
97+
resolve();
98+
});
99+
});
100+
}
101+
102+
async function runHeaderIntegrationTests() {
103+
console.log(
104+
`${colors.YELLOW}=== MCP Inspector CLI Header Integration Tests ===${colors.NC}`,
105+
);
106+
console.log(
107+
`${colors.BLUE}Testing header parsing and validation${colors.NC}`,
108+
);
109+
110+
// Test 1: Valid header format should parse successfully (connection will fail)
111+
await runHeaderTest(
112+
"Valid single header",
113+
[
114+
"https://example.com",
115+
"--method",
116+
"tools/list",
117+
"--transport",
118+
"http",
119+
"--header",
120+
"Authorization: Bearer token123",
121+
],
122+
false,
123+
);
124+
125+
// Test 2: Multiple headers should parse successfully
126+
await runHeaderTest(
127+
"Multiple headers",
128+
[
129+
"https://example.com",
130+
"--method",
131+
"tools/list",
132+
"--transport",
133+
"http",
134+
"--header",
135+
"Authorization: Bearer token123",
136+
"--header",
137+
"X-API-Key: secret123",
138+
],
139+
false,
140+
);
141+
142+
// Test 3: Invalid header format - no colon
143+
await runHeaderTest(
144+
"Invalid header format - no colon",
145+
[
146+
"https://example.com",
147+
"--method",
148+
"tools/list",
149+
"--transport",
150+
"http",
151+
"--header",
152+
"InvalidHeader",
153+
],
154+
false,
155+
"Invalid header format",
156+
);
157+
158+
// Test 4: Invalid header format - empty name
159+
await runHeaderTest(
160+
"Invalid header format - empty name",
161+
[
162+
"https://example.com",
163+
"--method",
164+
"tools/list",
165+
"--transport",
166+
"http",
167+
"--header",
168+
": value",
169+
],
170+
false,
171+
"Invalid header format",
172+
);
173+
174+
// Test 5: Invalid header format - empty value
175+
await runHeaderTest(
176+
"Invalid header format - empty value",
177+
[
178+
"https://example.com",
179+
"--method",
180+
"tools/list",
181+
"--transport",
182+
"http",
183+
"--header",
184+
"Header:",
185+
],
186+
false,
187+
"Invalid header format",
188+
);
189+
190+
// Test 6: Header with colons in value
191+
await runHeaderTest(
192+
"Header with colons in value",
193+
[
194+
"https://example.com",
195+
"--method",
196+
"tools/list",
197+
"--transport",
198+
"http",
199+
"--header",
200+
"X-Time: 2023:12:25:10:30:45",
201+
],
202+
false,
203+
);
204+
205+
// Test 7: Whitespace handling
206+
await runHeaderTest(
207+
"Whitespace handling in headers",
208+
[
209+
"https://example.com",
210+
"--method",
211+
"tools/list",
212+
"--transport",
213+
"http",
214+
"--header",
215+
" X-Header : value with spaces ",
216+
],
217+
false,
218+
);
219+
220+
console.log(`\n${colors.YELLOW}=== Test Results ===${colors.NC}`);
221+
console.log(`${colors.GREEN}Tests passed: ${testsPassed}${colors.NC}`);
222+
console.log(`${colors.RED}Tests failed: ${testsFailed}${colors.NC}`);
223+
224+
if (testsFailed === 0) {
225+
console.log(
226+
`${colors.GREEN}All header integration tests passed!${colors.NC}`,
227+
);
228+
process.exit(0);
229+
} else {
230+
console.log(
231+
`${colors.RED}Some header integration tests failed.${colors.NC}`,
232+
);
233+
process.exit(1);
234+
}
235+
}
236+
237+
// Handle graceful shutdown
238+
process.on("SIGINT", () => {
239+
console.log(`\n${colors.YELLOW}Test interrupted by user${colors.NC}`);
240+
process.exit(1);
241+
});
242+
243+
process.on("SIGTERM", () => {
244+
console.log(`\n${colors.YELLOW}Test terminated${colors.NC}`);
245+
process.exit(1);
246+
});
247+
248+
// Run the tests
249+
runHeaderIntegrationTests().catch((error) => {
250+
console.error(`${colors.RED}Test runner error: ${error.message}${colors.NC}`);
251+
process.exit(1);
252+
});

cli/src/cli.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ async function runWebClient(args: Args): Promise<void> {
106106
await spawnPromise("node", [inspectorClientPath, ...startArgs], {
107107
signal: abort.signal,
108108
echoOutput: true,
109+
// pipe the stdout through here, prevents issues with buffering and
110+
// dropping the end of console.out after 8192 chars due to node
111+
// closing the stdout pipe before the output has finished flushing
112+
stdio: "inherit",
109113
});
110114
} catch (e) {
111115
if (!cancelled || process.env.DEBUG) throw e;
@@ -151,6 +155,10 @@ async function runCli(args: Args): Promise<void> {
151155
env: { ...process.env, ...args.envArgs },
152156
signal: abort.signal,
153157
echoOutput: true,
158+
// pipe the stdout through here, prevents issues with buffering and
159+
// dropping the end of console.out after 8192 chars due to node
160+
// closing the stdout pipe before the output has finished flushing
161+
stdio: "inherit",
154162
});
155163
} catch (e) {
156164
if (!cancelled || process.env.DEBUG) {

cli/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,8 @@ async function main(): Promise<void> {
352352
const args = parseArgs();
353353
await callMethod(args);
354354

355-
// Let Node.js naturally exit instead of force-exiting
356-
// process.exit(0) was causing stdout truncation
355+
// Explicitly exit to ensure process terminates in CI
356+
process.exit(0);
357357
} catch (error) {
358358
handleError(error);
359359
}

0 commit comments

Comments
 (0)