Repository navigation
CLI: a URL wins over a positional command and over --transport stdio, both silently #2611
Description
Which version line?
v2 — current (@modelcontextprotocol/inspector@latest)
Which client?
CLI
Inspector version
2.9.0
Node version
v24.8.0
Operating system
macOS 27.0
Transport
Streamable HTTP
MCP server under inspection
Not a real server in any of these. Every case below is decided before a connection is attempted, by buildConfigFromOptions in core/mcp/node/config.ts, so https://example.com/mcp is enough to show which transport was chosen. No OAuth.
Steps to reproduce
- A positional stdio command together with
--server-url:
npx @modelcontextprotocol/[email protected] --cli node ./nonexistent-server.js \
--server-url https://example.com/mcp --method tools/list
--transport stdionext to an HTTP URL:
npx @modelcontextprotocol/[email protected] --cli --transport stdio \
--server-url https://example.com/mcp --method tools/list
- The same, with a URL whose path does not end in
/mcpor/sse:
npx @modelcontextprotocol/[email protected] --cli --transport stdio \
--server-url https://example.com/foo --method tools/list
- The first remote example in
clients/cli/README.md, copied as it stands:
npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com
Expected behavior
- The two targets contradict each other, so I expected the same kind of refusal the opposite mix already gets. A positional URL with extra arguments raises "Arguments cannot be passed to a URL-based MCP server", and a local command with
--transport httpraises "Only stdio transport can be used with local commands". - Either an error saying stdio cannot be used with a URL, or stdio honoured. Not silence.
- If the transport cannot be used with this target, say that. The URL path is irrelevant once
--transporthas been given. - A README example should run.
Actual behavior
node ./nonexistent-server.jsis dropped without a word and the Inspector connects tohttps://example.com/mcpover streamable HTTP. The giveaway is the error: it is a 405 from example.com, not a complaint about a missing file.--transport stdiohas no effect. The connection goes out over streamable HTTP, picked from the/mcpsuffix.- The error is "Transport type not specified and could not be determined from URL: https://example.com/foo", which is wrong on its own terms, since the transport was specified.
- "Transport type not specified and could not be determined from URL: https://my-mcp-server.example.com", exit 1. The heading above it says "default is SSE", which has not been true since the path sniffing landed. The two examples after it pass
--transport httpand are fine.
Logs, errors, or screenshots
$ npx @modelcontextprotocol/[email protected] --cli node ./nonexistent-server.js --server-url https://example.com/mcp --method tools/list
{"error":{"code":"error","message":"Error POSTing to endpoint: <!doctype html><html lang=\"en\"><head><title>Example Domain</title>...","status":405}}
$ npx @modelcontextprotocol/[email protected] --cli --transport stdio --server-url https://example.com/mcp --method tools/list
{"error":{"code":"error","message":"Error POSTing to endpoint: <!doctype html><html lang=\"en\"><head><title>Example Domain</title>...","status":405}}
$ npx @modelcontextprotocol/[email protected] --cli --transport stdio --server-url https://example.com/foo --method tools/list
{"error":{"code":"error","message":"Transport type not specified and could not be determined from URL: https://example.com/foo."}}
$ npx @modelcontextprotocol/[email protected] --cli https://my-mcp-server.example.com --method tools/list
{"error":{"code":"error","message":"Transport type not specified and could not be determined from URL: https://my-mcp-server.example.com."}}
All four share one cause in buildConfigFromOptions. url is resolved as urlFromTarget ?? options.serverUrl ?? null, and once it is truthy the function builds an HTTP or SSE config and returns, so the positional command is never looked at again. The guard that catches the opposite mix is if (rest.length > 0 && urlFromTarget), which only fires when the URL itself came from the positional argument, and the Only stdio transport can be used with local commands check sits after the early return, on the command path. Inside the URL branch, options.transport is only consulted for sse, http and streamable-http; stdio falls through to the path sniffing and then produces the "not specified" message.
Already prototyped a fix?
No diff, and no prompt either, since I have not built it. What I would want out of it, if it helps:
Decide the target before the transport. A positional command together with --server-url is a contradiction and should be refused with a message naming both, the way the mirror case already is. --transport stdio with a URL is also a contradiction and should be refused rather than ignored. The path sniffing should only run when --transport was absent, so a given-but-impossible transport never reports itself as missing. And the README's first remote example needs either --transport sse or --transport http added, with the "default is SSE" heading dropped, since there is no default any more.
Before you submit
I searched the existing issues for transport stdio, server-url, Transport type not specified, buildConfigFromOptions and positional command url, and found nothing covering this. The closest open ones, #2562 and #2574, are about OAuth URL overrides and stored-auth write-back.
Activity
cliffhall commentedon Oct 12, 2026
Triage: Priority Medium (total 6)
--transport stdiois silently overridden by a URL, and the first remote example inclients/cli/README.mdfails as writtenbuglabelBoard: #28, Status
Incoming(awaiting maintainer review — no milestone yet).cliffhall commentedon Oct 12, 2026
Fixed by #2701 (merged into v2/main).
fix(cli): refuse a URL that contradicts a positional command or --tra…