Skip to main content
Glama
remote-auth-tests.ts11 kB
/** * Remote Authorization Tests * Tests for per-session HTTP header-based authorization */ import * as path from 'path'; import { describe, test, after, before } from 'node:test'; import assert from 'node:assert'; import fetch from 'node-fetch'; import { launchServer, findAvailablePort, cleanupServers, ServerInstance, TransportMode, checkHealthEndpoint, HOST } from './utils/server-launcher.js'; console.log('🔐 Remote Authorization Tests'); console.log(''); // Configuration check const GITLAB_API_URL = process.env.GITLAB_API_URL || "https://gitlab.com"; const GITLAB_TOKEN = process.env.GITLAB_TOKEN_TEST || process.env.GITLAB_TOKEN; const TEST_PROJECT_ID = process.env.TEST_PROJECT_ID; console.log('🔧 Test Configuration:'); console.log(` GitLab URL: ${GITLAB_API_URL}`); console.log(` Token: ${GITLAB_TOKEN ? '✅ Provided' : '❌ Missing'}`); console.log(` Project ID: ${TEST_PROJECT_ID || '❌ Missing'}`); // Validate required configuration if (!GITLAB_TOKEN) { console.error('❌ Error: GITLAB_TOKEN_TEST or GITLAB_TOKEN environment variable is required for testing'); console.error(' Set one of these variables to your GitLab API token'); process.exit(1); } if (!TEST_PROJECT_ID) { console.error('❌ Error: TEST_PROJECT_ID environment variable is required for testing'); console.error(' Set this variable to a valid GitLab project ID (e.g., "123" or "group/project")'); process.exit(1); } console.log('✅ Configuration validated'); console.log(''); let servers: ServerInstance[] = []; // Cleanup function for all tests const cleanup = () => { cleanupServers(servers); servers = []; }; // Handle process termination process.on('SIGINT', cleanup); process.on('SIGTERM', cleanup); process.on('exit', cleanup); /** * Helper to send MCP request with custom headers */ async function sendMCPRequest( url: string, method: object, headers: Record<string, string> = {} ): Promise<any> { const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', ...headers, }, body: JSON.stringify(method), }); if (!response.ok) { const text = await response.text(); throw new Error(`HTTP ${response.status}: ${text}`); } return response.json(); } describe('Remote Authorization - Streamable HTTP with Authorization header', () => { let server: ServerInstance; let port: number; let mcpUrl: string; before(async () => { port = await findAvailablePort(); server = await launchServer({ mode: TransportMode.STREAMABLE_HTTP, port, timeout: 3000, env: { STREAMABLE_HTTP: 'true', REMOTE_AUTHORIZATION: 'true', GITLAB_API_URL: `${GITLAB_API_URL}/api/v4`, GITLAB_PROJECT_ID: TEST_PROJECT_ID, GITLAB_READ_ONLY_MODE: 'true', // Explicitly no GITLAB_PERSONAL_ACCESS_TOKEN } }); servers.push(server); mcpUrl = `http://${HOST}:${port}/mcp`; // Verify server started successfully assert.ok(server.process.pid !== undefined, 'Server process should have PID'); // Verify health check if (server.port) { const health = await checkHealthEndpoint(server.port); assert.strictEqual(health.status, 'healthy', 'Health status should be healthy'); } console.log('Server started with remote authorization enabled'); }); after(async () => { cleanup(); console.log('Server stopped'); }); test('should reject request without Authorization header', async () => { const initRequest = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' } } }; try { await sendMCPRequest(mcpUrl, initRequest, { 'mcp-session-id': 'test-session-no-auth' }); assert.fail('Should have rejected request without auth header'); } catch (error) { assert.ok(error instanceof Error); assert.ok(error.message.includes('401'), 'Should get 401 Unauthorized'); } }); test('should accept request with Authorization Bearer header', async () => { const sessionId = `test-session-bearer-${Date.now()}`; const initRequest = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' } } }; const response = await sendMCPRequest(mcpUrl, initRequest, { 'mcp-session-id': sessionId, 'authorization': `Bearer ${GITLAB_TOKEN}` }); assert.ok(response, 'Should get response'); assert.ok(response.result, 'Should have result'); assert.strictEqual(response.result.protocolVersion, '2024-11-05', 'Protocol version should match'); }); test('should reuse auth from first request in subsequent requests', async () => { const sessionId = `test-session-reuse-${Date.now()}`; // First request with auth const initRequest = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' } } }; const initResponse = await sendMCPRequest(mcpUrl, initRequest, { 'mcp-session-id': sessionId, 'authorization': `Bearer ${GITLAB_TOKEN}` }); assert.ok(initResponse.result, 'Init should succeed'); // Second request without auth header (should reuse) const listToolsRequest = { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} }; const listResponse = await sendMCPRequest(mcpUrl, listToolsRequest, { 'mcp-session-id': sessionId // No authorization header }); assert.ok(listResponse.result, 'List tools should succeed with reused auth'); assert.ok(Array.isArray(listResponse.result.tools), 'Should return tools array'); assert.ok(listResponse.result.tools.length > 0, 'Should have at least one tool'); }); test('should call tool with Bearer token', async () => { const sessionId = `test-session-tool-${Date.now()}`; // Initialize const initRequest = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' } } }; await sendMCPRequest(mcpUrl, initRequest, { 'mcp-session-id': sessionId, 'authorization': `Bearer ${GITLAB_TOKEN}` }); // Call tool const callToolRequest = { jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'get_project', arguments: { project_id: TEST_PROJECT_ID } } }; const toolResponse = await sendMCPRequest(mcpUrl, callToolRequest, { 'mcp-session-id': sessionId }); assert.ok(toolResponse.result, 'Tool call should succeed'); assert.ok(toolResponse.result.content, 'Should have content'); assert.ok(Array.isArray(toolResponse.result.content), 'Content should be array'); assert.ok(toolResponse.result.content.length > 0, 'Should have content items'); const projectData = JSON.parse(toolResponse.result.content[0].text); assert.ok(projectData.id, 'Should have project id'); assert.ok(projectData.name, 'Should have project name'); }); }); describe('Remote Authorization - Streamable HTTP with Private-Token header', () => { let server: ServerInstance; let port: number; let mcpUrl: string; before(async () => { port = await findAvailablePort(); server = await launchServer({ mode: TransportMode.STREAMABLE_HTTP, port, timeout: 3000, env: { STREAMABLE_HTTP: 'true', REMOTE_AUTHORIZATION: 'true', GITLAB_API_URL: `${GITLAB_API_URL}/api/v4`, GITLAB_PROJECT_ID: TEST_PROJECT_ID, GITLAB_READ_ONLY_MODE: 'true', } }); servers.push(server); mcpUrl = `http://${HOST}:${port}/mcp`; console.log('Server started for Private-Token tests'); }); after(async () => { cleanup(); console.log('Server stopped'); }); test('should accept request with Private-Token header', async () => { const sessionId = `test-session-private-${Date.now()}`; const initRequest = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' } } }; const response = await sendMCPRequest(mcpUrl, initRequest, { 'mcp-session-id': sessionId, 'private-token': GITLAB_TOKEN }); assert.ok(response.result, 'Should succeed with Private-Token'); assert.strictEqual(response.result.protocolVersion, '2024-11-05', 'Protocol version should match'); }); test('should call tool with Private-Token', async () => { const sessionId = `test-session-private-tool-${Date.now()}`; // Initialize const initRequest = { jsonrpc: '2.0', id: 1, method: 'initialize', params: { protocolVersion: '2024-11-05', capabilities: {}, clientInfo: { name: 'test-client', version: '1.0.0' } } }; await sendMCPRequest(mcpUrl, initRequest, { 'mcp-session-id': sessionId, 'private-token': GITLAB_TOKEN }); // Call tool const callToolRequest = { jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'list_merge_requests', arguments: { project_id: TEST_PROJECT_ID } } }; const toolResponse = await sendMCPRequest(mcpUrl, callToolRequest, { 'mcp-session-id': sessionId }); assert.ok(toolResponse.result, 'Tool call should succeed with Private-Token'); assert.ok(toolResponse.result.content, 'Should have content'); }); }); describe('Remote Authorization - SSE mode should be disabled', () => { test('should fail to start with SSE and REMOTE_AUTHORIZATION', async () => { const port = await findAvailablePort(); try { const server = await launchServer({ mode: TransportMode.SSE, port, timeout: 3000, env: { SSE: 'true', REMOTE_AUTHORIZATION: 'true', GITLAB_API_URL: `${GITLAB_API_URL}/api/v4`, } }); // If we get here, the server started when it shouldn't have servers.push(server); assert.fail('Server should not start with SSE and REMOTE_AUTHORIZATION=true'); } catch (error) { // Expected: server should fail to start assert.ok(error instanceof Error, 'Should throw an error'); console.log('✅ Server correctly rejected SSE mode with remote authorization'); } }); });

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