@daghis/teamcity-mcp 1.12.0 → 1.12.1
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 +118 -45
- package/package.json +2 -2
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.12.1](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.12.0...teamcity-mcp-v1.12.1) (2025-12-22)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* handle queued builds in get_build and get_build_status ([#324](https://github.com/Daghis/teamcity-mcp/issues/324)) ([4cb2dab](https://github.com/Daghis/teamcity-mcp/commit/4cb2dabfc9161e07708a899622d78b886742459d))
|
|
9
|
+
|
|
3
10
|
## [1.12.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.11.20...teamcity-mcp-v1.12.0) (2025-12-06)
|
|
4
11
|
|
|
5
12
|
|
package/dist/index.js
CHANGED
|
@@ -221,6 +221,12 @@ var build_status_manager_exports = {};
|
|
|
221
221
|
__export(build_status_manager_exports, {
|
|
222
222
|
BuildStatusManager: () => BuildStatusManager
|
|
223
223
|
});
|
|
224
|
+
function isAxios404(error3) {
|
|
225
|
+
return error3 != null && typeof error3 === "object" && "response" in error3 && error3.response?.status === 404;
|
|
226
|
+
}
|
|
227
|
+
function isAxios403(error3) {
|
|
228
|
+
return error3 != null && typeof error3 === "object" && "response" in error3 && error3.response?.status === 403;
|
|
229
|
+
}
|
|
224
230
|
var BuildStatusManager;
|
|
225
231
|
var init_build_status_manager = __esm({
|
|
226
232
|
"src/teamcity/build-status-manager.ts"() {
|
|
@@ -235,7 +241,11 @@ var init_build_status_manager = __esm({
|
|
|
235
241
|
this.cache = /* @__PURE__ */ new Map();
|
|
236
242
|
}
|
|
237
243
|
/**
|
|
238
|
-
* Get build status by ID or number
|
|
244
|
+
* Get build status by ID or number.
|
|
245
|
+
* Uses 3-step fallback to handle builds in queue and race conditions:
|
|
246
|
+
* 1. Try builds endpoint
|
|
247
|
+
* 2. On 404, try build queue (build may be queued)
|
|
248
|
+
* 3. On 404 again, retry builds endpoint (build may have left queue between checks)
|
|
239
249
|
*/
|
|
240
250
|
async getBuildStatus(options) {
|
|
241
251
|
if (!options.buildId && !options.buildNumber) {
|
|
@@ -252,40 +262,92 @@ var init_build_status_manager = __esm({
|
|
|
252
262
|
}
|
|
253
263
|
}
|
|
254
264
|
try {
|
|
255
|
-
|
|
256
|
-
if (options.buildId) {
|
|
257
|
-
const response = await this.client.builds.getBuild(
|
|
258
|
-
`id:${options.buildId}`,
|
|
259
|
-
this.getFieldSelection(options)
|
|
260
|
-
);
|
|
261
|
-
buildData = response.data;
|
|
262
|
-
} else {
|
|
263
|
-
const locator = this.buildLocator(options);
|
|
264
|
-
const response = await this.client.builds.getBuild(
|
|
265
|
-
locator,
|
|
266
|
-
this.getFieldSelection(options)
|
|
267
|
-
);
|
|
268
|
-
buildData = response.data;
|
|
269
|
-
}
|
|
270
|
-
if (buildData == null) {
|
|
271
|
-
throw new BuildNotFoundError("Build data is undefined");
|
|
272
|
-
}
|
|
273
|
-
const result = this.transformBuildResponse(buildData, options);
|
|
274
|
-
if (result.state === "finished" || result.state === "canceled") {
|
|
275
|
-
this.setCachedResult(cacheKey, result);
|
|
276
|
-
}
|
|
277
|
-
return result;
|
|
265
|
+
return await this.getBuildStatusFromBuildsEndpoint(options, cacheKey);
|
|
278
266
|
} catch (error3) {
|
|
279
|
-
if (error3
|
|
280
|
-
|
|
267
|
+
if (!isAxios404(error3) || !options.buildId) {
|
|
268
|
+
this.handleBuildStatusError(error3, options);
|
|
281
269
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
270
|
+
}
|
|
271
|
+
const buildId = options.buildId;
|
|
272
|
+
try {
|
|
273
|
+
return await this.getQueuedBuildStatus(buildId);
|
|
274
|
+
} catch (queueError) {
|
|
275
|
+
if (!isAxios404(queueError)) {
|
|
276
|
+
this.handleBuildStatusError(queueError, options);
|
|
286
277
|
}
|
|
287
|
-
throw error3;
|
|
288
278
|
}
|
|
279
|
+
try {
|
|
280
|
+
return await this.getBuildStatusFromBuildsEndpoint(options, cacheKey);
|
|
281
|
+
} catch (error3) {
|
|
282
|
+
this.handleBuildStatusError(error3, options);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Get build status from the builds endpoint
|
|
287
|
+
*/
|
|
288
|
+
async getBuildStatusFromBuildsEndpoint(options, cacheKey) {
|
|
289
|
+
let buildData;
|
|
290
|
+
if (options.buildId) {
|
|
291
|
+
const response = await this.client.builds.getBuild(
|
|
292
|
+
`id:${options.buildId}`,
|
|
293
|
+
this.getFieldSelection(options)
|
|
294
|
+
);
|
|
295
|
+
buildData = response.data;
|
|
296
|
+
} else {
|
|
297
|
+
const locator = this.buildLocator(options);
|
|
298
|
+
const response = await this.client.builds.getBuild(locator, this.getFieldSelection(options));
|
|
299
|
+
buildData = response.data;
|
|
300
|
+
}
|
|
301
|
+
if (buildData == null) {
|
|
302
|
+
throw new BuildNotFoundError("Build data is undefined");
|
|
303
|
+
}
|
|
304
|
+
const result = this.transformBuildResponse(buildData, options);
|
|
305
|
+
if (result.state === "finished" || result.state === "canceled") {
|
|
306
|
+
this.setCachedResult(cacheKey, result);
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get build status from the build queue
|
|
312
|
+
*/
|
|
313
|
+
async getQueuedBuildStatus(buildId) {
|
|
314
|
+
const response = await this.client.modules.buildQueue.getQueuedBuild(
|
|
315
|
+
`id:${buildId}`,
|
|
316
|
+
"id,number,state,status,buildTypeId,branchName,webUrl,queuedDate,waitReason"
|
|
317
|
+
);
|
|
318
|
+
const queuedBuild = response.data;
|
|
319
|
+
if (queuedBuild == null) {
|
|
320
|
+
throw new BuildNotFoundError("Queued build data is undefined");
|
|
321
|
+
}
|
|
322
|
+
const result = {
|
|
323
|
+
buildId: String(queuedBuild.id),
|
|
324
|
+
buildNumber: queuedBuild.number,
|
|
325
|
+
buildTypeId: queuedBuild.buildTypeId,
|
|
326
|
+
state: "queued",
|
|
327
|
+
status: void 0,
|
|
328
|
+
percentageComplete: 0,
|
|
329
|
+
branchName: queuedBuild.branchName,
|
|
330
|
+
webUrl: queuedBuild.webUrl,
|
|
331
|
+
waitReason: queuedBuild.waitReason
|
|
332
|
+
};
|
|
333
|
+
if (queuedBuild.queuedDate) {
|
|
334
|
+
result.queuedDate = this.parseDate(queuedBuild.queuedDate);
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Handle and re-throw build status errors with appropriate error types
|
|
340
|
+
*/
|
|
341
|
+
handleBuildStatusError(error3, options) {
|
|
342
|
+
if (isAxios404(error3)) {
|
|
343
|
+
throw new BuildNotFoundError(`Build not found: ${options.buildId ?? options.buildNumber}`);
|
|
344
|
+
}
|
|
345
|
+
if (isAxios403(error3)) {
|
|
346
|
+
throw new BuildAccessDeniedError(
|
|
347
|
+
`Access denied to build: ${options.buildId ?? options.buildNumber}`
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
throw error3;
|
|
289
351
|
}
|
|
290
352
|
/**
|
|
291
353
|
* Get build status using custom locator
|
|
@@ -954,7 +1016,7 @@ function debug2(message, meta) {
|
|
|
954
1016
|
// package.json
|
|
955
1017
|
var package_default = {
|
|
956
1018
|
name: "@daghis/teamcity-mcp",
|
|
957
|
-
version: "1.12.
|
|
1019
|
+
version: "1.12.1",
|
|
958
1020
|
description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
|
|
959
1021
|
mcpName: "io.github.Daghis/teamcity",
|
|
960
1022
|
main: "dist/index.js",
|
|
@@ -1040,7 +1102,7 @@ var package_default = {
|
|
|
1040
1102
|
"@types/jest": "^30.0.0",
|
|
1041
1103
|
"@types/js-yaml": "^4.0.9",
|
|
1042
1104
|
"@types/morgan": "^1.9.9",
|
|
1043
|
-
"@types/node": "^
|
|
1105
|
+
"@types/node": "^25.0.2",
|
|
1044
1106
|
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
1045
1107
|
"@typescript-eslint/parser": "^8.46.3",
|
|
1046
1108
|
"axios-retry": "^4.5.0",
|
|
@@ -38578,6 +38640,7 @@ var TeamCityAPI = class _TeamCityAPI {
|
|
|
38578
38640
|
|
|
38579
38641
|
// src/tools.ts
|
|
38580
38642
|
var isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
|
|
38643
|
+
var isAxios4042 = (error3) => (0, import_axios36.isAxiosError)(error3) && error3.response?.status === 404;
|
|
38581
38644
|
var sanitizeFileName = (artifactName) => {
|
|
38582
38645
|
const base = (0, import_node_path.basename)(artifactName || "artifact");
|
|
38583
38646
|
const safeBase = base.replace(/[^a-zA-Z0-9._-]/g, "_") || "artifact";
|
|
@@ -39001,7 +39064,7 @@ var DEV_TOOLS = [
|
|
|
39001
39064
|
},
|
|
39002
39065
|
{
|
|
39003
39066
|
name: "get_build",
|
|
39004
|
-
description: "Get details of a specific build",
|
|
39067
|
+
description: "Get details of a specific build (works for both queued and running/finished builds)",
|
|
39005
39068
|
inputSchema: {
|
|
39006
39069
|
type: "object",
|
|
39007
39070
|
properties: {
|
|
@@ -39016,6 +39079,18 @@ var DEV_TOOLS = [
|
|
|
39016
39079
|
schema,
|
|
39017
39080
|
async (typed) => {
|
|
39018
39081
|
const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
|
|
39082
|
+
try {
|
|
39083
|
+
const build2 = await adapter.getBuild(typed.buildId);
|
|
39084
|
+
return json(build2);
|
|
39085
|
+
} catch (error3) {
|
|
39086
|
+
if (!isAxios4042(error3)) throw error3;
|
|
39087
|
+
}
|
|
39088
|
+
try {
|
|
39089
|
+
const qb = await adapter.modules.buildQueue.getQueuedBuild(`id:${typed.buildId}`);
|
|
39090
|
+
return json({ ...qb.data, state: "queued" });
|
|
39091
|
+
} catch (queueError) {
|
|
39092
|
+
if (!isAxios4042(queueError)) throw queueError;
|
|
39093
|
+
}
|
|
39019
39094
|
const build = await adapter.getBuild(typed.buildId);
|
|
39020
39095
|
return json(build);
|
|
39021
39096
|
},
|
|
@@ -39233,21 +39308,19 @@ var DEV_TOOLS = [
|
|
|
39233
39308
|
if (typeof result.queuePosition === "number") {
|
|
39234
39309
|
enrich.canMoveToTop = result.queuePosition > 1;
|
|
39235
39310
|
}
|
|
39236
|
-
|
|
39237
|
-
|
|
39238
|
-
|
|
39239
|
-
|
|
39240
|
-
|
|
39241
|
-
|
|
39242
|
-
|
|
39243
|
-
} catch {
|
|
39244
|
-
}
|
|
39311
|
+
try {
|
|
39312
|
+
const countResp = await adapter.modules.buildQueue.getAllQueuedBuilds(
|
|
39313
|
+
void 0,
|
|
39314
|
+
"count"
|
|
39315
|
+
);
|
|
39316
|
+
enrich.totalQueued = countResp.data.count;
|
|
39317
|
+
} catch {
|
|
39245
39318
|
}
|
|
39246
|
-
if (
|
|
39319
|
+
if (!result.waitReason) {
|
|
39247
39320
|
try {
|
|
39248
39321
|
const targetBuildId = typed.buildId ?? result.buildId;
|
|
39249
39322
|
if (targetBuildId) {
|
|
39250
|
-
const qb = await adapter.modules.buildQueue.getQueuedBuild(targetBuildId);
|
|
39323
|
+
const qb = await adapter.modules.buildQueue.getQueuedBuild(`id:${targetBuildId}`);
|
|
39251
39324
|
enrich.waitReason = qb.data.waitReason;
|
|
39252
39325
|
}
|
|
39253
39326
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daghis/teamcity-mcp",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.1",
|
|
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",
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@types/jest": "^30.0.0",
|
|
87
87
|
"@types/js-yaml": "^4.0.9",
|
|
88
88
|
"@types/morgan": "^1.9.9",
|
|
89
|
-
"@types/node": "^
|
|
89
|
+
"@types/node": "^25.0.2",
|
|
90
90
|
"@typescript-eslint/eslint-plugin": "^8.46.3",
|
|
91
91
|
"@typescript-eslint/parser": "^8.46.3",
|
|
92
92
|
"axios-retry": "^4.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": "1.12.
|
|
10
|
+
"version": "1.12.1",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
15
15
|
"identifier": "@daghis/teamcity-mcp",
|
|
16
|
-
"version": "1.12.
|
|
16
|
+
"version": "1.12.1",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"runtimeArguments": [
|
|
19
19
|
{
|