@daghis/teamcity-mcp 1.3.4 → 1.4.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.0](https://github.com/Daghis/teamcity-mcp/compare/v1.3.5...v1.4.0) (2025-09-20)
4
+
5
+
6
+ ### Features
7
+
8
+ * **tests:** add batched mcp tool execution ([#163](https://github.com/Daghis/teamcity-mcp/issues/163)) ([5f48060](https://github.com/Daghis/teamcity-mcp/commit/5f4806043b95686d5dac41a9d67515740b82a3f8)), closes [#162](https://github.com/Daghis/teamcity-mcp/issues/162)
9
+
10
+ ## [1.3.5](https://github.com/Daghis/teamcity-mcp/compare/v1.3.4...v1.3.5) (2025-09-19)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **tools:** repair manage_build_steps updates ([#154](https://github.com/Daghis/teamcity-mcp/issues/154)) ([b557e44](https://github.com/Daghis/teamcity-mcp/commit/b557e4424d5129de9d3b6e3240e1a876488da040))
16
+
3
17
  ## [1.3.4](https://github.com/Daghis/teamcity-mcp/compare/v1.3.3...v1.3.4) (2025-09-19)
4
18
 
5
19
 
package/dist/index.js CHANGED
@@ -1317,6 +1317,9 @@ var BuildConfigurationUpdateManager = class {
1317
1317
  }
1318
1318
  };
1319
1319
 
1320
+ // src/teamcity/utils/build-locator.ts
1321
+ var toBuildLocator = (buildId) => buildId.includes(":") ? buildId : `id:${buildId}`;
1322
+
1320
1323
  // src/teamcity/build-results-manager.ts
1321
1324
  var BuildResultsManager = class _BuildResultsManager {
1322
1325
  client;
@@ -1399,7 +1402,7 @@ var BuildResultsManager = class _BuildResultsManager {
1399
1402
  */
1400
1403
  async fetchBuildSummary(buildId) {
1401
1404
  const response = await this.client.modules.builds.getBuild(
1402
- this.toBuildLocator(buildId),
1405
+ toBuildLocator(buildId),
1403
1406
  _BuildResultsManager.fields
1404
1407
  );
1405
1408
  return response.data;
@@ -1455,7 +1458,7 @@ var BuildResultsManager = class _BuildResultsManager {
1455
1458
  async fetchArtifacts(buildId, options) {
1456
1459
  try {
1457
1460
  const response = await this.client.modules.builds.getFilesListOfBuild(
1458
- this.toBuildLocator(buildId)
1461
+ toBuildLocator(buildId)
1459
1462
  );
1460
1463
  const artifactListing = response.data;
1461
1464
  let artifacts = artifactListing.file ?? [];
@@ -1507,7 +1510,7 @@ var BuildResultsManager = class _BuildResultsManager {
1507
1510
  async fetchStatistics(buildId) {
1508
1511
  try {
1509
1512
  const response = await this.client.modules.builds.getBuildStatisticValues(
1510
- this.toBuildLocator(buildId)
1513
+ toBuildLocator(buildId)
1511
1514
  );
1512
1515
  const payload = response.data;
1513
1516
  const properties = payload.property ?? [];
@@ -1573,8 +1576,9 @@ var BuildResultsManager = class _BuildResultsManager {
1573
1576
  */
1574
1577
  async fetchDependencies(buildId) {
1575
1578
  try {
1576
- const response = await this.client.request(
1577
- (ctx) => ctx.axios.get(`${ctx.baseUrl}/app/rest/builds/id:${buildId}/snapshot-dependencies`)
1579
+ const response = await this.client.modules.builds.getAllBuilds(
1580
+ `snapshotDependency:(to:(id:${buildId}))`,
1581
+ "build(id,number,buildTypeId,status)"
1578
1582
  );
1579
1583
  const depsData = response.data;
1580
1584
  const builds = depsData.build ?? [];
@@ -1606,20 +1610,25 @@ var BuildResultsManager = class _BuildResultsManager {
1606
1610
  const baseUrl = this.client.getApiConfig().baseUrl;
1607
1611
  return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
1608
1612
  }
1609
- toBuildLocator(buildId) {
1610
- return buildId.includes(":") ? buildId : `id:${buildId}`;
1611
- }
1612
1613
  async downloadArtifactContent(buildId, artifactPath) {
1613
1614
  const normalizedPath = artifactPath.split("/").map((segment) => encodeURIComponent(segment)).join("/");
1614
- const response = await this.client.request(
1615
- (ctx) => ctx.axios.get(
1616
- `${ctx.baseUrl}/app/rest/builds/id:${buildId}/artifacts/content/${normalizedPath}`,
1617
- {
1618
- responseType: "arraybuffer"
1619
- }
1620
- )
1615
+ const buildLocator = toBuildLocator(buildId);
1616
+ const response = await this.client.modules.builds.downloadFileOfBuild(
1617
+ `content/${normalizedPath}`,
1618
+ buildLocator,
1619
+ void 0,
1620
+ void 0,
1621
+ { responseType: "arraybuffer" }
1621
1622
  );
1622
- return response.data;
1623
+ const axiosResponse = response;
1624
+ const { data } = axiosResponse;
1625
+ if (data instanceof ArrayBuffer) {
1626
+ return data.slice(0);
1627
+ }
1628
+ if (Buffer.isBuffer(data)) {
1629
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
1630
+ }
1631
+ throw new Error("Artifact download returned unexpected binary payload type");
1623
1632
  }
1624
1633
  /**
1625
1634
  * Parse TeamCity date format
@@ -1693,12 +1702,24 @@ var BuildResultsManager = class _BuildResultsManager {
1693
1702
  // src/teamcity/client-adapter.ts
1694
1703
  var import_axios = __toESM(require("axios"));
1695
1704
  var FALLBACK_BASE_URL = "http://not-configured";
1705
+ var toRecord = (value) => {
1706
+ if (typeof value === "object" && value !== null) {
1707
+ return value;
1708
+ }
1709
+ return {};
1710
+ };
1711
+ var createBuildApiBridge = (api) => ({
1712
+ getAllBuilds: (locator, fields, options) => api.getAllBuilds(locator, fields, options),
1713
+ getBuild: (buildLocator, fields, options) => api.getBuild(buildLocator, fields, options),
1714
+ getMultipleBuilds: (locator, fields, options) => api.getMultipleBuilds(locator, fields, options),
1715
+ getBuildProblems: (buildLocator, fields, options) => api.getBuildProblems(buildLocator, fields, options)
1716
+ });
1696
1717
  var resolveModules = (api) => {
1697
1718
  const candidate = api.modules;
1698
1719
  if (candidate != null) {
1699
1720
  return candidate;
1700
1721
  }
1701
- const legacy = api;
1722
+ const legacy = toRecord(api);
1702
1723
  const pick = (key) => legacy[key] ?? {};
1703
1724
  const fallback = {
1704
1725
  agents: pick("agents"),
@@ -1760,7 +1781,7 @@ function createAdapterFromTeamCityAPI(api, options = {}) {
1760
1781
  });
1761
1782
  }
1762
1783
  const request = async (fn) => fn({ axios: httpInstance, baseUrl: resolvedApiConfig.baseUrl, requestId: void 0 });
1763
- const buildApi = modules.builds;
1784
+ const buildApi = createBuildApiBridge(modules.builds);
1764
1785
  return {
1765
1786
  modules,
1766
1787
  http: httpInstance,
@@ -2481,6 +2502,12 @@ var esm_default = axiosRetry;
2481
2502
  // src/teamcity/auth.ts
2482
2503
  var import_crypto = require("crypto");
2483
2504
  init_errors();
2505
+ var asTimingMetaContainer = (value) => {
2506
+ if (typeof value === "object" && value !== null) {
2507
+ return value;
2508
+ }
2509
+ return null;
2510
+ };
2484
2511
  function generateRequestId() {
2485
2512
  return (0, import_crypto.randomUUID)();
2486
2513
  }
@@ -2489,7 +2516,10 @@ function addRequestId(config2) {
2489
2516
  config2.headers["X-Request-ID"] = requestId;
2490
2517
  const configWithId = config2;
2491
2518
  configWithId.requestId = requestId;
2492
- config2._tcMeta = { start: Date.now() };
2519
+ const metaContainer = asTimingMetaContainer(config2);
2520
+ if (metaContainer) {
2521
+ metaContainer._tcMeta = { start: Date.now() };
2522
+ }
2493
2523
  info("Starting TeamCity API request", {
2494
2524
  requestId,
2495
2525
  method: config2.method?.toUpperCase(),
@@ -2503,7 +2533,7 @@ function addRequestId(config2) {
2503
2533
  }
2504
2534
  function logResponse(response) {
2505
2535
  const requestId = response.config?.requestId;
2506
- const meta = response.config._tcMeta;
2536
+ const meta = asTimingMetaContainer(response.config)?._tcMeta;
2507
2537
  const headers = response.headers;
2508
2538
  const headerDuration = headers?.["x-response-time"] ?? headers?.["x-response-duration"];
2509
2539
  const duration = headerDuration ?? (meta?.start ? Date.now() - meta.start : void 0);
@@ -2519,7 +2549,7 @@ function logResponse(response) {
2519
2549
  function logAndTransformError(error2) {
2520
2550
  const requestId = error2.config?.requestId;
2521
2551
  const tcError = TeamCityAPIError.fromAxiosError(error2, requestId);
2522
- const meta = error2.config?._tcMeta;
2552
+ const meta = asTimingMetaContainer(error2.config)?._tcMeta;
2523
2553
  const duration = meta?.start ? Date.now() - meta.start : void 0;
2524
2554
  const sanitize = (val) => {
2525
2555
  const redact = (s) => s.replace(/(token[=:\s]*)[^\s&]+/gi, "$1***").replace(/(password[=:\s]*)[^\s&]+/gi, "$1***").replace(/(apikey[=:\s]*)[^\s&]+/gi, "$1***").replace(/(authorization[=:\s:]*)[^\s&]+/gi, "$1***");
@@ -36242,7 +36272,7 @@ var TeamCityAPI = class _TeamCityAPI {
36242
36272
  return response.data;
36243
36273
  }
36244
36274
  async getBuild(buildId) {
36245
- const response = await this.builds.getBuild(this.toBuildLocator(buildId));
36275
+ const response = await this.builds.getBuild(toBuildLocator(buildId));
36246
36276
  return response.data;
36247
36277
  }
36248
36278
  async triggerBuild(buildTypeId, branchName, comment) {
@@ -36333,7 +36363,7 @@ var TeamCityAPI = class _TeamCityAPI {
36333
36363
  }
36334
36364
  async listBuildArtifacts(buildId, options) {
36335
36365
  return this.builds.getFilesListOfBuild(
36336
- this.toBuildLocator(buildId),
36366
+ toBuildLocator(buildId),
36337
36367
  options?.basePath,
36338
36368
  options?.locator,
36339
36369
  options?.fields,
@@ -36351,16 +36381,13 @@ var TeamCityAPI = class _TeamCityAPI {
36351
36381
  );
36352
36382
  }
36353
36383
  async getBuildStatistics(buildId, fields) {
36354
- return this.builds.getBuildStatisticValues(this.toBuildLocator(buildId), fields);
36384
+ return this.builds.getBuildStatisticValues(toBuildLocator(buildId), fields);
36355
36385
  }
36356
36386
  async listChangesForBuild(buildId, fields) {
36357
36387
  return this.changes.getAllChanges(`build:(id:${buildId})`, fields);
36358
36388
  }
36359
36389
  async listSnapshotDependencies(buildId) {
36360
- const response = await this.builds.getBuild(
36361
- this.toBuildLocator(buildId),
36362
- "snapshot-dependencies"
36363
- );
36390
+ const response = await this.builds.getBuild(toBuildLocator(buildId), "snapshot-dependencies");
36364
36391
  const dependencies = response.data["snapshot-dependencies"];
36365
36392
  if (dependencies == null) {
36366
36393
  return response;
@@ -36393,9 +36420,6 @@ var TeamCityAPI = class _TeamCityAPI {
36393
36420
  this.instance = void 0;
36394
36421
  this.instanceConfig = void 0;
36395
36422
  }
36396
- toBuildLocator(buildId) {
36397
- return buildId.includes(":") ? buildId : `id:${buildId}`;
36398
- }
36399
36423
  createApi(apiCtor) {
36400
36424
  return new apiCtor(this.config, this.baseUrl, this.axiosInstance);
36401
36425
  }
@@ -39215,16 +39239,39 @@ var FULL_MODE_TOOLS = [
39215
39239
  error: "Step ID is required for update action"
39216
39240
  });
39217
39241
  }
39218
- const props = Object.entries(typedArgs.properties ?? {});
39219
- for (const [k, v] of props) {
39220
- await adapter.modules.buildTypes.setBuildStepParameter(
39221
- typedArgs.buildTypeId,
39222
- typedArgs.stepId,
39223
- k,
39224
- String(v),
39225
- { headers: { "Content-Type": "text/plain", Accept: "application/json" } }
39226
- );
39242
+ const updatePayload = {};
39243
+ if (typedArgs.name != null) {
39244
+ updatePayload["name"] = typedArgs.name;
39245
+ }
39246
+ if (typedArgs.type != null) {
39247
+ updatePayload["type"] = typedArgs.type;
39248
+ }
39249
+ const rawProps = typedArgs.properties ?? {};
39250
+ const stepProps = Object.fromEntries(
39251
+ Object.entries(rawProps).map(([k, v]) => [k, String(v)])
39252
+ );
39253
+ if (stepProps["script.content"]) {
39254
+ stepProps["use.custom.script"] = stepProps["use.custom.script"] ?? "true";
39255
+ stepProps["script.type"] = stepProps["script.type"] ?? "customScript";
39256
+ }
39257
+ if (Object.keys(stepProps).length > 0) {
39258
+ updatePayload["properties"] = {
39259
+ property: Object.entries(stepProps).map(([name, value]) => ({ name, value }))
39260
+ };
39227
39261
  }
39262
+ if (Object.keys(updatePayload).length === 0) {
39263
+ return json({
39264
+ success: false,
39265
+ action: "update_build_step",
39266
+ error: "No update fields provided"
39267
+ });
39268
+ }
39269
+ await adapter.modules.buildTypes.replaceBuildStep(
39270
+ typedArgs.buildTypeId,
39271
+ typedArgs.stepId,
39272
+ void 0,
39273
+ updatePayload
39274
+ );
39228
39275
  return json({
39229
39276
  success: true,
39230
39277
  action: "update_build_step",