Skip to content

Commit 8412ea5

Browse files
committed
fix(mcp): write per-server cache from discoverServerTools to prevent post-OAuth refetch storm
1 parent 896eee3 commit 8412ea5

3 files changed

Lines changed: 33 additions & 4 deletions

File tree

apps/sim/app/api/mcp/oauth/callback/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
167167
}
168168

169169
try {
170-
await mcpService.clearCache(server.workspaceId)
170+
// discoverServerTools writes the result to this server's cache so the UI's
171+
// immediate refetch hits it instead of re-fetching live.
171172
await mcpService.discoverServerTools(session.user.id, server.id, server.workspaceId)
172173
} catch (e) {
173174
logger.warn('Post-auth tools refresh failed', toError(e).message)

apps/sim/lib/mcp/service.test.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,20 @@ const {
3838
})
3939

4040
vi.mock('@sim/db', () => {
41+
// `where(...)` resolves to the workspace's rows AND exposes `.limit()` for
42+
// chains like `getServerConfig` that do `select().from().where().limit(1)`.
43+
const where = (...args: unknown[]) => {
44+
const rowsPromise = Promise.resolve(mockGetWorkspaceServersRows(...args))
45+
const thenable = Object.assign(rowsPromise, {
46+
limit: (n: number) => rowsPromise.then((rows) => rows.slice(0, n)),
47+
})
48+
return thenable
49+
}
4150
const setter = vi.fn().mockReturnValue({ where: vi.fn().mockResolvedValue(undefined) })
4251
return {
4352
db: {
4453
select: vi.fn().mockReturnValue({
45-
from: vi.fn().mockReturnValue({
46-
where: (...args: unknown[]) => mockGetWorkspaceServersRows(...args),
47-
}),
54+
from: vi.fn().mockReturnValue({ where }),
4855
}),
4956
update: vi.fn().mockReturnValue({ set: setter }),
5057
insert: vi.fn(),
@@ -238,4 +245,18 @@ describe('McpService.discoverTools per-server caching', () => {
238245
expect(second.map((t) => t.name)).toEqual(['a-other'])
239246
expect(mockListTools).toHaveBeenCalledTimes(2)
240247
})
248+
249+
it('discoverServerTools primes the per-server cache for follow-up discoverTools', async () => {
250+
mockGetWorkspaceServersRows.mockResolvedValue([dbRow('mcp-a', 'A')])
251+
mockListTools.mockResolvedValueOnce([tool('a1', 'mcp-a')])
252+
253+
const tools = await mcpService.discoverServerTools(USER_ID, 'mcp-a', WORKSPACE_ID)
254+
expect(tools.map((t) => t.name)).toEqual(['a1'])
255+
expect(mockListTools).toHaveBeenCalledTimes(1)
256+
257+
mockListTools.mockClear()
258+
const second = await mcpService.discoverTools(USER_ID, WORKSPACE_ID)
259+
expect(second.map((t) => t.name)).toEqual(['a1'])
260+
expect(mockListTools).not.toHaveBeenCalled()
261+
})
241262
})

apps/sim/lib/mcp/service.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,13 @@ class McpService {
580580
try {
581581
const tools = await client.listTools()
582582
logger.info(`[${requestId}] Discovered ${tools.length} tools from server ${config.name}`)
583+
// Prime the per-server cache so the next discoverTools call hits it
584+
// instead of re-fetching live (which can stall on slow upstreams).
585+
await this.cacheAdapter
586+
.set(serverCacheKey(workspaceId, serverId), tools, this.cacheTimeout)
587+
.catch((err) =>
588+
logger.warn(`[${requestId}] Cache write failed for ${config.name}:`, err)
589+
)
583590
return tools
584591
} finally {
585592
await client.disconnect()

0 commit comments

Comments
 (0)