Skip to content

Commit ef59524

Browse files
test(mcp): exercise the real destination check in lifecycle tests (#8664)
1 parent 70e1980 commit ef59524

1 file changed

Lines changed: 27 additions & 23 deletions

File tree

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

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ import { mcpOauthMock, mcpOauthMockFns } from '@sim/testing/mocks/mcp-oauth.mock
1111
import { mcpServiceMock, mcpServiceMockFns } from '@sim/testing/mocks/mcp-service.mock'
1212
import { beforeEach, describe, expect, it, vi } from 'vitest'
1313

14-
const { mockGenerateMcpServerId } = vi.hoisted(() => ({
15-
mockGenerateMcpServerId: vi.fn(),
16-
}))
17-
1814
vi.mock('@sim/audit', () => auditMock)
1915
vi.mock('@sim/utils/id', () => idMock)
2016
vi.mock('@/lib/core/security/encryption', () => encryptionMock)
@@ -29,22 +25,13 @@ vi.mock('@/lib/mcp/domain-check', () => ({
2925
}))
3026
vi.mock('@/lib/mcp/oauth', () => mcpOauthMock)
3127
vi.mock('@/lib/mcp/service', () => mcpServiceMock)
32-
vi.mock('@/lib/mcp/utils', () => ({
33-
generateMcpServerId: mockGenerateMcpServerId,
34-
isSameMcpServerDestination: (a: string, b: string) => {
35-
const destination = (url: string) => {
36-
const parsed = new URL(url)
37-
return `${parsed.origin}${parsed.pathname}`
38-
}
39-
return destination(a) === destination(b)
40-
},
41-
}))
4228
vi.mock('@/lib/posthog/server', () => posthogServerMock)
4329

4430
import {
4531
performCreateMcpServer,
4632
performUpdateMcpServer,
4733
} from '@/lib/mcp/orchestration/server-lifecycle'
34+
import { generateMcpServerId } from '@/lib/mcp/utils'
4835

4936
const mockClearCache = mcpServiceMockFns.mockClearCache
5037
const mockOauthCredsChanged = mcpOauthMockFns.mockOauthCredsChanged
@@ -192,7 +179,6 @@ describe('MCP server lifecycle orchestration', () => {
192179
})
193180

194181
it('resets to disconnected when a create/upsert flips an existing OAuth server to headers', async () => {
195-
mockGenerateMcpServerId.mockReturnValue('server-1')
196182
dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'server-1' }])
197183
dbChainMockFns.limit.mockResolvedValueOnce([
198184
{
@@ -235,7 +221,10 @@ describe('MCP server lifecycle orchestration', () => {
235221
})
236222
)
237223
// ...and revoke the now-orphaned OAuth tokens.
238-
expect(mockRevokeOauthTokens).toHaveBeenCalledWith('server-1', 'workspace-1')
224+
expect(mockRevokeOauthTokens).toHaveBeenCalledWith(
225+
generateMcpServerId('workspace-1', 'https://example.com/mcp'),
226+
'workspace-1'
227+
)
239228
})
240229

241230
it('refuses a non-admin pointing an existing server at a different host', async () => {
@@ -260,6 +249,28 @@ describe('MCP server lifecycle orchestration', () => {
260249
expect(result).toMatchObject({ success: false, errorCode: 'forbidden' })
261250
})
262251

252+
it('refuses a non-admin changing only the credentials embedded in the URL', async () => {
253+
dbChainMockFns.limit.mockResolvedValueOnce([
254+
{
255+
url: 'https://user:pass@example.com/mcp',
256+
authType: 'headers',
257+
headers: {},
258+
oauthClientId: null,
259+
oauthClientSecret: null,
260+
},
261+
])
262+
263+
const result = await performUpdateMcpServer({
264+
workspaceId: 'workspace-1',
265+
userId: 'user-1',
266+
serverId: 'server-1',
267+
allowDestinationChange: false,
268+
url: 'https://attacker:pass@example.com/mcp',
269+
})
270+
271+
expect(result).toMatchObject({ success: false, errorCode: 'forbidden' })
272+
})
273+
263274
it('refuses a non-admin setting a URL on a server that has none', async () => {
264275
dbChainMockFns.limit.mockResolvedValueOnce([
265276
{
@@ -341,7 +352,6 @@ describe('MCP server lifecycle orchestration', () => {
341352
})
342353

343354
it('refuses a registration whose id collides with a server at a different host', async () => {
344-
mockGenerateMcpServerId.mockReturnValue('server-1')
345355
dbChainMockFns.limit.mockResolvedValueOnce([
346356
{
347357
id: 'server-1',
@@ -365,7 +375,6 @@ describe('MCP server lifecycle orchestration', () => {
365375
})
366376

367377
it('refuses a re-registration when the URL changed after it was checked', async () => {
368-
mockGenerateMcpServerId.mockReturnValue('server-1')
369378
dbChainMockFns.limit.mockResolvedValueOnce([
370379
{
371380
id: 'server-1',
@@ -390,7 +399,6 @@ describe('MCP server lifecycle orchestration', () => {
390399
})
391400

392401
it('registers a new server as disconnected rather than stamping a connection it never made', async () => {
393-
mockGenerateMcpServerId.mockReturnValue('server-1')
394402
dbChainMockFns.limit.mockResolvedValueOnce([])
395403
dbChainMockFns.limit.mockResolvedValueOnce([
396404
{
@@ -418,7 +426,6 @@ describe('MCP server lifecycle orchestration', () => {
418426
})
419427

420428
it('keeps an explicit auth type when an OAuth client ID is also supplied', async () => {
421-
mockGenerateMcpServerId.mockReturnValue('server-1')
422429
dbChainMockFns.limit.mockResolvedValueOnce([])
423430
dbChainMockFns.limit.mockResolvedValueOnce([
424431
{
@@ -449,7 +456,6 @@ describe('MCP server lifecycle orchestration', () => {
449456
})
450457

451458
it('leaves a re-registered server disconnected until discovery re-runs', async () => {
452-
mockGenerateMcpServerId.mockReturnValue('server-1')
453459
dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'server-1' }])
454460
dbChainMockFns.limit.mockResolvedValueOnce([
455461
{
@@ -499,7 +505,6 @@ describe('MCP server lifecycle orchestration', () => {
499505
* tool the server publishes, with no path back.
500506
*/
501507
it('keeps an OAuth server connected through a re-registration that only renames it', async () => {
502-
mockGenerateMcpServerId.mockReturnValue('server-1')
503508
dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'server-1' }])
504509
dbChainMockFns.limit.mockResolvedValueOnce([
505510
{
@@ -545,7 +550,6 @@ describe('MCP server lifecycle orchestration', () => {
545550
})
546551

547552
it('resets a re-registered server whose transport changes', async () => {
548-
mockGenerateMcpServerId.mockReturnValue('server-1')
549553
dbChainMockFns.returning.mockResolvedValueOnce([{ id: 'server-1' }])
550554
dbChainMockFns.limit.mockResolvedValueOnce([
551555
{

0 commit comments

Comments
 (0)