@daghis/teamcity-mcp 1.6.0 → 1.8.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/dist/index.js +126 -12
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.0](https://github.com/Daghis/teamcity-mcp/compare/v1.7.0...v1.8.0) (2025-09-20)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **tools:** support streaming artifacts in get_build_results ([#173](https://github.com/Daghis/teamcity-mcp/issues/173)) ([f95c5e4](https://github.com/Daghis/teamcity-mcp/commit/f95c5e4da9ffa80adc8b8f322c127a440c4524b5)), closes [#169](https://github.com/Daghis/teamcity-mcp/issues/169)
|
|
9
|
+
|
|
10
|
+
## [1.7.0](https://github.com/Daghis/teamcity-mcp/compare/v1.6.0...v1.7.0) (2025-09-20)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **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))
|
|
16
|
+
|
|
3
17
|
## [1.6.0](https://github.com/Daghis/teamcity-mcp/compare/v1.5.0...v1.6.0) (2025-09-20)
|
|
4
18
|
|
|
5
19
|
|
package/dist/index.js
CHANGED
|
@@ -1771,6 +1771,7 @@ var BuildResultsManager = class _BuildResultsManager {
|
|
|
1771
1771
|
*/
|
|
1772
1772
|
async fetchArtifacts(buildId, options) {
|
|
1773
1773
|
try {
|
|
1774
|
+
const encoding = options.artifactEncoding ?? "base64";
|
|
1774
1775
|
const response = await this.client.modules.builds.getFilesListOfBuild(
|
|
1775
1776
|
toBuildLocator(buildId)
|
|
1776
1777
|
);
|
|
@@ -1783,6 +1784,7 @@ var BuildResultsManager = class _BuildResultsManager {
|
|
|
1783
1784
|
artifacts.map(async (artifact) => {
|
|
1784
1785
|
const artifactPath = artifact.fullName ?? artifact.name;
|
|
1785
1786
|
const downloadHref = artifact.content?.href ?? `/app/rest/builds/id:${buildId}/artifacts/content/${artifactPath}`;
|
|
1787
|
+
const shouldInlineContent = encoding === "base64" && (options.downloadArtifacts?.length ? options.downloadArtifacts.includes(artifact.name) || options.downloadArtifacts.includes(artifactPath) : true);
|
|
1786
1788
|
const artifactData = {
|
|
1787
1789
|
name: artifact.name,
|
|
1788
1790
|
path: artifactPath,
|
|
@@ -1790,7 +1792,7 @@ var BuildResultsManager = class _BuildResultsManager {
|
|
|
1790
1792
|
modificationTime: artifact.modificationTime ?? "",
|
|
1791
1793
|
downloadUrl: this.buildAbsoluteUrl(downloadHref)
|
|
1792
1794
|
};
|
|
1793
|
-
if (
|
|
1795
|
+
if (shouldInlineContent) {
|
|
1794
1796
|
const maxSize = options.maxArtifactSize ?? _BuildResultsManager.defaultMaxArtifactSize;
|
|
1795
1797
|
if ((artifact.size ?? 0) <= maxSize) {
|
|
1796
1798
|
try {
|
|
@@ -1799,6 +1801,16 @@ var BuildResultsManager = class _BuildResultsManager {
|
|
|
1799
1801
|
} catch (err) {
|
|
1800
1802
|
}
|
|
1801
1803
|
}
|
|
1804
|
+
} else if (encoding === "stream") {
|
|
1805
|
+
artifactData.downloadHandle = {
|
|
1806
|
+
tool: "download_build_artifact",
|
|
1807
|
+
args: {
|
|
1808
|
+
buildId,
|
|
1809
|
+
artifactPath,
|
|
1810
|
+
encoding: "stream",
|
|
1811
|
+
...options.maxArtifactSize ? { maxSize: options.maxArtifactSize } : {}
|
|
1812
|
+
}
|
|
1813
|
+
};
|
|
1802
1814
|
}
|
|
1803
1815
|
return artifactData;
|
|
1804
1816
|
})
|
|
@@ -2117,6 +2129,7 @@ function createAdapterFromTeamCityAPI(api, options = {}) {
|
|
|
2117
2129
|
builds: buildApi,
|
|
2118
2130
|
listBuildArtifacts: (buildId, options2) => api.listBuildArtifacts(buildId, options2),
|
|
2119
2131
|
downloadArtifactContent: (buildId, artifactPath, requestOptions) => api.downloadBuildArtifact(buildId, artifactPath, requestOptions),
|
|
2132
|
+
downloadBuildLogContent: (buildId, requestOptions) => api.downloadBuildLog(buildId, requestOptions),
|
|
2120
2133
|
getBuildStatistics: (buildId, fields) => api.getBuildStatistics(buildId, fields),
|
|
2121
2134
|
listChangesForBuild: (buildId, fields) => api.listChangesForBuild(buildId, fields),
|
|
2122
2135
|
listSnapshotDependencies: (buildId) => api.listSnapshotDependencies(buildId),
|
|
@@ -36696,6 +36709,26 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
36696
36709
|
requestOptions
|
|
36697
36710
|
);
|
|
36698
36711
|
}
|
|
36712
|
+
async downloadBuildLog(buildId, options) {
|
|
36713
|
+
const rawParams = options?.params ?? void 0;
|
|
36714
|
+
const params = rawParams ? { ...rawParams } : {};
|
|
36715
|
+
if (!Object.prototype.hasOwnProperty.call(params, "plain")) {
|
|
36716
|
+
params["plain"] = true;
|
|
36717
|
+
}
|
|
36718
|
+
const rawHeaders = options?.headers ?? void 0;
|
|
36719
|
+
const headers = rawHeaders ? { ...rawHeaders } : {};
|
|
36720
|
+
const requestOptions = {
|
|
36721
|
+
...options,
|
|
36722
|
+
params,
|
|
36723
|
+
headers: {
|
|
36724
|
+
Accept: "text/plain",
|
|
36725
|
+
...headers
|
|
36726
|
+
},
|
|
36727
|
+
responseType: options?.responseType ?? "text",
|
|
36728
|
+
transformResponse: options?.transformResponse ?? [(data) => data]
|
|
36729
|
+
};
|
|
36730
|
+
return this.axiosInstance.get(`/app/rest/builds/id:${buildId}/log`, requestOptions);
|
|
36731
|
+
}
|
|
36699
36732
|
async getBuildStatistics(buildId, fields) {
|
|
36700
36733
|
return this.builds.getBuildStatisticValues(toBuildLocator(buildId), fields);
|
|
36701
36734
|
}
|
|
@@ -36764,6 +36797,7 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
36764
36797
|
};
|
|
36765
36798
|
|
|
36766
36799
|
// src/tools.ts
|
|
36800
|
+
var isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
36767
36801
|
function getMCPMode2() {
|
|
36768
36802
|
return getMCPMode();
|
|
36769
36803
|
}
|
|
@@ -37160,7 +37194,17 @@ var DEV_TOOLS = [
|
|
|
37160
37194
|
pageSize: { type: "number", description: "Lines per page (default 500)" },
|
|
37161
37195
|
startLine: { type: "number", description: "0-based start line (overrides page)" },
|
|
37162
37196
|
lineCount: { type: "number", description: "Max lines to return (overrides pageSize)" },
|
|
37163
|
-
tail: { type: "boolean", description: "Tail mode: return last N lines" }
|
|
37197
|
+
tail: { type: "boolean", description: "Tail mode: return last N lines" },
|
|
37198
|
+
encoding: {
|
|
37199
|
+
type: "string",
|
|
37200
|
+
description: "Response encoding: 'text' (default) or 'stream'",
|
|
37201
|
+
enum: ["text", "stream"],
|
|
37202
|
+
default: "text"
|
|
37203
|
+
},
|
|
37204
|
+
outputPath: {
|
|
37205
|
+
type: "string",
|
|
37206
|
+
description: "Optional absolute path to write streamed logs; defaults to a temp file when streaming"
|
|
37207
|
+
}
|
|
37164
37208
|
},
|
|
37165
37209
|
required: []
|
|
37166
37210
|
},
|
|
@@ -37173,9 +37217,24 @@ var DEV_TOOLS = [
|
|
|
37173
37217
|
pageSize: import_zod4.z.number().int().min(1).max(5e3).optional(),
|
|
37174
37218
|
startLine: import_zod4.z.number().int().min(0).optional(),
|
|
37175
37219
|
lineCount: import_zod4.z.number().int().min(1).max(5e3).optional(),
|
|
37176
|
-
tail: import_zod4.z.boolean().optional()
|
|
37177
|
-
|
|
37178
|
-
|
|
37220
|
+
tail: import_zod4.z.boolean().optional(),
|
|
37221
|
+
encoding: import_zod4.z.enum(["text", "stream"]).default("text"),
|
|
37222
|
+
outputPath: import_zod4.z.string().min(1).optional()
|
|
37223
|
+
}).superRefine((value, ctx) => {
|
|
37224
|
+
if (!value.buildId && typeof value.buildNumber === "undefined") {
|
|
37225
|
+
ctx.addIssue({
|
|
37226
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
37227
|
+
message: "Provide either buildId or buildNumber",
|
|
37228
|
+
path: ["buildId"]
|
|
37229
|
+
});
|
|
37230
|
+
}
|
|
37231
|
+
if (value.encoding === "stream" && value.tail) {
|
|
37232
|
+
ctx.addIssue({
|
|
37233
|
+
code: import_zod4.z.ZodIssueCode.custom,
|
|
37234
|
+
message: "Streaming mode does not support tail queries",
|
|
37235
|
+
path: ["tail"]
|
|
37236
|
+
});
|
|
37237
|
+
}
|
|
37179
37238
|
});
|
|
37180
37239
|
return runTool(
|
|
37181
37240
|
"fetch_build_log",
|
|
@@ -37252,10 +37311,22 @@ var DEV_TOOLS = [
|
|
|
37252
37311
|
}
|
|
37253
37312
|
return false;
|
|
37254
37313
|
};
|
|
37314
|
+
const normalizeError = (error2) => {
|
|
37315
|
+
if ((0, import_axios35.isAxiosError)(error2)) {
|
|
37316
|
+
const status = error2.response?.status;
|
|
37317
|
+
const statusText = (error2.response?.statusText ?? "").trim();
|
|
37318
|
+
const base = status ? `${status}${statusText ? ` ${statusText}` : ""}` : error2.message;
|
|
37319
|
+
return new Error(base || "Request failed");
|
|
37320
|
+
}
|
|
37321
|
+
if (error2 instanceof Error) {
|
|
37322
|
+
return error2;
|
|
37323
|
+
}
|
|
37324
|
+
return new Error(String(error2));
|
|
37325
|
+
};
|
|
37255
37326
|
const wait = (ms) => new Promise((resolve) => {
|
|
37256
37327
|
setTimeout(resolve, ms);
|
|
37257
37328
|
});
|
|
37258
|
-
const
|
|
37329
|
+
const attemptBuffered = async () => {
|
|
37259
37330
|
if (typed.tail) {
|
|
37260
37331
|
const count = typed.lineCount ?? typed.pageSize ?? 500;
|
|
37261
37332
|
const full = await adapter.getBuildLog(effectiveBuildId);
|
|
@@ -37303,16 +37374,52 @@ var DEV_TOOLS = [
|
|
|
37303
37374
|
}
|
|
37304
37375
|
});
|
|
37305
37376
|
};
|
|
37306
|
-
const
|
|
37377
|
+
const attemptStream = async () => {
|
|
37378
|
+
const effectivePageSize = typed.lineCount ?? typed.pageSize ?? 500;
|
|
37379
|
+
const startLine = typeof typed.startLine === "number" ? typed.startLine : ((typed.page ?? 1) - 1) * effectivePageSize;
|
|
37380
|
+
const response = await adapter.downloadBuildLogContent(effectiveBuildId, {
|
|
37381
|
+
params: {
|
|
37382
|
+
start: startLine,
|
|
37383
|
+
count: effectivePageSize
|
|
37384
|
+
},
|
|
37385
|
+
responseType: "stream"
|
|
37386
|
+
});
|
|
37387
|
+
const stream = response.data;
|
|
37388
|
+
if (!isReadableStream(stream)) {
|
|
37389
|
+
throw new Error("Streaming log download did not return a readable stream");
|
|
37390
|
+
}
|
|
37391
|
+
const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
|
|
37392
|
+
const defaultFileName = `build-log-${safeBuildId}-${startLine}-${(0, import_node_crypto.randomUUID)()}.log`;
|
|
37393
|
+
const targetPath = typed.outputPath ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), defaultFileName);
|
|
37394
|
+
await import_node_fs.promises.mkdir((0, import_node_path.dirname)(targetPath), { recursive: true });
|
|
37395
|
+
await (0, import_promises.pipeline)(stream, (0, import_node_fs.createWriteStream)(targetPath));
|
|
37396
|
+
const stats = await import_node_fs.promises.stat(targetPath);
|
|
37397
|
+
const page = Math.floor(startLine / effectivePageSize) + 1;
|
|
37398
|
+
return json({
|
|
37399
|
+
encoding: "stream",
|
|
37400
|
+
outputPath: targetPath,
|
|
37401
|
+
bytesWritten: stats.size,
|
|
37402
|
+
meta: {
|
|
37403
|
+
buildId: effectiveBuildId,
|
|
37404
|
+
buildNumber: typeof typed.buildNumber !== "undefined" ? String(typed.buildNumber) : void 0,
|
|
37405
|
+
buildTypeId: typed.buildTypeId,
|
|
37406
|
+
page,
|
|
37407
|
+
pageSize: effectivePageSize,
|
|
37408
|
+
startLine
|
|
37409
|
+
}
|
|
37410
|
+
});
|
|
37411
|
+
};
|
|
37412
|
+
const isStream = typed.encoding === "stream";
|
|
37413
|
+
const maxAttempts = isStream ? 3 : typed.tail ? 5 : 3;
|
|
37307
37414
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
37308
37415
|
try {
|
|
37309
|
-
return await
|
|
37416
|
+
return await (isStream ? attemptStream() : attemptBuffered());
|
|
37310
37417
|
} catch (error2) {
|
|
37311
37418
|
if (shouldRetry2(error2) && attempt < maxAttempts - 1) {
|
|
37312
37419
|
await wait(500 * (attempt + 1));
|
|
37313
37420
|
continue;
|
|
37314
37421
|
}
|
|
37315
|
-
throw error2;
|
|
37422
|
+
throw normalizeError(error2);
|
|
37316
37423
|
}
|
|
37317
37424
|
}
|
|
37318
37425
|
throw new Error("Unable to fetch build log after retries");
|
|
@@ -38254,6 +38361,12 @@ var DEV_TOOLS = [
|
|
|
38254
38361
|
maxArtifactSize: {
|
|
38255
38362
|
type: "number",
|
|
38256
38363
|
description: "Max artifact content size (bytes) when inlining"
|
|
38364
|
+
},
|
|
38365
|
+
artifactEncoding: {
|
|
38366
|
+
type: "string",
|
|
38367
|
+
description: "Encoding mode for artifacts when includeArtifacts is true",
|
|
38368
|
+
enum: ["base64", "stream"],
|
|
38369
|
+
default: "base64"
|
|
38257
38370
|
}
|
|
38258
38371
|
},
|
|
38259
38372
|
required: ["buildId"]
|
|
@@ -38266,7 +38379,8 @@ var DEV_TOOLS = [
|
|
|
38266
38379
|
includeChanges: import_zod4.z.boolean().optional(),
|
|
38267
38380
|
includeDependencies: import_zod4.z.boolean().optional(),
|
|
38268
38381
|
artifactFilter: import_zod4.z.string().min(1).optional(),
|
|
38269
|
-
maxArtifactSize: import_zod4.z.number().int().min(1).optional()
|
|
38382
|
+
maxArtifactSize: import_zod4.z.number().int().min(1).optional(),
|
|
38383
|
+
artifactEncoding: import_zod4.z.enum(["base64", "stream"]).default("base64")
|
|
38270
38384
|
});
|
|
38271
38385
|
return runTool(
|
|
38272
38386
|
"get_build_results",
|
|
@@ -38280,7 +38394,8 @@ var DEV_TOOLS = [
|
|
|
38280
38394
|
includeChanges: typed.includeChanges,
|
|
38281
38395
|
includeDependencies: typed.includeDependencies,
|
|
38282
38396
|
artifactFilter: typed.artifactFilter,
|
|
38283
|
-
maxArtifactSize: typed.maxArtifactSize
|
|
38397
|
+
maxArtifactSize: typed.maxArtifactSize,
|
|
38398
|
+
artifactEncoding: typed.artifactEncoding
|
|
38284
38399
|
});
|
|
38285
38400
|
return json(result);
|
|
38286
38401
|
},
|
|
@@ -38321,7 +38436,6 @@ var DEV_TOOLS = [
|
|
|
38321
38436
|
maxSize: import_zod4.z.number().int().positive().optional(),
|
|
38322
38437
|
outputPath: import_zod4.z.string().min(1).optional()
|
|
38323
38438
|
});
|
|
38324
|
-
const isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
38325
38439
|
const toTempFilePath = (artifactName) => {
|
|
38326
38440
|
const base = (0, import_node_path.basename)(artifactName || "artifact");
|
|
38327
38441
|
const safeStem = base.replace(/[^a-zA-Z0-9._-]/g, "_") || "artifact";
|