Skip to main content
Glama
dynamic-api-url-test.ts11.6 kB
/** * Dynamic GitLab API URL Test Suite * Tests the ability to connect to multiple GitLab instances via custom headers */ import { describe, test, after, before } from 'node:test'; import assert from 'node:assert'; import { launchServer, findAvailablePort, cleanupServers, ServerInstance, TransportMode, HOST } from './utils/server-launcher.js'; import { MockGitLabServer, findMockServerPort } from './utils/mock-gitlab-server.js'; import { CustomHeaderClient } from './clients/custom-header-client.js'; // Test constants const MOCK_TOKEN_1 = 'glpat-mock-token-instance-1'; const MOCK_TOKEN_2 = 'glpat-mock-token-instance-2'; // Port ranges const MOCK_GITLAB_PORT_BASE_1 = 9100; const MOCK_GITLAB_PORT_BASE_2 = 9200; const MCP_SERVER_PORT_BASE = 3100; console.log('🌐 Dynamic GitLab API URL Test Suite'); console.log(''); describe('Dynamic API URL - Multiple GitLab Instances', () => { let mcpUrl: string; let mockGitLab1: MockGitLabServer; let mockGitLab2: MockGitLabServer; let mockGitLabUrl1: string; let mockGitLabUrl2: string; let servers: ServerInstance[] = []; before(async () => { // Start first mock GitLab server const mockPort1 = await findMockServerPort(MOCK_GITLAB_PORT_BASE_1); mockGitLab1 = new MockGitLabServer({ port: mockPort1, validTokens: [MOCK_TOKEN_1] }); await mockGitLab1.start(); mockGitLabUrl1 = mockGitLab1.getUrl(); // Start second mock GitLab server const mockPort2 = await findMockServerPort(MOCK_GITLAB_PORT_BASE_2); mockGitLab2 = new MockGitLabServer({ port: mockPort2, validTokens: [MOCK_TOKEN_2] }); await mockGitLab2.start(); mockGitLabUrl2 = mockGitLab2.getUrl(); // Start MCP server with dynamic API URL enabled const mcpPort = await findAvailablePort(MCP_SERVER_PORT_BASE); const server = await launchServer({ mode: TransportMode.STREAMABLE_HTTP, port: mcpPort, timeout: 5000, env: { STREAMABLE_HTTP: 'true', REMOTE_AUTHORIZATION: 'true', ENABLE_DYNAMIC_API_URL: 'true', GITLAB_READ_ONLY_MODE: 'true', } }); servers.push(server); mcpUrl = `http://${HOST}:${mcpPort}/mcp`; console.log(`Mock GitLab Instance 1: ${mockGitLabUrl1}`); console.log(`Mock GitLab Instance 2: ${mockGitLabUrl2}`); console.log(`MCP Server: ${mcpUrl}`); }); after(async () => { cleanupServers(servers); if (mockGitLab1) { await mockGitLab1.stop(); } if (mockGitLab2) { await mockGitLab2.stop(); } }); test('should connect to first GitLab instance with custom API URL', async () => { const client = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': mockGitLabUrl1 }); await client.connect(mcpUrl); const tools = await client.listTools(); assert.ok(tools.tools.length > 0, 'Should have tools'); console.log(` ✓ Connected to instance 1, got ${tools.tools.length} tools`); await client.disconnect(); }); test('should connect to second GitLab instance with different API URL', async () => { const client = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_2}`, 'x-gitlab-api-url': mockGitLabUrl2 }); await client.connect(mcpUrl); const tools = await client.listTools(); assert.ok(tools.tools.length > 0, 'Should have tools'); console.log(` ✓ Connected to instance 2, got ${tools.tools.length} tools`); await client.disconnect(); }); test('should handle multiple concurrent connections to different instances', async () => { const client1 = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': mockGitLabUrl1 }); const client2 = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_2}`, 'x-gitlab-api-url': mockGitLabUrl2 }); // Connect both clients await Promise.all([ client1.connect(mcpUrl), client2.connect(mcpUrl) ]); // List tools from both const [tools1, tools2] = await Promise.all([ client1.listTools(), client2.listTools() ]); assert.ok(tools1.tools.length > 0, 'Instance 1 should have tools'); assert.ok(tools2.tools.length > 0, 'Instance 2 should have tools'); console.log(' ✓ Both instances accessible concurrently'); // Disconnect both await Promise.all([ client1.disconnect(), client2.disconnect() ]); }); test('should reject invalid API URL format', async () => { const client = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': 'not-a-valid-url' }); try { await client.connect(mcpUrl); await client.listTools(); await client.disconnect(); assert.fail('Should have rejected invalid URL'); } catch (error) { assert.ok(error instanceof Error); console.log(' ✓ Correctly rejected invalid URL format'); } }); test('should reject connection with wrong token for instance', async () => { const client = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, // Token for instance 1 'x-gitlab-api-url': mockGitLabUrl2 // But connecting to instance 2 }); try { await client.connect(mcpUrl); await client.listTools(); await client.disconnect(); assert.fail('Should have rejected wrong token'); } catch (error) { assert.ok(error instanceof Error); console.log(' ✓ Correctly rejected wrong token for instance'); } }); test('should maintain separate sessions for different API URLs', async () => { const client1 = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': mockGitLabUrl1 }); const client2 = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_2}`, 'x-gitlab-api-url': mockGitLabUrl2 }); // Connect both await client1.connect(mcpUrl); await client2.connect(mcpUrl); // Get session IDs const sessionId1 = client1.getSessionId(); const sessionId2 = client2.getSessionId(); assert.ok(sessionId1, 'Session 1 should exist'); assert.ok(sessionId2, 'Session 2 should exist'); assert.notStrictEqual(sessionId1, sessionId2, 'Sessions should be different'); console.log(' ✓ Separate sessions maintained for different instances'); // Make requests from both sessions const [tools1, tools2] = await Promise.all([ client1.listTools(), client2.listTools() ]); assert.ok(tools1.tools.length > 0, 'Instance 1 should work'); assert.ok(tools2.tools.length > 0, 'Instance 2 should work'); console.log(' ✓ Both sessions work independently'); await client1.disconnect(); await client2.disconnect(); }); test('should normalize API URLs correctly', async () => { // Test with URL that needs normalization (no /api/v4) const client = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': mockGitLabUrl1 // Without /api/v4 }); await client.connect(mcpUrl); const tools = await client.listTools(); assert.ok(tools.tools.length > 0, 'Should work with normalized URL'); console.log(' ✓ URL normalization works correctly'); await client.disconnect(); }); }); describe('Dynamic API URL - Connection Pool', () => { let mcpUrl: string; let metricsUrl: string; let mockGitLab1: MockGitLabServer; let mockGitLab2: MockGitLabServer; let mockGitLabUrl1: string; let mockGitLabUrl2: string; let servers: ServerInstance[] = []; before(async () => { // Start mock GitLab servers const mockPort1 = await findMockServerPort(MOCK_GITLAB_PORT_BASE_1 + 50); mockGitLab1 = new MockGitLabServer({ port: mockPort1, validTokens: [MOCK_TOKEN_1] }); await mockGitLab1.start(); mockGitLabUrl1 = mockGitLab1.getUrl(); const mockPort2 = await findMockServerPort(MOCK_GITLAB_PORT_BASE_2 + 50); mockGitLab2 = new MockGitLabServer({ port: mockPort2, validTokens: [MOCK_TOKEN_2] }); await mockGitLab2.start(); mockGitLabUrl2 = mockGitLab2.getUrl(); // Start MCP server const mcpPort = await findAvailablePort(MCP_SERVER_PORT_BASE + 50); const server = await launchServer({ mode: TransportMode.STREAMABLE_HTTP, port: mcpPort, timeout: 5000, env: { STREAMABLE_HTTP: 'true', REMOTE_AUTHORIZATION: 'true', ENABLE_DYNAMIC_API_URL: 'true', GITLAB_CLIENT_POOL_SIZE: '5', GITLAB_CLIENT_IDLE_TIMEOUT: '30000', } }); servers.push(server); mcpUrl = `http://${HOST}:${mcpPort}/mcp`; metricsUrl = `http://${HOST}:${mcpPort}/metrics`; console.log(`MCP Server: ${mcpUrl}`); console.log(`Metrics URL: ${metricsUrl}`); }); after(async () => { cleanupServers(servers); if (mockGitLab1) { await mockGitLab1.stop(); } if (mockGitLab2) { await mockGitLab2.stop(); } }); test('should track pool statistics via metrics endpoint', async () => { // Make some connections first const client1 = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': mockGitLabUrl1 }); const client2 = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_2}`, 'x-gitlab-api-url': mockGitLabUrl2 }); await client1.connect(mcpUrl); await client2.connect(mcpUrl); await client1.listTools(); await client2.listTools(); // Check metrics const response = await fetch(metricsUrl); assert.ok(response.ok, 'Metrics endpoint should be accessible'); const metrics = await response.json(); assert.ok(metrics.gitlabClientPool, 'Should have pool metrics'); assert.ok(typeof metrics.gitlabClientPool.size === 'number', 'Should have pool size'); assert.ok(typeof metrics.gitlabClientPool.maxSize === 'number', 'Should have max size'); console.log(' ✓ Pool metrics available'); console.log(` ℹ️ Pool size: ${metrics.gitlabClientPool.size}/${metrics.gitlabClientPool.maxSize}`); await client1.disconnect(); await client2.disconnect(); }); test('should reuse connections for same API URL', async () => { // Get initial metrics const response1 = await fetch(metricsUrl); const metrics1 = await response1.json(); const initialSize = metrics1.gitlabClientPool?.size || 0; // Create multiple clients to same instance const clients = []; for (let i = 0; i < 3; i++) { const client = new CustomHeaderClient({ 'authorization': `Bearer ${MOCK_TOKEN_1}`, 'x-gitlab-api-url': mockGitLabUrl1 }); await client.connect(mcpUrl); await client.listTools(); clients.push(client); } // Check metrics - should not have created 3 new pool entries const response2 = await fetch(metricsUrl); const metrics2 = await response2.json(); const finalSize = metrics2.gitlabClientPool?.size || 0; // Pool size should increase by at most 1 (for the shared URL) assert.ok(finalSize - initialSize <= 1, 'Should reuse connection for same URL'); console.log(' ✓ Connection reuse working'); console.log(` ℹ️ Pool size change: ${initialSize} → ${finalSize}`); // Cleanup for (const client of clients) { await client.disconnect(); } }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zereight/gitlab-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server