@@ -11,10 +11,6 @@ import { mcpOauthMock, mcpOauthMockFns } from '@sim/testing/mocks/mcp-oauth.mock
1111import { mcpServiceMock , mcpServiceMockFns } from '@sim/testing/mocks/mcp-service.mock'
1212import { beforeEach , describe , expect , it , vi } from 'vitest'
1313
14- const { mockGenerateMcpServerId } = vi . hoisted ( ( ) => ( {
15- mockGenerateMcpServerId : vi . fn ( ) ,
16- } ) )
17-
1814vi . mock ( '@sim/audit' , ( ) => auditMock )
1915vi . mock ( '@sim/utils/id' , ( ) => idMock )
2016vi . mock ( '@/lib/core/security/encryption' , ( ) => encryptionMock )
@@ -29,22 +25,13 @@ vi.mock('@/lib/mcp/domain-check', () => ({
2925} ) )
3026vi . mock ( '@/lib/mcp/oauth' , ( ) => mcpOauthMock )
3127vi . 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- } ) )
4228vi . mock ( '@/lib/posthog/server' , ( ) => posthogServerMock )
4329
4430import {
4531 performCreateMcpServer ,
4632 performUpdateMcpServer ,
4733} from '@/lib/mcp/orchestration/server-lifecycle'
34+ import { generateMcpServerId } from '@/lib/mcp/utils'
4835
4936const mockClearCache = mcpServiceMockFns . mockClearCache
5037const 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