@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 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
- let buildData;
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 != null && typeof error3 === "object" && "response" in error3 && error3.response?.status === 404) {
280
- throw new BuildNotFoundError(`Build not found: ${options.buildId ?? options.buildNumber}`);
267
+ if (!isAxios404(error3) || !options.buildId) {
268
+ this.handleBuildStatusError(error3, options);
281
269
  }
282
- if (error3 != null && typeof error3 === "object" && "response" in error3 && error3.response?.status === 403) {
283
- throw new BuildAccessDeniedError(
284
- `Access denied to build: ${options.buildId ?? options.buildNumber}`
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.0",
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": "^24.10.0",
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
- if (typed.includeQueueTotals) {
39237
- try {
39238
- const countResp = await adapter.modules.buildQueue.getAllQueuedBuilds(
39239
- void 0,
39240
- "count"
39241
- );
39242
- enrich.totalQueued = countResp.data.count;
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 (typed.includeQueueReason) {
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.0",
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": "^24.10.0",
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.0",
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.0",
16
+ "version": "1.12.1",
17
17
  "runtimeHint": "npx",
18
18
  "runtimeArguments": [
19
19
  {