@zereight/mcp-gitlab 2.0.13 → 2.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/build/gitlab-client-pool.js +113 -0
- package/build/index.js +450 -356
- package/build/test/client-pool-test.js +109 -0
- package/build/test/dynamic-api-url-test.js +304 -0
- package/build/test/dynamic-routing-tests.js +442 -0
- package/build/test/multi-server-test.js +182 -0
- package/build/test/remote-auth-simple-test.js +2 -0
- package/build/test/utils/mock-gitlab-server.js +73 -4
- package/build/test/utils/server-launcher.js +25 -9
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# GitLab MCP Server
|
|
2
2
|
|
|
3
|
+
> **New Feature**: Dynamic GitLab API URL support with connection pooling! See [Dynamic API URL Documentation](docs/dynamic-api-url.md) for details.
|
|
4
|
+
|
|
3
5
|
[](https://www.star-history.com/#zereight/gitlab-mcp&Date)
|
|
4
6
|
|
|
5
7
|
## @zereight/mcp-gitlab
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Agent } from "http";
|
|
2
|
+
import { Agent as HttpsAgent } from "https";
|
|
3
|
+
import { HttpProxyAgent } from "http-proxy-agent";
|
|
4
|
+
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
5
|
+
import { SocksProxyAgent } from "socks-proxy-agent";
|
|
6
|
+
import fs from "fs";
|
|
7
|
+
/**
|
|
8
|
+
* Manages a pool of HTTP/HTTPS agents for different GitLab API URLs.
|
|
9
|
+
* This allows the server to efficiently handle requests to multiple GitLab instances
|
|
10
|
+
* by reusing agents and their underlying TCP connections.
|
|
11
|
+
*/
|
|
12
|
+
export class GitLabClientPool {
|
|
13
|
+
clients = new Map();
|
|
14
|
+
options;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
// Initialization is now done on-demand
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Creates a pair of HTTP and HTTPS agents for a specific API URL,
|
|
21
|
+
* considering proxy and SSL/TLS settings.
|
|
22
|
+
* @param apiUrl The base URL for which to create the agents.
|
|
23
|
+
* @returns A `ClientAgents` object containing the configured agents.
|
|
24
|
+
*/
|
|
25
|
+
createAgentsForUrl(apiUrl) {
|
|
26
|
+
const { httpProxy, httpsProxy, rejectUnauthorized, caCertPath } = this.options;
|
|
27
|
+
const url = new URL(apiUrl);
|
|
28
|
+
let sslOptions = {};
|
|
29
|
+
if (rejectUnauthorized === false) {
|
|
30
|
+
sslOptions.rejectUnauthorized = false;
|
|
31
|
+
}
|
|
32
|
+
else if (caCertPath) {
|
|
33
|
+
try {
|
|
34
|
+
sslOptions.ca = fs.readFileSync(caCertPath);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(`Failed to read CA certificate from ${caCertPath}:`, error);
|
|
38
|
+
throw new Error(`Failed to read CA certificate: ${caCertPath}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
let httpAgent;
|
|
42
|
+
let httpsAgent;
|
|
43
|
+
// Configure HTTP agent with proxy if specified
|
|
44
|
+
if (httpProxy) {
|
|
45
|
+
httpAgent = httpProxy.startsWith("socks")
|
|
46
|
+
? new SocksProxyAgent(httpProxy)
|
|
47
|
+
: new HttpProxyAgent(httpProxy);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
httpAgent = new Agent({ keepAlive: true });
|
|
51
|
+
}
|
|
52
|
+
// Configure HTTPS agent with proxy and SSL options if specified
|
|
53
|
+
if (httpsProxy) {
|
|
54
|
+
httpsAgent = httpsProxy.startsWith("socks")
|
|
55
|
+
// The `as any` cast is used here to bypass a TypeScript type mismatch error.
|
|
56
|
+
// The `socks-proxy-agent` documentation indicates that TLS options like
|
|
57
|
+
// `rejectUnauthorized` and `ca` are valid in the constructor's options
|
|
58
|
+
// object, but the type definitions in this environment seem to disagree.
|
|
59
|
+
// This cast ensures the options are passed through at runtime.
|
|
60
|
+
? new SocksProxyAgent(httpsProxy, sslOptions)
|
|
61
|
+
: new HttpsProxyAgent(httpsProxy, { ...sslOptions });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
httpsAgent = new HttpsAgent({ ...sslOptions, keepAlive: true });
|
|
65
|
+
}
|
|
66
|
+
return { httpAgent, httpsAgent };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Retrieves the appropriate agent (HTTP or HTTPS) for a given API URL.
|
|
70
|
+
* If an agent for the URL does not exist, it creates and caches one.
|
|
71
|
+
* @param apiUrl The full URL of the request.
|
|
72
|
+
* @returns The corresponding `Agent` for the URL's protocol.
|
|
73
|
+
*/
|
|
74
|
+
getOrCreateAgentForUrl(apiUrl) {
|
|
75
|
+
const url = new URL(apiUrl);
|
|
76
|
+
const baseUrl = `${url.protocol}//${url.host}${url.pathname.substring(0, url.pathname.lastIndexOf('/api/v4') + '/api/v4'.length)}`;
|
|
77
|
+
if (!this.clients.has(baseUrl)) {
|
|
78
|
+
// Check pool size limit
|
|
79
|
+
if (this.options.poolMaxSize !== undefined && this.clients.size >= this.options.poolMaxSize) {
|
|
80
|
+
throw new Error(`Server capacity reached: Connection pool is full (max ${this.options.poolMaxSize} instances). Please try again later.`);
|
|
81
|
+
}
|
|
82
|
+
this.clients.set(baseUrl, this.createAgentsForUrl(baseUrl));
|
|
83
|
+
}
|
|
84
|
+
const agents = this.clients.get(baseUrl);
|
|
85
|
+
if (!agents) {
|
|
86
|
+
// This should not happen given the logic above, but it satisfies TypeScript
|
|
87
|
+
throw new Error(`Failed to create or get client for URL: ${baseUrl}`);
|
|
88
|
+
}
|
|
89
|
+
return url.protocol === "https:" ? agents.httpsAgent : agents.httpAgent;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Retrieves the client agents for a specific base API URL.
|
|
93
|
+
* @param apiUrl The base API URL (e.g., "https://gitlab.com/api/v4").
|
|
94
|
+
* @returns The `ClientAgents` object or undefined if not found.
|
|
95
|
+
*/
|
|
96
|
+
getClient(apiUrl) {
|
|
97
|
+
return this.clients.get(apiUrl);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Returns the default client agents, which corresponds to the first URL in the list.
|
|
101
|
+
* @returns The default `ClientAgents`.
|
|
102
|
+
*/
|
|
103
|
+
getDefaultClient() {
|
|
104
|
+
const defaultUrl = this.options.apiUrls?.[0];
|
|
105
|
+
if (!defaultUrl) {
|
|
106
|
+
throw new Error("No default API URL configured.");
|
|
107
|
+
}
|
|
108
|
+
if (!this.clients.has(defaultUrl)) {
|
|
109
|
+
this.clients.set(defaultUrl, this.createAgentsForUrl(defaultUrl));
|
|
110
|
+
}
|
|
111
|
+
return this.clients.get(defaultUrl);
|
|
112
|
+
}
|
|
113
|
+
}
|