From 4da55e817826f59a5563d66f350d717c05f8ffd2 Mon Sep 17 00:00:00 2001 From: Dustin Healy <54083382+dustinhealy@users.noreply.github.com> Date: Tue, 23 Jun 2026 17:07:16 -0700 Subject: [PATCH] fix(mcp): refresh user activity on app tool calls; scope resource cache by user appToolCall was missing the updateUserLastActivity call that callTool includes, so app interactions from an iframe would not reset the idle timer on user-scoped MCP connections. The client-side resourceCache in mcpApps.ts was keyed only on serverName:uri, meaning a second user logging in within the 5-minute TTL could receive cached HTML from the previous user's session. The key now includes the userId, threaded from the Recoil user atom via useAppBridge. --- client/src/hooks/MCP/useAppBridge.ts | 9 ++++++++- client/src/utils/mcpApps.ts | 12 ++++++++---- packages/api/src/mcp/MCPManager.ts | 1 + 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client/src/hooks/MCP/useAppBridge.ts b/client/src/hooks/MCP/useAppBridge.ts index b207e5e76b..437b0c3bc9 100644 --- a/client/src/hooks/MCP/useAppBridge.ts +++ b/client/src/hooks/MCP/useAppBridge.ts @@ -1,4 +1,5 @@ import { useEffect, useRef } from 'react'; +import { useRecoilValue } from 'recoil'; import { AppBridge, PostMessageTransport, @@ -7,6 +8,7 @@ import { import type { UIResource } from 'librechat-data-provider'; import { callMCPAppTool, fetchMCPResourceHtml } from '~/utils/mcpApps'; import { logger } from '~/utils'; +import store from '~/store'; type SizeParams = { width?: number; height?: number }; @@ -17,6 +19,7 @@ export function useAppBridge( toolResult: { content: []; structuredContent?: Record } | undefined, onSizeChanged: (params: SizeParams) => void, ) { + const user = useRecoilValue(store.user); const bridgeRef = useRef(null); useEffect(() => { @@ -63,7 +66,11 @@ export function useAppBridge( bridge.addEventListener('sandboxready', async () => { try { - const html = await fetchMCPResourceHtml(resource.serverName as string, resource.uri); + const html = await fetchMCPResourceHtml( + resource.serverName as string, + resource.uri, + user?.id, + ); await bridge!.sendSandboxResourceReady({ html, csp: resource.csp as never, diff --git a/client/src/utils/mcpApps.ts b/client/src/utils/mcpApps.ts index e139830152..ee1c0ee3cf 100644 --- a/client/src/utils/mcpApps.ts +++ b/client/src/utils/mcpApps.ts @@ -24,8 +24,8 @@ const CACHE_TTL_MS = 5 * 60 * 1000; type CacheEntry = { promise: Promise; ts: number }; const resourceCache = new Map(); -export async function readMCPResource(serverName: string, uri: string) { - const key = `${serverName}:${uri}`; +export async function readMCPResource(serverName: string, uri: string, userId?: string) { + const key = `${userId ?? ''}:${serverName}:${uri}`; const now = Date.now(); const existing = resourceCache.get(key); @@ -46,8 +46,12 @@ export async function readMCPResource(serverName: string, uri: string) { return promise; } -export async function fetchMCPResourceHtml(serverName: string, uri: string): Promise { - const result = (await readMCPResource(serverName, uri)) as { +export async function fetchMCPResourceHtml( + serverName: string, + uri: string, + userId?: string, +): Promise { + const result = (await readMCPResource(serverName, uri, userId)) as { contents?: Array<{ text?: string }>; }; return result?.contents?.[0]?.text ?? ''; diff --git a/packages/api/src/mcp/MCPManager.ts b/packages/api/src/mcp/MCPManager.ts index 699f35ad52..d3523d8da4 100644 --- a/packages/api/src/mcp/MCPManager.ts +++ b/packages/api/src/mcp/MCPManager.ts @@ -676,6 +676,7 @@ Please follow these instructions when using tools from the respective MCP server user?: import('@librechat/data-schemas').IUser; }): Promise { const logPrefix = `[MCP][User: ${userId}][${serverName}]`; + if (userId && user) this.updateUserLastActivity(userId); const connection = await this.getConnection({ serverName, user }); if (!(await connection.isConnected())) {