const crypto = require('crypto');
const express = require('express');
const request = require('supertest');
const mongoose = require('mongoose');
const cookieParser = require('cookie-parser');
const { getBasePath, PENDING_STALE_MS } = require('@librechat/api');
const { MongoMemoryServer } = require('mongodb-memory-server');

function generateTestCsrfToken(flowId) {
  return crypto
    .createHmac('sha256', process.env.JWT_SECRET)
    .update(flowId)
    .digest('hex')
    .slice(0, 32);
}

const mockRegistryInstance = {
  getServerConfig: jest.fn(),
  getOAuthServers: jest.fn(),
  getAllServerConfigs: jest.fn(),
  ensureConfigServers: jest.fn().mockResolvedValue({}),
  addServer: jest.fn(),
  updateServer: jest.fn(),
  removeServer: jest.fn(),
  getAllowedDomains: jest.fn().mockReturnValue(null),
  getAllowedAddresses: jest.fn().mockReturnValue(null),
  resolveAllowlists: jest.fn().mockResolvedValue({
    allowedDomains: null,
    allowedAddresses: null,
    useSSRFProtection: true,
  }),
};
let mockMCPUseAllowed = true;

jest.mock('@librechat/api', () => {
  const actual = jest.requireActual('@librechat/api');
  return {
    ...actual,
    MCPOAuthHandler: {
      initiateOAuthFlow: jest.fn(),
      getFlowState: jest.fn(),
      completeOAuthFlow: jest.fn(),
      generateFlowId: jest.fn(),
      generateTokenFlowId: jest.fn(),
      parseFlowId: jest.fn(),
      buildStoredClientMetadata: jest.fn((metadata, resourceMetadata) =>
        metadata
          ? {
              ...metadata,
              ...(resourceMetadata?.resource && { resource: resourceMetadata.resource }),
            }
          : undefined,
      ),
      resolveStateToFlowId: jest.fn(async (state) => state),
      storeStateMapping: jest.fn(),
      deleteStateMapping: jest.fn(),
    },
    MCPTokenStorage: {
      storeTokens: jest.fn(),
      getClientInfoAndMetadata: jest.fn(),
      getTokens: jest.fn(),
      deleteUserTokens: jest.fn(),
    },
    getUserMCPAuthMap: jest.fn(),
    generateCheckAccess: jest.fn(({ permissionType, permissions }) => (req, res, next) => {
      const { PermissionTypes, Permissions } = require('librechat-data-provider');
      const isMCPUseCheck =
        permissionType === PermissionTypes.MCP_SERVERS && permissions.includes(Permissions.USE);
      if (isMCPUseCheck && !mockMCPUseAllowed) {
        return res.status(403).json({ message: 'Forbidden: Insufficient permissions' });
      }
      return next();
    }),
    MCPServersRegistry: {
      getInstance: () => mockRegistryInstance,
    },
    // Error handling utilities (from @librechat/api mcp/errors)
    isMCPDomainNotAllowedError: (error) => error?.code === 'MCP_DOMAIN_NOT_ALLOWED',
    isMCPInspectionFailedError: (error) => error?.code === 'MCP_INSPECTION_FAILED',
    MCPErrorCodes: {
      DOMAIN_NOT_ALLOWED: 'MCP_DOMAIN_NOT_ALLOWED',
      INSPECTION_FAILED: 'MCP_INSPECTION_FAILED',
    },
  };
});

jest.mock('@librechat/data-schemas', () => ({
  getTenantId: jest.fn(),
  tenantStorage: {
    run: jest.fn((store, fn) => fn()),
  },
  logger: {
    debug: jest.fn(),
    info: jest.fn(),
    warn: jest.fn(),
    error: jest.fn(),
  },
  createModels: jest.fn(() => ({
    User: {
      findOne: jest.fn(),
      findById: jest.fn(),
    },
    Conversation: {
      findOne: jest.fn(),
      findById: jest.fn(),
    },
  })),
  createMethods: jest.fn(() => ({
    findUser: jest.fn(),
  })),
}));

jest.mock('~/models', () => ({
  findToken: jest.fn(),
  updateToken: jest.fn(),
  createToken: jest.fn(),
  deleteTokens: jest.fn(),
  findPluginAuthsByKeys: jest.fn(),
  getRoleByName: jest.fn(),
}));

jest.mock('~/server/services/Config', () => ({
  setCachedTools: jest.fn(),
  getCachedTools: jest.fn(),
  getMCPServerTools: jest.fn(),
  loadCustomConfig: jest.fn(),
  getAppConfig: jest.fn().mockResolvedValue({ mcpConfig: {} }),
}));

jest.mock('~/server/services/Config/mcp', () => ({
  updateMCPServerTools: jest.fn(),
}));

const mockResolveAllMcpConfigs = jest.fn().mockResolvedValue({});
const mockResolveMcpConfigNames = jest.fn().mockResolvedValue([]);
jest.mock('~/server/services/MCP', () => ({
  getMCPSetupData: jest.fn(),
  resolveConfigServers: jest.fn().mockResolvedValue({}),
  resolveMcpConfigNames: (...args) => mockResolveMcpConfigNames(...args),
  resolveAllMcpConfigs: (...args) => mockResolveAllMcpConfigs(...args),
  getServerConnectionStatus: jest.fn(),
}));

jest.mock('~/server/services/PluginService', () => ({
  getUserPluginAuthValue: jest.fn(),
}));

jest.mock('~/config', () => ({
  getMCPManager: jest.fn(),
  getFlowStateManager: jest.fn(),
  getOAuthReconnectionManager: jest.fn(),
  getMCPServersRegistry: jest.fn(() => mockRegistryInstance),
}));

jest.mock('~/cache', () => ({
  getLogStores: jest.fn(),
}));

jest.mock('~/server/middleware', () => ({
  requireJwtAuth: (req, res, next) => next(),
  canAccessMCPServerResource: () => (req, res, next) => next(),
}));

jest.mock('~/server/services/Tools/mcp', () => ({
  reinitMCPServer: jest.fn(),
}));

