feat: add cookie-based authentication support for enterprise GitLab (#101)
* feat: add cookie-based authentication support for GitLab instances 🍪 - Add GITLAB_AUTH_COOKIE_PATH environment variable support - Handle #HttpOnly_ prefix in cookie files properly - Enable redirect following when cookies are present - Maintain compatibility with existing token-based auth * chore: prepare fork for npm publishing as @mattweg/gitlab-mcp - Update package name to @mattweg/gitlab-mcp - Bump version to 1.0.63-fork.1 - Add attribution to original author zereight - Add deprecation notice referencing upstream PR #100 - Add repository and homepage URLs for fork * fix: remove duplicate documentation line - removed the duplicate GITLAB_AUTH_COOKIE_PATH from README.md * fix: move cookie header outside conditional block for universal auth support - Move cookie header setting outside if/else block to ensure it applies to both old (Private-Token) and new (Bearer) GitLab authentication - Fixes issue where cookies were only set for Bearer token auth - Maintains backward compatibility with existing authentication methods - Enables cookie-based authentication for all GitLab instance types Resolves authentication failures when using GITLAB_AUTH_COOKIE_PATH with GitLab instances that require cookie-based authentication. * 1.0.63 * fix: add support for macOS cookie format in auth cookie parsing 🍪 - Add fallback parsing for macOS cookie format - Handle cookie files with different structure than standard Netscape format - Maintain compatibility with existing Linux cookie parsing - Extract cookie name and value from space-separated format Resolves authentication failures when using GITLAB_AUTH_COOKIE_PATH on macOS systems. * 1.0.64 * chore: update version to 1.0.63-fork.3 * fix: implement proper cookie jar authentication for macOS - Replace static cookie string with fetch-cookie + tough-cookie - Add proper Netscape cookie format parsing with domain context - Enable automatic cookie handling during OAuth2 redirects - Fixes authentication issues on macOS with enterprise SSO * chore: update version to 1.0.63-fork.4 * feat: add cookie-based authentication support for enterprise GitLab instances Add support for Netscape cookie file authentication to enable access to enterprise GitLab instances that use SSO/OAuth2 redirects. - Add GITLAB_AUTH_COOKIE_PATH environment variable - Implement cookie jar with proper domain handling for redirects - Use conditional fetch assignment: cookie-enabled when path configured - Maintains backward compatibility: no cookies = original behavior - Zero changes to existing fetch() calls throughout codebase Enables authentication flows like: curl -L -b ~/.midway/cookie Useful for enterprise environments with federated authentication. * chore: update to fork version 1.0.63-fork.5 with cookie auth support * feat: add cookie-based authentication support for enterprise GitLab instances Add support for Netscape cookie file authentication to enable access to enterprise GitLab instances that use SSO/OAuth2 redirects. - Add GITLAB_AUTH_COOKIE_PATH environment variable - Implement cookie jar with proper domain handling for redirects - Use conditional fetch assignment: cookie-enabled when path configured - Maintains backward compatibility: no cookies = original behavior - Zero changes to existing fetch() calls throughout codebase Enables authentication flows like: curl -L -b ~/.midway/cookie Useful for enterprise environments with federated authentication. * feat: implement robust cookie-based authentication with hybrid parsing - Add support for Netscape cookie file format with #HttpOnly_ prefix handling - Implement hybrid approach using tough-cookie's parse() for robust cookie parsing - Add automatic session establishment for enterprise GitLab authentication - Support cookie file path via GITLAB_AUTH_COOKIE_PATH environment variable - Integrate with fetch-cookie for automatic redirect handling and session persistence - Ensure compatibility with Midway enterprise authentication flow This enables seamless authentication with enterprise GitLab instances that require cookie-based authentication while maintaining clean, maintainable code using widely-supported packages (tough-cookie + fetch-cookie). * chore: bump version to 1.0.63-fork.6 with ultra-clean cookie auth * fix: correct package name to @mattweg/gitlab-mcp for proper npx dependency resolution - Fix package name mismatch that prevented npx from installing dependencies - Bump version to 1.0.63-fork.7 - This resolves cookie authentication issues by ensuring fetch-cookie and tough-cookie are properly installed * Improve cookie authentication with robust session establishment * feat: add cookie-based authentication support This feature adds support for cookie-based authentication with GitLab instances by: - Adding a new GITLAB_AUTH_COOKIE_PATH environment variable to specify the path to a Netscape-format cookie file - Implementing a cookie jar parser that handles standard Netscape cookie format - Adding session establishment logic that checks for GitLab session cookies - Ensuring all API requests use the authenticated session This allows the MCP server to authenticate with GitLab instances that use cookie-based authentication, which is particularly useful for instances that require SSO or other authentication methods that don't support personal access tokens. --------- Co-authored-by: Moon (mattweg's AI assistant) <moon+ai-assistant@mattweg.dev> Co-authored-by: Matt Weg <mattweg@amazon.com>
This commit is contained in:
92
index.ts
92
index.ts
@ -4,7 +4,9 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
||||
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
||||
import fetch from "node-fetch";
|
||||
import nodeFetch from "node-fetch";
|
||||
import fetchCookie from "fetch-cookie";
|
||||
import { CookieJar, parse as parseCookie } from "tough-cookie";
|
||||
import { SocksProxyAgent } from "socks-proxy-agent";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
import { HttpProxyAgent } from "http-proxy-agent";
|
||||
@ -203,6 +205,7 @@ const server = new Server(
|
||||
);
|
||||
|
||||
const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN;
|
||||
const GITLAB_AUTH_COOKIE_PATH = process.env.GITLAB_AUTH_COOKIE_PATH;
|
||||
const IS_OLD = process.env.GITLAB_IS_OLD === "true";
|
||||
const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true";
|
||||
const USE_GITLAB_WIKI = process.env.USE_GITLAB_WIKI === "true";
|
||||
@ -245,6 +248,88 @@ if (HTTPS_PROXY) {
|
||||
httpsAgent = httpsAgent || new HttpsAgent(sslOptions);
|
||||
httpAgent = httpAgent || new Agent();
|
||||
|
||||
// Create cookie jar with clean Netscape file parsing
|
||||
const createCookieJar = (): CookieJar | null => {
|
||||
if (!GITLAB_AUTH_COOKIE_PATH) return null;
|
||||
|
||||
try {
|
||||
const cookiePath = GITLAB_AUTH_COOKIE_PATH.startsWith("~/")
|
||||
? path.join(process.env.HOME || "", GITLAB_AUTH_COOKIE_PATH.slice(2))
|
||||
: GITLAB_AUTH_COOKIE_PATH;
|
||||
|
||||
const jar = new CookieJar();
|
||||
const cookieContent = fs.readFileSync(cookiePath, "utf8");
|
||||
|
||||
cookieContent.split("\n").forEach(line => {
|
||||
// Handle #HttpOnly_ prefix
|
||||
if (line.startsWith("#HttpOnly_")) {
|
||||
line = line.slice(10);
|
||||
}
|
||||
// Skip comments and empty lines
|
||||
if (line.startsWith("#") || !line.trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse Netscape format: domain, flag, path, secure, expires, name, value
|
||||
const parts = line.split("\t");
|
||||
if (parts.length >= 7) {
|
||||
const [domain, , path, secure, expires, name, value] = parts;
|
||||
|
||||
// Build cookie string in standard format
|
||||
const cookieStr = `${name}=${value}; Domain=${domain}; Path=${path}${secure === "TRUE" ? "; Secure" : ""}${expires !== "0" ? `; Expires=${new Date(parseInt(expires) * 1000).toUTCString()}` : ""}`;
|
||||
|
||||
// Use tough-cookie's parse function for robust parsing
|
||||
const cookie = parseCookie(cookieStr);
|
||||
if (cookie) {
|
||||
const url = `${secure === "TRUE" ? "https" : "http"}://${domain.startsWith(".") ? domain.slice(1) : domain}`;
|
||||
jar.setCookieSync(cookie, url);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return jar;
|
||||
} catch (error) {
|
||||
console.error("Error loading cookie file:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize cookie jar and fetch
|
||||
const cookieJar = createCookieJar();
|
||||
const fetch = cookieJar ? fetchCookie(nodeFetch, cookieJar) : nodeFetch;
|
||||
|
||||
// Ensure session is established for the current request
|
||||
async function ensureSessionForRequest(): Promise<void> {
|
||||
if (!cookieJar || !GITLAB_AUTH_COOKIE_PATH) return;
|
||||
|
||||
// Extract the base URL from GITLAB_API_URL
|
||||
const apiUrl = new URL(GITLAB_API_URL);
|
||||
const baseUrl = `${apiUrl.protocol}//${apiUrl.hostname}`;
|
||||
|
||||
// Check if we already have GitLab session cookies
|
||||
const gitlabCookies = cookieJar.getCookiesSync(baseUrl);
|
||||
const hasSessionCookie = gitlabCookies.some(cookie =>
|
||||
cookie.key === '_gitlab_session' || cookie.key === 'remember_user_token'
|
||||
);
|
||||
|
||||
if (!hasSessionCookie) {
|
||||
try {
|
||||
// Establish session with a lightweight request
|
||||
await fetch(`${GITLAB_API_URL}/user`, {
|
||||
...DEFAULT_FETCH_CONFIG,
|
||||
redirect: 'follow'
|
||||
}).catch(() => {
|
||||
// Ignore errors - the important thing is that cookies get set during redirects
|
||||
});
|
||||
|
||||
// Small delay to ensure cookies are fully processed
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
} catch (error) {
|
||||
// Ignore session establishment errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modify DEFAULT_HEADERS to include agent configuration
|
||||
const DEFAULT_HEADERS: Record<string, string> = {
|
||||
Accept: "application/json",
|
||||
@ -3112,6 +3197,11 @@ server.setRequestHandler(CallToolRequestSchema, async request => {
|
||||
if (!request.params.arguments) {
|
||||
throw new Error("Arguments are required");
|
||||
}
|
||||
|
||||
// Ensure session is established for every request if cookie authentication is enabled
|
||||
if (GITLAB_AUTH_COOKIE_PATH) {
|
||||
await ensureSessionForRequest();
|
||||
}
|
||||
|
||||
switch (request.params.name) {
|
||||
case "fork_repository": {
|
||||
|
Reference in New Issue
Block a user