@daghis/teamcity-mcp 1.6.0 → 1.7.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 +7 -0
- package/dist/index.js +103 -9
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.0](https://github.com/Daghis/teamcity-mcp/compare/v1.6.0...v1.7.0) (2025-09-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **tools:** add streaming mode to fetch_build_log ([#171](https://github.com/Daghis/teamcity-mcp/issues/171)) ([1abce69](https://github.com/Daghis/teamcity-mcp/commit/1abce69b7fa3866ac289501e2369d5d06d57d57f))
|
|
9
|
+
|
|
3
10
|
## [1.6.0](https://github.com/Daghis/teamcity-mcp/compare/v1.5.0...v1.6.0) (2025-09-20)
|
|
4
11
|
|
|
5
12
|
|
package/dist/index.js
CHANGED
|
@@ -2117,6 +2117,7 @@ function createAdapterFromTeamCityAPI(api, options = {}) {
|
|
|
2117
2117
|
builds: buildApi,
|
|
2118
2118
|
listBuildArtifacts: (buildId, options2) => api.listBuildArtifacts(buildId, options2),
|
|
2119
2119
|
downloadArtifactContent: (buildId, artifactPath, requestOptions) => api.downloadBuildArtifact(buildId, artifactPath, requestOptions),
|
|
2120
|
+
downloadBuildLogContent: (buildId, requestOptions) => api.downloadBuildLog(buildId, requestOptions),
|
|
2120
2121
|
getBuildStatistics: (buildId, fields) => api.getBuildStatistics(buildId, fields),
|
|
2121
2122
|
listChangesForBuild: (buildId, fields) => api.listChangesForBuild(buildId, fields),
|
|
2122
2123
|
listSnapshotDependencies: (buildId) => api.listSnapshotDependencies(buildId),
|
|
@@ -36696,6 +36697,26 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
36696
36697
|
requestOptions
|
|
36697
36698
|
);
|
|
36698
36699
|
}
|
|
36700
|
+
async downloadBuildLog(buildId, options) {
|
|
36701
|
+
const rawParams = options?.params ?? void 0;
|
|
36702
|
+
const params = rawParams ? { ...rawParams } : {};
|
|
36703
|
+
if (!Object.prototype.hasOwnProperty.call(params, "plain")) {
|
|
36704
|
+
params["plain"] = true;
|
|
36705
|
+
}
|
|
36706
|
+
const rawHeaders = options?.headers ?? void 0;
|
|
36707
|
+
const headers = rawHeaders ? { ...rawHeaders } : {};
|
|
36708
|
+
const requestOptions = {
|
|
36709
|
+
...options,
|
|
36710
|
+
params,
|
|
36711
|
+
headers: {
|
|
36712
|
+
Accept: "text/plain",
|
|
36713
|
+
...headers
|
|
36714
|
+
},
|
|
36715
|
+
responseType: options?.responseType ?? "text",
|
|
36716
|
+
transformResponse: options?.transformResponse ?? [(data) => data]
|
|
36717
|
+
};
|
|
36718
|
+
return this.axiosInstance.get(`/app/rest/builds/id:${buildId}/log`, requestOptions);
|
|
36719
|
+
}
|
|
36699
36720
|
async getBuildStatistics(buildId, fields) {
|
|
36700
36721
|
return this.builds.getBuildStatisticValues(toBuildLocator(buildId), fields);
|
|
36701
36722
|
}
|
|
@@ -36764,6 +36785,7 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
36764
36785
|
};
|
|
36765
36786
|
|
|
36766
36787
|
// src/tools.ts
|
|
36788
|
+
var isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
36767
36789
|
function getMCPMode2() {
|
|
36768
36790
|
return getMCPMode();
|
|
36769
36791
|
}
|
|
@@ -37160,7 +37182,17 @@ var DEV_TOOLS = [
|
|
|
37160
37182
|
pageSize: { type: "number", description: "Lines per page (default 500)" },
|
|
37161
37183
|
startLine: { type: "number", description: "0-based start line (overrides page)" },
|
|
37162
37184
|
lineCount: { type: "number", description: "Max lines to return (overrides pageSize)" },
|
|
37163
|
-
tail: { type: "boolean", description: "Tail mode: return last N lines" }
|
|
37185
|
+
tail: { type: "boolean", description: "Tail mode: return last N lines" },
|
|
37186
|
+
encoding: {
|
|
37187
|
+
type: "string",
|
|
37188
|
+
description: "Response encoding: 'text' (default) or 'stream'",
|
|
37189
|
+
enum: ["text", "stream"],
|
|
37190
|
+
default: "text"
|
|
37191
|
+
},
|
|
37192
|
+
outputPath: {
|
|
37193
|
+
type: "string",
|
|
37194
|
+
description: "Optional absolute path to write streamed logs; defaults to a temp file when streaming"
|
|
37195
|
+
}
|
|
37164
37196
|
},
|
|
37165
37197
|
required: []
|
|
37166
37198
|
},
|
|
@@ -37173,9 +37205,24 @@ var DEV_TOOLS = [
|
|
|
37173
37205
|
pageSize: import_zod4.z.number().int().min(1).max(5e3).optional(),
|
|
37174
37206
|
startLine: import_zod4.z.number().int().min(0).optional(),
|
|
37175
37207
|
lineCount: import_zod4.z.number().int().min(1).max(5e3).optional(),
|
|
37176
|
-
tail: import_zod4.z.boolean().optional()
|
|
37177
|
-
|
|
37178
|
-
|
|
37208
|
+
tail: import_zod4.z.boolean().optional(),
|
|
37209
|
+
encoding: import_zod4.z.enum(["text", "stream"]).default("text"),
|
|
37210
|
+
outputPath: import_zod4.z.string().min(1).optional()
|
|
37211
|
+
}).superRefine((value, ctx) => {
|
|
37212
|
+
if (!value.buildId && typeof value.buildNumber === "undefined") {
|
|
37213
|
+
ctx.addIssue({
|
|
37214
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
37215
|
+
message: "Provide either buildId or buildNumber",
|
|
37216
|
+
path: ["buildId"]
|
|
37217
|
+
});
|
|
37218
|
+
}
|
|
37219
|
+
if (value.encoding === "stream" && value.tail) {
|
|
37220
|
+
ctx.addIssue({
|
|
37221
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
37222
|
+
message: "Streaming mode does not support tail queries",
|
|
37223
|
+
path: ["tail"]
|
|
37224
|
+
});
|
|
37225
|
+
}
|
|
37179
37226
|
});
|
|
37180
37227
|
return runTool(
|
|
37181
37228
|
"fetch_build_log",
|
|
@@ -37252,10 +37299,22 @@ var DEV_TOOLS = [
|
|
|
37252
37299
|
}
|
|
37253
37300
|
return false;
|
|
37254
37301
|
};
|
|
37302
|
+
const normalizeError = (error2) => {
|
|
37303
|
+
if ((0, import_axios35.isAxiosError)(error2)) {
|
|
37304
|
+
const status = error2.response?.status;
|
|
37305
|
+
const statusText = (error2.response?.statusText ?? "").trim();
|
|
37306
|
+
const base = status ? `${status}${statusText ? ` ${statusText}` : ""}` : error2.message;
|
|
37307
|
+
return new Error(base || "Request failed");
|
|
37308
|
+
}
|
|
37309
|
+
if (error2 instanceof Error) {
|
|
37310
|
+
return error2;
|
|
37311
|
+
}
|
|
37312
|
+
return new Error(String(error2));
|
|
37313
|
+
};
|
|
37255
37314
|
const wait = (ms) => new Promise((resolve) => {
|
|
37256
37315
|
setTimeout(resolve, ms);
|
|
37257
37316
|
});
|
|
37258
|
-
const
|
|
37317
|
+
const attemptBuffered = async () => {
|
|
37259
37318
|
if (typed.tail) {
|
|
37260
37319
|
const count = typed.lineCount ?? typed.pageSize ?? 500;
|
|
37261
37320
|
const full = await adapter.getBuildLog(effectiveBuildId);
|
|
@@ -37303,16 +37362,52 @@ var DEV_TOOLS = [
|
|
|
37303
37362
|
}
|
|
37304
37363
|
});
|
|
37305
37364
|
};
|
|
37306
|
-
const
|
|
37365
|
+
const attemptStream = async () => {
|
|
37366
|
+
const effectivePageSize = typed.lineCount ?? typed.pageSize ?? 500;
|
|
37367
|
+
const startLine = typeof typed.startLine === "number" ? typed.startLine : ((typed.page ?? 1) - 1) * effectivePageSize;
|
|
37368
|
+
const response = await adapter.downloadBuildLogContent(effectiveBuildId, {
|
|
37369
|
+
params: {
|
|
37370
|
+
start: startLine,
|
|
37371
|
+
count: effectivePageSize
|
|
37372
|
+
},
|
|
37373
|
+
responseType: "stream"
|
|
37374
|
+
});
|
|
37375
|
+
const stream = response.data;
|
|
37376
|
+
if (!isReadableStream(stream)) {
|
|
37377
|
+
throw new Error("Streaming log download did not return a readable stream");
|
|
37378
|
+
}
|
|
37379
|
+
const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
|
|
37380
|
+
const defaultFileName = `build-log-${safeBuildId}-${startLine}-${(0, import_node_crypto.randomUUID)()}.log`;
|
|
37381
|
+
const targetPath = typed.outputPath ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), defaultFileName);
|
|
37382
|
+
await import_node_fs.promises.mkdir((0, import_node_path.dirname)(targetPath), { recursive: true });
|
|
37383
|
+
await (0, import_promises.pipeline)(stream, (0, import_node_fs.createWriteStream)(targetPath));
|
|
37384
|
+
const stats = await import_node_fs.promises.stat(targetPath);
|
|
37385
|
+
const page = Math.floor(startLine / effectivePageSize) + 1;
|
|
37386
|
+
return json({
|
|
37387
|
+
encoding: "stream",
|
|
37388
|
+
outputPath: targetPath,
|
|
37389
|
+
bytesWritten: stats.size,
|
|
37390
|
+
meta: {
|
|
37391
|
+
buildId: effectiveBuildId,
|
|
37392
|
+
buildNumber: typeof typed.buildNumber !== "undefined" ? String(typed.buildNumber) : void 0,
|
|
37393
|
+
buildTypeId: typed.buildTypeId,
|
|
37394
|
+
page,
|
|
37395
|
+
pageSize: effectivePageSize,
|
|
37396
|
+
startLine
|
|
37397
|
+
}
|
|
37398
|
+
});
|
|
37399
|
+
};
|
|
37400
|
+
const isStream = typed.encoding === "stream";
|
|
37401
|
+
const maxAttempts = isStream ? 3 : typed.tail ? 5 : 3;
|
|
37307
37402
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
37308
37403
|
try {
|
|
37309
|
-
return await
|
|
37404
|
+
return await (isStream ? attemptStream() : attemptBuffered());
|
|
37310
37405
|
} catch (error2) {
|
|
37311
37406
|
if (shouldRetry2(error2) && attempt < maxAttempts - 1) {
|
|
37312
37407
|
await wait(500 * (attempt + 1));
|
|
37313
37408
|
continue;
|
|
37314
37409
|
}
|
|
37315
|
-
throw error2;
|
|
37410
|
+
throw normalizeError(error2);
|
|
37316
37411
|
}
|
|
37317
37412
|
}
|
|
37318
37413
|
throw new Error("Unable to fetch build log after retries");
|
|
@@ -38321,7 +38416,6 @@ var DEV_TOOLS = [
|
|
|
38321
38416
|
maxSize: import_zod4.z.number().int().positive().optional(),
|
|
38322
38417
|
outputPath: import_zod4.z.string().min(1).optional()
|
|
38323
38418
|
});
|
|
38324
|
-
const isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
38325
38419
|
const toTempFilePath = (artifactName) => {
|
|
38326
38420
|
const base = (0, import_node_path.basename)(artifactName || "artifact");
|
|
38327
38421
|
const safeStem = base.replace(/[^a-zA-Z0-9._-]/g, "_") || "artifact";
|