@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 +14 -0
- package/README.md +8 -0
- package/dist/index.js +126 -9
- package/package.json +2 -2
- package/server.json +2 -2
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.
|
|
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": "^
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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": "^
|
|
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
|
+
"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.
|
|
16
|
+
"version": "2.12.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"runtimeArguments": [
|
|
19
19
|
{
|