@daghis/teamcity-mcp 2.12.5 → 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 +7 -0
- package/dist/index.js +185 -27
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
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
|
+
|
|
3
10
|
## [2.12.5](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.4...teamcity-mcp-v2.12.5) (2026-10-03)
|
|
4
11
|
|
|
5
12
|
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,71 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
42
42
|
mod
|
|
43
43
|
));
|
|
44
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
|
+
|
|
45
110
|
// src/teamcity/errors.ts
|
|
46
111
|
function isRetryableError(error3) {
|
|
47
112
|
if (error3 instanceof TeamCityAPIError) {
|
|
@@ -66,6 +131,7 @@ function isRetryableError(error3) {
|
|
|
66
131
|
var TeamCityAPIError, TeamCityAuthorizationError, TeamCityNotFoundError, TeamCityRateLimitError, TeamCityServerError, TeamCityNetworkError, TeamCityRequestError, TeamCityTimeoutError, BuildNotFoundError, BuildAccessDeniedError;
|
|
67
132
|
var init_errors = __esm({
|
|
68
133
|
"src/teamcity/errors.ts"() {
|
|
134
|
+
init_stream();
|
|
69
135
|
TeamCityAPIError = class _TeamCityAPIError extends Error {
|
|
70
136
|
code;
|
|
71
137
|
statusCode;
|
|
@@ -89,7 +155,7 @@ var init_errors = __esm({
|
|
|
89
155
|
*/
|
|
90
156
|
static fromAxiosError(error3, requestId) {
|
|
91
157
|
if (error3.response) {
|
|
92
|
-
const data = error3.response.data;
|
|
158
|
+
const data = isReadableStream(error3.response.data) ? void 0 : error3.response.data;
|
|
93
159
|
return new _TeamCityAPIError(
|
|
94
160
|
(typeof data?.["message"] === "string" ? data["message"] : null) ?? error3.message,
|
|
95
161
|
(typeof data?.["code"] === "string" ? data["code"] : null) ?? `HTTP_${error3.response.status}`,
|
|
@@ -956,7 +1022,21 @@ var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
|
956
1022
|
|
|
957
1023
|
// src/utils/logger/index.ts
|
|
958
1024
|
var import_node_fs = require("node:fs");
|
|
1025
|
+
var import_node_util = require("node:util");
|
|
959
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
|
+
}
|
|
960
1040
|
var TeamCityLogger = class _TeamCityLogger {
|
|
961
1041
|
winston;
|
|
962
1042
|
config;
|
|
@@ -1052,7 +1132,8 @@ var TeamCityLogger = class _TeamCityLogger {
|
|
|
1052
1132
|
if (projectId) contextParts.push(`project=${projectId}`);
|
|
1053
1133
|
if (duration !== void 0) contextParts.push(`${duration}ms`);
|
|
1054
1134
|
const contextString = contextParts.length > 0 ? ` [${contextParts.join(" ")}]` : "";
|
|
1055
|
-
const
|
|
1135
|
+
const metaEntries = Object.entries(otherMeta);
|
|
1136
|
+
const metaString = metaEntries.length > 0 ? ` ${safeStringify(Object.fromEntries(metaEntries))}` : "";
|
|
1056
1137
|
return `${baseLog}${contextString}${metaString}`;
|
|
1057
1138
|
}
|
|
1058
1139
|
/**
|
|
@@ -1225,7 +1306,7 @@ function debug2(message, meta) {
|
|
|
1225
1306
|
// package.json
|
|
1226
1307
|
var package_default = {
|
|
1227
1308
|
name: "@daghis/teamcity-mcp",
|
|
1228
|
-
version: "2.12.
|
|
1309
|
+
version: "2.12.6",
|
|
1229
1310
|
description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
|
|
1230
1311
|
mcpName: "io.github.Daghis/teamcity",
|
|
1231
1312
|
main: "dist/index.js",
|
|
@@ -4847,6 +4928,7 @@ var esm_default = axiosRetry;
|
|
|
4847
4928
|
// src/teamcity/auth.ts
|
|
4848
4929
|
var import_crypto = require("crypto");
|
|
4849
4930
|
init_errors();
|
|
4931
|
+
init_stream();
|
|
4850
4932
|
var asTimingMetaContainer = (value) => {
|
|
4851
4933
|
if (typeof value === "object" && value !== null) {
|
|
4852
4934
|
return value;
|
|
@@ -4891,10 +4973,46 @@ function logResponse(response) {
|
|
|
4891
4973
|
});
|
|
4892
4974
|
return response;
|
|
4893
4975
|
}
|
|
4894
|
-
|
|
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) {
|
|
4895
5005
|
if (error3 instanceof TeamCityAPIError) {
|
|
4896
5006
|
return Promise.reject(error3);
|
|
4897
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
|
+
}
|
|
4898
5016
|
const requestId = error3.config?.requestId;
|
|
4899
5017
|
const tcError = TeamCityAPIError.fromAxiosError(error3, requestId);
|
|
4900
5018
|
const meta = asTimingMetaContainer(error3.config)?._tcMeta;
|
|
@@ -4950,6 +5068,7 @@ function validateConfiguration(baseUrl, token) {
|
|
|
4950
5068
|
|
|
4951
5069
|
// src/api-client.ts
|
|
4952
5070
|
init_errors();
|
|
5071
|
+
init_stream();
|
|
4953
5072
|
|
|
4954
5073
|
// src/teamcity-client/api/agent-api.ts
|
|
4955
5074
|
var import_axios5 = __toESM(require("axios"));
|
|
@@ -38427,6 +38546,24 @@ var extractRetryAfterMilliseconds = (value) => {
|
|
|
38427
38546
|
}
|
|
38428
38547
|
return void 0;
|
|
38429
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
|
+
};
|
|
38430
38567
|
var TeamCityAPI = class _TeamCityAPI {
|
|
38431
38568
|
static instance;
|
|
38432
38569
|
static instanceConfig;
|
|
@@ -38491,6 +38628,7 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38491
38628
|
this.http = this.axiosInstance;
|
|
38492
38629
|
esm_default(this.axiosInstance, {
|
|
38493
38630
|
retries: 3,
|
|
38631
|
+
onRetry: (_retryCount, error3) => discardStreamBody(error3.response?.data),
|
|
38494
38632
|
retryDelay: (retryCount, error3) => {
|
|
38495
38633
|
const reqId = error3?.config?.requestId;
|
|
38496
38634
|
const tcError = TeamCityAPIError.fromAxiosError(error3, reqId);
|
|
@@ -38748,27 +38886,47 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38748
38886
|
);
|
|
38749
38887
|
}
|
|
38750
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];
|
|
38751
38893
|
const rawParams = options?.params ?? void 0;
|
|
38752
|
-
const
|
|
38753
|
-
|
|
38754
|
-
|
|
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
|
+
}
|
|
38755
38929
|
}
|
|
38756
|
-
const rawHeaders = options?.headers ?? void 0;
|
|
38757
|
-
const headers = rawHeaders ? { ...rawHeaders } : {};
|
|
38758
|
-
const requestOptions = {
|
|
38759
|
-
...options,
|
|
38760
|
-
params,
|
|
38761
|
-
headers: {
|
|
38762
|
-
Accept: "text/plain",
|
|
38763
|
-
...headers
|
|
38764
|
-
},
|
|
38765
|
-
responseType: options?.responseType ?? "text",
|
|
38766
|
-
transformResponse: options?.transformResponse ?? [(data) => data]
|
|
38767
|
-
};
|
|
38768
|
-
return this.axiosInstance.get(
|
|
38769
|
-
`/app/rest/builds/${toBuildLocator(buildId)}/log`,
|
|
38770
|
-
requestOptions
|
|
38771
|
-
);
|
|
38772
38930
|
}
|
|
38773
38931
|
async getBuildStatistics(buildId, fields) {
|
|
38774
38932
|
return this.builds.getBuildStatisticValues(toBuildLocator(buildId), fields);
|
|
@@ -38857,7 +39015,7 @@ var extraHeadersEqual = (a, b) => {
|
|
|
38857
39015
|
};
|
|
38858
39016
|
|
|
38859
39017
|
// src/tools.ts
|
|
38860
|
-
var
|
|
39018
|
+
var isReadableStream2 = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
38861
39019
|
var isAxios4042 = (error3) => (0, import_axios36.isAxiosError)(error3) && error3.response?.status === 404;
|
|
38862
39020
|
var buildIdentifierSchema = import_zod4.z.object({
|
|
38863
39021
|
buildId: import_zod4.z.string().min(1).optional(),
|
|
@@ -38982,7 +39140,7 @@ var writeArtifactStreamToDisk = async (artifact, stream, options) => {
|
|
|
38982
39140
|
var buildArtifactPayload = async (artifact, encoding, options) => {
|
|
38983
39141
|
if (encoding === "stream") {
|
|
38984
39142
|
const contentStream = artifact.content;
|
|
38985
|
-
if (!
|
|
39143
|
+
if (!isReadableStream2(contentStream)) {
|
|
38986
39144
|
throw new Error("Streaming download did not return a readable stream");
|
|
38987
39145
|
}
|
|
38988
39146
|
const { outputPath, bytesWritten } = await writeArtifactStreamToDisk(
|
|
@@ -39066,7 +39224,7 @@ var downloadArtifactByUrl = async (adapter, request, encoding, options) => {
|
|
|
39066
39224
|
}
|
|
39067
39225
|
if (encoding === "stream") {
|
|
39068
39226
|
const stream = response.data;
|
|
39069
|
-
if (!
|
|
39227
|
+
if (!isReadableStream2(stream)) {
|
|
39070
39228
|
throw new Error("Streaming download did not return a readable stream");
|
|
39071
39229
|
}
|
|
39072
39230
|
const artifact2 = {
|
|
@@ -40237,7 +40395,7 @@ var DEV_TOOLS = [
|
|
|
40237
40395
|
responseType: "stream"
|
|
40238
40396
|
});
|
|
40239
40397
|
const stream = response.data;
|
|
40240
|
-
if (!
|
|
40398
|
+
if (!isReadableStream2(stream)) {
|
|
40241
40399
|
throw new Error("Streaming log download did not return a readable stream");
|
|
40242
40400
|
}
|
|
40243
40401
|
const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
|
package/package.json
CHANGED
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
|
{
|