@daghis/teamcity-mcp 2.12.10 → 2.12.12
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/dist/index.js +216 -41
- package/package.json +3 -3
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.12.12](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.11...teamcity-mcp-v2.12.12) (2026-10-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **security:** bump zod, body-parser, fast-uri, ip-address and qs ([#554](https://github.com/Daghis/teamcity-mcp/issues/554)) ([5094e49](https://github.com/Daghis/teamcity-mcp/commit/5094e496f520aec345f3305cfc47bf3abab963af))
|
|
9
|
+
|
|
10
|
+
## [2.12.11](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.10...teamcity-mcp-v2.12.11) (2026-10-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **build-log:** keep memory bounded for log chunks, tails and streams ([#549](https://github.com/Daghis/teamcity-mcp/issues/549)) ([7815ab1](https://github.com/Daghis/teamcity-mcp/commit/7815ab10a2b7a395a680d665ca68ebe52a7d629e))
|
|
16
|
+
|
|
3
17
|
## [2.12.10](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.9...teamcity-mcp-v2.12.10) (2026-10-21)
|
|
4
18
|
|
|
5
19
|
|
package/dist/index.js
CHANGED
|
@@ -68,11 +68,49 @@ async function* sliceLines(source, startLine, endLine) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
async function* readLines(source, keep = () => true) {
|
|
72
|
+
const decoder = new import_string_decoder.StringDecoder("utf8");
|
|
73
|
+
let pending = [];
|
|
74
|
+
let started = false;
|
|
75
|
+
let index = 0;
|
|
76
|
+
let afterCarriageReturn = false;
|
|
77
|
+
const append = (piece) => {
|
|
78
|
+
if (piece) {
|
|
79
|
+
started = true;
|
|
80
|
+
if (keep(index)) {
|
|
81
|
+
pending.push(piece);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
for await (const raw of source) {
|
|
86
|
+
let text = decoder.write(toBuffer(raw));
|
|
87
|
+
if (afterCarriageReturn && text.startsWith("\n")) {
|
|
88
|
+
text = text.slice(1);
|
|
89
|
+
}
|
|
90
|
+
afterCarriageReturn = text.endsWith("\r");
|
|
91
|
+
const segments = text.split(LINE_BREAK);
|
|
92
|
+
const unfinished = segments.splice(-1).join("");
|
|
93
|
+
for (const segment of segments) {
|
|
94
|
+
append(segment);
|
|
95
|
+
const line = pending.join("");
|
|
96
|
+
pending = [];
|
|
97
|
+
started = false;
|
|
98
|
+
index += 1;
|
|
99
|
+
yield line;
|
|
100
|
+
}
|
|
101
|
+
append(unfinished);
|
|
102
|
+
}
|
|
103
|
+
append(decoder.end());
|
|
104
|
+
if (started) {
|
|
105
|
+
yield pending.join("");
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
var import_stream, import_string_decoder, isReadableStream, destroyStream, discardStreamBody, toBuffer, sliceStreamLines, LINE_BREAK, readLineRange, readLastLines;
|
|
72
109
|
var init_stream = __esm({
|
|
73
110
|
"src/teamcity/utils/stream.ts"() {
|
|
74
111
|
"use strict";
|
|
75
112
|
import_stream = require("stream");
|
|
113
|
+
import_string_decoder = require("string_decoder");
|
|
76
114
|
isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function" && typeof value.on === "function";
|
|
77
115
|
destroyStream = (stream) => {
|
|
78
116
|
stream.on("error", () => void 0);
|
|
@@ -104,6 +142,46 @@ var init_stream = __esm({
|
|
|
104
142
|
};
|
|
105
143
|
return slice;
|
|
106
144
|
};
|
|
145
|
+
LINE_BREAK = /\r\n|\r|\n/;
|
|
146
|
+
readLineRange = async (source, startLine, lineCount) => {
|
|
147
|
+
const endLine = startLine + lineCount;
|
|
148
|
+
const lines = [];
|
|
149
|
+
let index = 0;
|
|
150
|
+
try {
|
|
151
|
+
const inRange = (line) => line >= startLine && line < endLine;
|
|
152
|
+
for await (const line of readLines(source, inRange)) {
|
|
153
|
+
if (index >= endLine) {
|
|
154
|
+
return { lines, startLine, nextStartLine: endLine };
|
|
155
|
+
}
|
|
156
|
+
if (index >= startLine) {
|
|
157
|
+
lines.push(line);
|
|
158
|
+
}
|
|
159
|
+
index += 1;
|
|
160
|
+
}
|
|
161
|
+
} finally {
|
|
162
|
+
destroyStream(source);
|
|
163
|
+
}
|
|
164
|
+
return { lines, startLine: Math.min(startLine, index), totalLines: index };
|
|
165
|
+
};
|
|
166
|
+
readLastLines = async (source, lineCount) => {
|
|
167
|
+
const window = [];
|
|
168
|
+
let totalLines = 0;
|
|
169
|
+
try {
|
|
170
|
+
for await (const line of readLines(source)) {
|
|
171
|
+
if (lineCount > 0) {
|
|
172
|
+
window[totalLines % lineCount] = line;
|
|
173
|
+
}
|
|
174
|
+
totalLines += 1;
|
|
175
|
+
}
|
|
176
|
+
} finally {
|
|
177
|
+
destroyStream(source);
|
|
178
|
+
}
|
|
179
|
+
if (totalLines <= lineCount) {
|
|
180
|
+
return { lines: window, totalLines };
|
|
181
|
+
}
|
|
182
|
+
const oldest = totalLines % lineCount;
|
|
183
|
+
return { lines: [...window.slice(oldest), ...window.slice(0, oldest)], totalLines };
|
|
184
|
+
};
|
|
107
185
|
}
|
|
108
186
|
});
|
|
109
187
|
|
|
@@ -1310,7 +1388,7 @@ function debug2(message, meta) {
|
|
|
1310
1388
|
// package.json
|
|
1311
1389
|
var package_default = {
|
|
1312
1390
|
name: "@daghis/teamcity-mcp",
|
|
1313
|
-
version: "2.12.
|
|
1391
|
+
version: "2.12.12",
|
|
1314
1392
|
description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
|
|
1315
1393
|
mcpName: "io.github.Daghis/teamcity",
|
|
1316
1394
|
main: "dist/index.js",
|
|
@@ -1380,7 +1458,7 @@ var package_default = {
|
|
|
1380
1458
|
dotenv: "17.4.2",
|
|
1381
1459
|
tslib: "^2.8.1",
|
|
1382
1460
|
winston: "^3.11.0",
|
|
1383
|
-
zod: "^4.
|
|
1461
|
+
zod: "^4.6.6"
|
|
1384
1462
|
},
|
|
1385
1463
|
devDependencies: {
|
|
1386
1464
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
|
@@ -1414,7 +1492,7 @@ var package_default = {
|
|
|
1414
1492
|
"js-yaml": "$js-yaml"
|
|
1415
1493
|
},
|
|
1416
1494
|
axios: "^1.20.0",
|
|
1417
|
-
"body-parser": "^2.
|
|
1495
|
+
"body-parser": "^2.4.0",
|
|
1418
1496
|
"follow-redirects": "^1.16.1",
|
|
1419
1497
|
"glob@>=10.2.0 <10.5.0": "^10.5.0",
|
|
1420
1498
|
hono: "^4.12.14",
|
|
@@ -1427,7 +1505,7 @@ var package_default = {
|
|
|
1427
1505
|
|
|
1428
1506
|
// src/tools.ts
|
|
1429
1507
|
var import_node_crypto = require("node:crypto");
|
|
1430
|
-
var
|
|
1508
|
+
var import_node_fs3 = require("node:fs");
|
|
1431
1509
|
var import_node_os = require("node:os");
|
|
1432
1510
|
var import_node_path = require("node:path");
|
|
1433
1511
|
var import_promises = require("node:stream/promises");
|
|
@@ -4119,6 +4197,7 @@ function createAdapterFromTeamCityAPI(api, options = {}) {
|
|
|
4119
4197
|
triggerBuild: (buildTypeId, branchName, comment) => api.triggerBuild(buildTypeId, branchName, comment),
|
|
4120
4198
|
getBuildLog: (buildId) => api.getBuildLog(buildId),
|
|
4121
4199
|
getBuildLogChunk: (buildId, options2) => api.getBuildLogChunk(buildId, options2),
|
|
4200
|
+
getBuildLogTail: (buildId, lineCount) => api.getBuildLogTail(buildId, lineCount),
|
|
4122
4201
|
listBuildTypes: (projectId) => api.listBuildTypes(projectId),
|
|
4123
4202
|
getBuildType: (buildTypeId) => api.getBuildType(buildTypeId),
|
|
4124
4203
|
listTestFailures: (buildId) => api.listTestFailures(buildId),
|
|
@@ -4280,6 +4359,75 @@ function sleep(ms) {
|
|
|
4280
4359
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4281
4360
|
}
|
|
4282
4361
|
|
|
4362
|
+
// src/utils/disk-space.ts
|
|
4363
|
+
var import_node_fs2 = require("node:fs");
|
|
4364
|
+
var import_node_stream = require("node:stream");
|
|
4365
|
+
var DEFAULT_MIN_FREE_DISK_BYTES = 1024 ** 3;
|
|
4366
|
+
var RECHECK_EVERY_BYTES = 64 * 1024 ** 2;
|
|
4367
|
+
var formatBytes = (bytes) => bytes >= 1024 ** 3 ? `${(bytes / 1024 ** 3).toFixed(2)} GiB` : `${(bytes / 1024 ** 2).toFixed(1)} MiB`;
|
|
4368
|
+
var InsufficientDiskSpaceError = class extends Error {
|
|
4369
|
+
constructor(directory, freeBytes, minFreeBytes, requiredBytes) {
|
|
4370
|
+
const need = requiredBytes === void 0 ? "" : `${formatBytes(requiredBytes)} needed, `;
|
|
4371
|
+
super(
|
|
4372
|
+
`Not enough disk space in ${directory}: ${need}${formatBytes(freeBytes)} free, at least ${formatBytes(minFreeBytes)} must stay free`
|
|
4373
|
+
);
|
|
4374
|
+
this.directory = directory;
|
|
4375
|
+
this.freeBytes = freeBytes;
|
|
4376
|
+
this.minFreeBytes = minFreeBytes;
|
|
4377
|
+
this.requiredBytes = requiredBytes;
|
|
4378
|
+
this.name = "InsufficientDiskSpaceError";
|
|
4379
|
+
}
|
|
4380
|
+
directory;
|
|
4381
|
+
freeBytes;
|
|
4382
|
+
minFreeBytes;
|
|
4383
|
+
requiredBytes;
|
|
4384
|
+
};
|
|
4385
|
+
var getFreeDiskBytes = async (directory) => {
|
|
4386
|
+
const stats = await import_node_fs2.promises.statfs(directory);
|
|
4387
|
+
return stats.bavail * stats.bsize;
|
|
4388
|
+
};
|
|
4389
|
+
var assertDiskSpace = async (directory, requiredBytes = 0, minFreeBytes = DEFAULT_MIN_FREE_DISK_BYTES) => {
|
|
4390
|
+
const freeBytes = await getFreeDiskBytes(directory);
|
|
4391
|
+
if (freeBytes - requiredBytes < minFreeBytes) {
|
|
4392
|
+
throw new InsufficientDiskSpaceError(
|
|
4393
|
+
directory,
|
|
4394
|
+
freeBytes,
|
|
4395
|
+
minFreeBytes,
|
|
4396
|
+
requiredBytes > 0 ? requiredBytes : void 0
|
|
4397
|
+
);
|
|
4398
|
+
}
|
|
4399
|
+
return freeBytes;
|
|
4400
|
+
};
|
|
4401
|
+
var createDiskSpaceGuard = (directory, initialFreeBytes, minFreeBytes, recheckEveryBytes = RECHECK_EVERY_BYTES) => {
|
|
4402
|
+
let written = 0;
|
|
4403
|
+
let sinceCheck = 0;
|
|
4404
|
+
let reportedFreeBytes = initialFreeBytes;
|
|
4405
|
+
const estimatedFreeBytes = () => Math.min(reportedFreeBytes, initialFreeBytes - written);
|
|
4406
|
+
return new import_node_stream.Transform({
|
|
4407
|
+
transform(chunk, _encoding, callback) {
|
|
4408
|
+
written += chunk.length;
|
|
4409
|
+
sinceCheck += chunk.length;
|
|
4410
|
+
if (estimatedFreeBytes() < minFreeBytes) {
|
|
4411
|
+
callback(new InsufficientDiskSpaceError(directory, estimatedFreeBytes(), minFreeBytes));
|
|
4412
|
+
return;
|
|
4413
|
+
}
|
|
4414
|
+
if (sinceCheck < recheckEveryBytes) {
|
|
4415
|
+
callback(null, chunk);
|
|
4416
|
+
return;
|
|
4417
|
+
}
|
|
4418
|
+
sinceCheck = 0;
|
|
4419
|
+
getFreeDiskBytes(directory).then((freeBytes) => {
|
|
4420
|
+
reportedFreeBytes = freeBytes;
|
|
4421
|
+
if (estimatedFreeBytes() < minFreeBytes) {
|
|
4422
|
+
callback(new InsufficientDiskSpaceError(directory, estimatedFreeBytes(), minFreeBytes));
|
|
4423
|
+
} else {
|
|
4424
|
+
callback(null, chunk);
|
|
4425
|
+
}
|
|
4426
|
+
}, callback);
|
|
4427
|
+
}
|
|
4428
|
+
});
|
|
4429
|
+
};
|
|
4430
|
+
|
|
4283
4431
|
// src/utils/list-builds-locator.ts
|
|
4284
4432
|
var SIMPLE_BRANCH_VALUES = /* @__PURE__ */ new Set([
|
|
4285
4433
|
"default:true",
|
|
@@ -4931,6 +5079,9 @@ axiosRetry.linearDelay = linearDelay;
|
|
|
4931
5079
|
axiosRetry.isRetryableError = isRetryableError2;
|
|
4932
5080
|
var esm_default = axiosRetry;
|
|
4933
5081
|
|
|
5082
|
+
// src/api-client.ts
|
|
5083
|
+
var import_stream4 = require("stream");
|
|
5084
|
+
|
|
4934
5085
|
// src/teamcity/auth.ts
|
|
4935
5086
|
var import_node_string_decoder = require("node:string_decoder");
|
|
4936
5087
|
var import_crypto = require("crypto");
|
|
@@ -38850,20 +39001,17 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38850
39001
|
nextStartLine: lines.length === lineCount ? startLine + lines.length : void 0
|
|
38851
39002
|
};
|
|
38852
39003
|
} catch {
|
|
38853
|
-
|
|
38854
|
-
const allLines = full.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
|
|
38855
|
-
if (allLines.length > 0 && allLines[allLines.length - 1] === "") allLines.pop();
|
|
38856
|
-
const start = Math.max(0, Math.min(startLine, allLines.length));
|
|
38857
|
-
const end = Math.min(allLines.length, start + lineCount);
|
|
38858
|
-
const slice = allLines.slice(start, end);
|
|
38859
|
-
return {
|
|
38860
|
-
lines: slice,
|
|
38861
|
-
startLine: start,
|
|
38862
|
-
nextStartLine: end < allLines.length ? end : void 0,
|
|
38863
|
-
totalLines: allLines.length
|
|
38864
|
-
};
|
|
39004
|
+
return readLineRange(await this.streamBuildLog(buildId), startLine, lineCount);
|
|
38865
39005
|
}
|
|
38866
39006
|
}
|
|
39007
|
+
async getBuildLogTail(buildId, lineCount) {
|
|
39008
|
+
return readLastLines(await this.streamBuildLog(buildId), lineCount);
|
|
39009
|
+
}
|
|
39010
|
+
async streamBuildLog(buildId) {
|
|
39011
|
+
const response = await this.downloadBuildLog(buildId, { responseType: "stream" });
|
|
39012
|
+
const data = response.data;
|
|
39013
|
+
return isReadableStream(data) ? data : import_stream4.Readable.from([String(data ?? "")]);
|
|
39014
|
+
}
|
|
38867
39015
|
async listBuildTypes(projectId) {
|
|
38868
39016
|
const locator = projectId ? `affectedProject:(id:${projectId})` : void 0;
|
|
38869
39017
|
const response = await this.buildTypes.getAllBuildTypes(locator);
|
|
@@ -39113,7 +39261,7 @@ var ensureUniquePath = async (candidate) => {
|
|
|
39113
39261
|
const probe = async (attempt) => {
|
|
39114
39262
|
const next = attempt === 0 ? candidate : `${stem}-${attempt}${ext}`;
|
|
39115
39263
|
try {
|
|
39116
|
-
const handle = await
|
|
39264
|
+
const handle = await import_node_fs3.promises.open(next, "wx");
|
|
39117
39265
|
await handle.close();
|
|
39118
39266
|
return next;
|
|
39119
39267
|
} catch (error3) {
|
|
@@ -39130,7 +39278,7 @@ var resolveStreamOutputPath = async (artifact, options) => {
|
|
|
39130
39278
|
const { explicitOutputPath } = options;
|
|
39131
39279
|
if (typeof explicitOutputPath === "string" && explicitOutputPath.length > 0) {
|
|
39132
39280
|
const target = explicitOutputPath;
|
|
39133
|
-
await
|
|
39281
|
+
await import_node_fs3.promises.mkdir((0, import_node_path.dirname)(target), { recursive: true });
|
|
39134
39282
|
return target;
|
|
39135
39283
|
}
|
|
39136
39284
|
if (options.outputDir) {
|
|
@@ -39143,17 +39291,17 @@ var resolveStreamOutputPath = async (artifact, options) => {
|
|
|
39143
39291
|
if (relativePath.startsWith("..") || (0, import_node_path.isAbsolute)(relativePath)) {
|
|
39144
39292
|
throw new Error("Resolved artifact path escapes the configured output directory");
|
|
39145
39293
|
}
|
|
39146
|
-
await
|
|
39294
|
+
await import_node_fs3.promises.mkdir((0, import_node_path.dirname)(candidate), { recursive: true });
|
|
39147
39295
|
return ensureUniquePath(candidate);
|
|
39148
39296
|
}
|
|
39149
39297
|
const tempFilePath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), buildRandomFileName(artifact.name));
|
|
39150
|
-
await
|
|
39298
|
+
await import_node_fs3.promises.mkdir((0, import_node_path.dirname)(tempFilePath), { recursive: true });
|
|
39151
39299
|
return tempFilePath;
|
|
39152
39300
|
};
|
|
39153
39301
|
var writeArtifactStreamToDisk = async (artifact, stream, options) => {
|
|
39154
39302
|
const targetPath = await resolveStreamOutputPath(artifact, options);
|
|
39155
|
-
await (0, import_promises.pipeline)(stream, (0,
|
|
39156
|
-
const stats = await
|
|
39303
|
+
await (0, import_promises.pipeline)(stream, (0, import_node_fs3.createWriteStream)(targetPath));
|
|
39304
|
+
const stats = await import_node_fs3.promises.stat(targetPath);
|
|
39157
39305
|
return { outputPath: targetPath, bytesWritten: stats.size };
|
|
39158
39306
|
};
|
|
39159
39307
|
var buildArtifactPayload = async (artifact, encoding, options) => {
|
|
@@ -40241,13 +40389,17 @@ var DEV_TOOLS = [
|
|
|
40241
40389
|
tail: { type: "boolean", description: "Tail mode: return last N lines" },
|
|
40242
40390
|
encoding: {
|
|
40243
40391
|
type: "string",
|
|
40244
|
-
description: "Response encoding: 'text' (default) or 'stream'",
|
|
40392
|
+
description: "Response encoding: 'text' (default) or 'stream'. 'stream' writes the whole log, or the requested line range, to outputPath; logs can be several GB",
|
|
40245
40393
|
enum: ["text", "stream"],
|
|
40246
40394
|
default: "text"
|
|
40247
40395
|
},
|
|
40248
40396
|
outputPath: {
|
|
40249
40397
|
type: "string",
|
|
40250
40398
|
description: "Optional absolute path to write streamed logs; defaults to a temp file when streaming"
|
|
40399
|
+
},
|
|
40400
|
+
minFreeDiskMb: {
|
|
40401
|
+
type: "number",
|
|
40402
|
+
description: "Streaming only: disk space in MiB that must stay free on the target disk, otherwise the download aborts (default 1024, 0 disables the reserve)"
|
|
40251
40403
|
}
|
|
40252
40404
|
},
|
|
40253
40405
|
required: []
|
|
@@ -40263,7 +40415,8 @@ var DEV_TOOLS = [
|
|
|
40263
40415
|
lineCount: import_zod4.z.coerce.number().int().min(1).max(5e3).optional(),
|
|
40264
40416
|
tail: import_zod4.z.boolean().optional(),
|
|
40265
40417
|
encoding: import_zod4.z.enum(["text", "stream"]).default("text"),
|
|
40266
|
-
outputPath: import_zod4.z.string().min(1).optional()
|
|
40418
|
+
outputPath: import_zod4.z.string().min(1).optional(),
|
|
40419
|
+
minFreeDiskMb: import_zod4.z.coerce.number().min(0).optional()
|
|
40267
40420
|
}).superRefine((value, ctx) => {
|
|
40268
40421
|
if (!value.buildId && typeof value.buildNumber === "undefined") {
|
|
40269
40422
|
ctx.addIssue({
|
|
@@ -40358,12 +40511,11 @@ var DEV_TOOLS = [
|
|
|
40358
40511
|
const attemptBuffered = async () => {
|
|
40359
40512
|
if (typed.tail) {
|
|
40360
40513
|
const count = typed.lineCount ?? typed.pageSize ?? 500;
|
|
40361
|
-
const
|
|
40362
|
-
|
|
40363
|
-
|
|
40364
|
-
|
|
40365
|
-
const start =
|
|
40366
|
-
const lines = allLines.slice(start);
|
|
40514
|
+
const { lines, totalLines: total } = await adapter.getBuildLogTail(
|
|
40515
|
+
effectiveBuildId,
|
|
40516
|
+
count
|
|
40517
|
+
);
|
|
40518
|
+
const start = total - lines.length;
|
|
40367
40519
|
return json({
|
|
40368
40520
|
lines,
|
|
40369
40521
|
meta: {
|
|
@@ -40406,11 +40558,9 @@ var DEV_TOOLS = [
|
|
|
40406
40558
|
const attemptStream = async () => {
|
|
40407
40559
|
const effectivePageSize = typed.lineCount ?? typed.pageSize ?? 500;
|
|
40408
40560
|
const startLine = typeof typed.startLine === "number" ? typed.startLine : ((typed.page ?? 1) - 1) * effectivePageSize;
|
|
40561
|
+
const ranged = typed.startLine !== void 0 || typed.page !== void 0 || typed.lineCount !== void 0 || typed.pageSize !== void 0;
|
|
40409
40562
|
const response = await adapter.downloadBuildLogContent(effectiveBuildId, {
|
|
40410
|
-
params: {
|
|
40411
|
-
start: startLine,
|
|
40412
|
-
count: effectivePageSize
|
|
40413
|
-
},
|
|
40563
|
+
params: ranged ? { start: startLine, count: effectivePageSize } : void 0,
|
|
40414
40564
|
responseType: "stream"
|
|
40415
40565
|
});
|
|
40416
40566
|
const stream = response.data;
|
|
@@ -40420,9 +40570,34 @@ var DEV_TOOLS = [
|
|
|
40420
40570
|
const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
|
|
40421
40571
|
const defaultFileName = `build-log-${safeBuildId}-${startLine}-${(0, import_node_crypto.randomUUID)()}.log`;
|
|
40422
40572
|
const targetPath = typed.outputPath ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), defaultFileName);
|
|
40423
|
-
|
|
40424
|
-
|
|
40425
|
-
const
|
|
40573
|
+
const targetDir = (0, import_node_path.dirname)(targetPath);
|
|
40574
|
+
const contentLength = Number(response.headers?.["content-length"]);
|
|
40575
|
+
const minFreeBytes = typed.minFreeDiskMb === void 0 ? DEFAULT_MIN_FREE_DISK_BYTES : typed.minFreeDiskMb * 1024 ** 2;
|
|
40576
|
+
let freeBytes;
|
|
40577
|
+
try {
|
|
40578
|
+
await import_node_fs3.promises.mkdir(targetDir, { recursive: true });
|
|
40579
|
+
freeBytes = await assertDiskSpace(
|
|
40580
|
+
targetDir,
|
|
40581
|
+
!ranged && Number.isFinite(contentLength) ? contentLength : 0,
|
|
40582
|
+
minFreeBytes
|
|
40583
|
+
);
|
|
40584
|
+
} catch (error3) {
|
|
40585
|
+
stream.destroy();
|
|
40586
|
+
throw error3;
|
|
40587
|
+
}
|
|
40588
|
+
try {
|
|
40589
|
+
await (0, import_promises.pipeline)(
|
|
40590
|
+
stream,
|
|
40591
|
+
createDiskSpaceGuard(targetDir, freeBytes, minFreeBytes),
|
|
40592
|
+
(0, import_node_fs3.createWriteStream)(targetPath)
|
|
40593
|
+
);
|
|
40594
|
+
} catch (error3) {
|
|
40595
|
+
if (error3 instanceof InsufficientDiskSpaceError) {
|
|
40596
|
+
await import_node_fs3.promises.rm(targetPath, { force: true });
|
|
40597
|
+
}
|
|
40598
|
+
throw error3;
|
|
40599
|
+
}
|
|
40600
|
+
const stats = await import_node_fs3.promises.stat(targetPath);
|
|
40426
40601
|
const page = Math.floor(startLine / effectivePageSize) + 1;
|
|
40427
40602
|
return json({
|
|
40428
40603
|
encoding: "stream",
|
|
@@ -40432,8 +40607,8 @@ var DEV_TOOLS = [
|
|
|
40432
40607
|
buildId: effectiveBuildId,
|
|
40433
40608
|
buildNumber: typeof typed.buildNumber !== "undefined" ? String(typed.buildNumber) : void 0,
|
|
40434
40609
|
buildTypeId: typed.buildTypeId,
|
|
40435
|
-
page,
|
|
40436
|
-
pageSize: effectivePageSize,
|
|
40610
|
+
page: ranged ? page : void 0,
|
|
40611
|
+
pageSize: ranged ? effectivePageSize : void 0,
|
|
40437
40612
|
startLine
|
|
40438
40613
|
}
|
|
40439
40614
|
});
|
|
@@ -45117,7 +45292,7 @@ var FULL_MODE_TOOLS = [
|
|
|
45117
45292
|
if (typedArgs.privateKeyContent && typedArgs.privateKeyPath) {
|
|
45118
45293
|
throw new Error("Provide only one of privateKeyContent or privateKeyPath, not both");
|
|
45119
45294
|
}
|
|
45120
|
-
const keyContent = typedArgs.privateKeyPath ? await
|
|
45295
|
+
const keyContent = typedArgs.privateKeyPath ? await import_node_fs3.promises.readFile(typedArgs.privateKeyPath, "utf-8") : typedArgs.privateKeyContent;
|
|
45121
45296
|
const formData = new FormData();
|
|
45122
45297
|
formData.append("privateKey", new Blob([keyContent]), "key");
|
|
45123
45298
|
const api = TeamCityAPI.getInstance();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daghis/teamcity-mcp",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.12",
|
|
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",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"dotenv": "17.4.2",
|
|
71
71
|
"tslib": "^2.8.1",
|
|
72
72
|
"winston": "^3.11.0",
|
|
73
|
-
"zod": "^4.
|
|
73
|
+
"zod": "^4.6.6"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@esbuild-plugins/tsconfig-paths": "^0.1.2",
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
"js-yaml": "$js-yaml"
|
|
105
105
|
},
|
|
106
106
|
"axios": "^1.20.0",
|
|
107
|
-
"body-parser": "^2.
|
|
107
|
+
"body-parser": "^2.4.0",
|
|
108
108
|
"follow-redirects": "^1.16.1",
|
|
109
109
|
"glob@>=10.2.0 <10.5.0": "^10.5.0",
|
|
110
110
|
"hono": "^4.12.14",
|
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.12",
|
|
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.12",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"runtimeArguments": [
|
|
19
19
|
{
|