@daghis/teamcity-mcp 2.10.0 → 2.12.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.12.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.11.0...teamcity-mcp-v2.12.0) (2026-05-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * **http:** forward TEAMCITY_HEADER_* env vars on every request ([#492](https://github.com/Daghis/teamcity-mcp/issues/492)) ([c406511](https://github.com/Daghis/teamcity-mcp/commit/c4065111e2e8c6e807ec9a4383cbbe8d280ca00e))
9
+
10
+ ## [2.11.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.10.0...teamcity-mcp-v2.11.0) (2026-05-03)
11
+
12
+
13
+ ### Features
14
+
15
+ * **artifacts:** add list_build_artifacts tool for subdirectory browsing ([#489](https://github.com/Daghis/teamcity-mcp/issues/489)) ([eacad61](https://github.com/Daghis/teamcity-mcp/commit/eacad6115cbb33a6e660bd096e59869c4fcf4ea8))
16
+
3
17
  ## [2.10.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.9.0...teamcity-mcp-v2.10.0) (2026-04-26)
4
18
 
5
19
 
package/README.md CHANGED
@@ -156,6 +156,14 @@ MCP_MODE=dev
156
156
  # TEAMCITY_KEEP_ALIVE=true
157
157
  # TEAMCITY_COMPRESSION=true
158
158
 
159
+ # Extra headers attached to every TeamCity request — useful when TeamCity
160
+ # sits behind a reverse proxy that gates access on custom headers (e.g.
161
+ # Cloudflare Zero Trust service tokens). One env var per header; the part
162
+ # after `TEAMCITY_HEADER_` is used verbatim as the HTTP header name.
163
+ # Example (note the literal hyphens — most shells need quoting):
164
+ # TEAMCITY_HEADER_CF-Access-Client-Id=<id>
165
+ # TEAMCITY_HEADER_CF-Access-Client-Secret=<secret>
166
+
159
167
  # Retry
160
168
  # TEAMCITY_RETRY_ENABLED=true
161
169
  # TEAMCITY_MAX_RETRIES=3
package/dist/index.js CHANGED
@@ -691,6 +691,17 @@ function setServerInstance(server) {
691
691
  function getServerInstance() {
692
692
  return serverInstance;
693
693
  }
694
+ var HEADER_ENV_PREFIX = "TEAMCITY_HEADER_";
695
+ function getTeamCityExtraHeaders() {
696
+ const headers = {};
697
+ for (const [key, value] of Object.entries(process.env)) {
698
+ if (!key.startsWith(HEADER_ENV_PREFIX) || value === void 0) continue;
699
+ const headerName = key.slice(HEADER_ENV_PREFIX.length);
700
+ if (headerName === "") continue;
701
+ headers[headerName] = value;
702
+ }
703
+ return Object.keys(headers).length > 0 ? headers : void 0;
704
+ }
694
705
  function getTeamCityUrl() {
695
706
  const config2 = getConfig();
696
707
  if (!config2.teamcity?.url || config2.teamcity.url.length === 0) {
@@ -1205,7 +1216,7 @@ function debug2(message, meta) {
1205
1216
  // package.json
1206
1217
  var package_default = {
1207
1218
  name: "@daghis/teamcity-mcp",
1208
- version: "2.10.0",
1219
+ version: "2.12.0",
1209
1220
  description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
1210
1221
  mcpName: "io.github.Daghis/teamcity",
1211
1222
  main: "dist/index.js",
@@ -1294,7 +1305,7 @@ var package_default = {
1294
1305
  "eslint-plugin-prettier": "^5.0.1",
1295
1306
  globals: "^17.4.0",
1296
1307
  jest: "^30.1.3",
1297
- "jest-junit": "^16.0.0",
1308
+ "jest-junit": "^17.0.0",
1298
1309
  "js-yaml": "^4.1.1",
1299
1310
  prettier: "^3.1.0",
1300
1311
  "ts-jest": "^29.4.9",
@@ -1550,13 +1561,20 @@ var ArtifactManager = class _ArtifactManager {
1550
1561
  const buildLocator = toBuildLocator(buildId);
1551
1562
  const response = await this.client.modules.builds.getFilesListOfBuild(
1552
1563
  buildLocator,
1553
- void 0,
1564
+ options.path,
1554
1565
  void 0,
1555
1566
  "file(name,fullName,size,modificationTime,href,children(file(name,fullName,size,modificationTime,href)))"
1556
1567
  );
1557
1568
  const baseUrl = this.getBaseUrl();
1558
1569
  const artifactPayload = this.ensureArtifactListingResponse(response.data, buildId);
1559
- let artifacts = this.parseArtifacts(artifactPayload, buildId, options.includeNested, baseUrl);
1570
+ let artifacts = this.parseArtifacts(
1571
+ artifactPayload,
1572
+ buildId,
1573
+ options.includeNested,
1574
+ baseUrl,
1575
+ [],
1576
+ options.includeDirectories
1577
+ );
1560
1578
  artifacts = this.applyFilters(artifacts, options);
1561
1579
  if (options.limit ?? options.offset) {
1562
1580
  artifacts = this.paginate(
@@ -1788,7 +1806,7 @@ var ArtifactManager = class _ArtifactManager {
1788
1806
  }
1789
1807
  return payload;
1790
1808
  }
1791
- parseArtifacts(data, buildId, includeNested, baseUrl, parentSegments = []) {
1809
+ parseArtifacts(data, buildId, includeNested, baseUrl, parentSegments = [], includeDirectories) {
1792
1810
  const artifacts = [];
1793
1811
  const files = data.file ?? [];
1794
1812
  for (const file of files) {
@@ -1796,13 +1814,24 @@ var ArtifactManager = class _ArtifactManager {
1796
1814
  const resolvedPath = pathSegments.join("/");
1797
1815
  const isDirectory = Boolean(file.children);
1798
1816
  if (isDirectory) {
1817
+ if (includeDirectories && resolvedPath) {
1818
+ artifacts.push({
1819
+ name: file.name ?? pathSegments[pathSegments.length - 1] ?? "",
1820
+ path: resolvedPath,
1821
+ size: file.size ?? 0,
1822
+ modificationTime: file.modificationTime ?? "",
1823
+ downloadUrl: `${baseUrl}/app/rest/builds/id:${buildId}/artifacts/content/${this.encodeArtifactPath(pathSegments)}`,
1824
+ isDirectory: true
1825
+ });
1826
+ }
1799
1827
  if (includeNested && file.children) {
1800
1828
  const nested = this.parseArtifacts(
1801
1829
  file.children,
1802
1830
  buildId,
1803
1831
  includeNested,
1804
1832
  baseUrl,
1805
- pathSegments
1833
+ pathSegments,
1834
+ includeDirectories
1806
1835
  );
1807
1836
  artifacts.push(...nested);
1808
1837
  }
@@ -38441,6 +38470,7 @@ var TeamCityAPI = class _TeamCityAPI {
38441
38470
  baseURL: basePath,
38442
38471
  timeout,
38443
38472
  headers: {
38473
+ ...config2.extraHeaders ?? {},
38444
38474
  Authorization: `Bearer ${config2.token}`,
38445
38475
  Accept: "application/json",
38446
38476
  "Content-Type": "application/json"
@@ -38469,6 +38499,7 @@ var TeamCityAPI = class _TeamCityAPI {
38469
38499
  baseOptions: {
38470
38500
  timeout,
38471
38501
  headers: {
38502
+ ...config2.extraHeaders ?? {},
38472
38503
  Authorization: `Bearer ${config2.token}`,
38473
38504
  Accept: "application/json"
38474
38505
  }
@@ -38551,7 +38582,8 @@ var TeamCityAPI = class _TeamCityAPI {
38551
38582
  if (this.instance == null) {
38552
38583
  const envConfig = this.normalizeConfig({
38553
38584
  baseUrl: getTeamCityUrl(),
38554
- token: getTeamCityToken()
38585
+ token: getTeamCityToken(),
38586
+ extraHeaders: getTeamCityExtraHeaders()
38555
38587
  });
38556
38588
  this.instance = new _TeamCityAPI(envConfig);
38557
38589
  this.instanceConfig = envConfig;
@@ -38782,15 +38814,34 @@ var TeamCityAPI = class _TeamCityAPI {
38782
38814
  return {
38783
38815
  baseUrl: config2.baseUrl.replace(/\/$/, ""),
38784
38816
  token: config2.token,
38785
- timeout: config2.timeout
38817
+ timeout: config2.timeout,
38818
+ extraHeaders: normalizeExtraHeaders(config2.extraHeaders)
38786
38819
  };
38787
38820
  }
38788
38821
  static configsEqual(a, b) {
38789
38822
  if (a == null || b == null) {
38790
38823
  return false;
38791
38824
  }
38792
- return a.baseUrl === b.baseUrl && a.token === b.token && a.timeout === b.timeout;
38825
+ return a.baseUrl === b.baseUrl && a.token === b.token && a.timeout === b.timeout && extraHeadersEqual(a.extraHeaders, b.extraHeaders);
38826
+ }
38827
+ };
38828
+ var normalizeExtraHeaders = (headers) => {
38829
+ if (headers == null) {
38830
+ return void 0;
38793
38831
  }
38832
+ const entries = Object.entries(headers);
38833
+ if (entries.length === 0) {
38834
+ return void 0;
38835
+ }
38836
+ return Object.fromEntries(entries.sort(([a], [b]) => a.localeCompare(b)));
38837
+ };
38838
+ var extraHeadersEqual = (a, b) => {
38839
+ if (a === b) return true;
38840
+ if (a == null || b == null) return a == null && b == null;
38841
+ const keysA = Object.keys(a);
38842
+ const keysB = Object.keys(b);
38843
+ if (keysA.length !== keysB.length) return false;
38844
+ return keysA.every((key) => a[key] === b[key]);
38794
38845
  };
38795
38846
 
38796
38847
  // src/tools.ts
@@ -41424,6 +41475,72 @@ var DEV_TOOLS = [
41424
41475
  );
41425
41476
  }
41426
41477
  },
41478
+ {
41479
+ name: "list_build_artifacts",
41480
+ annotations: {
41481
+ readOnlyHint: true,
41482
+ destructiveHint: false,
41483
+ idempotentHint: true,
41484
+ openWorldHint: true
41485
+ },
41486
+ description: "List artifact files and directories for a build, optionally browsing into subdirectories. Returns an array of artifact entries with name, path, size, and isDirectory flag.",
41487
+ inputSchema: {
41488
+ type: "object",
41489
+ properties: {
41490
+ ...buildIdentifierInputProperties,
41491
+ path: {
41492
+ type: "string",
41493
+ description: 'Sub-path to list (e.g. "okd" or "okd/subdir"). Omit to list top-level artifacts.'
41494
+ },
41495
+ includeNested: {
41496
+ type: "boolean",
41497
+ description: "Recursively include all files within subdirectories (default: false)"
41498
+ },
41499
+ nameFilter: {
41500
+ type: "string",
41501
+ description: 'Glob pattern to filter artifacts by name (e.g. "*.yaml")'
41502
+ },
41503
+ pathFilter: {
41504
+ type: "string",
41505
+ description: "Glob pattern to filter artifacts by full path"
41506
+ },
41507
+ extension: {
41508
+ type: "string",
41509
+ description: 'Filter by file extension (e.g. "yaml", ".yaml")'
41510
+ }
41511
+ }
41512
+ },
41513
+ handler: async (args) => {
41514
+ const schema = buildIdentifierSchema.and(
41515
+ import_zod4.z.object({
41516
+ path: import_zod4.z.string().trim().min(1).optional(),
41517
+ includeNested: import_zod4.z.boolean().optional(),
41518
+ nameFilter: import_zod4.z.string().trim().min(1).optional(),
41519
+ pathFilter: import_zod4.z.string().trim().min(1).optional(),
41520
+ extension: import_zod4.z.string().trim().min(1).optional()
41521
+ })
41522
+ );
41523
+ return runTool(
41524
+ "list_build_artifacts",
41525
+ schema,
41526
+ async (typed) => {
41527
+ const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
41528
+ const { locator: buildLocator } = resolveBuildLocator(typed);
41529
+ const manager = new ArtifactManager(adapter);
41530
+ const artifacts = await manager.listArtifacts(buildLocator, {
41531
+ path: typed.path,
41532
+ includeNested: typed.includeNested,
41533
+ includeDirectories: true,
41534
+ nameFilter: typed.nameFilter,
41535
+ pathFilter: typed.pathFilter,
41536
+ extension: typed.extension
41537
+ });
41538
+ return json({ artifacts });
41539
+ },
41540
+ args
41541
+ );
41542
+ }
41543
+ },
41427
41544
  {
41428
41545
  name: "download_build_artifact",
41429
41546
  annotations: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daghis/teamcity-mcp",
3
- "version": "2.10.0",
3
+ "version": "2.12.0",
4
4
  "description": "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
5
5
  "mcpName": "io.github.Daghis/teamcity",
6
6
  "main": "dist/index.js",
@@ -89,7 +89,7 @@
89
89
  "eslint-plugin-prettier": "^5.0.1",
90
90
  "globals": "^17.4.0",
91
91
  "jest": "^30.1.3",
92
- "jest-junit": "^16.0.0",
92
+ "jest-junit": "^17.0.0",
93
93
  "js-yaml": "^4.1.1",
94
94
  "prettier": "^3.1.0",
95
95
  "ts-jest": "^29.4.9",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://github.com/Daghis/teamcity-mcp",
10
- "version": "2.10.0",
10
+ "version": "2.12.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@daghis/teamcity-mcp",
16
- "version": "2.10.0",
16
+ "version": "2.12.0",
17
17
  "runtimeHint": "npx",
18
18
  "runtimeArguments": [
19
19
  {