refactor: implement split storage for inspector config

- Add is_session_item field to ConfigItem type to control storage location
- Store auth tokens in sessionStorage (cleared on browser close)
- Store other settings in localStorage (persist between sessions)
- Maintain backward compatibility with existing config structure

This provides better security for auth tokens while preserving user
preferences across browser sessions.
This commit is contained in:
Felix Weinberger 2025-06-11 11:58:21 +01:00
commit ab062d8f9d
No known key found for this signature in database
4 changed files with 67 additions and 20 deletions

View file

@ -68,6 +68,7 @@ import {
getInitialCommand, getInitialCommand,
getInitialArgs, getInitialArgs,
initializeInspectorConfig, initializeInspectorConfig,
saveInspectorConfig,
} from "./utils/configUtils"; } from "./utils/configUtils";
const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1"; const CONFIG_LOCAL_STORAGE_KEY = "inspectorConfig_v1";
@ -226,7 +227,7 @@ const App = () => {
}, [headerName]); }, [headerName]);
useEffect(() => { useEffect(() => {
localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config)); saveInspectorConfig(CONFIG_LOCAL_STORAGE_KEY, config);
}, [config]); }, [config]);
// Auto-connect to previously saved serverURL after OAuth callback // Auto-connect to previously saved serverURL after OAuth callback

View file

@ -2,6 +2,7 @@ export type ConfigItem = {
label: string; label: string;
description: string; description: string;
value: string | number | boolean; value: string | number | boolean;
is_session_item: boolean;
}; };
/** /**

View file

@ -36,28 +36,33 @@ export const DEFAULT_INSPECTOR_CONFIG: InspectorConfig = {
label: "Request Timeout", label: "Request Timeout",
description: "Timeout for requests to the MCP server (ms)", description: "Timeout for requests to the MCP server (ms)",
value: 10000, value: 10000,
is_session_item: false,
}, },
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: { MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: {
label: "Reset Timeout on Progress", label: "Reset Timeout on Progress",
description: "Reset timeout on progress notifications", description: "Reset timeout on progress notifications",
value: true, value: true,
is_session_item: false,
}, },
MCP_REQUEST_MAX_TOTAL_TIMEOUT: { MCP_REQUEST_MAX_TOTAL_TIMEOUT: {
label: "Maximum Total Timeout", label: "Maximum Total Timeout",
description: description:
"Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)", "Maximum total timeout for requests sent to the MCP server (ms) (Use with progress notifications)",
value: 60000, value: 60000,
is_session_item: false,
}, },
MCP_PROXY_FULL_ADDRESS: { MCP_PROXY_FULL_ADDRESS: {
label: "Inspector Proxy Address", label: "Inspector Proxy Address",
description: description:
"Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577", "Set this if you are running the MCP Inspector Proxy on a non-default address. Example: http://10.1.1.22:5577",
value: "", value: "",
is_session_item: false,
}, },
MCP_PROXY_AUTH_TOKEN: { MCP_PROXY_AUTH_TOKEN: {
label: "Proxy Session Token", label: "Proxy Session Token",
description: description:
"Session token for authenticating with the MCP Proxy Server (displayed in proxy console on startup)", "Session token for authenticating with the MCP Proxy Server (displayed in proxy console on startup)",
value: "", value: "",
is_session_item: true,
}, },
} as const; } as const;

View file

@ -104,27 +104,67 @@ export const getConfigOverridesFromQueryParams = (
export const initializeInspectorConfig = ( export const initializeInspectorConfig = (
localStorageKey: string, localStorageKey: string,
): InspectorConfig => { ): InspectorConfig => {
const savedConfig = localStorage.getItem(localStorageKey); // Read persistent config from localStorage
let baseConfig: InspectorConfig; const savedPersistentConfig = localStorage.getItem(localStorageKey);
if (savedConfig) { // Read ephemeral config from sessionStorage
// merge default config with saved config const savedEphemeralConfig = sessionStorage.getItem(
const mergedConfig = { `${localStorageKey}_ephemeral`,
...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) // Start with default config
for (const [key, value] of Object.entries(mergedConfig)) { let baseConfig = { ...DEFAULT_INSPECTOR_CONFIG };
mergedConfig[key as keyof InspectorConfig] = {
...value, // Apply saved persistent config
label: DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].label, if (savedPersistentConfig) {
}; const parsedPersistentConfig = JSON.parse(savedPersistentConfig);
} baseConfig = { ...baseConfig, ...parsedPersistentConfig };
baseConfig = mergedConfig;
} else {
baseConfig = DEFAULT_INSPECTOR_CONFIG;
} }
// Apply saved ephemeral config
if (savedEphemeralConfig) {
const parsedEphemeralConfig = JSON.parse(savedEphemeralConfig);
baseConfig = { ...baseConfig, ...parsedEphemeralConfig };
}
// Ensure all config items have the latest labels/descriptions from defaults
for (const [key, value] of Object.entries(baseConfig)) {
baseConfig[key as keyof InspectorConfig] = {
...value,
label: DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].label,
description:
DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].description,
is_session_item:
DEFAULT_INSPECTOR_CONFIG[key as keyof InspectorConfig].is_session_item,
};
}
// Apply query param overrides // Apply query param overrides
const overrides = getConfigOverridesFromQueryParams(DEFAULT_INSPECTOR_CONFIG); const overrides = getConfigOverridesFromQueryParams(DEFAULT_INSPECTOR_CONFIG);
return { ...baseConfig, ...overrides }; return { ...baseConfig, ...overrides };
}; };
export const saveInspectorConfig = (
localStorageKey: string,
config: InspectorConfig,
): void => {
const persistentConfig: Partial<InspectorConfig> = {};
const ephemeralConfig: Partial<InspectorConfig> = {};
// Split config based on is_session_item flag
for (const [key, value] of Object.entries(config)) {
if (value.is_session_item) {
ephemeralConfig[key as keyof InspectorConfig] = value;
} else {
persistentConfig[key as keyof InspectorConfig] = value;
}
}
// Save persistent config to localStorage
localStorage.setItem(localStorageKey, JSON.stringify(persistentConfig));
// Save ephemeral config to sessionStorage
sessionStorage.setItem(
`${localStorageKey}_ephemeral`,
JSON.stringify(ephemeralConfig),
);
};