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:
parent
32e6bdd13c
commit
ab062d8f9d
4 changed files with 67 additions and 20 deletions
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue