const mongoose = require('mongoose'); const { ResourceType, PrincipalType, PrincipalModel } = require('librechat-data-provider'); const { MongoMemoryServer } = require('mongodb-memory-server'); const { canAccessMCPServerResource } = require('./canAccessMCPServerResource'); const { User, Role, AclEntry } = require('~/db/models'); const { createMCPServer } = require('~/models'); describe('canAccessMCPServerResource middleware', () => { let mongoServer; let req, res, next; let testUser; beforeAll(async () => { mongoServer = await MongoMemoryServer.create(); const mongoUri = mongoServer.getUri(); await mongoose.connect(mongoUri); }); afterAll(async () => { await mongoose.disconnect(); await mongoServer.stop(); }); beforeEach(async () => { await mongoose.connection.dropDatabase(); await Role.create({ name: 'test-role', permissions: { MCPSERVERS: { USE: true, CREATE: true, SHARED_GLOBAL: false, }, }, }); // Create a test user testUser = await User.create({ email: 'test@example.com', name: 'Test User', username: 'testuser', role: 'test-role', }); req = { user: { id: testUser._id, role: testUser.role }, params: {}, }; res = { status: jest.fn().mockReturnThis(), json: jest.fn(), }; next = jest.fn(); jest.clearAllMocks(); }); describe('middleware factory', () => { test('should throw error if requiredPermission is not provided', () => { expect(() => canAccessMCPServerResource({})).toThrow( 'canAccessMCPServerResource: requiredPermission is required and must be a number', ); }); test('should throw error if requiredPermission is not a number', () => { expect(() => canAccessMCPServerResource({ requiredPermission: '1' })).toThrow( 'canAccessMCPServerResource: requiredPermission is required and must be a number', ); }); test('should throw error if requiredPermission is null', () => { expect(() => canAccessMCPServerResource({ requiredPermission: null })).toThrow( 'canAccessMCPServerResource: requiredPermission is required and must be a number', ); }); test('should create middleware with default resourceIdParam (serverName)', () => { const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); expect(typeof middleware).toBe('function'); expect(middleware.length).toBe(3); // Express middleware signature }); test('should create middleware with custom resourceIdParam', () => { const middleware = canAccessMCPServerResource({ requiredPermission: 2, resourceIdParam: 'mcpId', }); expect(typeof middleware).toBe('function'); expect(middleware.length).toBe(3); }); }); describe('permission checking with real MCP servers', () => { test('should allow access when user is the MCP server author', async () => { // Create an MCP server owned by the test user const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Test MCP Server', }, author: testUser._id, }); // Create ACL entry for the author (owner permissions) await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, // All permissions (1+2+4+8) grantedBy: testUser._id, }); req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); // VIEW permission await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); test('should deny access when user is not the author and has no ACL entry', async () => { // Create an MCP server owned by a different user const otherUser = await User.create({ email: 'other@example.com', name: 'Other User', username: 'otheruser', role: 'test-role', }); const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Other User MCP Server', }, author: otherUser._id, }); // Create ACL entry for the other user (owner) await AclEntry.create({ principalType: PrincipalType.USER, principalId: otherUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, // All permissions grantedBy: otherUser._id, }); req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); // VIEW permission await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); expect(res.json).toHaveBeenCalledWith({ error: 'Forbidden', message: 'Insufficient permissions to access this mcpServer', }); }); test('should allow access when user has ACL entry with sufficient permissions', async () => { // Create an MCP server owned by a different user const otherUser = await User.create({ email: 'other2@example.com', name: 'Other User 2', username: 'otheruser2', role: 'test-role', }); const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Shared MCP Server', }, author: otherUser._id, }); // Create ACL entry granting view permission to test user await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 1, // VIEW permission grantedBy: otherUser._id, }); req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); // VIEW permission await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); test('should deny access when ACL permissions are insufficient', async () => { // Create an MCP server owned by a different user const otherUser = await User.create({ email: 'other3@example.com', name: 'Other User 3', username: 'otheruser3', role: 'test-role', }); const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Limited Access MCP Server', }, author: otherUser._id, }); // Create ACL entry granting only view permission await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 1, // VIEW permission only grantedBy: otherUser._id, }); req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 2 }); // EDIT permission required await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(403); expect(res.json).toHaveBeenCalledWith({ error: 'Forbidden', message: 'Insufficient permissions to access this mcpServer', }); }); test('should handle non-existent MCP server', async () => { req.params.serverName = 'non-existent-mcp-server'; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(404); expect(res.json).toHaveBeenCalledWith({ error: 'Not Found', message: 'mcpServer not found', }); }); test('should use custom resourceIdParam', async () => { const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Custom Param MCP Server', }, author: testUser._id, }); // Create ACL entry for the author await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, // All permissions grantedBy: testUser._id, }); req.params.mcpId = mcpServer.serverName; // Using custom param name const middleware = canAccessMCPServerResource({ requiredPermission: 1, resourceIdParam: 'mcpId', }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); }); describe('permission levels', () => { let mcpServer; beforeEach(async () => { mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Permission Test MCP Server', }, author: testUser._id, }); // Create ACL entry with all permissions for the owner await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, // All permissions (1+2+4+8) grantedBy: testUser._id, }); req.params.serverName = mcpServer.serverName; }); test('should support view permission (1)', async () => { const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('should support edit permission (2)', async () => { const middleware = canAccessMCPServerResource({ requiredPermission: 2 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('should support delete permission (4)', async () => { const middleware = canAccessMCPServerResource({ requiredPermission: 4 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('should support share permission (8)', async () => { const middleware = canAccessMCPServerResource({ requiredPermission: 8 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('should support combined permissions', async () => { const viewAndEdit = 1 | 2; // 3 const middleware = canAccessMCPServerResource({ requiredPermission: viewAndEdit }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); }); }); describe('integration with resolveMCPServerId', () => { test('should resolve serverName to MongoDB ObjectId correctly', async () => { const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Integration Test MCP Server', }, author: testUser._id, }); // Create ACL entry for the author await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, // All permissions grantedBy: testUser._id, }); req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); // Verify that req.resourceAccess was set correctly expect(req.resourceAccess).toBeDefined(); expect(req.resourceAccess.resourceType).toBe(ResourceType.MCPSERVER); expect(req.resourceAccess.resourceId.toString()).toBe(mcpServer._id.toString()); expect(req.resourceAccess.customResourceId).toBe(mcpServer.serverName); }); test('should work with MCP server CRUD operations', async () => { // Create MCP server const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'CRUD Test MCP Server', description: 'Testing integration', }, author: testUser._id, }); // Create ACL entry for the author await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, // All permissions grantedBy: testUser._id, }); req.params.serverName = mcpServer.serverName; // Test view access const viewMiddleware = canAccessMCPServerResource({ requiredPermission: 1 }); await viewMiddleware(req, res, next); expect(next).toHaveBeenCalled(); jest.clearAllMocks(); // Update the MCP server const { updateMCPServer } = require('~/models'); await updateMCPServer(mcpServer.serverName, { config: { type: 'sse', url: 'https://example.com/mcp', title: 'CRUD Test MCP Server', description: 'Updated description', }, }); // Test edit access const editMiddleware = canAccessMCPServerResource({ requiredPermission: 2 }); await editMiddleware(req, res, next); expect(next).toHaveBeenCalled(); }); test('should handle stdio type MCP server', async () => { const mcpServer = await createMCPServer({ config: { type: 'stdio', command: 'node', args: ['server.js'], title: 'Stdio MCP Server', }, author: testUser._id, }); // Create ACL entry for the author await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer._id, permBits: 15, grantedBy: testUser._id, }); req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(req.resourceAccess.resourceInfo.config.type).toBe('stdio'); }); }); describe('authentication and authorization edge cases', () => { test('should return 400 when serverName parameter is missing', async () => { // Don't set req.params.serverName const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(400); expect(res.json).toHaveBeenCalledWith({ error: 'Bad Request', message: 'serverName is required', }); }); test('should return 401 when user is not authenticated', async () => { req.user = null; req.params.serverName = 'some-server'; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(401); expect(res.json).toHaveBeenCalledWith({ error: 'Unauthorized', message: 'Authentication required', }); }); test('should return 401 when user id is missing', async () => { req.user = { role: 'test-role' }; // No id req.params.serverName = 'some-server'; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(401); expect(res.json).toHaveBeenCalledWith({ error: 'Unauthorized', message: 'Authentication required', }); }); test('should allow admin users to bypass permission checks', async () => { const { SystemRoles } = require('librechat-data-provider'); // Create an MCP server owned by another user const otherUser = await User.create({ email: 'owner@example.com', name: 'Owner User', username: 'owneruser', role: 'test-role', }); const mcpServer = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp', title: 'Admin Test MCP Server', }, author: otherUser._id, }); // Set user as admin req.user = { id: testUser._id, role: SystemRoles.ADMIN }; req.params.serverName = mcpServer.serverName; const middleware = canAccessMCPServerResource({ requiredPermission: 4 }); // DELETE permission await middleware(req, res, next); expect(next).toHaveBeenCalled(); expect(res.status).not.toHaveBeenCalled(); }); }); describe('error handling', () => { test('should handle server returning null gracefully (treated as not found)', async () => { // When an MCP server is not found, findMCPServerById returns null // which the middleware correctly handles as a 404 req.params.serverName = 'definitely-non-existent-server'; const middleware = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware(req, res, next); expect(next).not.toHaveBeenCalled(); expect(res.status).toHaveBeenCalledWith(404); expect(res.json).toHaveBeenCalledWith({ error: 'Not Found', message: 'mcpServer not found', }); }); }); describe('multiple servers with same title', () => { test('should handle MCP servers with auto-generated suffixes', async () => { // Create multiple servers with the same title (will have different serverNames) const mcpServer1 = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp1', title: 'Duplicate Title', }, author: testUser._id, }); const mcpServer2 = await createMCPServer({ config: { type: 'sse', url: 'https://example.com/mcp2', title: 'Duplicate Title', }, author: testUser._id, }); // Create ACL entries for both await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer1._id, permBits: 15, grantedBy: testUser._id, }); await AclEntry.create({ principalType: PrincipalType.USER, principalId: testUser._id, principalModel: PrincipalModel.USER, resourceType: ResourceType.MCPSERVER, resourceId: mcpServer2._id, permBits: 15, grantedBy: testUser._id, }); // Verify they have different serverNames expect(mcpServer1.serverName).toBe('duplicate-title'); expect(mcpServer2.serverName).toBe('duplicate-title-2'); // Test access to first server req.params.serverName = mcpServer1.serverName; const middleware1 = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware1(req, res, next); expect(next).toHaveBeenCalled(); expect(req.resourceAccess.resourceId.toString()).toBe(mcpServer1._id.toString()); jest.clearAllMocks(); // Test access to second server req.params.serverName = mcpServer2.serverName; const middleware2 = canAccessMCPServerResource({ requiredPermission: 1 }); await middleware2(req, res, next); expect(next).toHaveBeenCalled(); expect(req.resourceAccess.resourceId.toString()).toBe(mcpServer2._id.toString()); }); }); });