diff --git a/index.ts b/index.ts index dc1feeb..a444934 100644 --- a/index.ts +++ b/index.ts @@ -7,12 +7,20 @@ import { ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import fetch from "node-fetch"; +import { SocksProxyAgent } from 'socks-proxy-agent'; +import { HttpsProxyAgent } from 'https-proxy-agent'; +import { HttpProxyAgent } from 'http-proxy-agent'; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; import { fileURLToPath } from "url"; import { dirname } from "path"; import fs from "fs"; import path from "path"; + +// Add type imports for proxy agents +import { Agent } from 'http'; +import { URL } from 'url'; + import { GitLabForkSchema, GitLabReferenceSchema, @@ -126,6 +134,47 @@ const server = new Server( const GITLAB_PERSONAL_ACCESS_TOKEN = process.env.GITLAB_PERSONAL_ACCESS_TOKEN; const GITLAB_READ_ONLY_MODE = process.env.GITLAB_READ_ONLY_MODE === "true"; +// Add proxy configuration +const HTTP_PROXY = process.env.HTTP_PROXY; +const HTTPS_PROXY = process.env.HTTPS_PROXY; + +// Configure proxy agents if proxies are set +let httpAgent: Agent | undefined = undefined; +let httpsAgent: Agent | undefined = undefined; + +if (HTTP_PROXY) { + if (HTTP_PROXY.startsWith('socks')) { + httpAgent = new SocksProxyAgent(HTTP_PROXY); + } else { + httpAgent = new HttpProxyAgent(HTTP_PROXY); + } +} +if (HTTPS_PROXY) { + if (HTTPS_PROXY.startsWith('socks')) { + httpsAgent = new SocksProxyAgent(HTTPS_PROXY); + } else { + httpsAgent = new HttpsProxyAgent(HTTPS_PROXY); + } +} + +// Modify DEFAULT_HEADERS to include agent configuration +const DEFAULT_HEADERS = { + Accept: "application/json", + "Content-Type": "application/json", + Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, +}; + +// Create a default fetch configuration object that includes proxy agents if set +const DEFAULT_FETCH_CONFIG = { + headers: DEFAULT_HEADERS, + agent: (parsedUrl: URL) => { + if (parsedUrl.protocol === 'https:') { + return httpsAgent; + } + return httpAgent; + } +}; + // Define all available tools const allTools = [ { @@ -356,16 +405,6 @@ if (!GITLAB_PERSONAL_ACCESS_TOKEN) { process.exit(1); } -/** - * Common headers for GitLab API requests - * GitLab API 공통 헤더 (Common headers for GitLab API) - */ -const DEFAULT_HEADERS = { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, -}; - /** * Utility function for handling GitLab API errors * API 에러 처리를 위한 유틸리티 함수 (Utility function for handling API errors) @@ -406,7 +445,6 @@ async function forkProject( projectId: string, namespace?: string ): Promise { - // API 엔드포인트 URL 생성 const url = new URL( `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/fork` ); @@ -416,8 +454,8 @@ async function forkProject( } const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: DEFAULT_HEADERS, }); // 이미 존재하는 프로젝트인 경우 처리 @@ -449,8 +487,8 @@ async function createBranch( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: DEFAULT_HEADERS, body: JSON.stringify({ branch: options.name, ref: options.ref, @@ -474,7 +512,7 @@ async function getDefaultBranchRef(projectId: string): Promise { ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -512,7 +550,7 @@ async function getFileContents( url.searchParams.append("ref", ref); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); // 파일을 찾을 수 없는 경우 처리 @@ -552,8 +590,8 @@ async function createIssue( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: DEFAULT_HEADERS, body: JSON.stringify({ title: options.title, description: options.description, @@ -603,7 +641,7 @@ async function listIssues( }); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -630,7 +668,7 @@ async function getIssue( ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -665,8 +703,8 @@ async function updateIssue( } const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "PUT", - headers: DEFAULT_HEADERS, body: JSON.stringify(body), }); @@ -691,8 +729,8 @@ async function deleteIssue(projectId: string, issueIid: number): Promise { ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "DELETE", - headers: DEFAULT_HEADERS, }); await handleGitLabError(response); @@ -717,7 +755,7 @@ async function listIssueLinks( ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -746,7 +784,7 @@ async function getIssueLink( ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -779,8 +817,8 @@ async function createIssueLink( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: DEFAULT_HEADERS, body: JSON.stringify({ target_project_id: targetProjectId, target_issue_iid: targetIssueIid, @@ -814,8 +852,8 @@ async function deleteIssueLink( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "DELETE", - headers: DEFAULT_HEADERS, }); await handleGitLabError(response); @@ -838,12 +876,8 @@ async function createMergeRequest( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, - }, body: JSON.stringify({ title: options.title, description: options.description, @@ -889,7 +923,7 @@ async function listMergeRequestDiscussions( ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -930,8 +964,8 @@ async function updateMergeRequestNote( } const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "PUT", - headers: DEFAULT_HEADERS, body: JSON.stringify(payload), }); @@ -1014,12 +1048,8 @@ async function createOrUpdateFile( } const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method, - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, - }, body: JSON.stringify(body), }); @@ -1059,12 +1089,8 @@ async function createTree( } const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, - }, body: JSON.stringify({ files: files.map((file) => ({ file_path: file.path, @@ -1113,12 +1139,8 @@ async function createCommit( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, - }, body: JSON.stringify({ branch, commit_message: message, @@ -1169,11 +1191,7 @@ async function searchProjects( url.searchParams.append("sort", "desc"); const response = await fetch(url.toString(), { - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, - }, + ...DEFAULT_FETCH_CONFIG, }); if (!response.ok) { @@ -1209,12 +1227,8 @@ async function createRepository( options: z.infer ): Promise { const response = await fetch(`${GITLAB_API_URL}/projects`, { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - Authorization: `Bearer ${GITLAB_PERSONAL_ACCESS_TOKEN}`, - }, body: JSON.stringify({ name: options.name, description: options.description, @@ -1255,7 +1269,7 @@ async function getMergeRequest( ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -1287,7 +1301,7 @@ async function getMergeRequestDiffs( } const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -1319,8 +1333,8 @@ async function updateMergeRequest( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "PUT", - headers: DEFAULT_HEADERS, body: JSON.stringify(options), }); @@ -1353,8 +1367,8 @@ async function createNote( ); const response = await fetch(url.toString(), { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: DEFAULT_HEADERS, body: JSON.stringify({ body }), }); @@ -1398,7 +1412,7 @@ async function listNamespaces(options: { } const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -1417,7 +1431,7 @@ async function getNamespace(id: string): Promise { const url = new URL(`${GITLAB_API_URL}/namespaces/${encodeURIComponent(id)}`); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -1446,7 +1460,7 @@ async function verifyNamespaceExistence( } const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -1490,7 +1504,7 @@ async function getProject( } const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -1524,8 +1538,7 @@ async function listProjects( const response = await fetch( `${GITLAB_API_URL}/projects?${params.toString()}`, { - method: "GET", - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, } ); @@ -1566,7 +1579,7 @@ async function listLabels( // Make the API request const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); // Handle errors @@ -1606,7 +1619,7 @@ async function getLabel( // Make the API request const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); // Handle errors @@ -1632,8 +1645,8 @@ async function createLabel( const response = await fetch( `${GITLAB_API_URL}/projects/${encodeURIComponent(projectId)}/labels`, { + ...DEFAULT_FETCH_CONFIG, method: "POST", - headers: DEFAULT_HEADERS, body: JSON.stringify(options), } ); @@ -1665,8 +1678,8 @@ async function updateLabel( projectId )}/labels/${encodeURIComponent(String(labelId))}`, { + ...DEFAULT_FETCH_CONFIG, method: "PUT", - headers: DEFAULT_HEADERS, body: JSON.stringify(options), } ); @@ -1695,8 +1708,8 @@ async function deleteLabel( projectId )}/labels/${encodeURIComponent(String(labelId))}`, { + ...DEFAULT_FETCH_CONFIG, method: "DELETE", - headers: DEFAULT_HEADERS, } ); @@ -1766,8 +1779,7 @@ async function listGroupProjects( ); const response = await fetch(url.toString(), { - method: "GET", - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -2013,7 +2025,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { } const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -2036,7 +2048,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -2055,7 +2067,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); @@ -2076,7 +2088,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { ); const response = await fetch(url.toString(), { - headers: DEFAULT_HEADERS, + ...DEFAULT_FETCH_CONFIG, }); await handleGitLabError(response); diff --git a/package-lock.json b/package-lock.json index ed49554..061ea92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,20 @@ { "name": "@zereight/mcp-gitlab", - "version": "1.0.29", + "version": "1.0.30", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@zereight/mcp-gitlab", - "version": "1.0.29", + "version": "1.0.30", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "1.8.0", "@types/node-fetch": "^2.6.12", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "node-fetch": "^3.3.2", + "socks-proxy-agent": "^8.0.5", "zod-to-json-schema": "^3.23.5" }, "bin": { @@ -79,6 +82,15 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -713,6 +725,32 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -731,6 +769,19 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -752,6 +803,12 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1201,6 +1258,50 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", + "integrity": "sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/package.json b/package.json index 9ee5f3e..43c9ef5 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,10 @@ "dependencies": { "@modelcontextprotocol/sdk": "1.8.0", "@types/node-fetch": "^2.6.12", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "node-fetch": "^3.3.2", + "socks-proxy-agent": "^8.0.5", "zod-to-json-schema": "^3.23.5" }, "devDependencies": {