describe('MCP Routes', () => {
  let app;
  let mongoServer;
  let mcpRouter;
  let currentUser;

  beforeAll(async () => {
    mongoServer = await MongoMemoryServer.create();
    await mongoose.connect(mongoServer.getUri());

    require('~/db/models');

    mcpRouter = require('../mcp');

    app = express();
    app.use(express.json());
    app.use(cookieParser());

    app.use((req, res, next) => {
      req.user = currentUser ?? { id: 'test-user-id' };
      next();
    });

    app.use('/api/mcp', mcpRouter);
  });

  afterAll(async () => {
    await mongoose.disconnect();
    await mongoServer.stop();
  });

  beforeEach(() => {
    jest.clearAllMocks();
    currentUser = undefined;
    mockResolveAllMcpConfigs.mockResolvedValue({});
    mockResolveMcpConfigNames.mockResolvedValue([]);
    const { MCPOAuthHandler } = require('@librechat/api');
    const { getTenantId } = require('@librechat/data-schemas');
    getTenantId.mockReturnValue(undefined);
    MCPOAuthHandler.generateFlowId.mockImplementation((userId, serverName, tenantId) => {
      const flowId = `${userId}:${serverName}`;
      return tenantId ? `tenant:${encodeURIComponent(tenantId)}:${flowId}` : flowId;
    });
    MCPOAuthHandler.generateTokenFlowId.mockImplementation((userId, serverName, tenantId) => {
      const flowId = `${userId}:${serverName}`;
      return tenantId ? `tenant:${encodeURIComponent(tenantId)}:${flowId}` : flowId;
    });
    MCPOAuthHandler.parseFlowId.mockImplementation((flowId) => {
      const parts = flowId.split(':');
      if (parts[0] === 'tenant') {
        if (parts.length < 4 || !parts[1] || !parts[2]) {
          return null;
        }
        let tenantId;
        try {
          tenantId = decodeURIComponent(parts[1]);
        } catch {
          return null;
        }
        return {
          tenantId,
          userId: parts[2],
          serverName: parts.slice(3).join(':'),
        };
      }
      if (parts.length < 2 || !parts[0]) {
        return null;
      }
      return {
        userId: parts[0],
        serverName: parts.slice(1).join(':'),
      };
    });
    MCPOAuthHandler.buildStoredClientMetadata.mockImplementation((metadata, resourceMetadata) =>
      metadata
        ? {
            ...metadata,
            ...(resourceMetadata?.resource && { resource: resourceMetadata.resource }),
          }
        : undefined,
    );
    mockMCPUseAllowed = true;
    /**
     * Reset registry method implementations every test. `clearAllMocks` resets
     * call records but NOT implementations, so a `.mockRejectedValue(...)` set
     * by an earlier test leaks into later ones — including the new
     * `getServerConfig` lookup in updateMCPServerController.
     */
    mockRegistryInstance.getServerConfig.mockReset().mockResolvedValue(undefined);
    mockRegistryInstance.addServer.mockReset();
    mockRegistryInstance.updateServer.mockReset();
    mockRegistryInstance.removeServer.mockReset();
  });

  describe('GET /:serverName/oauth/initiate', () => {
    const { MCPOAuthHandler } = require('@librechat/api');
    const { getLogStores } = require('~/cache');

    it('should reuse stored authorization URL without starting a new OAuth flow', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
          createdAt: Date.now(),
          metadata: {
            serverName: 'test-server',
            userId: 'test-user-id',
            authorizationUrl: 'https://oauth.example.com/auth?state=stored-state',
          },
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe('https://oauth.example.com/auth?state=stored-state');
      expect(response.headers['set-cookie']?.join('')).toContain('oauth_csrf=');
      expect(MCPOAuthHandler.initiateOAuthFlow).not.toHaveBeenCalled();
      expect(MCPOAuthHandler.storeStateMapping).not.toHaveBeenCalled();
      expect(mockRegistryInstance.getServerConfig).not.toHaveBeenCalled();
    });

    it('should accept tenant-scoped flow IDs when a tenant is active', async () => {
      const { getTenantId } = require('@librechat/data-schemas');
      getTenantId.mockReturnValue('tenant-a');
      const tenantFlowId = 'tenant:tenant-a:test-user-id:test-server';
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
          createdAt: Date.now(),
          metadata: {
            serverName: 'test-server',
            userId: 'test-user-id',
            authorizationUrl: 'https://oauth.example.com/auth?state=stored-state',
          },
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: tenantFlowId,
      });

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe('https://oauth.example.com/auth?state=stored-state');
      expect(mockFlowManager.getFlowState).toHaveBeenCalledWith(tenantFlowId, 'mcp_oauth');
    });

    it('should reject non-tenant flow IDs when a tenant is active', async () => {
      const { getTenantId } = require('@librechat/data-schemas');
      getTenantId.mockReturnValue('tenant-a');
      const mockFlowManager = { getFlowState: jest.fn() };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Flow mismatch' });
      expect(mockFlowManager.getFlowState).not.toHaveBeenCalled();
    });

    it('should reject stored authorization URL when flow is no longer pending', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'COMPLETED',
          createdAt: Date.now(),
          metadata: {
            serverName: 'test-server',
            userId: 'test-user-id',
            authorizationUrl: 'https://oauth.example.com/auth?state=stored-state',
          },
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Invalid flow state' });
      expect(MCPOAuthHandler.initiateOAuthFlow).not.toHaveBeenCalled();
      expect(MCPOAuthHandler.storeStateMapping).not.toHaveBeenCalled();
    });

    it('should initiate OAuth flow when stored authorization URL is missing', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
          createdAt: Date.now(),
          metadata: {
            serverUrl: 'https://test-server.com',
            state: 'old-state-value',
            oauth: { clientId: 'test-client-id' },
          },
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      mockRegistryInstance.getServerConfig.mockResolvedValue({});

      MCPOAuthHandler.initiateOAuthFlow.mockResolvedValue({
        authorizationUrl: 'https://oauth.example.com/auth',
        flowId: 'test-user-id:test-server',
        flowMetadata: { state: 'random-state-value' },
      });
      MCPOAuthHandler.storeStateMapping.mockResolvedValue();
      mockFlowManager.initFlow = jest.fn().mockResolvedValue();

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe('https://oauth.example.com/auth');
      expect(MCPOAuthHandler.initiateOAuthFlow).toHaveBeenCalledWith(
        'test-server',
        'https://test-server.com',
        'test-user-id',
        {},
        { clientId: 'test-client-id' },
        null,
        undefined,
        null,
        undefined,
      );
      expect(MCPOAuthHandler.deleteStateMapping).toHaveBeenCalledWith(
        'old-state-value',
        mockFlowManager,
      );
      expect(mockFlowManager.initFlow).toHaveBeenCalledWith(
        'test-user-id:test-server',
        'mcp_oauth',
        expect.objectContaining({
          state: 'random-state-value',
          authorizationUrl: 'https://oauth.example.com/auth',
        }),
      );
      expect(MCPOAuthHandler.storeStateMapping).toHaveBeenCalledWith(
        'random-state-value',
        'test-user-id:test-server',
        mockFlowManager,
      );
    });

    it('should return 403 when userId does not match authenticated user', async () => {
      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'different-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'User mismatch' });
    });

    it('should return 403 when flowId does not match authenticated user and server', async () => {
      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'other-user-id:test-server',
      });

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Flow mismatch' });
      expect(getLogStores).not.toHaveBeenCalled();
    });

    it('should return 403 when flowId query value is not a string', async () => {
      const response = await request(app)
        .get('/api/mcp/test-server/oauth/initiate')
        .query('userId=test-user-id&flowId=test-user-id:test-server&flowId=other-flow');

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Flow mismatch' });
      expect(getLogStores).not.toHaveBeenCalled();
    });

    it('should return 404 when flow state is not found', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue(null),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: 'Flow not found' });
    });

    it('should return 400 when flow state has missing OAuth config', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          metadata: {
            serverUrl: 'https://test-server.com',
          },
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Invalid flow state' });
    });

    it('should return 500 when OAuth initiation throws unexpected error', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockRejectedValue(new Error('Database error')),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to initiate OAuth' });
    });

    it('should return 400 when flow state metadata is null', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          id: 'test-user-id:test-server',
          metadata: null,
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/test-server/oauth/initiate').query({
        userId: 'test-user-id',
        flowId: 'test-user-id:test-server',
      });

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Invalid flow state' });
    });
  });

  describe('GET /:serverName/oauth/callback', () => {
    const { MCPOAuthHandler, MCPTokenStorage } = require('@librechat/api');
    const { getLogStores } = require('~/cache');

    it('should redirect to error page when OAuth error is received', async () => {
      const response = await request(app).get('/api/mcp/test-server/oauth/callback').query({
        error: 'access_denied',
        state: 'test-user-id:test-server',
      });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/error?error=access_denied`);
    });

    describe('OAuth error callback failFlow', () => {
      it('should fail the flow when OAuth error is received with valid CSRF cookie', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          failFlow: jest.fn().mockResolvedValue(true),
        };

        getLogStores.mockReturnValueOnce({});
        require('~/config').getFlowStateManager.mockReturnValueOnce(mockFlowManager);
        MCPOAuthHandler.resolveStateToFlowId.mockResolvedValueOnce(flowId);

        const csrfToken = generateTestCsrfToken(flowId);
        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .set('Cookie', [`oauth_csrf=${csrfToken}`])
          .query({
            error: 'invalid_client',
            state: flowId,
          });
        const basePath = getBasePath();

        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(`${basePath}/oauth/error?error=invalid_client`);
        expect(mockFlowManager.failFlow).toHaveBeenCalledWith(
          flowId,
          'mcp_oauth',
          'invalid_client',
        );
      });

      it('should fail the flow when OAuth error is received with valid session cookie', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          failFlow: jest.fn().mockResolvedValue(true),
        };

        getLogStores.mockReturnValueOnce({});
        require('~/config').getFlowStateManager.mockReturnValueOnce(mockFlowManager);
        MCPOAuthHandler.resolveStateToFlowId.mockResolvedValueOnce(flowId);

        const sessionToken = generateTestCsrfToken('test-user-id');
        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .set('Cookie', [`oauth_session=${sessionToken}`])
          .query({
            error: 'invalid_client',
            state: flowId,
          });
        const basePath = getBasePath();

        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(`${basePath}/oauth/error?error=invalid_client`);
        expect(mockFlowManager.failFlow).toHaveBeenCalledWith(
          flowId,
          'mcp_oauth',
          'invalid_client',
        );
      });

      it('should NOT fail the flow when OAuth error is received without cookies (DoS prevention)', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          failFlow: jest.fn(),
        };

        getLogStores.mockReturnValueOnce({});
        require('~/config').getFlowStateManager.mockReturnValueOnce(mockFlowManager);
        MCPOAuthHandler.resolveStateToFlowId.mockResolvedValueOnce(flowId);

        const response = await request(app).get('/api/mcp/test-server/oauth/callback').query({
          error: 'invalid_client',
          state: flowId,
        });
        const basePath = getBasePath();

        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(`${basePath}/oauth/error?error=invalid_client`);
        expect(mockFlowManager.failFlow).not.toHaveBeenCalled();
      });

      it('should redirect instead of hanging when OAuth error flow ID is malformed', async () => {
        const mockFlowManager = {
          failFlow: jest.fn(),
        };

        getLogStores.mockReturnValueOnce({});
        require('~/config').getFlowStateManager.mockReturnValueOnce(mockFlowManager);
        MCPOAuthHandler.resolveStateToFlowId.mockResolvedValueOnce('malformed-flow-id');

        const response = await request(app).get('/api/mcp/test-server/oauth/callback').query({
          error: 'invalid_client',
          state: 'opaque-state',
        });
        const basePath = getBasePath();

        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(`${basePath}/oauth/error?error=invalid_client`);
        expect(mockFlowManager.failFlow).not.toHaveBeenCalled();
      });
    });

    it('should redirect to error page when code is missing', async () => {
      const response = await request(app).get('/api/mcp/test-server/oauth/callback').query({
        state: 'test-user-id:test-server',
      });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/error?error=missing_code`);
    });

    it('should redirect to error page when state is missing', async () => {
      const response = await request(app).get('/api/mcp/test-server/oauth/callback').query({
        code: 'test-auth-code',
      });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/error?error=missing_state`);
    });

    it('should redirect to error page when CSRF cookie is missing', async () => {
      const response = await request(app).get('/api/mcp/test-server/oauth/callback').query({
        code: 'test-auth-code',
        state: 'test-user-id:test-server',
      });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(
        `${basePath}/oauth/error?error=csrf_validation_failed`,
      );
    });

    it('should redirect to error page when CSRF cookie does not match state', async () => {
      const csrfToken = generateTestCsrfToken('different-flow-id');
      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: 'test-user-id:test-server',
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(
        `${basePath}/oauth/error?error=csrf_validation_failed`,
      );
    });

    it('should redirect to error page when flow state is not found', async () => {
      MCPOAuthHandler.getFlowState.mockResolvedValue(null);
      const flowId = 'invalid-flow:id';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/error?error=invalid_state`);
    });

    describe('CSRF fallback via active PENDING flow', () => {
      it('should proceed when a fresh PENDING flow exists and no cookies are present', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          getFlowState: jest.fn().mockResolvedValue({
            status: 'PENDING',
            createdAt: Date.now(),
          }),
          completeFlow: jest.fn().mockResolvedValue(true),
          deleteFlow: jest.fn().mockResolvedValue(true),
        };
        const mockFlowState = {
          serverName: 'test-server',
          userId: 'test-user-id',
          metadata: {},
          clientInfo: {},
          codeVerifier: 'test-verifier',
        };

        getLogStores.mockReturnValue({});
        require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
        MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
        MCPOAuthHandler.completeOAuthFlow.mockResolvedValue({
          access_token: 'test-token',
        });
        MCPTokenStorage.storeTokens.mockResolvedValue();
        mockRegistryInstance.getServerConfig.mockResolvedValue({});

        const mockMcpManager = {
          getUserConnection: jest.fn().mockResolvedValue({
            fetchTools: jest.fn().mockResolvedValue([]),
          }),
        };
        require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
        require('~/config').getOAuthReconnectionManager.mockReturnValue({
          clearReconnection: jest.fn(),
        });
        require('~/server/services/Config/mcp').updateMCPServerTools.mockResolvedValue();

        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .query({ code: 'test-code', state: flowId });

        const basePath = getBasePath();
        expect(response.status).toBe(302);
        expect(response.headers.location).toContain(`${basePath}/oauth/success`);
      });

      it('should forward the merged server config so the tool cache gate sees request-scoped servers', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          getFlowState: jest.fn().mockResolvedValue({
            status: 'PENDING',
            createdAt: Date.now(),
          }),
          completeFlow: jest.fn().mockResolvedValue(true),
          deleteFlow: jest.fn().mockResolvedValue(true),
        };
        const mockFlowState = {
          serverName: 'test-server',
          userId: 'test-user-id',
          metadata: {},
          clientInfo: {},
          codeVerifier: 'test-verifier',
        };
        const mergedServerConfig = {
          type: 'streamable-http',
          url: 'https://override.example.com/{{LIBRECHAT_BODY_CONVERSATIONID}}/mcp',
          source: 'config',
        };
        const fetchedTools = [{ name: 'search', inputSchema: { type: 'object' } }];

        getLogStores.mockReturnValue({});
        require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
        MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
        MCPOAuthHandler.completeOAuthFlow.mockResolvedValue({
          access_token: 'test-token',
        });
        MCPTokenStorage.storeTokens.mockResolvedValue();
        mockRegistryInstance.getServerConfig.mockResolvedValue({});
        mockResolveAllMcpConfigs.mockResolvedValueOnce({ 'test-server': mergedServerConfig });

        const mockMcpManager = {
          getUserConnection: jest.fn().mockResolvedValue({
            fetchTools: jest.fn().mockResolvedValue(fetchedTools),
          }),
        };
        require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
        require('~/config').getOAuthReconnectionManager.mockReturnValue({
          clearReconnection: jest.fn(),
        });
        const { updateMCPServerTools } = require('~/server/services/Config/mcp');
        updateMCPServerTools.mockResolvedValue();

        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .query({ code: 'test-code', state: flowId });

        expect(response.status).toBe(302);
        expect(mockResolveAllMcpConfigs).toHaveBeenCalledWith('test-user-id');
        expect(mockMcpManager.getUserConnection).toHaveBeenCalledWith(
          expect.objectContaining({ serverConfig: mergedServerConfig }),
        );
        expect(updateMCPServerTools).toHaveBeenCalledWith({
          userId: 'test-user-id',
          serverName: 'test-server',
          tools: fetchedTools,
          serverConfig: mergedServerConfig,
        });
      });

      it('should reject when no PENDING flow exists and no cookies are present', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          getFlowState: jest.fn().mockResolvedValue(null),
        };

        getLogStores.mockReturnValue({});
        require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .query({ code: 'test-code', state: flowId });

        const basePath = getBasePath();
        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(
          `${basePath}/oauth/error?error=csrf_validation_failed`,
        );
      });

      it('should reject when only a COMPLETED flow exists (not PENDING)', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          getFlowState: jest.fn().mockResolvedValue({
            status: 'COMPLETED',
            createdAt: Date.now(),
          }),
        };

        getLogStores.mockReturnValue({});
        require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .query({ code: 'test-code', state: flowId });

        const basePath = getBasePath();
        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(
          `${basePath}/oauth/error?error=csrf_validation_failed`,
        );
      });

      it('should reject when PENDING flow is stale (older than PENDING_STALE_MS)', async () => {
        const flowId = 'test-user-id:test-server';
        const mockFlowManager = {
          getFlowState: jest.fn().mockResolvedValue({
            status: 'PENDING',
            createdAt: Date.now() - PENDING_STALE_MS - 60 * 1000,
          }),
        };

        getLogStores.mockReturnValue({});
        require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

        const response = await request(app)
          .get('/api/mcp/test-server/oauth/callback')
          .query({ code: 'test-code', state: flowId });

        const basePath = getBasePath();
        expect(response.status).toBe(302);
        expect(response.headers.location).toBe(
          `${basePath}/oauth/error?error=csrf_validation_failed`,
        );
      });
    });

    it('should handle OAuth callback successfully', async () => {
      // mockRegistryInstance is defined at the top of the file
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { toolFlowId: 'tool-flow-123' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      };
      const mockTokens = {
        access_token: 'test-access-token',
        refresh_token: 'test-refresh-token',
      };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({});
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const mockUserConnection = {
        fetchTools: jest.fn().mockResolvedValue([
          {
            name: 'test-tool',
            description: 'A test tool',
            inputSchema: { type: 'object' },
          },
        ]),
      };
      const mockMcpManager = {
        getUserConnection: jest.fn().mockResolvedValue(mockUserConnection),
      };
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      const { Constants } = require('librechat-data-provider');
      getCachedTools.mockResolvedValue({
        [`existing-tool${Constants.mcp_delimiter}test-server`]: { type: 'function' },
        [`other-tool${Constants.mcp_delimiter}other-server`]: { type: 'function' },
      });
      setCachedTools.mockResolvedValue();

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/success?serverName=test-server`);
      expect(MCPOAuthHandler.completeOAuthFlow).toHaveBeenCalledWith(
        flowId,
        'test-auth-code',
        mockFlowManager,
        {},
      );
      expect(MCPTokenStorage.storeTokens).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: 'test-user-id',
          serverName: 'test-server',
          tokens: mockTokens,
          clientInfo: mockFlowState.clientInfo,
          metadata: mockFlowState.metadata,
        }),
      );
      const storeInvocation = MCPTokenStorage.storeTokens.mock.invocationCallOrder[0];
      const connectInvocation = mockMcpManager.getUserConnection.mock.invocationCallOrder[0];
      expect(storeInvocation).toBeLessThan(connectInvocation);
      expect(mockFlowManager.completeFlow).toHaveBeenCalledWith(
        'tool-flow-123',
        'mcp_oauth',
        mockTokens,
      );
      expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith(
        'test-user-id:test-server',
        'mcp_get_tokens',
      );
    });

    it('should clear tenant-scoped token flow state after storing callback tokens', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: {
          toolFlowId: 'tool-flow-123',
          token_endpoint: 'https://auth.example.com/token',
        },
        resourceMetadata: { resource: 'https://api.example.com/' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
        tenantId: 'tenant-a',
      };
      const mockTokens = {
        access_token: 'test-access-token',
        refresh_token: 'test-refresh-token',
      };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      require('~/config').getOAuthReconnectionManager.mockReturnValue({
        clearReconnection: jest.fn(),
      });
      require('~/config').getMCPManager.mockReturnValue({
        getUserConnection: jest.fn().mockResolvedValue({
          fetchTools: jest.fn().mockResolvedValue([]),
        }),
      });
      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });

      expect(response.status).toBe(302);
      expect(MCPTokenStorage.storeTokens).toHaveBeenCalledWith(
        expect.objectContaining({
          metadata: expect.objectContaining({
            resource: 'https://api.example.com/',
          }),
        }),
      );
      expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith(
        'tenant:tenant-a:test-user-id:test-server',
        'mcp_get_tokens',
      );
      expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith(flowId, 'mcp_get_tokens');
    });

    it('should complete pending token flow waiters after storing callback tokens', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockImplementation((id, type) => {
          if (type === 'mcp_get_tokens' && id === 'tenant:tenant-a:test-user-id:test-server') {
            return Promise.resolve({
              type: 'mcp_get_tokens',
              status: 'PENDING',
            });
          }
          return Promise.resolve({ status: 'PENDING' });
        }),
        completeFlow: jest.fn().mockResolvedValue(true),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: {},
        clientInfo: {},
        codeVerifier: 'test-verifier',
        tenantId: 'tenant-a',
      };
      const mockTokens = {
        access_token: 'fresh-access-token',
        refresh_token: 'fresh-refresh-token',
      };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      require('~/config').getOAuthReconnectionManager.mockReturnValue({
        clearReconnection: jest.fn(),
      });
      require('~/config').getMCPManager.mockReturnValue({
        getUserConnection: jest.fn().mockResolvedValue({
          fetchTools: jest.fn().mockResolvedValue([]),
        }),
      });
      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });

      expect(response.status).toBe(302);
      expect(mockFlowManager.completeFlow).toHaveBeenCalledWith(
        'tenant:tenant-a:test-user-id:test-server',
        'mcp_get_tokens',
        mockTokens,
      );
      expect(mockFlowManager.deleteFlow).not.toHaveBeenCalledWith(
        'tenant:tenant-a:test-user-id:test-server',
        'mcp_get_tokens',
      );
      expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith(flowId, 'mcp_get_tokens');
    });

    it('should use oauthHeaders from flow state when present', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { toolFlowId: 'tool-flow-123' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
        oauthHeaders: { 'X-Custom-Auth': 'header-value' },
      };
      const mockTokens = { access_token: 'tok', refresh_token: 'ref' };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      require('~/config').getOAuthReconnectionManager.mockReturnValue({
        clearReconnection: jest.fn(),
      });
      require('~/config').getMCPManager.mockReturnValue({
        getUserConnection: jest.fn().mockResolvedValue({
          fetchTools: jest.fn().mockResolvedValue([]),
        }),
      });
      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({ code: 'auth-code', state: flowId });

      expect(MCPOAuthHandler.completeOAuthFlow).toHaveBeenCalledWith(
        flowId,
        'auth-code',
        mockFlowManager,
        { 'X-Custom-Auth': 'header-value' },
      );
      expect(mockRegistryInstance.getServerConfig).not.toHaveBeenCalled();
    });

    it('should fall back to registry oauth_headers when flow state lacks them', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { toolFlowId: 'tool-flow-123' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      };
      const mockTokens = { access_token: 'tok', refresh_token: 'ref' };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({
        oauth_headers: { 'X-Registry-Header': 'from-registry' },
      });
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      require('~/config').getOAuthReconnectionManager.mockReturnValue({
        clearReconnection: jest.fn(),
      });
      require('~/config').getMCPManager.mockReturnValue({
        getUserConnection: jest.fn().mockResolvedValue({
          fetchTools: jest.fn().mockResolvedValue([]),
        }),
      });
      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({ code: 'auth-code', state: flowId });

      expect(MCPOAuthHandler.completeOAuthFlow).toHaveBeenCalledWith(
        flowId,
        'auth-code',
        mockFlowManager,
        { 'X-Registry-Header': 'from-registry' },
      );
      expect(mockRegistryInstance.getServerConfig).toHaveBeenCalledWith(
        'test-server',
        'test-user-id',
        undefined,
      );
    });

    it('should redirect to error page when callback processing fails', async () => {
      MCPOAuthHandler.getFlowState.mockRejectedValue(new Error('Callback error'));
      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/error?error=callback_failed`);
    });

    it('should handle system-level OAuth completion', async () => {
      // mockRegistryInstance is defined at the top of the file
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'system',
        metadata: { toolFlowId: 'tool-flow-123' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      };
      const mockTokens = {
        access_token: 'test-access-token',
        refresh_token: 'test-refresh-token',
      };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({});
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/success?serverName=test-server`);
      expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith(flowId, 'mcp_get_tokens');
    });

    it('should handle reconnection failure after OAuth', async () => {
      // mockRegistryInstance is defined at the top of the file
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { toolFlowId: 'tool-flow-123' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      };
      const mockTokens = {
        access_token: 'test-access-token',
        refresh_token: 'test-refresh-token',
      };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({});
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const mockMcpManager = {
        getUserConnection: jest.fn().mockRejectedValue(new Error('Reconnection failed')),
      };
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/success?serverName=test-server`);
      expect(MCPTokenStorage.storeTokens).toHaveBeenCalled();
      expect(mockFlowManager.deleteFlow).toHaveBeenCalledWith(flowId, 'mcp_get_tokens');
    });

    it('should redirect to error page if token storage fails', async () => {
      // mockRegistryInstance is defined at the top of the file
      const mockFlowManager = {
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const mockFlowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { toolFlowId: 'tool-flow-123' },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      };
      const mockTokens = {
        access_token: 'test-access-token',
        refresh_token: 'test-refresh-token',
      };

      MCPOAuthHandler.getFlowState.mockResolvedValue(mockFlowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockRejectedValue(new Error('store failed'));
      mockRegistryInstance.getServerConfig.mockResolvedValue({});
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const mockMcpManager = {
        getUserConnection: jest.fn(),
      };
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/error?error=callback_failed`);
      expect(mockMcpManager.getUserConnection).not.toHaveBeenCalled();
    });

    it('should use original flow state credentials when storing tokens', async () => {
      // mockRegistryInstance is defined at the top of the file
      const mockFlowManager = {
        getFlowState: jest.fn(),
        completeFlow: jest.fn().mockResolvedValue(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      const clientInfo = {
        client_id: 'client123',
        client_secret: 'client_secret',
      };
      const flowState = {
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { toolFlowId: 'tool-flow-123', serverUrl: 'http://example.com' },
        clientInfo: clientInfo,
        codeVerifier: 'test-verifier',
        status: 'PENDING',
      };
      const mockTokens = {
        access_token: 'test-access-token',
        refresh_token: 'test-refresh-token',
      };

      // First call checks idempotency (status PENDING = not completed)
      // Second call retrieves flow state for processing
      mockFlowManager.getFlowState
        .mockResolvedValueOnce({ status: 'PENDING' })
        .mockResolvedValueOnce(flowState);

      MCPOAuthHandler.getFlowState.mockResolvedValue(flowState);
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({});
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const mockUserConnection = {
        fetchTools: jest.fn().mockResolvedValue([]),
      };
      const mockMcpManager = {
        getUserConnection: jest.fn().mockResolvedValue(mockUserConnection),
      };
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      require('~/config').getOAuthReconnectionManager = jest.fn().mockReturnValue({
        clearReconnection: jest.fn(),
      });

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/success?serverName=test-server`);

      expect(MCPTokenStorage.storeTokens).toHaveBeenCalledWith(
        expect.objectContaining({
          userId: 'test-user-id',
          serverName: 'test-server',
          tokens: mockTokens,
          clientInfo: clientInfo,
          metadata: flowState.metadata,
        }),
      );
    });

    it('should prevent duplicate token exchange with idempotency check', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn(),
      };

      // Flow is already completed
      mockFlowManager.getFlowState.mockResolvedValue({
        status: 'COMPLETED',
        serverName: 'test-server',
        userId: 'test-user-id',
      });

      MCPOAuthHandler.getFlowState.mockResolvedValue({
        status: 'COMPLETED',
        serverName: 'test-server',
        userId: 'test-user-id',
      });

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get('/api/mcp/test-server/oauth/callback')
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .query({
          code: 'test-auth-code',
          state: flowId,
        });
      const basePath = getBasePath();

      expect(response.status).toBe(302);
      expect(response.headers.location).toBe(`${basePath}/oauth/success?serverName=test-server`);

      expect(MCPOAuthHandler.completeOAuthFlow).not.toHaveBeenCalled();
      expect(MCPTokenStorage.storeTokens).not.toHaveBeenCalled();
    });
  });

  describe('GET /oauth/tokens/:flowId', () => {
    const { getLogStores } = require('~/cache');

    it('should return tokens for completed flow', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'COMPLETED',
          result: {
            access_token: 'test-access-token',
            refresh_token: 'test-refresh-token',
          },
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/oauth/tokens/test-user-id:flow-123');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        tokens: {
          access_token: 'test-access-token',
          refresh_token: 'test-refresh-token',
        },
      });
    });

    it('should return tokens for a tenant-prefixed flow owned by the user', async () => {
      const { getTenantId } = require('@librechat/data-schemas');
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'COMPLETED',
          result: {
            access_token: 'tenant-access-token',
          },
        }),
      };

      getTenantId.mockReturnValue('tenant-a');
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get(
        '/api/mcp/oauth/tokens/tenant:tenant-a:test-user-id:test-server',
      );

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        tokens: {
          access_token: 'tenant-access-token',
        },
      });
      expect(mockFlowManager.getFlowState).toHaveBeenCalledWith(
        'tenant:tenant-a:test-user-id:test-server',
        'mcp_oauth',
      );
    });

    it('should reject tenant-prefixed token flow access from another tenant', async () => {
      const { getTenantId } = require('@librechat/data-schemas');
      getTenantId.mockReturnValue('tenant-b');

      const response = await request(app).get(
        '/api/mcp/oauth/tokens/tenant:tenant-a:test-user-id:test-server',
      );

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Access denied' });
    });

    it('should return 401 when user is not authenticated', async () => {
      const unauthApp = express();
      unauthApp.use(express.json());
      unauthApp.use((req, res, next) => {
        req.user = null;
        next();
      });
      unauthApp.use('/api/mcp', mcpRouter);

      const response = await request(unauthApp).get('/api/mcp/oauth/tokens/test-flow-id');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'User not authenticated' });
    });

    it('should return 403 when user tries to access flow they do not own', async () => {
      const response = await request(app).get('/api/mcp/oauth/tokens/other-user-id:flow-123');

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Access denied' });
    });

    it('should return 404 when flow is not found', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue(null),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get(
        '/api/mcp/oauth/tokens/test-user-id:non-existent-flow',
      );

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: 'Flow not found' });
    });

    it('should return 400 when flow is not completed', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
          result: null,
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/oauth/tokens/test-user-id:pending-flow');

      expect(response.status).toBe(400);
      expect(response.body).toEqual({ error: 'Flow not completed' });
    });

    it('should return 500 when token retrieval throws an unexpected error', async () => {
      getLogStores.mockImplementation(() => {
        throw new Error('Database connection failed');
      });

      const response = await request(app).get('/api/mcp/oauth/tokens/test-user-id:error-flow');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to get tokens' });
    });
  });

  describe('GET /oauth/status/:flowId', () => {
    const { getLogStores } = require('~/cache');

    it('should return flow status when flow exists', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
          error: null,
        }),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/oauth/status/test-user-id:test-server');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        status: 'PENDING',
        completed: false,
        failed: false,
        error: null,
      });
    });

    it('should return flow status for a tenant-prefixed flow owned by the user', async () => {
      const { getTenantId } = require('@librechat/data-schemas');
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
          error: null,
        }),
      };

      getTenantId.mockReturnValue('tenant-a');
      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get(
        '/api/mcp/oauth/status/tenant:tenant-a:test-user-id:test-server',
      );

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        status: 'PENDING',
        completed: false,
        failed: false,
        error: null,
      });
      expect(mockFlowManager.getFlowState).toHaveBeenCalledWith(
        'tenant:tenant-a:test-user-id:test-server',
        'mcp_oauth',
      );
    });

    it('should reject tenant-prefixed status access from another tenant', async () => {
      const { getTenantId } = require('@librechat/data-schemas');
      getTenantId.mockReturnValue('tenant-b');

      const response = await request(app).get(
        '/api/mcp/oauth/status/tenant:tenant-a:test-user-id:test-server',
      );

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Access denied' });
    });

    it('should return 403 when flowId does not match authenticated user', async () => {
      const response = await request(app).get('/api/mcp/oauth/status/other-user-id:test-server');

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ error: 'Access denied' });
    });

    it('should return 404 when flow is not found', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue(null),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/oauth/status/test-user-id:non-existent');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: 'Flow not found' });
    });

    it('should return 500 when status check fails', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockRejectedValue(new Error('Database error')),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const response = await request(app).get('/api/mcp/oauth/status/test-user-id:error-server');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to get flow status' });
    });
  });

  describe('POST /oauth/cancel/:serverName', () => {
    const { MCPOAuthHandler } = require('@librechat/api');
    const { getLogStores } = require('~/cache');

    it('should cancel OAuth flow successfully', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          status: 'PENDING',
        }),
        failFlow: jest.fn().mockResolvedValue(),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      MCPOAuthHandler.generateFlowId.mockReturnValue('test-user-id:test-server');

      const response = await request(app).post('/api/mcp/oauth/cancel/test-server');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        message: 'OAuth flow for test-server cancelled successfully',
      });

      expect(mockFlowManager.failFlow).toHaveBeenCalledWith(
        'test-user-id:test-server',
        'mcp_oauth',
        'User cancelled OAuth flow',
      );
    });

    it('should return success message when no active flow exists', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue(null),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      MCPOAuthHandler.generateFlowId.mockReturnValue('test-user-id:test-server');

      const response = await request(app).post('/api/mcp/oauth/cancel/test-server');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        message: 'No active OAuth flow to cancel',
      });
    });

    it('should return 500 when cancellation fails', async () => {
      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        failFlow: jest.fn().mockRejectedValue(new Error('Database error')),
      };

      getLogStores.mockReturnValue({});
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      MCPOAuthHandler.generateFlowId.mockReturnValue('test-user-id:test-server');

      const response = await request(app).post('/api/mcp/oauth/cancel/test-server');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to cancel OAuth flow' });
    });

    it('should return 401 when user is not authenticated', async () => {
      const unauthApp = express();
      unauthApp.use(express.json());
      unauthApp.use((req, res, next) => {
        req.user = null;
        next();
      });
      unauthApp.use('/api/mcp', mcpRouter);

      const response = await request(unauthApp).post('/api/mcp/oauth/cancel/test-server');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'User not authenticated' });
    });
  });

  describe('POST /:serverName/reinitialize', () => {
    // mockRegistryInstance is defined at the top of the file

    it('should return 404 when server is not found in configuration', async () => {
      const mockMcpManager = {
        disconnectUserConnection: jest.fn().mockResolvedValue(),
      };

      mockRegistryInstance.getServerConfig.mockResolvedValue(null);
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      require('~/config').getFlowStateManager.mockReturnValue({});
      require('~/cache').getLogStores.mockReturnValue({});

      const response = await request(app).post('/api/mcp/non-existent-server/reinitialize');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({
        error: "MCP server 'non-existent-server' not found in configuration",
      });
    });

    it('should handle OAuth requirement during reinitialize', async () => {
      const mockMcpManager = {
        disconnectUserConnection: jest.fn().mockResolvedValue(),
        mcpConfigs: {},
        getUserConnection: jest.fn().mockImplementation(async ({ oauthStart }) => {
          if (oauthStart) {
            await oauthStart('https://oauth.example.com/auth');
          }
          throw new Error('OAuth flow initiated - return early');
        }),
      };

      mockRegistryInstance.getServerConfig.mockResolvedValue({
        customUserVars: {},
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      require('~/config').getFlowStateManager.mockReturnValue({});
      require('~/cache').getLogStores.mockReturnValue({});
      require('~/server/services/Tools/mcp').reinitMCPServer.mockResolvedValue({
        success: true,
        message: "MCP server 'oauth-server' ready for OAuth authentication",
        serverName: 'oauth-server',
        oauthRequired: true,
        oauthUrl: 'https://oauth.example.com/auth',
      });

      const response = await request(app).post('/api/mcp/oauth-server/reinitialize');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        message: "MCP server 'oauth-server' ready for OAuth authentication",
        serverName: 'oauth-server',
        oauthRequired: true,
        oauthUrl: 'https://oauth.example.com/auth',
      });
    });

    it('should return 500 when reinitialize fails with non-OAuth error', async () => {
      const mockMcpManager = {
        disconnectUserConnection: jest.fn().mockResolvedValue(),
        mcpConfigs: {},
        getUserConnection: jest.fn().mockRejectedValue(new Error('Connection failed')),
      };

      mockRegistryInstance.getServerConfig.mockResolvedValue({});
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      require('~/config').getFlowStateManager.mockReturnValue({});
      require('~/cache').getLogStores.mockReturnValue({});
      require('~/server/services/Tools/mcp').reinitMCPServer.mockResolvedValue(null);

      const response = await request(app).post('/api/mcp/error-server/reinitialize');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({
        error: 'Failed to reinitialize MCP server for user',
      });
    });

    it('should return 500 when unexpected error occurs', async () => {
      const mockMcpManager = {
        disconnectUserConnection: jest.fn(),
      };

      mockRegistryInstance.getServerConfig.mockImplementation(() => {
        throw new Error('Config loading failed');
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const response = await request(app).post('/api/mcp/test-server/reinitialize');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Internal server error' });
    });

    it('should return 401 when user is not authenticated', async () => {
      const unauthApp = express();
      unauthApp.use(express.json());
      unauthApp.use((req, res, next) => {
        req.user = null;
        next();
      });
      unauthApp.use('/api/mcp', mcpRouter);

      const response = await request(unauthApp).post('/api/mcp/test-server/reinitialize');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'User not authenticated' });
    });

    it('should successfully reinitialize server and cache tools', async () => {
      const mockUserConnection = {
        fetchTools: jest.fn().mockResolvedValue([
          { name: 'tool1', description: 'Test tool 1', inputSchema: { type: 'object' } },
          { name: 'tool2', description: 'Test tool 2', inputSchema: { type: 'object' } },
        ]),
      };

      const mockMcpManager = {
        disconnectUserConnection: jest.fn().mockResolvedValue(),
        getUserConnection: jest.fn().mockResolvedValue(mockUserConnection),
      };

      mockRegistryInstance.getServerConfig.mockResolvedValue({
        endpoint: 'http://test-server.com',
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      require('~/config').getFlowStateManager.mockReturnValue({});
      require('~/cache').getLogStores.mockReturnValue({});

      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      const { updateMCPServerTools } = require('~/server/services/Config/mcp');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();
      updateMCPServerTools.mockResolvedValue();

      require('~/server/services/Tools/mcp').reinitMCPServer.mockResolvedValue({
        success: true,
        message: "MCP server 'test-server' reinitialized successfully",
        serverName: 'test-server',
        oauthRequired: false,
        oauthUrl: null,
      });

      const response = await request(app).post('/api/mcp/test-server/reinitialize');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        message: "MCP server 'test-server' reinitialized successfully",
        serverName: 'test-server',
        oauthRequired: false,
        oauthUrl: null,
      });
      expect(mockMcpManager.disconnectUserConnection).toHaveBeenCalledWith(
        'test-user-id',
        'test-server',
      );
    });

    it('should handle server with custom user variables', async () => {
      const mockUserConnection = {
        fetchTools: jest.fn().mockResolvedValue([]),
      };

      const mockMcpManager = {
        disconnectUserConnection: jest.fn().mockResolvedValue(),
        getUserConnection: jest.fn().mockResolvedValue(mockUserConnection),
      };

      mockRegistryInstance.getServerConfig.mockResolvedValue({
        endpoint: 'http://test-server.com',
        customUserVars: {
          API_KEY: 'some-env-var',
        },
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      require('~/config').getFlowStateManager.mockReturnValue({});
      require('~/cache').getLogStores.mockReturnValue({});
      require('@librechat/api').getUserMCPAuthMap.mockResolvedValue({
        'mcp:test-server': {
          API_KEY: 'api-key-value',
        },
      });
      require('~/models').findPluginAuthsByKeys.mockResolvedValue([
        { key: 'API_KEY', value: 'api-key-value' },
      ]);

      const { getCachedTools, setCachedTools } = require('~/server/services/Config');
      const { updateMCPServerTools } = require('~/server/services/Config/mcp');
      getCachedTools.mockResolvedValue({});
      setCachedTools.mockResolvedValue();
      updateMCPServerTools.mockResolvedValue();

      require('~/server/services/Tools/mcp').reinitMCPServer.mockResolvedValue({
        success: true,
        message: "MCP server 'test-server' reinitialized successfully",
        serverName: 'test-server',
        oauthRequired: false,
        oauthUrl: null,
      });

      const response = await request(app).post('/api/mcp/test-server/reinitialize');

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(require('@librechat/api').getUserMCPAuthMap).toHaveBeenCalledWith({
        userId: 'test-user-id',
        servers: ['test-server'],
        findPluginAuthsByKeys: require('~/models').findPluginAuthsByKeys,
      });
    });
  });

  describe('GET /connection/status', () => {
    const { getMCPSetupData, getServerConnectionStatus } = require('~/server/services/MCP');

    it('should return connection status for all servers', async () => {
      const mockMcpConfig = {
        server1: { endpoint: 'http://server1.com' },
        server2: { endpoint: 'http://server2.com' },
      };

      getMCPSetupData.mockResolvedValue({
        mcpConfig: mockMcpConfig,
        appConnections: {},
        userConnections: {},
        oauthServers: [],
      });

      getServerConnectionStatus
        .mockResolvedValueOnce({
          connectionState: 'connected',
          requiresOAuth: false,
        })
        .mockResolvedValueOnce({
          connectionState: 'disconnected',
          requiresOAuth: true,
        });

      const response = await request(app).get('/api/mcp/connection/status');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        oauthTimeout: expect.any(Number),
        connectionStatus: {
          server1: {
            connectionState: 'connected',
            requiresOAuth: false,
          },
          server2: {
            connectionState: 'disconnected',
            requiresOAuth: true,
          },
        },
      });

      expect(getMCPSetupData).toHaveBeenCalledWith('test-user-id', expect.any(Object));
      expect(getServerConnectionStatus).toHaveBeenCalledTimes(2);
    });

    it('should return 500 when connection status check fails', async () => {
      getMCPSetupData.mockRejectedValue(new Error('Database error'));

      const response = await request(app).get('/api/mcp/connection/status');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to get connection status' });
    });

    it('should return 401 when user is not authenticated', async () => {
      const unauthApp = express();
      unauthApp.use(express.json());
      unauthApp.use((req, res, next) => {
        req.user = null;
        next();
      });
      unauthApp.use('/api/mcp', mcpRouter);

      const response = await request(unauthApp).get('/api/mcp/connection/status');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'User not authenticated' });
    });
  });

  describe('GET /connection/status/:serverName', () => {
    const { getMCPSetupData, getServerConnectionStatus } = require('~/server/services/MCP');

    it('should return connection status for OAuth-required server', async () => {
      const mockMcpConfig = {
        'oauth-server': { endpoint: 'http://oauth-server.com' },
      };

      getMCPSetupData.mockResolvedValue({
        mcpConfig: mockMcpConfig,
        appConnections: {},
        userConnections: {},
        oauthServers: [],
      });

      getServerConnectionStatus.mockResolvedValue({
        connectionState: 'requires_auth',
        requiresOAuth: true,
      });

      const response = await request(app).get('/api/mcp/connection/status/oauth-server');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        serverName: 'oauth-server',
        connectionStatus: 'requires_auth',
        requiresOAuth: true,
      });
    });

    it('should return 404 when server is not found in configuration', async () => {
      getMCPSetupData.mockResolvedValue({
        mcpConfig: {
          'other-server': { endpoint: 'http://other-server.com' },
        },
        appConnections: {},
        userConnections: {},
        oauthServers: [],
      });

      const response = await request(app).get('/api/mcp/connection/status/non-existent-server');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({
        error: "MCP server 'non-existent-server' not found in configuration",
      });
    });

    it('should return 500 when connection status check fails', async () => {
      getMCPSetupData.mockRejectedValue(new Error('Database connection failed'));

      const response = await request(app).get('/api/mcp/connection/status/test-server');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to get connection status' });
    });

    it('should return 401 when user is not authenticated', async () => {
      const unauthApp = express();
      unauthApp.use(express.json());
      unauthApp.use((req, res, next) => {
        req.user = null;
        next();
      });
      unauthApp.use('/api/mcp', mcpRouter);

      const response = await request(unauthApp).get('/api/mcp/connection/status/test-server');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'User not authenticated' });
    });
  });

  describe('GET /:serverName/auth-values', () => {
    const { getUserPluginAuthValue } = require('~/server/services/PluginService');
    // mockRegistryInstance is defined at the top of the file

    it('should return auth value flags for server', async () => {
      const mockMcpManager = {};

      mockRegistryInstance.getServerConfig.mockResolvedValue({
        customUserVars: {
          API_KEY: 'some-env-var',
          SECRET_TOKEN: 'another-env-var',
        },
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      getUserPluginAuthValue.mockResolvedValueOnce('some-api-key-value').mockResolvedValueOnce('');

      const response = await request(app).get('/api/mcp/test-server/auth-values');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        serverName: 'test-server',
        authValueFlags: {
          API_KEY: true,
          SECRET_TOKEN: false,
        },
      });

      expect(getUserPluginAuthValue).toHaveBeenCalledTimes(2);
    });

    it('should return 404 when server is not found in configuration', async () => {
      const mockMcpManager = {};

      mockRegistryInstance.getServerConfig.mockResolvedValue(null);
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const response = await request(app).get('/api/mcp/non-existent-server/auth-values');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({
        error: "MCP server 'non-existent-server' not found in configuration",
      });
    });

    it('should handle errors when checking auth values', async () => {
      const mockMcpManager = {};

      mockRegistryInstance.getServerConfig.mockResolvedValue({
        customUserVars: {
          API_KEY: 'some-env-var',
        },
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);
      getUserPluginAuthValue.mockRejectedValue(new Error('Database error'));

      const response = await request(app).get('/api/mcp/test-server/auth-values');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        serverName: 'test-server',
        authValueFlags: {
          API_KEY: false,
        },
      });
    });

    it('should return 500 when auth values check throws unexpected error', async () => {
      const mockMcpManager = {};

      mockRegistryInstance.getServerConfig.mockImplementation(() => {
        throw new Error('Config loading failed');
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const response = await request(app).get('/api/mcp/test-server/auth-values');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Failed to check auth value flags' });
    });

    it('should handle customUserVars that is not an object', async () => {
      const mockMcpManager = {};

      mockRegistryInstance.getServerConfig.mockResolvedValue({
        customUserVars: 'not-an-object',
      });
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const response = await request(app).get('/api/mcp/test-server/auth-values');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        success: true,
        serverName: 'test-server',
        authValueFlags: {},
      });
    });

    it('should return 401 when user is not authenticated in auth-values endpoint', async () => {
      const appWithoutAuth = express();
      appWithoutAuth.use(express.json());
      appWithoutAuth.use('/api/mcp', mcpRouter);

      const response = await request(appWithoutAuth).get('/api/mcp/test-server/auth-values');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ error: 'User not authenticated' });
    });
  });

  describe('GET /:serverName/oauth/callback - Edge Cases', () => {
    it('should handle OAuth callback without toolFlowId (falsy toolFlowId)', async () => {
      const { MCPOAuthHandler, MCPTokenStorage } = require('@librechat/api');
      const mockTokens = {
        access_token: 'edge-access-token',
        refresh_token: 'edge-refresh-token',
      };
      MCPOAuthHandler.getFlowState = jest.fn().mockResolvedValue({
        id: 'test-user-id:test-server',
        userId: 'test-user-id',
        metadata: {
          serverUrl: 'https://example.com',
          oauth: {},
          // No toolFlowId property
        },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      });
      MCPOAuthHandler.completeOAuthFlow = jest.fn().mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({});

      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({ status: 'PENDING' }),
        completeFlow: jest.fn(),
        deleteFlow: jest.fn().mockResolvedValue(true),
      };
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);

      const mockMcpManager = {
        getUserConnection: jest.fn().mockResolvedValue({
          fetchTools: jest.fn().mockResolvedValue([]),
        }),
      };
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get(`/api/mcp/test-server/oauth/callback?code=test-code&state=${flowId}`)
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .expect(302);

      const basePath = getBasePath();

      expect(mockFlowManager.completeFlow).not.toHaveBeenCalled();
      expect(response.headers.location).toContain(`${basePath}/oauth/success`);
    });

    it('should handle null cached tools in OAuth callback (triggers || {} fallback)', async () => {
      const { getCachedTools } = require('~/server/services/Config');
      getCachedTools.mockResolvedValue(null);
      const { MCPOAuthHandler, MCPTokenStorage } = require('@librechat/api');
      const mockTokens = {
        access_token: 'edge-access-token',
        refresh_token: 'edge-refresh-token',
      };

      const mockFlowManager = {
        getFlowState: jest.fn().mockResolvedValue({
          id: 'test-user-id:test-server',
          userId: 'test-user-id',
          metadata: { serverUrl: 'https://example.com', oauth: {} },
          clientInfo: {},
          codeVerifier: 'test-verifier',
        }),
        completeFlow: jest.fn(),
      };
      require('~/config').getFlowStateManager.mockReturnValue(mockFlowManager);
      MCPOAuthHandler.getFlowState.mockResolvedValue({
        serverName: 'test-server',
        userId: 'test-user-id',
        metadata: { serverUrl: 'https://example.com', oauth: {} },
        clientInfo: {},
        codeVerifier: 'test-verifier',
      });
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue(mockTokens);
      MCPTokenStorage.storeTokens.mockResolvedValue();
      mockRegistryInstance.getServerConfig.mockResolvedValue({});

      const mockMcpManager = {
        getUserConnection: jest.fn().mockResolvedValue({
          fetchTools: jest
            .fn()
            .mockResolvedValue([{ name: 'test-tool', description: 'Test tool' }]),
        }),
      };
      require('~/config').getMCPManager.mockReturnValue(mockMcpManager);

      const flowId = 'test-user-id:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      const response = await request(app)
        .get(`/api/mcp/test-server/oauth/callback?code=test-code&state=${flowId}`)
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .expect(302);

      const basePath = getBasePath();

      expect(response.headers.location).toContain(`${basePath}/oauth/success`);
    });
  });

  describe('GET /:serverName/oauth/callback - Tenant Context', () => {
    beforeEach(() => {
      const { getTenantId, tenantStorage } = require('@librechat/data-schemas');
      const { MCPOAuthHandler, MCPTokenStorage } = require('@librechat/api');
      getTenantId.mockReset();
      tenantStorage.run.mockReset();
      tenantStorage.run.mockImplementation((store, fn) => fn());
      MCPOAuthHandler.resolveStateToFlowId.mockReset();
      MCPOAuthHandler.getFlowState.mockReset();
      MCPOAuthHandler.completeOAuthFlow.mockReset();
      MCPTokenStorage.storeTokens.mockReset();
    });

    it('should wrap callback body in tenantStorage.run when flowState has tenantId and no current context', async () => {
      const { getTenantId, tenantStorage } = require('@librechat/data-schemas');
      const { MCPOAuthHandler, MCPTokenStorage } = require('@librechat/api');
      const flowId = 'user123:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      getTenantId.mockReturnValue(undefined);

      MCPOAuthHandler.resolveStateToFlowId.mockResolvedValue(flowId);
      MCPOAuthHandler.getFlowState.mockResolvedValue({
        serverName: 'test-server',
        userId: 'user123',
        tenantId: 'tenant-abc',
        metadata: {},
        clientInfo: {},
        codeVerifier: 'test-verifier',
      });
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue({
        access_token: 'token',
        token_type: 'bearer',
      });
      MCPTokenStorage.storeTokens.mockResolvedValue();

      const response = await request(app)
        .get(`/api/mcp/test-server/oauth/callback?code=test-code&state=${flowId}`)
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .expect(302);

      expect(tenantStorage.run).toHaveBeenCalledWith(
        { tenantId: 'tenant-abc' },
        expect.any(Function),
      );
      expect(MCPTokenStorage.storeTokens).toHaveBeenCalled();

      const basePath = getBasePath();
      expect(response.headers.location).toContain(`${basePath}/oauth/success`);
    });

    it('should not call tenantStorage.run when flowState has no tenantId', async () => {
      const { getTenantId, tenantStorage } = require('@librechat/data-schemas');
      const { MCPOAuthHandler, MCPTokenStorage } = require('@librechat/api');
      const flowId = 'user123:test-server';
      const csrfToken = generateTestCsrfToken(flowId);

      getTenantId.mockReturnValue(undefined);

      MCPOAuthHandler.resolveStateToFlowId.mockResolvedValue(flowId);
      MCPOAuthHandler.getFlowState.mockResolvedValue({
        serverName: 'test-server',
        userId: 'user123',
        metadata: {},
        clientInfo: {},
        codeVerifier: 'test-verifier',
      });
      MCPOAuthHandler.completeOAuthFlow.mockResolvedValue({
        access_token: 'token',
        token_type: 'bearer',
      });
      MCPTokenStorage.storeTokens.mockResolvedValue();

      await request(app)
        .get(`/api/mcp/test-server/oauth/callback?code=test-code&state=${flowId}`)
        .set('Cookie', [`oauth_csrf=${csrfToken}`])
        .expect(302);

      expect(tenantStorage.run).not.toHaveBeenCalled();
    });
  });

  describe('GET /tools', () => {
    it('should deny MCP tools when user lacks MCP server use permission', async () => {
      mockMCPUseAllowed = false;

      const response = await request(app).get('/api/mcp/tools');

      expect(response.status).toBe(403);
      expect(response.body).toEqual({ message: 'Forbidden: Insufficient permissions' });
      expect(mockResolveAllMcpConfigs).not.toHaveBeenCalled();
    });

    it('should continue returning MCP tools when one server cache lookup fails', async () => {
      const { Constants } = require('librechat-data-provider');
      const { logger } = require('@librechat/data-schemas');
      const { getMCPServerTools } = require('~/server/services/Config');

      mockResolveAllMcpConfigs.mockResolvedValueOnce({
        'bad-server': {
          type: 'sse',
          url: 'https://bad.example.com/sse',
        },
        'good-server': {
          type: 'sse',
          url: 'https://good.example.com/sse',
          iconPath: '/icons/good.svg',
        },
      });

      // Mock order matches Object.keys() order from the config above.
      getMCPServerTools
        .mockRejectedValueOnce(new Error('cache unavailable'))
        .mockResolvedValueOnce({
          [`search${Constants.mcp_delimiter}good-server`]: {
            type: 'function',
            function: {
              name: `search${Constants.mcp_delimiter}good-server`,
              description: 'Search good server',
              parameters: { type: 'object' },
            },
          },
        });

      const mockGetServerToolFunctions = jest.fn().mockResolvedValue(null);
      require('~/config').getMCPManager.mockReturnValue({
        getServerToolFunctions: mockGetServerToolFunctions,
      });

      const response = await request(app).get('/api/mcp/tools');

      expect(response.status).toBe(200);
      expect(logger.error).toHaveBeenCalledWith(
        '[getMCPTools] Error fetching cached tools for bad-server:',
        expect.any(Error),
      );
      expect(mockGetServerToolFunctions).toHaveBeenCalledWith('test-user-id', 'bad-server');
      expect(response.body.servers['good-server']).toMatchObject({
        name: 'good-server',
        icon: '/icons/good.svg',
        tools: [
          {
            name: 'search',
            pluginKey: `search${Constants.mcp_delimiter}good-server`,
            description: 'Search good server',
          },
        ],
      });
      expect(response.body.servers['bad-server']).toMatchObject({
        name: 'bad-server',
        tools: [],
      });
    });

    it('should return configured servers when all cache lookups fail', async () => {
      const { logger } = require('@librechat/data-schemas');
      const { getMCPServerTools } = require('~/server/services/Config');

      mockResolveAllMcpConfigs.mockResolvedValueOnce({
        'first-server': {
          type: 'sse',
          url: 'https://first.example.com/sse',
        },
        'second-server': {
          type: 'sse',
          url: 'https://second.example.com/sse',
        },
      });

      getMCPServerTools.mockRejectedValue(new Error('cache unavailable'));

      const mockGetServerToolFunctions = jest.fn().mockResolvedValue(null);
      require('~/config').getMCPManager.mockReturnValue({
        getServerToolFunctions: mockGetServerToolFunctions,
      });

      const response = await request(app).get('/api/mcp/tools');

      expect(response.status).toBe(200);
      expect(response.body.servers['first-server']).toMatchObject({
        name: 'first-server',
        tools: [],
      });
      expect(response.body.servers['second-server']).toMatchObject({
        name: 'second-server',
        tools: [],
      });
      expect(logger.error).toHaveBeenCalledTimes(2);
      expect(mockGetServerToolFunctions).toHaveBeenCalledTimes(2);
    });
  });

  describe('GET /servers', () => {
    // mockRegistryInstance is defined at the top of the file

    it('should return all server configs for authenticated user', async () => {
      const mockServerConfigs = {
        'server-1': {
          type: 'sse',
          url: 'http://server1.com/sse',
          title: 'Server 1',
          source: 'user',
        },
        'server-2': {
          type: 'sse',
          url: 'http://server2.com/sse',
          title: 'Server 2',
          source: 'user',
        },
      };

      mockResolveAllMcpConfigs.mockResolvedValue(mockServerConfigs);

      const response = await request(app).get('/api/mcp/servers');

      expect(response.status).toBe(200);
      expect(response.body['server-1']).toMatchObject({
        type: 'sse',
        url: 'http://server1.com/sse',
        title: 'Server 1',
      });
      expect(response.body['server-2']).toMatchObject({
        type: 'sse',
        url: 'http://server2.com/sse',
        title: 'Server 2',
      });
      expect(response.body['server-1'].headers).toBeUndefined();
      expect(response.body['server-2'].headers).toBeUndefined();
      expect(mockResolveAllMcpConfigs).toHaveBeenCalledWith(
        'test-user-id',
        expect.objectContaining({ id: 'test-user-id' }),
      );
    });

    it('should return empty object when no servers are configured', async () => {
      mockResolveAllMcpConfigs.mockResolvedValue({});

      const response = await request(app).get('/api/mcp/servers');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({});
    });

    it('should return 401 when user is not authenticated', async () => {
      const unauthApp = express();
      unauthApp.use(express.json());
      unauthApp.use((req, _res, next) => {
        req.user = null;
        next();
      });
      unauthApp.use('/api/mcp', mcpRouter);

      const response = await request(unauthApp).get('/api/mcp/servers');

      expect(response.status).toBe(401);
      expect(response.body).toEqual({ message: 'Unauthorized' });
    });

    it('should return 500 when server config retrieval fails', async () => {
      mockResolveAllMcpConfigs.mockRejectedValue(new Error('Database error'));

      const response = await request(app).get('/api/mcp/servers');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: 'Database error' });
    });
  });

  describe('POST /servers', () => {
    it('should create MCP server with valid SSE config', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test SSE Server',
        description: 'A test SSE server',
      };

      mockRegistryInstance.addServer.mockResolvedValue({
        serverName: 'test-sse-server',
        config: { ...validConfig, source: 'user' },
      });

      const response = await request(app).post('/api/mcp/servers').send({ config: validConfig });

      expect(response.status).toBe(201);
      expect(response.body.serverName).toBe('test-sse-server');
      expect(response.body.type).toBe('sse');
      expect(response.body.url).toBe('https://mcp-server.example.com/sse');
      expect(response.body.title).toBe('Test SSE Server');
      expect(mockRegistryInstance.addServer).toHaveBeenCalledWith(
        'temp_server_name',
        expect.objectContaining({
          type: 'sse',
          url: 'https://mcp-server.example.com/sse',
        }),
        'DB',
        'test-user-id',
        [],
      );
    });

    it('should reserve config-managed server names when creating MCP server', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test SSE Server',
      };

      mockResolveMcpConfigNames.mockResolvedValueOnce(['config_slack']);
      mockRegistryInstance.addServer.mockResolvedValue({
        serverName: 'test-sse-server',
        config: validConfig,
      });

      const response = await request(app).post('/api/mcp/servers').send({ config: validConfig });

      expect(response.status).toBe(201);
      expect(mockRegistryInstance.addServer).toHaveBeenCalledWith(
        'temp_server_name',
        expect.objectContaining({
          type: 'sse',
          url: 'https://mcp-server.example.com/sse',
        }),
        'DB',
        'test-user-id',
        ['config_slack'],
      );
    });

    it('should reject stdio config for security reasons', async () => {
      const stdioConfig = {
        type: 'stdio',
        command: 'node',
        args: ['server.js'],
        title: 'Test Stdio Server',
      };

      const response = await request(app).post('/api/mcp/servers').send({ config: stdioConfig });

      // Stdio transport is not allowed via API - only admins can configure it via YAML
      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
    });

    it('should return 400 for invalid configuration', async () => {
      const invalidConfig = {
        type: 'sse',
        // Missing required 'url' field
        title: 'Invalid Server',
      };

      const response = await request(app).post('/api/mcp/servers').send({ config: invalidConfig });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(response.body.errors).toBeDefined();
    });

    it('should return 400 for SSE config with invalid URL protocol', async () => {
      const invalidConfig = {
        type: 'sse',
        url: 'ws://invalid-protocol.example.com/sse',
        title: 'Invalid Protocol Server',
      };

      const response = await request(app).post('/api/mcp/servers').send({ config: invalidConfig });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
    });

    it('should reject SSE URL containing env variable references', async () => {
      const response = await request(app)
        .post('/api/mcp/servers')
        .send({
          config: {
            type: 'sse',
            url: 'http://attacker.com/?secret=${JWT_SECRET}',
          },
        });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(mockRegistryInstance.addServer).not.toHaveBeenCalled();
    });

    it('should reject streamable-http URL containing env variable references', async () => {
      const response = await request(app)
        .post('/api/mcp/servers')
        .send({
          config: {
            type: 'streamable-http',
            url: 'http://attacker.com/?key=${CREDS_KEY}&iv=${CREDS_IV}',
          },
        });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(mockRegistryInstance.addServer).not.toHaveBeenCalled();
    });

    it('should reject websocket URL containing env variable references', async () => {
      const response = await request(app)
        .post('/api/mcp/servers')
        .send({
          config: {
            type: 'websocket',
            url: 'ws://attacker.com/?secret=${MONGO_URI}',
          },
        });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(mockRegistryInstance.addServer).not.toHaveBeenCalled();
    });

    it('should redact secrets from create response', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test Server',
      };

      mockRegistryInstance.addServer.mockResolvedValue({
        serverName: 'test-server',
        config: {
          ...validConfig,
          apiKey: { source: 'admin', authorization_type: 'bearer', key: 'admin-secret-key' },
          oauth: { client_id: 'cid', client_secret: 'admin-oauth-secret' },
          headers: { Authorization: 'Bearer leaked-token' },
        },
      });

      const response = await request(app).post('/api/mcp/servers').send({ config: validConfig });

      expect(response.status).toBe(201);
      expect(response.body.apiKey?.key).toBeUndefined();
      expect(response.body.oauth?.client_secret).toBeUndefined();
      expect(response.body.headers).toBeUndefined();
      expect(response.body.apiKey?.source).toBe('admin');
      expect(response.body.oauth?.client_id).toBe('cid');
    });

    it('should return 500 when registry throws error', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test Server',
      };

      mockRegistryInstance.addServer.mockRejectedValue(new Error('Database connection failed'));

      const response = await request(app).post('/api/mcp/servers').send({ config: validConfig });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ message: 'Database connection failed' });
    });

    describe('OBO permission gate', () => {
      const oboConfig = {
        type: 'streamable-http',
        url: 'https://mcp-server.example.com/mcp',
        title: 'OBO Server',
        obo: { scopes: 'api://mcp-server-id/Mcp.Tools.ReadWrite' },
      };
      const db = require('~/models');

      beforeEach(() => {
        currentUser = { id: 'test-user-id', role: 'USER' };
        mockRegistryInstance.addServer.mockResolvedValue({
          serverName: 'obo-server',
          config: oboConfig,
        });
      });

      it('rejects POST with obo body when role lacks CONFIGURE_OBO', async () => {
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              SHARE: false,
              SHARE_PUBLIC: false,
              CONFIGURE_OBO: false,
            },
          },
        });

        const response = await request(app).post('/api/mcp/servers').send({ config: oboConfig });

        expect(response.status).toBe(403);
        expect(response.body.message).toMatch(/Insufficient permissions to configure OBO/);
        expect(mockRegistryInstance.addServer).not.toHaveBeenCalled();
      });

      it('allows POST with obo body when role has CONFIGURE_OBO', async () => {
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              SHARE: false,
              SHARE_PUBLIC: false,
              CONFIGURE_OBO: true,
            },
          },
        });

        const response = await request(app).post('/api/mcp/servers').send({ config: oboConfig });

        expect(response.status).toBe(201);
        expect(mockRegistryInstance.addServer).toHaveBeenCalled();
      });

      it('allows POST without obo body regardless of CONFIGURE_OBO', async () => {
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              CONFIGURE_OBO: false,
            },
          },
        });

        const nonOboConfig = {
          type: 'streamable-http',
          url: 'https://mcp-server.example.com/mcp',
          title: 'Plain Server',
        };
        mockRegistryInstance.addServer.mockResolvedValue({
          serverName: 'plain-server',
          config: nonOboConfig,
        });

        const response = await request(app).post('/api/mcp/servers').send({ config: nonOboConfig });

        expect(response.status).toBe(201);
        expect(db.getRoleByName).not.toHaveBeenCalled();
        expect(mockRegistryInstance.addServer).toHaveBeenCalled();
      });

      it('rejects PATCH with obo body when role lacks CONFIGURE_OBO', async () => {
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              CONFIGURE_OBO: false,
            },
          },
        });

        const response = await request(app)
          .patch('/api/mcp/servers/obo-server')
          .send({ config: oboConfig });

        expect(response.status).toBe(403);
        expect(response.body.message).toMatch(/Insufficient permissions to configure OBO/);
        expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled();
      });

      it('allows PATCH without CONFIGURE_OBO when OBO is unchanged', async () => {
        // Editor without CONFIGURE_OBO should still be able to edit non-OBO fields
        // (title, URL, description) on an OBO server as long as the OBO block is
        // re-sent unchanged. Closes the regression where any save of an OBO server
        // by such a user was rejected even when OBO itself was not being modified.
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              CONFIGURE_OBO: false,
            },
          },
        });
        mockRegistryInstance.getServerConfig.mockResolvedValue({
          ...oboConfig,
          obo: { scopes: 'api://mcp-server-id/Mcp.Tools.ReadWrite' },
        });
        mockRegistryInstance.updateServer.mockResolvedValue({
          ...oboConfig,
          title: 'Renamed OBO Server',
        });

        const response = await request(app)
          .patch('/api/mcp/servers/obo-server')
          .send({
            config: {
              ...oboConfig,
              title: 'Renamed OBO Server',
              obo: { scopes: 'api://mcp-server-id/Mcp.Tools.ReadWrite' },
            },
          });

        expect(response.status).toBe(200);
        expect(mockRegistryInstance.updateServer).toHaveBeenCalled();
      });

      it('rejects PATCH that removes OBO from an existing OBO server without CONFIGURE_OBO', async () => {
        // Closes the silent-downgrade vector: a user with UPDATE but not
        // CONFIGURE_OBO must not be able to convert an OBO server to non-OBO,
        // because doing so de-secures the server end-to-end.
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              CONFIGURE_OBO: false,
            },
          },
        });
        mockRegistryInstance.getServerConfig.mockResolvedValue({
          ...oboConfig,
          obo: { scopes: 'api://mcp-server-id/Mcp.Tools.ReadWrite' },
        });

        // Submit body that omits the obo field (auth_type changed away from OBO)
        const downgradePayload = {
          type: 'streamable-http',
          url: 'https://mcp-server.example.com/mcp',
          title: 'OBO Server',
        };
        const response = await request(app)
          .patch('/api/mcp/servers/obo-server')
          .send({ config: downgradePayload });

        expect(response.status).toBe(403);
        expect(response.body.message).toMatch(/Insufficient permissions to configure OBO/);
        expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled();
      });

      it('rejects PATCH that redirects the URL of an existing OBO server without CONFIGURE_OBO', async () => {
        // Closes the OBO redirect vector — the original trust-boundary concern
        // CONFIGURE_OBO was introduced to address. A user with UPDATE but
        // without the permission must not be able to point an existing OBO
        // server at an attacker-controlled endpoint, which would cause OBO
        // tokens minted for other users to be exfiltrated to that endpoint.
        // The same allowlist policy also covers `proxy`, `headers`, transport
        // type, and auth blocks.
        db.getRoleByName.mockResolvedValue({
          name: 'USER',
          permissions: {
            MCP_SERVERS: {
              USE: true,
              CREATE: true,
              CONFIGURE_OBO: false,
            },
          },
        });
        mockRegistryInstance.getServerConfig.mockResolvedValue({
          ...oboConfig,
          obo: { scopes: 'api://mcp-server-id/Mcp.Tools.ReadWrite' },
        });

        const redirectPayload = {
          ...oboConfig,
          url: 'https://attacker.example.com/mcp',
          obo: { scopes: 'api://mcp-server-id/Mcp.Tools.ReadWrite' },
        };
        const response = await request(app)
          .patch('/api/mcp/servers/obo-server')
          .send({ config: redirectPayload });

        expect(response.status).toBe(403);
        expect(response.body.message).toMatch(/Insufficient permissions to configure OBO/);
        expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled();
      });
    });

    it('should fail closed when config-managed names cannot be resolved', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test Server',
      };

      mockResolveMcpConfigNames.mockRejectedValueOnce(new Error('Config lookup failed'));

      const response = await request(app).post('/api/mcp/servers').send({ config: validConfig });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ message: 'Config lookup failed' });
      expect(mockRegistryInstance.addServer).not.toHaveBeenCalled();
    });
  });

  describe('GET /servers/:serverName', () => {
    it('should return server config when found', async () => {
      const mockConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test Server',
        source: 'user',
      };

      mockRegistryInstance.getServerConfig.mockResolvedValue(mockConfig);

      const response = await request(app).get('/api/mcp/servers/test-server');

      expect(response.status).toBe(200);
      expect(response.body.type).toBe('sse');
      expect(response.body.url).toBe('https://mcp-server.example.com/sse');
      expect(response.body.title).toBe('Test Server');
      expect(mockRegistryInstance.getServerConfig).toHaveBeenCalledWith(
        'test-server',
        'test-user-id',
        {},
      );
    });

    it('should return 404 when server not found', async () => {
      mockRegistryInstance.getServerConfig.mockResolvedValue(undefined);

      const response = await request(app).get('/api/mcp/servers/non-existent-server');

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ message: 'MCP server not found' });
    });

    it('should redact secrets from get response', async () => {
      mockRegistryInstance.getServerConfig.mockResolvedValue({
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Secret Server',
        apiKey: { source: 'admin', authorization_type: 'bearer', key: 'decrypted-admin-key' },
        oauth: { client_id: 'cid', client_secret: 'decrypted-oauth-secret' },
        headers: { Authorization: 'Bearer internal-token' },
        oauth_headers: { 'X-OAuth': 'secret-value' },
      });

      const response = await request(app).get('/api/mcp/servers/secret-server');

      expect(response.status).toBe(200);
      expect(response.body.title).toBe('Secret Server');
      expect(response.body.apiKey?.key).toBeUndefined();
      expect(response.body.apiKey?.source).toBe('admin');
      expect(response.body.oauth?.client_secret).toBeUndefined();
      expect(response.body.oauth?.client_id).toBe('cid');
      expect(response.body.headers).toBeUndefined();
      expect(response.body.oauth_headers).toBeUndefined();
    });

    it('should return 500 when registry throws error', async () => {
      mockRegistryInstance.getServerConfig.mockRejectedValue(new Error('Database error'));

      const response = await request(app).get('/api/mcp/servers/error-server');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ message: 'Database error' });
    });
  });

  describe('PATCH /servers/:serverName', () => {
    it('should update server with valid config', async () => {
      const updatedConfig = {
        type: 'sse',
        url: 'https://updated-mcp-server.example.com/sse',
        title: 'Updated Server',
        description: 'Updated description',
      };

      mockRegistryInstance.updateServer.mockResolvedValue({ ...updatedConfig, source: 'user' });

      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({ config: updatedConfig });

      expect(response.status).toBe(200);
      expect(response.body.type).toBe('sse');
      expect(response.body.url).toBe('https://updated-mcp-server.example.com/sse');
      expect(response.body.title).toBe('Updated Server');
      expect(mockRegistryInstance.updateServer).toHaveBeenCalledWith(
        'test-server',
        expect.objectContaining({
          type: 'sse',
          url: 'https://updated-mcp-server.example.com/sse',
        }),
        'DB',
        'test-user-id',
      );
    });

    it('should redact secrets from update response', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Updated Server',
      };

      mockRegistryInstance.updateServer.mockResolvedValue({
        ...validConfig,
        apiKey: { source: 'admin', authorization_type: 'bearer', key: 'preserved-admin-key' },
        oauth: { client_id: 'cid', client_secret: 'preserved-oauth-secret' },
        headers: { Authorization: 'Bearer internal-token' },
        env: { DATABASE_URL: 'postgres://admin:pass@localhost/db' },
      });

      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({ config: validConfig });

      expect(response.status).toBe(200);
      expect(response.body.title).toBe('Updated Server');
      expect(response.body.apiKey?.key).toBeUndefined();
      expect(response.body.apiKey?.source).toBe('admin');
      expect(response.body.oauth?.client_secret).toBeUndefined();
      expect(response.body.oauth?.client_id).toBe('cid');
      expect(response.body.headers).toBeUndefined();
      expect(response.body.env).toBeUndefined();
    });

    it('should return 400 for invalid configuration', async () => {
      const invalidConfig = {
        type: 'sse',
        // Missing required 'url' field
        title: 'Invalid Update',
      };

      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({ config: invalidConfig });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(response.body.errors).toBeDefined();
    });

    it('should reject SSE URL containing env variable references', async () => {
      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({
          config: {
            type: 'sse',
            url: 'http://attacker.com/?secret=${JWT_SECRET}',
          },
        });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled();
    });

    it('should reject streamable-http URL containing env variable references', async () => {
      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({
          config: {
            type: 'streamable-http',
            url: 'http://attacker.com/?key=${CREDS_KEY}',
          },
        });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled();
    });

    it('should reject websocket URL containing env variable references', async () => {
      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({
          config: {
            type: 'websocket',
            url: 'ws://attacker.com/?secret=${MONGO_URI}',
          },
        });

      expect(response.status).toBe(400);
      expect(response.body.message).toBe('Invalid configuration');
      expect(mockRegistryInstance.updateServer).not.toHaveBeenCalled();
    });

    it('should return 500 when registry throws error', async () => {
      const validConfig = {
        type: 'sse',
        url: 'https://mcp-server.example.com/sse',
        title: 'Test Server',
      };

      mockRegistryInstance.updateServer.mockRejectedValue(new Error('Update failed'));

      const response = await request(app)
        .patch('/api/mcp/servers/test-server')
        .send({ config: validConfig });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ message: 'Update failed' });
    });
  });

  describe('DELETE /servers/:serverName', () => {
    it('should delete server successfully', async () => {
      mockRegistryInstance.removeServer.mockResolvedValue(undefined);

      const response = await request(app).delete('/api/mcp/servers/test-server');

      expect(response.status).toBe(200);
      expect(response.body).toEqual({ message: 'MCP server deleted successfully' });
      expect(mockRegistryInstance.removeServer).toHaveBeenCalledWith(
        'test-server',
        'DB',
        'test-user-id',
      );
    });

    it('should return 500 when registry throws error', async () => {
      mockRegistryInstance.removeServer.mockRejectedValue(new Error('Deletion failed'));

      const response = await request(app).delete('/api/mcp/servers/error-server');

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ message: 'Deletion failed' });
    });
  });
});
