@zereight/mcp-gitlab 2.3.7-next.136 → 2.3.7-next.137

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/build/index.js CHANGED
@@ -94,7 +94,7 @@ import { listMcpResources, listMcpResourceTemplates, readMcpResource, } from "./
94
94
  import { getMcpPrompt, listMcpPrompts } from "./server/mcp-prompts.js";
95
95
  import { isInitializationRequestBody, isUnauthenticatedDiscoveryRequestBody, readAcceptHeader, readMcpSessionIdHeader, redactSessionIdForLog, } from "./server/request-helpers.js";
96
96
  export { readMcpSessionIdHeader } from "./server/request-helpers.js";
97
- import { apiUrlHostnameForLog, apiUrlUserinfoWarning, commaSplitHint, configuredApiUrlErrors, encodeGitLabPath, encodeGitLabPathSegment, invalidListEntryError, isCleartextRemoteUrl, normalizeGitLabApiUrl, parseConfiguredGitLabApiUrls, } from "./utils/url.js";
97
+ import { apiUrlUserinfoWarning, commaSplitHint, configuredApiUrlErrors, encodeGitLabPath, encodeGitLabPathSegment, invalidListEntryError, isCleartextRemoteUrl, normalizeGitLabApiUrl, parseConfiguredGitLabApiUrls, } from "./utils/url.js";
98
98
  import { estimateMergeCommitCount, filterDiffsByPatterns, openSafeOutputWriteStream, resolveDownloadRoot, readSafeExistingFile, summarizeWebhookEvents, } from "./utils/helpers.js";
99
99
  import { graphqlQueryContainsWriteOperation, graphqlQueryContainsDeleteOperation, } from "./utils/graphql-query.js";
100
100
  import { resolveNestedWikiUpdateTitle } from "./utils/wiki-title.js";
@@ -12976,8 +12976,9 @@ function initOAuthClient() {
12976
12976
  }
12977
12977
  if (isCleartextRemoteUrl(GITLAB_API_URL)) {
12978
12978
  throw new Error(
12979
- // The host name only: a port can hold the start of a password (see apiUrlHostnameForLog).
12980
- `Refusing to send OAuth tokens over cleartext HTTP to http://${apiUrlHostnameForLog(GITLAB_API_URL)}. ` +
12979
+ // The host name only, from the list GITLAB_API_URL is the first of: a port, or a
12980
+ // whole URL split off by a comma, can be part of a password (see parseConfiguredGitLabApiUrls).
12981
+ `Refusing to send OAuth tokens over cleartext HTTP to http://${CONFIGURED_GITLAB_API_URLS.hostnamesForLog[0]}. ` +
12981
12982
  "Use an https:// GITLAB_API_URL (http:// is only allowed for localhost).");
12982
12983
  }
12983
12984
  oauthClient = createGitLabOAuthClient(GITLAB_API_URL.replace(/\/api\/v4$/, ""));
@@ -13017,9 +13018,11 @@ async function runServer() {
13017
13018
  }
13018
13019
  });
13019
13020
  }
13020
- // Host names only. GITLAB_API_URL is GITLAB_API_URLS[0], so it is logged the same way.
13021
- logger.info(`Configured GitLab API URLs: ${GITLAB_API_URLS.map(apiUrlHostnameForLog).join(", ")}`);
13022
- logger.info(`Default GitLab API URL: ${apiUrlHostnameForLog(GITLAB_API_URL)}`);
13021
+ // Host names only, decided for the whole list (see parseConfiguredGitLabApiUrls).
13022
+ // GITLAB_API_URL is GITLAB_API_URLS[0], so it is logged the same way.
13023
+ const hostnames = CONFIGURED_GITLAB_API_URLS.hostnamesForLog;
13024
+ logger.info(`Configured GitLab API URLs: ${hostnames.join(", ")}`);
13025
+ logger.info(`Default GitLab API URL: ${hostnames[0]}`);
13023
13026
  if (GITLAB_OAUTH_ALLOWED_GROUPS) {
13024
13027
  logger.info(`Group access control enabled for: ${GITLAB_OAUTH_ALLOWED_GROUPS.join(", ")}`);
13025
13028
  }
