@daghis/teamcity-mcp 2.12.1 → 2.12.6
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 +35 -0
- package/README.md +12 -13
- package/dist/index.js +205 -50
- package/package.json +4 -4
- package/server.json +2 -2
- package/tsconfig.test.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.12.6](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.5...teamcity-mcp-v2.12.6) (2026-10-03)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **build-log:** repair streamed downloads and error logging ([#526](https://github.com/Daghis/teamcity-mcp/issues/526)) ([aca2eea](https://github.com/Daghis/teamcity-mcp/commit/aca2eea6b996668e75ed69318c8846cdfeee624d))
|
|
9
|
+
|
|
10
|
+
## [2.12.5](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.4...teamcity-mcp-v2.12.5) (2026-10-03)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **publish:** use Node 24 and pin npm for trusted publishing ([#536](https://github.com/Daghis/teamcity-mcp/issues/536)) ([533814b](https://github.com/Daghis/teamcity-mcp/commit/533814bbe8d25fb7f7bb89f14a92e9a261d4ebbb))
|
|
16
|
+
|
|
17
|
+
## [2.12.4](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.3...teamcity-mcp-v2.12.4) (2026-10-02)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **deps:** bump axios to 1.20 and patch transitive advisories ([#534](https://github.com/Daghis/teamcity-mcp/issues/534)) ([d5a9296](https://github.com/Daghis/teamcity-mcp/commit/d5a92964633a60f6626d50dd6cfa509e9ae68bdb))
|
|
23
|
+
|
|
24
|
+
## [2.12.3](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.2...teamcity-mcp-v2.12.3) (2026-10-02)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* **api-client:** keep the original error when retries are exhausted ([#529](https://github.com/Daghis/teamcity-mcp/issues/529)) ([3a417ef](https://github.com/Daghis/teamcity-mcp/commit/3a417ef17260b2991885e2943e04a22e9c754aa8))
|
|
30
|
+
|
|
31
|
+
## [2.12.2](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.1...teamcity-mcp-v2.12.2) (2026-08-31)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Bug Fixes
|
|
35
|
+
|
|
36
|
+
* **deps:** resolve all Dependabot alerts; cap axios at ~1.18.0 ([#527](https://github.com/Daghis/teamcity-mcp/issues/527)) ([74c7b66](https://github.com/Daghis/teamcity-mcp/commit/74c7b66d954a8c8bb55f5ded35b4981eef76b512))
|
|
37
|
+
|
|
3
38
|
## [2.12.1](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.0...teamcity-mcp-v2.12.1) (2026-05-12)
|
|
4
39
|
|
|
5
40
|
|
package/README.md
CHANGED
|
@@ -11,6 +11,11 @@ A Model Control Protocol (MCP) server that bridges AI coding assistants with Jet
|
|
|
11
11
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/@Daghis/teamcity-mcp/badge" alt="TeamCity Server MCP server" />
|
|
12
12
|
</a>
|
|
13
13
|
|
|
14
|
+
> [!NOTE]
|
|
15
|
+
> **Project status (June 2026): stable, low-key maintenance.** This does what it set out to do and is no longer under active development. It still works and stays installable; issues and PRs may get slow or no response, and security fixes are best-effort.
|
|
16
|
+
>
|
|
17
|
+
> JetBrains now ships official AI integration for TeamCity — a built-in MCP and the [TeamCity CLI](https://github.com/JetBrains/teamcity-cli) with an installable agent skill — which is the better default for most workflows. See [_How this compares to JetBrains' official tooling_](#how-this-compares-to-jetbrains-official-tooling) below before adopting.
|
|
18
|
+
|
|
14
19
|
## Overview
|
|
15
20
|
|
|
16
21
|
The TeamCity MCP Server allows developers using AI-powered coding assistants (Claude Code, Cursor, Windsurf) to interact with TeamCity directly from their development environment via MCP tools.
|
|
@@ -47,23 +52,17 @@ See the [Tools Mode Matrix](docs/mcp-tools-mode-matrix.md) for the complete list
|
|
|
47
52
|
- Performance-conscious: fast startup with minimal overhead
|
|
48
53
|
- Clean codebase with clear module boundaries
|
|
49
54
|
|
|
50
|
-
##
|
|
51
|
-
|
|
52
|
-
TeamCity 2026.1 ships with a built-in MCP endpoint at `<server-url>/app/mcp` exposing three tools: build log retrieval, a generic REST GET, and a build trigger (forced to `personal=true`). It is server-resident, requires no install, and is a sensible default for read-and-rerun workflows.
|
|
55
|
+
## How this compares to JetBrains' official tooling
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
**As of June 2026,** JetBrains ships first-party AI integration for TeamCity: a built-in MCP endpoint and the [TeamCity CLI](https://github.com/JetBrains/teamcity-cli), which includes an installable agent skill. Together these are JetBrains' recommended path and cover the common AI workflows — reading logs, diagnosing failures, and rerunning builds — with no install and official support.
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
58
|
-
| Read build logs, trigger builds, simple read flows | Built-in (TeamCity 2026.1+) — zero install |
|
|
59
|
-
| Manage parameters, agents, queue, mutes, build configs, VCS roots | teamcity-mcp |
|
|
60
|
-
| TeamCity server older than 2026.1 | teamcity-mcp |
|
|
61
|
-
| Multi-server / multi-tenant deployment | teamcity-mcp (HTTP transport — [PR #491](https://github.com/Daghis/teamcity-mcp/pull/491)) |
|
|
62
|
-
| Typed tool surface for better agent reliability on chained operations | teamcity-mcp |
|
|
59
|
+
teamcity-mcp predates that tooling and overlaps with it. Broadly, the official tooling is the better default today; teamcity-mcp's remaining edge is a broader set of write and management operations exposed as an MCP server. That gap is real but narrowing, and JetBrains' tooling is evolving quickly — so rather than pin down a feature-by-feature comparison here (it would go stale fast), check the current docs and pick what fits:
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
- [TeamCity 2026.1 announcement](https://blog.jetbrains.com/teamcity/2026/05/teamcity-20261/) — overview of the official AI integration
|
|
62
|
+
- [AI agent integration docs](https://www.jetbrains.com/help/teamcity/ai-agent-integration.html) — the built-in MCP
|
|
63
|
+
- [TeamCity CLI](https://github.com/JetBrains/teamcity-cli) — the terminal + agent-skill path
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
If you're comfortable with JetBrains' CLI, you may not need this project at all. It stays MIT-licensed and installable for whatever the built-ins don't yet reach — fork it if you want to take it further yourself.
|
|
67
66
|
|
|
68
67
|
## Installation
|
|
69
68
|
|
package/dist/index.js
CHANGED
|
@@ -6,11 +6,20 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __esm = (fn, res) => function __init() {
|
|
10
|
-
|
|
9
|
+
var __esm = (fn, res, err) => function __init() {
|
|
10
|
+
if (err) throw err[0];
|
|
11
|
+
try {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw err = [e], e;
|
|
15
|
+
}
|
|
11
16
|
};
|
|
12
17
|
var __commonJS = (cb, mod) => function __require() {
|
|
13
|
-
|
|
18
|
+
try {
|
|
19
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
20
|
+
} catch (e) {
|
|
21
|
+
throw mod = 0, e;
|
|
22
|
+
}
|
|
14
23
|
};
|
|
15
24
|
var __export = (target, all) => {
|
|
16
25
|
for (var name in all)
|
|
@@ -33,6 +42,71 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
42
|
mod
|
|
34
43
|
));
|
|
35
44
|
|
|
45
|
+
// src/teamcity/utils/stream.ts
|
|
46
|
+
async function* sliceLines(source, startLine, endLine) {
|
|
47
|
+
if (endLine !== void 0 && endLine <= startLine) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
let line = 0;
|
|
51
|
+
for await (const raw of source) {
|
|
52
|
+
const chunk = toBuffer(raw);
|
|
53
|
+
let pos = 0;
|
|
54
|
+
while (pos < chunk.length) {
|
|
55
|
+
const newline = chunk.indexOf(10, pos);
|
|
56
|
+
const stop = newline === -1 ? chunk.length : newline + 1;
|
|
57
|
+
if (line >= startLine) {
|
|
58
|
+
yield chunk.subarray(pos, stop);
|
|
59
|
+
}
|
|
60
|
+
if (newline === -1) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
line += 1;
|
|
64
|
+
pos = stop;
|
|
65
|
+
if (endLine !== void 0 && line >= endLine) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
var import_stream, isReadableStream, destroyStream, discardStreamBody, toBuffer, sliceStreamLines;
|
|
72
|
+
var init_stream = __esm({
|
|
73
|
+
"src/teamcity/utils/stream.ts"() {
|
|
74
|
+
"use strict";
|
|
75
|
+
import_stream = require("stream");
|
|
76
|
+
isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function" && typeof value.on === "function";
|
|
77
|
+
destroyStream = (stream) => {
|
|
78
|
+
stream.on("error", () => void 0);
|
|
79
|
+
const destroyable = stream;
|
|
80
|
+
if (typeof destroyable.destroy === "function") {
|
|
81
|
+
destroyable.destroy();
|
|
82
|
+
} else {
|
|
83
|
+
stream.resume();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
discardStreamBody = (value) => {
|
|
87
|
+
if (isReadableStream(value)) {
|
|
88
|
+
destroyStream(value);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
toBuffer = (chunk) => {
|
|
92
|
+
if (Buffer.isBuffer(chunk)) {
|
|
93
|
+
return chunk;
|
|
94
|
+
}
|
|
95
|
+
return chunk instanceof Uint8Array ? Buffer.from(chunk) : Buffer.from(String(chunk));
|
|
96
|
+
};
|
|
97
|
+
sliceStreamLines = (source, startLine, lineCount) => {
|
|
98
|
+
const endLine = lineCount === void 0 ? void 0 : startLine + lineCount;
|
|
99
|
+
const slice = import_stream.Readable.from(sliceLines(source, startLine, endLine), { objectMode: false });
|
|
100
|
+
const destroySlice = slice._destroy.bind(slice);
|
|
101
|
+
slice._destroy = (error3, callback) => {
|
|
102
|
+
destroyStream(source);
|
|
103
|
+
destroySlice(error3, callback);
|
|
104
|
+
};
|
|
105
|
+
return slice;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
36
110
|
// src/teamcity/errors.ts
|
|
37
111
|
function isRetryableError(error3) {
|
|
38
112
|
if (error3 instanceof TeamCityAPIError) {
|
|
@@ -57,6 +131,7 @@ function isRetryableError(error3) {
|
|
|
57
131
|
var TeamCityAPIError, TeamCityAuthorizationError, TeamCityNotFoundError, TeamCityRateLimitError, TeamCityServerError, TeamCityNetworkError, TeamCityRequestError, TeamCityTimeoutError, BuildNotFoundError, BuildAccessDeniedError;
|
|
58
132
|
var init_errors = __esm({
|
|
59
133
|
"src/teamcity/errors.ts"() {
|
|
134
|
+
init_stream();
|
|
60
135
|
TeamCityAPIError = class _TeamCityAPIError extends Error {
|
|
61
136
|
code;
|
|
62
137
|
statusCode;
|
|
@@ -80,7 +155,7 @@ var init_errors = __esm({
|
|
|
80
155
|
*/
|
|
81
156
|
static fromAxiosError(error3, requestId) {
|
|
82
157
|
if (error3.response) {
|
|
83
|
-
const data = error3.response.data;
|
|
158
|
+
const data = isReadableStream(error3.response.data) ? void 0 : error3.response.data;
|
|
84
159
|
return new _TeamCityAPIError(
|
|
85
160
|
(typeof data?.["message"] === "string" ? data["message"] : null) ?? error3.message,
|
|
86
161
|
(typeof data?.["code"] === "string" ? data["code"] : null) ?? `HTTP_${error3.response.status}`,
|
|
@@ -947,7 +1022,21 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
947
1022
|
|
|
948
1023
|
// src/utils/logger/index.ts
|
|
949
1024
|
var import_node_fs = require("node:fs");
|
|
1025
|
+
var import_node_util = require("node:util");
|
|
950
1026
|
var import_winston = __toESM(require("winston"));
|
|
1027
|
+
function safeStringify(value) {
|
|
1028
|
+
try {
|
|
1029
|
+
return (0, import_node_util.inspect)(value, {
|
|
1030
|
+
depth: 4,
|
|
1031
|
+
maxArrayLength: 50,
|
|
1032
|
+
maxStringLength: 1e3,
|
|
1033
|
+
breakLength: Infinity,
|
|
1034
|
+
compact: true
|
|
1035
|
+
});
|
|
1036
|
+
} catch {
|
|
1037
|
+
return "[Uninspectable value]";
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
951
1040
|
var TeamCityLogger = class _TeamCityLogger {
|
|
952
1041
|
winston;
|
|
953
1042
|
config;
|
|
@@ -1043,7 +1132,8 @@ var TeamCityLogger = class _TeamCityLogger {
|
|
|
1043
1132
|
if (projectId) contextParts.push(`project=${projectId}`);
|
|
1044
1133
|
if (duration !== void 0) contextParts.push(`${duration}ms`);
|
|
1045
1134
|
const contextString = contextParts.length > 0 ? ` [${contextParts.join(" ")}]` : "";
|
|
1046
|
-
const
|
|
1135
|
+
const metaEntries = Object.entries(otherMeta);
|
|
1136
|
+
const metaString = metaEntries.length > 0 ? ` ${safeStringify(Object.fromEntries(metaEntries))}` : "";
|
|
1047
1137
|
return `${baseLog}${contextString}${metaString}`;
|
|
1048
1138
|
}
|
|
1049
1139
|
/**
|
|
@@ -1216,7 +1306,7 @@ function debug2(message, meta) {
|
|
|
1216
1306
|
// package.json
|
|
1217
1307
|
var package_default = {
|
|
1218
1308
|
name: "@daghis/teamcity-mcp",
|
|
1219
|
-
version: "2.12.
|
|
1309
|
+
version: "2.12.6",
|
|
1220
1310
|
description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
|
|
1221
1311
|
mcpName: "io.github.Daghis/teamcity",
|
|
1222
1312
|
main: "dist/index.js",
|
|
@@ -1245,7 +1335,7 @@ var package_default = {
|
|
|
1245
1335
|
"lint:check": "node ./node_modules/eslint/bin/eslint.js 'src/**/*.{ts,tsx}' 'tests/**/*.test.{ts,tsx,js}' --no-error-on-unmatched-pattern",
|
|
1246
1336
|
format: "node ./node_modules/prettier/bin/prettier.cjs --write 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,js,json,md}'",
|
|
1247
1337
|
"format:check": "node ./node_modules/prettier/bin/prettier.cjs --check 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,js,json,md}'",
|
|
1248
|
-
typecheck: "node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.build.json",
|
|
1338
|
+
typecheck: "node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.build.json && npm run typecheck:all",
|
|
1249
1339
|
"typecheck:all": "node ./node_modules/typescript/bin/tsc --noEmit",
|
|
1250
1340
|
check: "npm run typecheck && npm run lint:check && npm run format:check",
|
|
1251
1341
|
clean: "rm -rf dist coverage",
|
|
@@ -1281,7 +1371,7 @@ var package_default = {
|
|
|
1281
1371
|
},
|
|
1282
1372
|
dependencies: {
|
|
1283
1373
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
1284
|
-
axios: "^1.
|
|
1374
|
+
axios: "^1.20.0",
|
|
1285
1375
|
"axios-retry": "^4.5.0",
|
|
1286
1376
|
dotenv: "17.4.2",
|
|
1287
1377
|
tslib: "^2.8.1",
|
|
@@ -1316,7 +1406,7 @@ var package_default = {
|
|
|
1316
1406
|
},
|
|
1317
1407
|
overrides: {
|
|
1318
1408
|
"@hono/node-server": "^1.19.13",
|
|
1319
|
-
axios: "^1.
|
|
1409
|
+
axios: "^1.20.0",
|
|
1320
1410
|
"body-parser": "^2.2.1",
|
|
1321
1411
|
"follow-redirects": "^1.16.0",
|
|
1322
1412
|
"glob@>=10.2.0 <10.5.0": "^10.5.0",
|
|
@@ -4838,6 +4928,7 @@ var esm_default = axiosRetry;
|
|
|
4838
4928
|
// src/teamcity/auth.ts
|
|
4839
4929
|
var import_crypto = require("crypto");
|
|
4840
4930
|
init_errors();
|
|
4931
|
+
init_stream();
|
|
4841
4932
|
var asTimingMetaContainer = (value) => {
|
|
4842
4933
|
if (typeof value === "object" && value !== null) {
|
|
4843
4934
|
return value;
|
|
@@ -4882,7 +4973,46 @@ function logResponse(response) {
|
|
|
4882
4973
|
});
|
|
4883
4974
|
return response;
|
|
4884
4975
|
}
|
|
4885
|
-
|
|
4976
|
+
var STREAM_BODY_TIMEOUT_MS = 5e3;
|
|
4977
|
+
var streamToString = async (stream, maxBytes = 64 * 1024) => {
|
|
4978
|
+
const chunks = [];
|
|
4979
|
+
let total = 0;
|
|
4980
|
+
for await (const chunk of stream) {
|
|
4981
|
+
const buf = toBuffer(chunk);
|
|
4982
|
+
chunks.push(buf);
|
|
4983
|
+
total += buf.length;
|
|
4984
|
+
if (total >= maxBytes) {
|
|
4985
|
+
break;
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
return Buffer.concat(chunks).toString("utf8").slice(0, maxBytes);
|
|
4989
|
+
};
|
|
4990
|
+
var snapshotStreamBody = async (stream, timeoutMs) => {
|
|
4991
|
+
let timer;
|
|
4992
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
4993
|
+
timer = setTimeout(() => reject(new Error("Timed out reading streamed error body")), timeoutMs);
|
|
4994
|
+
});
|
|
4995
|
+
try {
|
|
4996
|
+
return await Promise.race([streamToString(stream), deadline]);
|
|
4997
|
+
} catch {
|
|
4998
|
+
destroyStream(stream);
|
|
4999
|
+
return void 0;
|
|
5000
|
+
} finally {
|
|
5001
|
+
clearTimeout(timer);
|
|
5002
|
+
}
|
|
5003
|
+
};
|
|
5004
|
+
async function logAndTransformError(error3) {
|
|
5005
|
+
if (error3 instanceof TeamCityAPIError) {
|
|
5006
|
+
return Promise.reject(error3);
|
|
5007
|
+
}
|
|
5008
|
+
const response = error3.response;
|
|
5009
|
+
if (response && isReadableStream(response.data)) {
|
|
5010
|
+
const requestTimeout = error3.config?.timeout ?? 0;
|
|
5011
|
+
response.data = await snapshotStreamBody(
|
|
5012
|
+
response.data,
|
|
5013
|
+
requestTimeout > 0 ? Math.min(requestTimeout, STREAM_BODY_TIMEOUT_MS) : STREAM_BODY_TIMEOUT_MS
|
|
5014
|
+
);
|
|
5015
|
+
}
|
|
4886
5016
|
const requestId = error3.config?.requestId;
|
|
4887
5017
|
const tcError = TeamCityAPIError.fromAxiosError(error3, requestId);
|
|
4888
5018
|
const meta = asTimingMetaContainer(error3.config)?._tcMeta;
|
|
@@ -4938,6 +5068,7 @@ function validateConfiguration(baseUrl, token) {
|
|
|
4938
5068
|
|
|
4939
5069
|
// src/api-client.ts
|
|
4940
5070
|
init_errors();
|
|
5071
|
+
init_stream();
|
|
4941
5072
|
|
|
4942
5073
|
// src/teamcity-client/api/agent-api.ts
|
|
4943
5074
|
var import_axios5 = __toESM(require("axios"));
|
|
@@ -38415,6 +38546,24 @@ var extractRetryAfterMilliseconds = (value) => {
|
|
|
38415
38546
|
}
|
|
38416
38547
|
return void 0;
|
|
38417
38548
|
};
|
|
38549
|
+
var toOptionalLineNumber = (value) => {
|
|
38550
|
+
if (value === void 0 || value === null || value === "") {
|
|
38551
|
+
return void 0;
|
|
38552
|
+
}
|
|
38553
|
+
const parsed = Number(value);
|
|
38554
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
38555
|
+
};
|
|
38556
|
+
var sliceLogData = (data, startLine, lineCount) => {
|
|
38557
|
+
if (isReadableStream(data)) {
|
|
38558
|
+
return sliceStreamLines(data, startLine, lineCount);
|
|
38559
|
+
}
|
|
38560
|
+
if (typeof data === "string") {
|
|
38561
|
+
const lines = data.split("\n");
|
|
38562
|
+
const end = lineCount === void 0 ? void 0 : startLine + lineCount;
|
|
38563
|
+
return lines.slice(startLine, end).join("\n");
|
|
38564
|
+
}
|
|
38565
|
+
return data;
|
|
38566
|
+
};
|
|
38418
38567
|
var TeamCityAPI = class _TeamCityAPI {
|
|
38419
38568
|
static instance;
|
|
38420
38569
|
static instanceConfig;
|
|
@@ -38479,6 +38628,7 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38479
38628
|
this.http = this.axiosInstance;
|
|
38480
38629
|
esm_default(this.axiosInstance, {
|
|
38481
38630
|
retries: 3,
|
|
38631
|
+
onRetry: (_retryCount, error3) => discardStreamBody(error3.response?.data),
|
|
38482
38632
|
retryDelay: (retryCount, error3) => {
|
|
38483
38633
|
const reqId = error3?.config?.requestId;
|
|
38484
38634
|
const tcError = TeamCityAPIError.fromAxiosError(error3, reqId);
|
|
@@ -38736,27 +38886,47 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38736
38886
|
);
|
|
38737
38887
|
}
|
|
38738
38888
|
async downloadBuildLog(buildId, options) {
|
|
38889
|
+
const responseType = options?.responseType ?? "text";
|
|
38890
|
+
const rawHeaders = options?.headers ?? void 0;
|
|
38891
|
+
const headers = { Accept: "text/plain", ...rawHeaders ?? {} };
|
|
38892
|
+
const transformResponse = options?.transformResponse ?? [(data) => data];
|
|
38739
38893
|
const rawParams = options?.params ?? void 0;
|
|
38740
|
-
const
|
|
38741
|
-
|
|
38742
|
-
|
|
38894
|
+
const { start, count, ...primaryParams } = rawParams ?? {};
|
|
38895
|
+
const startLine = toOptionalLineNumber(start);
|
|
38896
|
+
const lineCount = toOptionalLineNumber(count);
|
|
38897
|
+
try {
|
|
38898
|
+
const response = await this.axiosInstance.get(`/downloadBuildLog.html`, {
|
|
38899
|
+
...options,
|
|
38900
|
+
params: { ...primaryParams, buildId },
|
|
38901
|
+
headers,
|
|
38902
|
+
responseType,
|
|
38903
|
+
transformResponse
|
|
38904
|
+
});
|
|
38905
|
+
if (startLine === void 0 && lineCount === void 0) {
|
|
38906
|
+
return response;
|
|
38907
|
+
}
|
|
38908
|
+
return { ...response, data: sliceLogData(response.data, startLine ?? 0, lineCount) };
|
|
38909
|
+
} catch (primaryError) {
|
|
38910
|
+
const status = primaryError instanceof TeamCityAPIError ? primaryError.statusCode : void 0;
|
|
38911
|
+
if (status === void 0 || [401, 403, 429, 503].includes(status)) {
|
|
38912
|
+
throw primaryError;
|
|
38913
|
+
}
|
|
38914
|
+
const params = rawParams ? { ...rawParams } : {};
|
|
38915
|
+
if (!Object.prototype.hasOwnProperty.call(params, "plain")) {
|
|
38916
|
+
params["plain"] = true;
|
|
38917
|
+
}
|
|
38918
|
+
try {
|
|
38919
|
+
return await this.axiosInstance.get(`/app/rest/builds/${toBuildLocator(buildId)}/log`, {
|
|
38920
|
+
...options,
|
|
38921
|
+
params,
|
|
38922
|
+
headers,
|
|
38923
|
+
responseType,
|
|
38924
|
+
transformResponse
|
|
38925
|
+
});
|
|
38926
|
+
} catch {
|
|
38927
|
+
throw primaryError;
|
|
38928
|
+
}
|
|
38743
38929
|
}
|
|
38744
|
-
const rawHeaders = options?.headers ?? void 0;
|
|
38745
|
-
const headers = rawHeaders ? { ...rawHeaders } : {};
|
|
38746
|
-
const requestOptions = {
|
|
38747
|
-
...options,
|
|
38748
|
-
params,
|
|
38749
|
-
headers: {
|
|
38750
|
-
Accept: "text/plain",
|
|
38751
|
-
...headers
|
|
38752
|
-
},
|
|
38753
|
-
responseType: options?.responseType ?? "text",
|
|
38754
|
-
transformResponse: options?.transformResponse ?? [(data) => data]
|
|
38755
|
-
};
|
|
38756
|
-
return this.axiosInstance.get(
|
|
38757
|
-
`/app/rest/builds/${toBuildLocator(buildId)}/log`,
|
|
38758
|
-
requestOptions
|
|
38759
|
-
);
|
|
38760
38930
|
}
|
|
38761
38931
|
async getBuildStatistics(buildId, fields) {
|
|
38762
38932
|
return this.builds.getBuildStatisticValues(toBuildLocator(buildId), fields);
|
|
@@ -38845,7 +39015,7 @@ var extraHeadersEqual = (a, b) => {
|
|
|
38845
39015
|
};
|
|
38846
39016
|
|
|
38847
39017
|
// src/tools.ts
|
|
38848
|
-
var
|
|
39018
|
+
var isReadableStream2 = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
38849
39019
|
var isAxios4042 = (error3) => (0, import_axios36.isAxiosError)(error3) && error3.response?.status === 404;
|
|
38850
39020
|
var buildIdentifierSchema = import_zod4.z.object({
|
|
38851
39021
|
buildId: import_zod4.z.string().min(1).optional(),
|
|
@@ -38970,7 +39140,7 @@ var writeArtifactStreamToDisk = async (artifact, stream, options) => {
|
|
|
38970
39140
|
var buildArtifactPayload = async (artifact, encoding, options) => {
|
|
38971
39141
|
if (encoding === "stream") {
|
|
38972
39142
|
const contentStream = artifact.content;
|
|
38973
|
-
if (!
|
|
39143
|
+
if (!isReadableStream2(contentStream)) {
|
|
38974
39144
|
throw new Error("Streaming download did not return a readable stream");
|
|
38975
39145
|
}
|
|
38976
39146
|
const { outputPath, bytesWritten } = await writeArtifactStreamToDisk(
|
|
@@ -39054,7 +39224,7 @@ var downloadArtifactByUrl = async (adapter, request, encoding, options) => {
|
|
|
39054
39224
|
}
|
|
39055
39225
|
if (encoding === "stream") {
|
|
39056
39226
|
const stream = response.data;
|
|
39057
|
-
if (!
|
|
39227
|
+
if (!isReadableStream2(stream)) {
|
|
39058
39228
|
throw new Error("Streaming download did not return a readable stream");
|
|
39059
39229
|
}
|
|
39060
39230
|
const artifact2 = {
|
|
@@ -40147,24 +40317,9 @@ var DEV_TOOLS = [
|
|
|
40147
40317
|
}
|
|
40148
40318
|
const shouldRetry2 = (error3) => {
|
|
40149
40319
|
if (error3 instanceof TeamCityAPIError) {
|
|
40150
|
-
|
|
40151
|
-
return true;
|
|
40152
|
-
}
|
|
40153
|
-
return isRetryableError(error3);
|
|
40154
|
-
}
|
|
40155
|
-
if ((0, import_axios36.isAxiosError)(error3)) {
|
|
40156
|
-
const status = error3.response?.status;
|
|
40157
|
-
if (status === 404) {
|
|
40158
|
-
return true;
|
|
40159
|
-
}
|
|
40160
|
-
if (status != null && status >= 500 && status < 600) {
|
|
40161
|
-
return true;
|
|
40162
|
-
}
|
|
40163
|
-
if (!status) {
|
|
40164
|
-
return true;
|
|
40165
|
-
}
|
|
40320
|
+
return error3.statusCode === 404;
|
|
40166
40321
|
}
|
|
40167
|
-
return
|
|
40322
|
+
return (0, import_axios36.isAxiosError)(error3) && error3.response?.status === 404;
|
|
40168
40323
|
};
|
|
40169
40324
|
const normalizeError = (error3) => {
|
|
40170
40325
|
if ((0, import_axios36.isAxiosError)(error3)) {
|
|
@@ -40240,7 +40395,7 @@ var DEV_TOOLS = [
|
|
|
40240
40395
|
responseType: "stream"
|
|
40241
40396
|
});
|
|
40242
40397
|
const stream = response.data;
|
|
40243
|
-
if (!
|
|
40398
|
+
if (!isReadableStream2(stream)) {
|
|
40244
40399
|
throw new Error("Streaming log download did not return a readable stream");
|
|
40245
40400
|
}
|
|
40246
40401
|
const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daghis/teamcity-mcp",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.6",
|
|
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",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"lint:check": "node ./node_modules/eslint/bin/eslint.js 'src/**/*.{ts,tsx}' 'tests/**/*.test.{ts,tsx,js}' --no-error-on-unmatched-pattern",
|
|
30
30
|
"format": "node ./node_modules/prettier/bin/prettier.cjs --write 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,js,json,md}'",
|
|
31
31
|
"format:check": "node ./node_modules/prettier/bin/prettier.cjs --check 'src/**/*.{ts,tsx,json,md}' 'tests/**/*.{ts,tsx,js,json,md}'",
|
|
32
|
-
"typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.build.json",
|
|
32
|
+
"typecheck": "node ./node_modules/typescript/bin/tsc --noEmit -p tsconfig.build.json && npm run typecheck:all",
|
|
33
33
|
"typecheck:all": "node ./node_modules/typescript/bin/tsc --noEmit",
|
|
34
34
|
"check": "npm run typecheck && npm run lint:check && npm run format:check",
|
|
35
35
|
"clean": "rm -rf dist coverage",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
68
|
-
"axios": "^1.
|
|
68
|
+
"axios": "^1.20.0",
|
|
69
69
|
"axios-retry": "^4.5.0",
|
|
70
70
|
"dotenv": "17.4.2",
|
|
71
71
|
"tslib": "^2.8.1",
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
},
|
|
101
101
|
"overrides": {
|
|
102
102
|
"@hono/node-server": "^1.19.13",
|
|
103
|
-
"axios": "^1.
|
|
103
|
+
"axios": "^1.20.0",
|
|
104
104
|
"body-parser": "^2.2.1",
|
|
105
105
|
"follow-redirects": "^1.16.0",
|
|
106
106
|
"glob@>=10.2.0 <10.5.0": "^10.5.0",
|
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.12.
|
|
10
|
+
"version": "2.12.6",
|
|
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.12.
|
|
16
|
+
"version": "2.12.6",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"runtimeArguments": [
|
|
19
19
|
{
|