Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,11 +357,11 @@ npx @modelcontextprotocol/inspector --config mcp.json

> **Tip:** You can easily generate this configuration format using the **Server Entry** and **Servers File** buttons in the Inspector UI, as described in the Servers File Export section above.

You can also set the initial `transport` type, `serverUrl`, `serverCommand`, and `serverArgs` via query params, for example:
You can also set the initial `transport` type, `serverUrl`, `connectionType`, `serverCommand`, and `serverArgs` via query params, for example:

```
http://localhost:6274/?transport=sse&serverUrl=http://localhost:8787/sse
http://localhost:6274/?transport=streamable-http&serverUrl=http://localhost:8787/mcp
http://localhost:6274/?transport=streamable-http&serverUrl=http://localhost:8787/mcp&connectionType=direct
http://localhost:6274/?transport=stdio&serverCommand=npx&serverArgs=arg1%20arg2
```

Expand Down
11 changes: 11 additions & 0 deletions client/bin/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ async function startDevServer(serverOptions) {
abort,
transport,
serverUrl,
connectionType,
} = serverOptions;
const serverCommand = "npx";
const serverArgs = ["tsx", "watch", "--clear-screen=false", "src/index.ts"];
Expand All @@ -51,6 +52,7 @@ async function startDevServer(serverOptions) {
MCP_ENV_VARS: JSON.stringify(envVars),
...(transport ? { MCP_TRANSPORT: transport } : {}),
...(serverUrl ? { MCP_SERVER_URL: serverUrl } : {}),
...(connectionType ? { MCP_CONNECTION_TYPE: connectionType } : {}),
},
signal: abort.signal,
echoOutput: true,
Expand Down Expand Up @@ -91,6 +93,7 @@ async function startProdServer(serverOptions) {
mcpServerArgs,
transport,
serverUrl,
connectionType,
} = serverOptions;
const inspectorServerPath = resolve(
__dirname,
Expand All @@ -110,6 +113,7 @@ async function startProdServer(serverOptions) {
: []),
...(transport ? [`--transport=${transport}`] : []),
...(serverUrl ? [`--server-url=${serverUrl}`] : []),
...(connectionType ? [`--connection-type=${connectionType}`] : []),
],
{
env: {
Expand Down Expand Up @@ -233,6 +237,7 @@ async function main() {
let isDev = false;
let transport = null;
let serverUrl = null;
let connectionType = null;

for (let i = 0; i < args.length; i++) {
const arg = args[i];
Expand All @@ -257,6 +262,11 @@ async function main() {
continue;
}

if (parsingFlags && arg === "--connection-type" && i + 1 < args.length) {
connectionType = args[++i];
continue;
}

if (parsingFlags && arg === "-e" && i + 1 < args.length) {
const envVar = args[++i];
const equalsIndex = envVar.indexOf("=");
Expand Down Expand Up @@ -310,6 +320,7 @@ async function main() {
mcpServerArgs,
transport,
serverUrl,
connectionType,
};

const result = isDev
Expand Down
15 changes: 9 additions & 6 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ import {
getInitialTransportType,
getInitialCommand,
getInitialArgs,
getInitialConnectionType,
initializeInspectorConfig,
saveInspectorConfig,
getMCPTaskTtl,
Expand Down Expand Up @@ -179,13 +180,9 @@ const App = () => {
"stdio" | "sse" | "streamable-http"
>(getInitialTransportType);
const [connectionType, setConnectionType] = useState<"direct" | "proxy">(
() => {
return (
(localStorage.getItem("lastConnectionType") as "direct" | "proxy") ||
"proxy"
);
},
getInitialConnectionType,
);

const [logLevel, setLogLevel] = useState<LoggingLevel>("debug");
const [notifications, setNotifications] = useState<ServerNotification[]>([]);
const [roots, setRoots] = useState<Root[]>([]);
Expand Down Expand Up @@ -739,6 +736,12 @@ const App = () => {
if (data.defaultServerUrl) {
setSseUrl(data.defaultServerUrl);
}
if (
data.defaultConnectionType === "direct" ||
data.defaultConnectionType === "proxy"
) {
setConnectionType(data.defaultConnectionType);
}
})
.catch((error) =>
console.error("Error fetching default environment:", error),
Expand Down
57 changes: 57 additions & 0 deletions client/src/__tests__/App.config.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jest.mock("../utils/configUtils", () => ({
getInitialSseUrl: jest.fn(() => "http://localhost:3001/sse"),
getInitialCommand: jest.fn(() => "mcp-server-everything"),
getInitialArgs: jest.fn(() => ""),
getInitialConnectionType: jest.fn(() => "proxy"),
initializeInspectorConfig: jest.fn(() => DEFAULT_INSPECTOR_CONFIG),
saveInspectorConfig: jest.fn(),
}));
Expand Down Expand Up @@ -210,6 +211,62 @@ describe("App - Config Endpoint", () => {
);
});

test("applies defaultConnectionType from /config response", async () => {
const mockConfig = {
...DEFAULT_INSPECTOR_CONFIG,
MCP_PROXY_AUTH_TOKEN: {
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
value: "test-proxy-token",
},
};
mockInitializeInspectorConfig.mockReturnValue(mockConfig);

(global.fetch as jest.Mock).mockResolvedValue({
json: () =>
Promise.resolve({
defaultEnvironment: {},
defaultConnectionType: "direct",
}),
});

localStorage.setItem("lastConnectionType", "proxy");

render(<App />);

await waitFor(() => {
expect(localStorage.getItem("lastConnectionType")).toBe("direct");
});
});

test("ignores invalid defaultConnectionType values from /config", async () => {
const mockConfig = {
...DEFAULT_INSPECTOR_CONFIG,
MCP_PROXY_AUTH_TOKEN: {
...DEFAULT_INSPECTOR_CONFIG.MCP_PROXY_AUTH_TOKEN,
value: "test-proxy-token",
},
};
mockInitializeInspectorConfig.mockReturnValue(mockConfig);

(global.fetch as jest.Mock).mockResolvedValue({
json: () =>
Promise.resolve({
defaultEnvironment: {},
defaultConnectionType: "bogus",
}),
});

localStorage.setItem("lastConnectionType", "proxy");

render(<App />);

await waitFor(() => {
expect(global.fetch).toHaveBeenCalled();
});

expect(localStorage.getItem("lastConnectionType")).toBe("proxy");
});

test("handles config endpoint errors gracefully", async () => {
const mockConfig = {
...DEFAULT_INSPECTOR_CONFIG,
Expand Down
51 changes: 50 additions & 1 deletion client/src/utils/__tests__/configUtils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getMCPProxyAuthToken } from "../configUtils";
import { getMCPProxyAuthToken, getInitialConnectionType } from "../configUtils";
import { DEFAULT_INSPECTOR_CONFIG } from "../../lib/constants";
import { InspectorConfig } from "../../lib/configurationTypes";

Expand Down Expand Up @@ -69,4 +69,53 @@ describe("configUtils", () => {
});
});
});

describe("getInitialConnectionType", () => {
const originalLocation = window.location;

const setLocation = (search: string) => {
Object.defineProperty(window, "location", {
configurable: true,
value: new URL(`http://localhost:6274/${search}`),
});
};

beforeEach(() => {
localStorage.clear();
});

afterEach(() => {
Object.defineProperty(window, "location", {
configurable: true,
value: originalLocation,
});
});

test("returns 'direct' when query param is 'direct'", () => {
setLocation("?connectionType=direct");
expect(getInitialConnectionType()).toBe("direct");
});

test("returns 'proxy' when query param is 'proxy'", () => {
setLocation("?connectionType=proxy");
expect(getInitialConnectionType()).toBe("proxy");
});

test("falls back to localStorage when query param is missing", () => {
setLocation("");
localStorage.setItem("lastConnectionType", "direct");
expect(getInitialConnectionType()).toBe("direct");
});

test("ignores invalid query param values and falls back to localStorage", () => {
setLocation("?connectionType=bogus");
localStorage.setItem("lastConnectionType", "direct");
expect(getInitialConnectionType()).toBe("direct");
});

test("defaults to 'proxy' when nothing is set", () => {
setLocation("");
expect(getInitialConnectionType()).toBe("proxy");
});
});
});
11 changes: 11 additions & 0 deletions client/src/utils/configUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ export const getInitialArgs = (): string => {
return localStorage.getItem("lastArgs") || "";
};

export const getInitialConnectionType = (): "direct" | "proxy" => {
const param = getSearchParam("connectionType");
if (param === "proxy" || param === "direct") {
return param;
}
return (
(localStorage.getItem("lastConnectionType") as "direct" | "proxy") ||
"proxy"
);
};

// Returns a map of config key -> value from query params if present
export const getConfigOverridesFromQueryParams = (
defaultConfig: InspectorConfig,
Expand Down
2 changes: 2 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const { values } = parseArgs({
command: { type: "string", default: "" },
transport: { type: "string", default: "" },
"server-url": { type: "string", default: "" },
"connection-type": { type: "string", default: "" },
},
});

Expand Down Expand Up @@ -922,6 +923,7 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
defaultArgs: values.args,
defaultTransport: values.transport,
defaultServerUrl: values["server-url"],
defaultConnectionType: values["connection-type"],
});
} catch (error) {
console.error("Error in /config route:", error);
Expand Down