@@ -102,19 +102,43 @@ function apiUrlProblem(url) {
102
102
  * them would fail every request and echo the password in the error.
103
103
  */
104
104
  export function parseConfiguredGitLabApiUrls(value) {
105
- const entries = splitCommaSeparatedUrls(value).map(stripUrlUserinfo);
105
+ const rawEntries = splitCommaSeparatedUrls(value);
106
+ const entries = rawEntries.map(stripUrlUserinfo);
107
+ const urls = entries.map(entry => normalizeGitLabApiUrl(entry.url));
106
108
  return {
107
- urls: entries.map(entry => normalizeGitLabApiUrl(entry.url)),
109
+ urls,
108
110
  hadUserinfo: entries.some(entry => entry.hadUserinfo),
111
+ hostnamesForLog: apiUrlHostnamesForLog(rawEntries, urls),
109
112
  };
110
113
  }
114
+ const INVALID_HOSTNAME = "<invalid>";
111
115
  /**
112
- * The host name of a configured API URL, for the startup log. A password with a
113
- * literal comma can be split into two URLs, and "http://alice:12" then reads
116
+ * The host name of each configured API URL, for the startup log. `rawEntries`
117
+ * are the entries as split, before userinfo is dropped, and `urls` the URLs
118
+ * built from them. A password with literal commas can be split into several
119
+ * URLs: "http://alice:12,http://password:34,http://tail@host" gives
120
+ * "http://password:34", whose host is part of the password, and no single
121
+ * entry shows that. The password ends at an "@", so every entry before the last
122
+ * one with an "@" shows as "<invalid>", even when it is a whole URL of its own.
123
+ */
124
+ function apiUrlHostnamesForLog(rawEntries, urls) {
125
+ let lastWithAt = -1;
126
+ rawEntries.forEach((entry, index) => {
127
+ if (entry.includes("@")) {
128
+ lastWithAt = index;
129
+ }
130
+ });
131
+ return urls.map((url, index) => index < lastWithAt ? INVALID_HOSTNAME : apiUrlHostnameForLog(url));
132
+ }
133
+ /**
134
+ * The host name of one configured API URL, for the startup log. A password with
135
+ * a literal comma can be split into two URLs, and "http://alice:12" then reads
114
136
  * the start of the password as a port, which text redaction keeps. The port,
115
137
  * userinfo, path, and query are left out. An IPv6 host keeps its brackets.
116
138
  * An "@" after the host means the host can be part of a password
117
139
  * ("http://sec/ret@host" from "alice:12,http://sec/ret@host"), so it is not shown.
140
+ * The host of a URL cut out of the middle of a password looks like any other
141
+ * host here, so the startup log uses the list from parseConfiguredGitLabApiUrls.
118
142
  */
119
143
  export function apiUrlHostnameForLog(url) {
120
144
  let parsed;
@@ -122,10 +146,10 @@ export function apiUrlHostnameForLog(url) {
122
146
  parsed = new URL(url);
123
147
  }
124
148
  catch {
125
- return "<invalid>";
149
+ return INVALID_HOSTNAME;
126
150
  }
127
151
  const atAfterHost = parsed.pathname.includes("@") || parsed.search.includes("@") || parsed.hash.includes("@");
128
- return parsed.hostname.length === 0 || atAfterHost ? "<invalid>" : parsed.hostname;
152
+ return parsed.hostname.length === 0 || atAfterHost ? INVALID_HOSTNAME : parsed.hostname;
129
153
  }
130
154
  const LOOPBACK_HOSTNAMES = new Set(["localhost", "127.0.0.1", "[::1]"]);
131
155
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zereight/mcp-gitlab",
3
- "version": "2.3.7-next.136",
3
+ "version": "2.3.7-next.137",
4
4
  "mcpName": "io.github.zereight/gitlab-mcp",
5
5
  "description": "GitLab MCP server for projects, merge requests, issues, pipelines, wiki, releases, and more",
6
6
  "keywords": [