diff --git a/client/src/App.tsx b/client/src/App.tsx index 537f80bfa..0f028cba7 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -22,6 +22,7 @@ import { SESSION_KEYS, getServerSpecificKey } from "./lib/constants"; import { AuthDebuggerState, EMPTY_DEBUGGER_STATE } from "./lib/auth-types"; import { OAuthStateMachine } from "./lib/oauth-state-machine"; import { cacheToolOutputSchemas } from "./utils/schemaUtils"; +import { saveToolParamsForCache } from "./utils/toolCache"; import React, { Suspense, useCallback, @@ -703,6 +704,12 @@ const App = () => { const callTool = async (name: string, params: Record) => { lastToolCallOriginTabRef.current = currentTabRef.current; + // Save tool parameters to cache before making the call + const tool = tools.find((t) => t.name === name); + if (tool && sseUrl) { + saveToolParamsForCache(sseUrl, name, tool, params); + } + try { const response = await sendMCPRequest( { @@ -1035,6 +1042,7 @@ const App = () => { clearError("resources"); readResource(uri); }} + serverUrl={sseUrl} /> @@ -34,6 +35,7 @@ const ToolsTab = ({ nextCursor, resourceContent, onReadResource, + serverUrl, }: { tools: Tool[]; listTools: () => void; @@ -46,6 +48,7 @@ const ToolsTab = ({ error: string | null; resourceContent: Record; onReadResource?: (uri: string) => void; + serverUrl: string; }) => { const [params, setParams] = useState>({}); const [isToolRunning, setIsToolRunning] = useState(false); @@ -53,18 +56,36 @@ const ToolsTab = ({ const [isMetaExpanded, setIsMetaExpanded] = useState(false); useEffect(() => { - const params = Object.entries( - selectedTool?.inputSchema.properties ?? [], + if (!selectedTool) { + setParams({}); + return; + } + + // Generate default parameters from schema + const defaultParams = Object.entries( + selectedTool.inputSchema.properties ?? [], ).map(([key, value]) => [ key, generateDefaultValue( value as JsonSchemaType, key, - selectedTool?.inputSchema as JsonSchemaType, + selectedTool.inputSchema as JsonSchemaType, ), ]); - setParams(Object.fromEntries(params)); - }, [selectedTool]); + const defaultParamsObj = Object.fromEntries(defaultParams); + + // Fetch cached params from localStorage if they exist + const cachedParams = loadToolParamsFromCache( + serverUrl, + selectedTool.name, + selectedTool, + ); + + // Merge cached params with defaults, giving preference to cached values + const mergedParams = { ...defaultParamsObj, ...cachedParams }; + + setParams(mergedParams); + }, [selectedTool, serverUrl]); return ( diff --git a/client/src/utils/toolCache.ts b/client/src/utils/toolCache.ts new file mode 100644 index 000000000..4c34b7632 --- /dev/null +++ b/client/src/utils/toolCache.ts @@ -0,0 +1,63 @@ +import { Tool } from "@modelcontextprotocol/sdk/types.js"; + +export interface CacheKey { + serverUrl: string; + toolName: string; + paramNames: string[]; +} + +export function generateCacheKey( + serverUrl: string, + toolName: string, + tool: Tool, +): string { + const paramNames = Object.keys(tool.inputSchema.properties ?? {}).sort(); + const key = `tool_params_${btoa(`${serverUrl}_${toolName}_${JSON.stringify(paramNames)}`)}`; + return key; +} + +export function saveToolParamsForCache( + serverUrl: string, + toolName: string, + tool: Tool, + params: Record, +): void { + try { + const cacheKey = generateCacheKey(serverUrl, toolName, tool); + const cacheData = { + params, + timestamp: Date.now(), + toolName, + serverUrl, + }; + localStorage.setItem(cacheKey, JSON.stringify(cacheData)); + } catch (error) { + console.warn("Failed to save tool parameters to cache:", error); + } +} + +export function loadToolParamsFromCache( + serverUrl: string, + toolName: string, + tool: Tool, +): Record | null { + try { + const cacheKey = generateCacheKey(serverUrl, toolName, tool); + const cached = localStorage.getItem(cacheKey); + + if (!cached) { + return null; + } + + const cacheData = JSON.parse(cached); + + if (!cacheData.params) { + return null; + } + + return cacheData.params; + } catch (error) { + console.warn("Failed to load tool parameters from cache:", error); + return null; + } +}