Skip to content

Commit f3f06ef

Browse files
authored
Merge branch 'main' into con-refused
2 parents 5868c6b + 13014a0 commit f3f06ef

26 files changed

+978
-299
lines changed

.github/workflows/e2e_tests.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ on:
88

99
jobs:
1010
test:
11-
timeout-minutes: 5
11+
# Installing Playright dependencies can take quite awhile, and also depends on GitHub CI load.
12+
timeout-minutes: 15
1213
runs-on: ubuntu-latest
1314

1415
steps:
@@ -20,8 +21,10 @@ jobs:
2021
- uses: actions/checkout@v4
2122

2223
- uses: actions/setup-node@v4
24+
id: setup_node
2325
with:
24-
node-version: 18
26+
node-version-file: package.json
27+
cache: npm
2528

2629
# Cache Playwright browsers
2730
- name: Cache Playwright browsers
@@ -44,11 +47,12 @@ jobs:
4447
if: steps.cache-playwright.outputs.cache-hit != 'true'
4548

4649
- name: Run Playwright tests
50+
id: playwright-tests
4751
run: npm run test:e2e
4852

4953
- name: Upload Playwright Report and Screenshots
5054
uses: actions/upload-artifact@v4
51-
if: always()
55+
if: steps.playwright-tests.conclusion != 'skipped'
5256
with:
5357
name: playwright-report
5458
path: |
@@ -59,15 +63,15 @@ jobs:
5963

6064
- name: Publish Playwright Test Summary
6165
uses: daun/playwright-report-summary@v3
62-
if: always()
66+
if: steps.playwright-tests.conclusion != 'skipped'
6367
with:
6468
create-comment: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
6569
report-file: client/results.json
6670
comment-title: "🎭 Playwright E2E Test Results"
6771
job-summary: true
6872
icon-style: "emojis"
6973
custom-info: |
70-
**Test Environment:** Ubuntu Latest, Node.js 18
74+
**Test Environment:** Ubuntu Latest, Node.js ${{ steps.setup_node.outputs.node-version }}
7175
**Browsers:** Chromium, Firefox
7276
7377
📊 [View Detailed HTML Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) (download artifacts)

.github/workflows/main.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919

2020
- uses: actions/setup-node@v4
2121
with:
22-
node-version: 18
22+
node-version-file: package.json
2323
cache: npm
2424

2525
# Working around https://github.com/npm/cli/issues/4828
@@ -53,7 +53,7 @@ jobs:
5353
- uses: actions/checkout@v4
5454
- uses: actions/setup-node@v4
5555
with:
56-
node-version: 18
56+
node-version-file: package.json
5757
cache: npm
5858
registry-url: "https://registry.npmjs.org"
5959

.node-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
22.x.x

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://127.0.0.1:6274 to interact with the inspector
10+
4. Use the web UI at http://localhost:6274 to interact with the inspector
1111

1212
## Development Process & Pull Requests
1313

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,20 @@ DANGEROUSLY_OMIT_AUTH=true npm start
168168

169169
#### Local-only Binding
170170

171-
By default, the MCP Inspector proxy server binds only to `127.0.0.1` (localhost) to prevent network access. This ensures the server is not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable:
171+
By default, both the MCP Inspector proxy server and client bind only to `localhost` to prevent network access. This ensures they are not accessible from other devices on the network. If you need to bind to all interfaces for development purposes, you can override this with the `HOST` environment variable:
172172

173173
```bash
174174
HOST=0.0.0.0 npm start
175175
```
176176

177-
**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes.
177+
**Warning:** Only bind to all interfaces in trusted network environments, as this exposes the proxy server's ability to execute local processes and both services to network access.
178178

179179
#### DNS Rebinding Protection
180180

181181
To prevent DNS rebinding attacks, the MCP Inspector validates the `Origin` header on incoming requests. By default, only requests from the client origin are allowed (respects `CLIENT_PORT` if set, defaulting to port 6274). You can configure additional allowed origins by setting the `ALLOWED_ORIGINS` environment variable (comma-separated list):
182182

183183
```bash
184-
ALLOWED_ORIGINS=http://localhost:6274,http://127.0.0.1:6274,http://localhost:8000 npm start
184+
ALLOWED_ORIGINS=http://localhost:6274,http://localhost:8000 npm start
185185
```
186186

187187
### Configuration
@@ -299,9 +299,12 @@ npx @modelcontextprotocol/inspector --cli node build/index.js --method resources
299299
# List available prompts
300300
npx @modelcontextprotocol/inspector --cli node build/index.js --method prompts/list
301301

302-
# Connect to a remote MCP server
302+
# Connect to a remote MCP server (default is SSE transport)
303303
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com
304304

305+
# Connect to a remote MCP server (with Streamable HTTP transport)
306+
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --transport http
307+
305308
# Call a tool on a remote server
306309
npx @modelcontextprotocol/inspector --cli https://my-mcp-server.example.com --method tools/call --tool-name remotetool --tool-arg param=value
307310

cli/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@modelcontextprotocol/inspector-cli",
3-
"version": "0.14.3",
3+
"version": "0.15.0",
44
"description": "CLI for the Model Context Protocol inspector",
55
"license": "MIT",
66
"author": "Anthropic, PBC (https://anthropic.com)",
@@ -21,7 +21,7 @@
2121
},
2222
"devDependencies": {},
2323
"dependencies": {
24-
"@modelcontextprotocol/sdk": "^1.13.0",
24+
"@modelcontextprotocol/sdk": "^1.13.1",
2525
"commander": "^13.1.0",
2626
"spawn-rx": "^5.1.2"
2727
}

cli/scripts/cli-tests.js

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ console.log(`${colors.BLUE}- Resource-related options (--uri)${colors.NC}`);
4444
console.log(
4545
`${colors.BLUE}- Prompt-related options (--prompt-name, --prompt-args)${colors.NC}`,
4646
);
47-
console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}\n`);
47+
console.log(`${colors.BLUE}- Logging options (--log-level)${colors.NC}`);
48+
console.log(
49+
`${colors.BLUE}- Transport types (--transport http/sse/stdio)${colors.NC}`,
50+
);
51+
console.log(
52+
`${colors.BLUE}- Transport inference from URL suffixes (/mcp, /sse)${colors.NC}`,
53+
);
54+
console.log(`\n`);
4855

4956
// Get directory paths
5057
const SCRIPTS_DIR = __dirname;
@@ -62,9 +69,11 @@ if (!fs.existsSync(OUTPUT_DIR)) {
6269
}
6370

6471
// Create a temporary directory for test files
65-
const TEMP_DIR = fs.mkdirSync(path.join(os.tmpdir(), "mcp-inspector-tests"), {
66-
recursive: true,
67-
});
72+
const TEMP_DIR = path.join(os.tmpdir(), "mcp-inspector-tests");
73+
fs.mkdirSync(TEMP_DIR, { recursive: true });
74+
75+
// Track servers for cleanup
76+
let runningServers = [];
6877

6978
process.on("exit", () => {
7079
try {
@@ -74,6 +83,21 @@ process.on("exit", () => {
7483
`${colors.RED}Failed to remove temp directory: ${err.message}${colors.NC}`,
7584
);
7685
}
86+
87+
runningServers.forEach((server) => {
88+
try {
89+
process.kill(-server.pid);
90+
} catch (e) {}
91+
});
92+
});
93+
94+
process.on("SIGINT", () => {
95+
runningServers.forEach((server) => {
96+
try {
97+
process.kill(-server.pid);
98+
} catch (e) {}
99+
});
100+
process.exit(1);
77101
});
78102

79103
// Use the existing sample config file
@@ -121,6 +145,11 @@ async function runBasicTest(testName, ...args) {
121145
stdio: ["ignore", "pipe", "pipe"],
122146
});
123147

148+
const timeout = setTimeout(() => {
149+
console.log(`${colors.YELLOW}Test timed out: ${testName}${colors.NC}`);
150+
child.kill();
151+
}, 10000);
152+
124153
// Pipe stdout and stderr to the output file
125154
child.stdout.pipe(outputStream);
126155
child.stderr.pipe(outputStream);
@@ -135,6 +164,7 @@ async function runBasicTest(testName, ...args) {
135164
});
136165

137166
child.on("close", (code) => {
167+
clearTimeout(timeout);
138168
outputStream.end();
139169

140170
if (code === 0) {
@@ -201,6 +231,13 @@ async function runErrorTest(testName, ...args) {
201231
stdio: ["ignore", "pipe", "pipe"],
202232
});
203233

234+
const timeout = setTimeout(() => {
235+
console.log(
236+
`${colors.YELLOW}Error test timed out: ${testName}${colors.NC}`,
237+
);
238+
child.kill();
239+
}, 10000);
240+
204241
// Pipe stdout and stderr to the output file
205242
child.stdout.pipe(outputStream);
206243
child.stderr.pipe(outputStream);
@@ -215,6 +252,7 @@ async function runErrorTest(testName, ...args) {
215252
});
216253

217254
child.on("close", (code) => {
255+
clearTimeout(timeout);
218256
outputStream.end();
219257

220258
// For error tests, we expect a non-zero exit code
@@ -611,6 +649,79 @@ async function runTests() {
611649
"debug",
612650
);
613651

652+
console.log(
653+
`\n${colors.YELLOW}=== Running HTTP Transport Tests ===${colors.NC}`,
654+
);
655+
656+
console.log(
657+
`${colors.BLUE}Starting server-everything in streamableHttp mode.${colors.NC}`,
658+
);
659+
const httpServer = spawn(
660+
"npx",
661+
["@modelcontextprotocol/server-everything", "streamableHttp"],
662+
{
663+
detached: true,
664+
stdio: "ignore",
665+
},
666+
);
667+
runningServers.push(httpServer);
668+
669+
await new Promise((resolve) => setTimeout(resolve, 3000));
670+
671+
// Test 25: HTTP transport inferred from URL ending with /mcp
672+
await runBasicTest(
673+
"http_transport_inferred",
674+
"http://127.0.0.1:3001/mcp",
675+
"--cli",
676+
"--method",
677+
"tools/list",
678+
);
679+
680+
// Test 26: HTTP transport with explicit --transport http flag
681+
await runBasicTest(
682+
"http_transport_with_explicit_flag",
683+
"http://127.0.0.1:3001",
684+
"--transport",
685+
"http",
686+
"--cli",
687+
"--method",
688+
"tools/list",
689+
);
690+
691+
// Test 27: HTTP transport with suffix and --transport http flag
692+
await runBasicTest(
693+
"http_transport_with_explicit_flag_and_suffix",
694+
"http://127.0.0.1:3001/mcp",
695+
"--transport",
696+
"http",
697+
"--cli",
698+
"--method",
699+
"tools/list",
700+
);
701+
702+
// Test 28: SSE transport given to HTTP server (should fail)
703+
await runErrorTest(
704+
"sse_transport_given_to_http_server",
705+
"http://127.0.0.1:3001",
706+
"--transport",
707+
"sse",
708+
"--cli",
709+
"--method",
710+
"tools/list",
711+
);
712+
713+
// Kill HTTP server
714+
try {
715+
process.kill(-httpServer.pid);
716+
console.log(
717+
`${colors.BLUE}HTTP server killed, waiting for port to be released...${colors.NC}`,
718+
);
719+
} catch (e) {
720+
console.log(
721+
`${colors.RED}Error killing HTTP server: ${e.message}${colors.NC}`,
722+
);
723+
}
724+
614725
// Print test summary
615726
console.log(`\n${colors.YELLOW}=== Test Summary ===${colors.NC}`);
616727
console.log(`${colors.GREEN}Passed: ${PASSED_TESTS}${colors.NC}`);

cli/src/index.ts

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,13 @@ type Args = {
2929
logLevel?: LogLevel;
3030
toolName?: string;
3131
toolArg?: Record<string, string>;
32+
transport?: "sse" | "stdio" | "http";
3233
};
3334

34-
function createTransportOptions(target: string[]): TransportOptions {
35+
function createTransportOptions(
36+
target: string[],
37+
transport?: "sse" | "stdio" | "http",
38+
): TransportOptions {
3539
if (target.length === 0) {
3640
throw new Error(
3741
"Target is required. Specify a URL or a command to execute.",
@@ -50,16 +54,38 @@ function createTransportOptions(target: string[]): TransportOptions {
5054
throw new Error("Arguments cannot be passed to a URL-based MCP server.");
5155
}
5256

57+
let transportType: "sse" | "stdio" | "http";
58+
if (transport) {
59+
if (!isUrl && transport !== "stdio") {
60+
throw new Error("Only stdio transport can be used with local commands.");
61+
}
62+
if (isUrl && transport === "stdio") {
63+
throw new Error("stdio transport cannot be used with URLs.");
64+
}
65+
transportType = transport;
66+
} else if (isUrl) {
67+
const url = new URL(command);
68+
if (url.pathname.endsWith("/mcp")) {
69+
transportType = "http";
70+
} else if (url.pathname.endsWith("/sse")) {
71+
transportType = "sse";
72+
} else {
73+
transportType = "sse";
74+
}
75+
} else {
76+
transportType = "stdio";
77+
}
78+
5379
return {
54-
transportType: isUrl ? "sse" : "stdio",
80+
transportType,
5581
command: isUrl ? undefined : command,
5682
args: isUrl ? undefined : commandArgs,
5783
url: isUrl ? command : undefined,
5884
};
5985
}
6086

6187
async function callMethod(args: Args): Promise<void> {
62-
const transportOptions = createTransportOptions(args.target);
88+
const transportOptions = createTransportOptions(args.target, args.transport);
6389
const transport = createTransport(transportOptions);
6490
const client = new Client({
6591
name: "inspector-cli",
@@ -214,6 +240,22 @@ function parseArgs(): Args {
214240

215241
return value as LogLevel;
216242
},
243+
)
244+
//
245+
// Transport options
246+
//
247+
.option(
248+
"--transport <type>",
249+
"Transport type (sse, http, or stdio). Auto-detected from URL: /mcp → http, /sse → sse, commands → stdio",
250+
(value: string) => {
251+
const validTransports = ["sse", "http", "stdio"];
252+
if (!validTransports.includes(value)) {
253+
throw new Error(
254+
`Invalid transport type: ${value}. Valid types are: ${validTransports.join(", ")}`,
255+
);
256+
}
257+
return value as "sse" | "http" | "stdio";
258+
},
217259
);
218260

219261
// Parse only the arguments before --

0 commit comments

Comments
 (0)