feat(client): make all config initialize-able via search params

This commit is contained in:
Kent C. Dodds
2025-05-05 17:44:33 -06:00
parent 3c5c38462b
commit 2915cccd85
3 changed files with 68 additions and 23 deletions

View File

@@ -101,6 +101,12 @@ http://localhost:6274/?transport=streamable-http&serverUrl=http://localhost:8787
http://localhost:6274/?transport=stdio&serverCommand=npx&serverArgs=arg1%20arg2 http://localhost:6274/?transport=stdio&serverCommand=npx&serverArgs=arg1%20arg2
``` ```
You can also set initial config settings via query params, for example:
```
http://localhost:6274/?MCP_SERVER_REQUEST_TIMEOUT=10000&MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS=false&MCP_PROXY_FULL_ADDRESS=http://10.1.1.22:5577
```
Note that if both the query param and the corresponding localStorage item are set, the query param will take precedence. Note that if both the query param and the corresponding localStorage item are set, the query param will take precedence.
### From this repository ### From this repository

View File

@@ -49,7 +49,6 @@ import RootsTab from "./components/RootsTab";
import SamplingTab, { PendingRequest } from "./components/SamplingTab"; import SamplingTab, { PendingRequest } from "./components/SamplingTab";
import Sidebar from "./components/Sidebar"; import Sidebar from "./components/Sidebar";
import ToolsTab from "./components/ToolsTab"; import ToolsTab from "./components/ToolsTab";
import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
import { InspectorConfig } from "./lib/configurationTypes"; import { InspectorConfig } from "./lib/configurationTypes";
import { import {
getMCPProxyAddress, getMCPProxyAddress,
@@ -57,6 +56,7 @@ import {
getInitialTransportType, getInitialTransportType,
getInitialCommand, getInitialCommand,
getInitialArgs, getInitialArgs,
initializeInspectorConfig,
} from "./utils/configUtils"; } from "./utils/configUtils";
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1"; const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
@@ -92,27 +92,9 @@ const App = () => {
const [roots, setRoots] = useState<Root[]>([]); const [roots, setRoots] = useState<Root[]>([]);
const [env, setEnv] = useState<Record<string, string>>({}); const [env, setEnv] = useState<Record<string, string>>({});
const [config, setConfig] = useState<InspectorConfig>(() => { const [config, setConfig] = useState<InspectorConfig>(() =>
const savedConfig = localStorage.getItem(CONFIG_LOCAL_STORAGE_KEY); initializeInspectorConfig(CONFIG_LOCAL_STORAGE_KEY),
if (savedConfig) { );
// merge default config with saved config
const mergedConfig = {
...DEFAULT_INSPECTOR_CONFIG,
...JSON.parse(savedConfig),
} as InspectorConfig;
// update description of keys to match the new description (in case of any updates to the default config description)
Object.entries(mergedConfig).forEach(([key, value]) => {
mergedConfig[key as keyof InspectorConfig] = {
...value,
label: DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].label,
};
});
return mergedConfig;
}
return DEFAULT_INSPECTOR_CONFIG;
});
const [bearerToken, setBearerToken] = useState<string>(() => { const [bearerToken, setBearerToken] = useState<string>(() => {
return localStorage.getItem("lastBearerToken") || ""; return localStorage.getItem("lastBearerToken") || "";
}); });

View File

@@ -1,5 +1,8 @@
import { InspectorConfig } from "@/lib/configurationTypes"; import { InspectorConfig } from "@/lib/configurationTypes";
import { DEFAULT_MCP_PROXY_LISTEN_PORT } from "@/lib/constants"; import {
DEFAULT_MCP_PROXY_LISTEN_PORT,
DEFAULT_INSPECTOR_CONFIG,
} from "@/lib/constants";
export const getMCPProxyAddress = (config: InspectorConfig): string => { export const getMCPProxyAddress = (config: InspectorConfig): string => {
const proxyFullAddress = config.MCP_PROXY_FULL_ADDRESS.value as string; const proxyFullAddress = config.MCP_PROXY_FULL_ADDRESS.value as string;
@@ -67,3 +70,57 @@ export const getInitialArgs = (): string => {
if (param) return param; if (param) return param;
return localStorage.getItem("lastArgs") || ""; return localStorage.getItem("lastArgs") || "";
}; };
// Returns a map of config key -> value from query params if present
export const getConfigOverridesFromQueryParams = (
defaultConfig: InspectorConfig,
): Partial<InspectorConfig> => {
const url = new URL(window.location.href);
const overrides: Partial<InspectorConfig> = {};
for (const key of Object.keys(defaultConfig)) {
const param = url.searchParams.get(key);
if (param !== null) {
// Try to coerce to correct type based on default value
const defaultValue = defaultConfig[key as keyof InspectorConfig].value;
let value: string | number | boolean = param;
if (typeof defaultValue === "number") {
value = Number(param);
} else if (typeof defaultValue === "boolean") {
value = param === "true";
}
overrides[key as keyof InspectorConfig] = {
...defaultConfig[key as keyof InspectorConfig],
value,
};
}
}
return overrides;
};
export const initializeInspectorConfig = (
localStorageKey: string,
): InspectorConfig => {
const savedConfig = localStorage.getItem(localStorageKey);
let baseConfig: InspectorConfig;
if (savedConfig) {
// merge default config with saved config
const mergedConfig = {
...DEFAULT_INSPECTOR_CONFIG,
...JSON.parse(savedConfig),
} as InspectorConfig;
// update description of keys to match the new description (in case of any updates to the default config description)
for (const [key, value] of Object.entries(mergedConfig)) {
mergedConfig[key as keyof InspectorConfig] = {
...value,
label: DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].label,
};
}
baseConfig = mergedConfig;
} else {
baseConfig = DEFAULT_INSPECTOR_CONFIG;
}
// Apply query param overrides
const overrides = getConfigOverridesFromQueryParams(DEFAULT_INSPECTOR_CONFIG);
return { ...baseConfig, ...overrides };
};