Skip to content

Commit d3c3d09

Browse files
committed
Address PR review feedback (#5754)
- Clear MCP caches when implicit OAuth promotion changes the resolved auth type - Cover unchanged-client-ID promotion with a lifecycle regression test
1 parent ef7b8dc commit d3c3d09

2 files changed

Lines changed: 95 additions & 1 deletion

File tree

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import {
5+
auditMock,
6+
dbChainMock,
7+
dbChainMockFns,
8+
drizzleOrmMock,
9+
encryptionMock,
10+
loggerMock,
11+
posthogServerMock,
12+
resetDbChainMock,
13+
schemaMock,
14+
} from '@sim/testing'
15+
import { beforeEach, describe, expect, it, vi } from 'vitest'
16+
17+
const { mockClearCache, mockOauthCredsChanged } = vi.hoisted(() => ({
18+
mockClearCache: vi.fn(),
19+
mockOauthCredsChanged: vi.fn(),
20+
}))
21+
22+
vi.mock('@sim/audit', () => auditMock)
23+
vi.mock('@sim/db', () => ({
24+
...dbChainMock,
25+
mcpServers: schemaMock.mcpServers,
26+
}))
27+
vi.mock('@sim/db/schema', () => ({
28+
mcpServerOauth: schemaMock.mcpServerOauth,
29+
}))
30+
vi.mock('@sim/logger', () => loggerMock)
31+
vi.mock('@sim/utils/id', () => ({ generateId: vi.fn() }))
32+
vi.mock('drizzle-orm', () => drizzleOrmMock)
33+
vi.mock('@/lib/core/security/encryption', () => encryptionMock)
34+
vi.mock('@/lib/mcp/domain-check', () => ({
35+
McpDnsResolutionError: class extends Error {},
36+
McpDomainNotAllowedError: class extends Error {},
37+
McpSsrfError: class extends Error {},
38+
validateMcpDomain: vi.fn(),
39+
validateMcpServerSsrf: vi.fn(),
40+
}))
41+
vi.mock('@/lib/mcp/oauth', () => ({
42+
detectMcpAuthType: vi.fn(),
43+
oauthCredsChanged: mockOauthCredsChanged,
44+
revokeMcpOauthTokens: vi.fn(),
45+
}))
46+
vi.mock('@/lib/mcp/service', () => ({
47+
mcpService: { clearCache: mockClearCache },
48+
}))
49+
vi.mock('@/lib/mcp/utils', () => ({ generateMcpServerId: vi.fn() }))
50+
vi.mock('@/lib/posthog/server', () => posthogServerMock)
51+
52+
import { performUpdateMcpServer } from '@/lib/mcp/orchestration/server-lifecycle'
53+
54+
describe('MCP server lifecycle orchestration', () => {
55+
beforeEach(() => {
56+
vi.clearAllMocks()
57+
resetDbChainMock()
58+
mockOauthCredsChanged.mockResolvedValue(false)
59+
})
60+
61+
it('clears the workspace cache when an OAuth client ID implicitly changes the auth type', async () => {
62+
dbChainMockFns.limit.mockResolvedValueOnce([
63+
{
64+
url: 'https://example.com/mcp',
65+
authType: 'headers',
66+
oauthClientId: 'client-1',
67+
oauthClientSecret: null,
68+
},
69+
])
70+
dbChainMockFns.returning.mockResolvedValueOnce([
71+
{
72+
id: 'server-1',
73+
workspaceId: 'workspace-1',
74+
name: 'Example',
75+
transport: 'streamable-http',
76+
url: 'https://example.com/mcp',
77+
authType: 'oauth',
78+
},
79+
])
80+
81+
const result = await performUpdateMcpServer({
82+
workspaceId: 'workspace-1',
83+
userId: 'user-1',
84+
serverId: 'server-1',
85+
oauthClientId: 'client-1',
86+
oauthClientIdProvided: true,
87+
})
88+
89+
expect(result.success).toBe(true)
90+
expect(dbChainMockFns.set).toHaveBeenCalledWith(expect.objectContaining({ authType: 'oauth' }))
91+
expect(mockClearCache).toHaveBeenCalledWith('workspace-1')
92+
})
93+
})

apps/sim/lib/mcp/orchestration/server-lifecycle.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ export async function performUpdateMcpServer(
351351
})
352352
const shouldClearOauth = urlChanged || credsChanged
353353
const resolvedAuthType = (updateData.authType ?? currentServer.authType) as McpAuthType
354+
const authTypeChanged = resolvedAuthType !== currentServer.authType
354355
if (shouldClearOauth && resolvedAuthType === 'oauth') {
355356
updateData.connectionStatus = 'disconnected'
356357
updateData.lastConnected = null
@@ -385,7 +386,7 @@ export async function performUpdateMcpServer(
385386
urlChanged ||
386387
credsChanged ||
387388
params.transport !== undefined ||
388-
params.authType !== undefined ||
389+
authTypeChanged ||
389390
params.enabled !== undefined ||
390391
params.headers !== undefined ||
391392
params.timeout !== undefined ||

0 commit comments

Comments
 (0)