mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
* Feature: Dynamic MCP Server with Full UI Management * 🚦 feat: Add MCP Connection Status icons to MCPBuilder panel (#10805) * feature: Add MCP server connection status icons to MCPBuilder panel * refactor: Simplify MCPConfigDialog rendering in MCPBuilderPanel --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai> * fix: address code review feedback for MCP server management - Fix OAuth secret preservation to avoid mutating input parameter by creating a merged config copy in ServerConfigsDB.update() - Improve error handling in getResourcePermissionsMap to propagate critical errors instead of silently returning empty Map - Extract duplicated MCP server filter logic by exposing selectableServers from useMCPServerManager hook and using it in MCPSelect component * test: Update PermissionService tests to throw errors on invalid resource types - Changed the test for handling invalid resource types to ensure it throws an error instead of returning an empty permissions map. - Updated the expectation to check for the specific error message when an invalid resource type is provided. * feat: Implement retry logic for MCP server creation to handle race conditions - Enhanced the createMCPServer method to include retry logic with exponential backoff for handling duplicate key errors during concurrent server creation. - Updated tests to verify that all concurrent requests succeed and that unique server names are generated. - Added a helper function to identify MongoDB duplicate key errors, improving error handling during server creation. * refactor: StatusIcon to use CircleCheck for connected status - Replaced the PlugZap icon with CircleCheck in the ConnectedStatusIcon component to better represent the connected state. - Ensured consistent icon usage across the component for improved visual clarity. * test: Update AccessControlService tests to throw errors on invalid resource types - Modified the test for invalid resource types to ensure it throws an error with a specific message instead of returning an empty permissions map. - This change enhances error handling and improves test coverage for the AccessControlService. * fix: Update error message for missing server name in MCP server retrieval - Changed the error message returned when the server name is not provided from 'MCP ID is required' to 'Server name is required' for better clarity and accuracy in the API response. --------- Co-authored-by: Atef Bellaaj <slalom.bellaaj@external.daimlertruck.com> Co-authored-by: Danny Avila <danny@librechat.ai>
627 lines
20 KiB
JavaScript
627 lines
20 KiB
JavaScript
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());
|
|
});
|
|
});
|
|
});
|