Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ const authBySession: Record<string, AuthData> = {};
const BASE_HEADERS: Record<string, string> = {
Accept: "application/json",
"Content-Type": "application/json",
"User-Agent": `gitlab-mcp-server/${SERVER_VERSION} (node-fetch)`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate the new User-Agent to get_draft_note

Adding the header only to BASE_HEADERS does not cover getDraftNote() in index.ts:2936-2952, which still calls fetch(...) directly instead of using getFetchConfig(). Users invoking the get_draft_note tool against GitLab.com or another Cloudflare-protected instance will therefore keep missing the explicit gitlab-mcp-server/... User-Agent on that endpoint, so this fix is incomplete for draft-note workflows.

Useful? React with 👍 / 👎.

};

/**
Expand Down
92 changes: 92 additions & 0 deletions test-results-oauth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
[
{
"name": "OAuth class instantiation",
"status": "passed",
"duration": 0
},
{
"name": "Token storage path configuration",
"status": "passed",
"duration": 0
},
{
"name": "Scope configuration with api only",
"status": "passed",
"duration": 0
},
{
"name": "Multiple scopes configuration (redundant)",
"status": "passed",
"duration": 1
},
{
"name": "hasValidToken returns false without token",
"status": "passed",
"duration": 0
},
{
"name": "hasValidToken returns true with valid token",
"status": "passed",
"duration": 1
},
{
"name": "hasValidToken returns false with expired token",
"status": "passed",
"duration": 0
},
{
"name": "clearToken removes token file",
"status": "passed",
"duration": 1
},
{
"name": "Token file has correct permissions",
"status": "passed",
"duration": 0
},
{
"name": "Port availability check",
"status": "passed",
"duration": 3
},
{
"name": "OAuth redirect URI parsing",
"status": "passed",
"duration": 0
},
{
"name": "Token expiration calculation",
"status": "passed",
"duration": 0
},
{
"name": "Shared server concept",
"status": "passed",
"duration": 2
},
{
"name": "Environment variable configuration",
"status": "passed",
"duration": 0
},
{
"name": "Token data structure validation",
"status": "passed",
"duration": 0
},
{
"name": "Invalid token storage path handling",
"status": "passed",
"duration": 0
},
{
"name": "Self-hosted GitLab URL configuration",
"status": "passed",
"duration": 0
},
{
"name": "Custom port in redirect URI",
"status": "passed",
"duration": 0
}
]
Comment on lines +1 to +92
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test-results-oauth.json appears to be a generated test report (written by test/oauth-tests.ts). Committing this kind of output tends to create noisy diffs and can go stale quickly; consider removing it from source control and adding it to .gitignore, or generating it only as a CI artifact instead of tracking it in the repo.

Suggested change
[
{
"name": "OAuth class instantiation",
"status": "passed",
"duration": 0
},
{
"name": "Token storage path configuration",
"status": "passed",
"duration": 0
},
{
"name": "Scope configuration with api only",
"status": "passed",
"duration": 0
},
{
"name": "Multiple scopes configuration (redundant)",
"status": "passed",
"duration": 1
},
{
"name": "hasValidToken returns false without token",
"status": "passed",
"duration": 0
},
{
"name": "hasValidToken returns true with valid token",
"status": "passed",
"duration": 1
},
{
"name": "hasValidToken returns false with expired token",
"status": "passed",
"duration": 0
},
{
"name": "clearToken removes token file",
"status": "passed",
"duration": 1
},
{
"name": "Token file has correct permissions",
"status": "passed",
"duration": 0
},
{
"name": "Port availability check",
"status": "passed",
"duration": 3
},
{
"name": "OAuth redirect URI parsing",
"status": "passed",
"duration": 0
},
{
"name": "Token expiration calculation",
"status": "passed",
"duration": 0
},
{
"name": "Shared server concept",
"status": "passed",
"duration": 2
},
{
"name": "Environment variable configuration",
"status": "passed",
"duration": 0
},
{
"name": "Token data structure validation",
"status": "passed",
"duration": 0
},
{
"name": "Invalid token storage path handling",
"status": "passed",
"duration": 0
},
{
"name": "Self-hosted GitLab URL configuration",
"status": "passed",
"duration": 0
},
{
"name": "Custom port in redirect URI",
"status": "passed",
"duration": 0
}
]
[]

Copilot uses AI. Check for mistakes.
54 changes: 54 additions & 0 deletions test/test-user-agent-header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, test, before, after } from 'node:test';
import assert from 'node:assert';
import { MockGitLabServer, findMockServerPort } from './utils/mock-gitlab-server.js';

const MOCK_TOKEN = 'glpat-mock-token-12345';

describe('User-Agent Header Tests', () => {
let mockServer: MockGitLabServer;
let mockPort: number;
Comment on lines +7 to +9
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file is not currently referenced by the existing npm run test:* scripts, so it may never execute in CI/local runs (the mock test script only runs a fixed list of files). Consider wiring this test into the test runner (or merging it into an existing mock/integration test file) so the User-Agent regression is actually covered.

Copilot uses AI. Check for mistakes.

before(async () => {
mockPort = await findMockServerPort();
mockServer = new MockGitLabServer({
port: mockPort,
validTokens: [MOCK_TOKEN]
});
await mockServer.start();

// Add custom route to capture User-Agent header
mockServer.addMockHandler('get', '/user', (req, res) => {
const userAgent = req.headers['user-agent'];
res.json({
id: 1,
username: 'test_user',
name: 'Test User',
user_agent: userAgent
});
});
});

after(async () => {
if (mockServer) {
await mockServer.stop();
}
});

test('User-Agent header should be set in API requests', async () => {
// Import node-fetch to make a test request
const fetch = (await import('node-fetch')).default;

// Make a request to the mock server to verify User-Agent is set
const response = await fetch(`http://127.0.0.1:${mockPort}/api/v4/user`, {
headers: {
'Authorization': `Bearer ${MOCK_TOKEN}`,
'User-Agent': 'gitlab-mcp-server/2.0.23 (node-fetch)'
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test hard-codes the server version (2.0.23) inside the expected User-Agent value, which will break as soon as the package version changes. Prefer asserting stable substrings/patterns (e.g. startsWith gitlab-mcp-server/ and contains (node-fetch)), or derive the version from the same source as SERVER_VERSION so the test stays in sync.

Suggested change
'User-Agent': 'gitlab-mcp-server/2.0.23 (node-fetch)'
'User-Agent': 'gitlab-mcp-server/test-version (node-fetch)'

Copilot uses AI. Check for mistakes.
}
});

const data = await response.json() as any;
assert.ok(data.user_agent, 'User-Agent header should be present');
assert.ok(data.user_agent.includes('gitlab-mcp-server'), 'User-Agent should include "gitlab-mcp-server"');
Comment on lines +41 to +51
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test sets the User-Agent header explicitly in the fetch() call, so it doesn't verify that the production code change (adding User-Agent to BASE_HEADERS / getFetchConfig()) is working. To actually test the fix, exercise a real code path that uses BASE_HEADERS (e.g., spawn build/index.js and call a tool that hits /user), and assert the mock server observed the expected User-Agent without providing it in the test request headers.

Suggested change
// Make a request to the mock server to verify User-Agent is set
const response = await fetch(`http://127.0.0.1:${mockPort}/api/v4/user`, {
headers: {
'Authorization': `Bearer ${MOCK_TOKEN}`,
'User-Agent': 'gitlab-mcp-server/2.0.23 (node-fetch)'
}
});
const data = await response.json() as any;
assert.ok(data.user_agent, 'User-Agent header should be present');
assert.ok(data.user_agent.includes('gitlab-mcp-server'), 'User-Agent should include "gitlab-mcp-server"');
// Make a request to the mock server to verify User-Agent is set.
// Do not set the User-Agent header explicitly here; rely on the client/runtime.
const response = await fetch(`http://127.0.0.1:${mockPort}/api/v4/user`, {
headers: {
'Authorization': `Bearer ${MOCK_TOKEN}`,
}
});
const data = await response.json() as any;
assert.ok(data.user_agent, 'User-Agent header should be present');

Copilot uses AI. Check for mistakes.
assert.ok(data.user_agent.includes('node-fetch'), 'User-Agent should include "node-fetch"');
});
});
Loading