@daghis/teamcity-mcp 1.10.2 → 1.10.4

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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.10.2"
2
+ ".": "1.10.4"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.4](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.3...teamcity-mcp-v1.10.4) (2025-10-04)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **tools:** normalize branch locators for list_builds ([#228](https://github.com/Daghis/teamcity-mcp/issues/228)) ([16fe7f0](https://github.com/Daghis/teamcity-mcp/commit/16fe7f0e2b46d04e66253ddb3cd9a06c5febf0b6))
9
+
10
+ ## [1.10.3](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.2...teamcity-mcp-v1.10.3) (2025-09-27)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **tools:** honor trigger_build branch overrides (210) ([#223](https://github.com/Daghis/teamcity-mcp/issues/223)) ([7222c28](https://github.com/Daghis/teamcity-mcp/commit/7222c28c4fc9a307222ee9a50fa518127f5187de))
16
+
3
17
  ## [1.10.2](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.1...teamcity-mcp-v1.10.2) (2025-09-27)
4
18
 
5
19
 
package/dist/index.js CHANGED
@@ -610,9 +610,7 @@ function loadConfig() {
610
610
  }
611
611
  var cachedConfig = null;
612
612
  function getConfig() {
613
- if (!cachedConfig) {
614
- cachedConfig = loadConfig();
615
- }
613
+ cachedConfig ??= loadConfig();
616
614
  return cachedConfig;
617
615
  }
618
616
  function isTest() {
@@ -1756,18 +1754,14 @@ var BuildConfigurationCloneManager = class {
1756
1754
  };
1757
1755
  }
1758
1756
  if (options.copyBuildCounter && source.buildNumberCounter) {
1759
- if (configPayload.settings == null) {
1760
- configPayload.settings = { property: [] };
1761
- }
1757
+ configPayload.settings ??= { property: [] };
1762
1758
  configPayload.settings.property?.push({
1763
1759
  name: "buildNumberCounter",
1764
1760
  value: source.buildNumberCounter.toString()
1765
1761
  });
1766
1762
  }
1767
1763
  if (source.buildNumberFormat) {
1768
- if (configPayload.settings == null) {
1769
- configPayload.settings = { property: [] };
1770
- }
1764
+ configPayload.settings ??= { property: [] };
1771
1765
  configPayload.settings.property?.push({
1772
1766
  name: "buildNumberPattern",
1773
1767
  value: source.buildNumberFormat
@@ -3586,6 +3580,100 @@ async function fetchAllPages(fetchFn, options = {}) {
3586
3580
  return allItems;
3587
3581
  }
3588
3582
 
3583
+ // src/utils/list-builds-locator.ts
3584
+ var SIMPLE_BRANCH_VALUES = /* @__PURE__ */ new Set([
3585
+ "default:true",
3586
+ "default:false",
3587
+ "default:any",
3588
+ "unspecified:true",
3589
+ "unspecified:false",
3590
+ "unspecified:any",
3591
+ "branched:true",
3592
+ "branched:false",
3593
+ "branched:any"
3594
+ ]);
3595
+ var BRANCH_PREFIXES_ALLOW_UNWRAPPED = ["default:", "unspecified:", "branched:", "policy:"];
3596
+ function splitLocatorParts(locator) {
3597
+ const parts = [];
3598
+ let current = "";
3599
+ let depth = 0;
3600
+ for (const char of locator) {
3601
+ if (char === "," && depth === 0) {
3602
+ const piece = current.trim();
3603
+ if (piece.length > 0) {
3604
+ parts.push(piece);
3605
+ }
3606
+ current = "";
3607
+ continue;
3608
+ }
3609
+ if (char === "(") {
3610
+ depth += 1;
3611
+ } else if (char === ")" && depth > 0) {
3612
+ depth -= 1;
3613
+ }
3614
+ current += char;
3615
+ }
3616
+ const finalPiece = current.trim();
3617
+ if (finalPiece.length > 0) {
3618
+ parts.push(finalPiece);
3619
+ }
3620
+ return parts;
3621
+ }
3622
+ function wrapBranchValue(value) {
3623
+ const trimmed = value.trim();
3624
+ if (trimmed.length === 0) {
3625
+ return trimmed;
3626
+ }
3627
+ if (trimmed.startsWith("(")) {
3628
+ return trimmed;
3629
+ }
3630
+ const lower = trimmed.toLowerCase();
3631
+ if (SIMPLE_BRANCH_VALUES.has(lower)) {
3632
+ return trimmed;
3633
+ }
3634
+ if (BRANCH_PREFIXES_ALLOW_UNWRAPPED.some((prefix) => lower.startsWith(prefix))) {
3635
+ return trimmed;
3636
+ }
3637
+ if (trimmed.includes("*") && !trimmed.includes(":")) {
3638
+ return trimmed;
3639
+ }
3640
+ if (trimmed.includes("/") || trimmed.includes(":") || /\s/.test(trimmed)) {
3641
+ return `(${trimmed})`;
3642
+ }
3643
+ return trimmed;
3644
+ }
3645
+ function normalizeBranchSegment(segment) {
3646
+ const trimmed = segment.trim();
3647
+ if (trimmed.length === 0) {
3648
+ return trimmed;
3649
+ }
3650
+ if (!trimmed.toLowerCase().startsWith("branch:")) {
3651
+ return trimmed;
3652
+ }
3653
+ const rawValue = trimmed.slice("branch:".length).trim();
3654
+ if (rawValue.length === 0) {
3655
+ return trimmed;
3656
+ }
3657
+ if (rawValue.startsWith("(")) {
3658
+ return `branch:${rawValue}`;
3659
+ }
3660
+ return `branch:${wrapBranchValue(rawValue)}`;
3661
+ }
3662
+ function normalizeLocatorSegments(locator) {
3663
+ if (!locator) {
3664
+ return [];
3665
+ }
3666
+ return splitLocatorParts(locator).map((segment) => normalizeBranchSegment(segment)).filter((segment) => segment.length > 0);
3667
+ }
3668
+ function hasBranchSegment(segments) {
3669
+ return segments.some((segment) => segment.toLowerCase().startsWith("branch:"));
3670
+ }
3671
+ function buildBranchSegmentInput(branchInput) {
3672
+ const normalized = branchInput.trim();
3673
+ const withPrefix = normalized.toLowerCase().startsWith("branch:") ? normalized : `branch:${normalized}`;
3674
+ return normalizeBranchSegment(withPrefix);
3675
+ }
3676
+
3589
3677
  // src/utils/mcp.ts
3590
3678
  var import_zod3 = require("zod");
3591
3679
 
@@ -3686,9 +3774,7 @@ init_errors();
3686
3774
  var ErrorLogger = class _ErrorLogger {
3687
3775
  static instance;
3688
3776
  static getInstance() {
3689
- if (_ErrorLogger.instance == null) {
3690
- _ErrorLogger.instance = new _ErrorLogger();
3691
- }
3777
+ _ErrorLogger.instance ??= new _ErrorLogger();
3692
3778
  return _ErrorLogger.instance;
3693
3779
  }
3694
3780
  /**
@@ -3773,9 +3859,7 @@ var GlobalErrorHandler = class _GlobalErrorHandler {
3773
3859
  }
3774
3860
  static instance;
3775
3861
  static getInstance(options) {
3776
- if (_GlobalErrorHandler.instance == null) {
3777
- _GlobalErrorHandler.instance = new _GlobalErrorHandler(options);
3778
- }
3862
+ _GlobalErrorHandler.instance ??= new _GlobalErrorHandler(options);
3779
3863
  return _GlobalErrorHandler.instance;
3780
3864
  }
3781
3865
  /**
@@ -38447,6 +38531,7 @@ var DEV_TOOLS = [
38447
38531
  locator: { type: "string", description: "Optional build locator to filter builds" },
38448
38532
  projectId: { type: "string", description: "Filter by project ID" },
38449
38533
  buildTypeId: { type: "string", description: "Filter by build type ID" },
38534
+ branch: { type: "string", description: "Filter by branch (logical or VCS name)" },
38450
38535
  status: {
38451
38536
  type: "string",
38452
38537
  enum: ["SUCCESS", "FAILURE", "ERROR"],
@@ -38467,6 +38552,7 @@ var DEV_TOOLS = [
38467
38552
  locator: import_zod4.z.string().min(1).optional(),
38468
38553
  projectId: import_zod4.z.string().min(1).optional(),
38469
38554
  buildTypeId: import_zod4.z.string().min(1).optional(),
38555
+ branch: import_zod4.z.string().min(1).optional(),
38470
38556
  status: import_zod4.z.enum(["SUCCESS", "FAILURE", "ERROR"]).optional(),
38471
38557
  count: import_zod4.z.number().int().min(1).max(1e3).default(10).optional(),
38472
38558
  pageSize: import_zod4.z.number().int().min(1).max(1e3).optional(),
@@ -38479,10 +38565,17 @@ var DEV_TOOLS = [
38479
38565
  schema,
38480
38566
  async (typed) => {
38481
38567
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
38482
- const baseParts = [];
38483
- if (typed.locator) baseParts.push(typed.locator);
38568
+ const locatorSegments = normalizeLocatorSegments(typed.locator);
38569
+ const hasBranchInLocator = hasBranchSegment(locatorSegments);
38570
+ const baseParts = [...locatorSegments];
38484
38571
  if (typed.projectId) baseParts.push(`project:(id:${typed.projectId})`);
38485
38572
  if (typed.buildTypeId) baseParts.push(`buildType:(id:${typed.buildTypeId})`);
38573
+ if (typed.branch) {
38574
+ const branchSegment = buildBranchSegmentInput(typed.branch);
38575
+ if (!hasBranchInLocator) {
38576
+ baseParts.push(branchSegment);
38577
+ }
38578
+ }
38486
38579
  if (typed.status) baseParts.push(`status:${typed.status}`);
38487
38580
  const pageSize = typed.pageSize ?? typed.count ?? 100;
38488
38581
  const baseFetch = async ({ count, start }) => {
@@ -38549,7 +38642,12 @@ var DEV_TOOLS = [
38549
38642
  properties: {
38550
38643
  buildTypeId: { type: "string", description: "Build type ID to trigger" },
38551
38644
  branchName: { type: "string", description: "Branch to build (optional)" },
38552
- comment: { type: "string", description: "Build comment (optional)" }
38645
+ comment: { type: "string", description: "Build comment (optional)" },
38646
+ properties: {
38647
+ type: "object",
38648
+ description: "Optional build parameters to set when triggering the build",
38649
+ additionalProperties: { type: "string" }
38650
+ }
38553
38651
  },
38554
38652
  required: ["buildTypeId"]
38555
38653
  },
@@ -38557,35 +38655,84 @@ var DEV_TOOLS = [
38557
38655
  const schema = import_zod4.z.object({
38558
38656
  buildTypeId: import_zod4.z.string().min(1),
38559
38657
  branchName: import_zod4.z.string().min(1).max(255).optional(),
38560
- comment: import_zod4.z.string().max(500).optional()
38658
+ comment: import_zod4.z.string().max(500).optional(),
38659
+ properties: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.string()).optional()
38561
38660
  });
38562
38661
  return runTool(
38563
38662
  "trigger_build",
38564
38663
  schema,
38565
38664
  async (typed) => {
38566
38665
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
38567
- try {
38568
- const build = await adapter.triggerBuild(
38569
- typed.buildTypeId,
38570
- typed.branchName,
38571
- typed.comment
38666
+ const directBranch = typed.branchName?.trim();
38667
+ const normalizedDirectBranch = directBranch && directBranch.length > 0 ? directBranch : void 0;
38668
+ const rawPropertyBranch = typed.properties?.["teamcity.build.branch"];
38669
+ const trimmedPropertyBranch = rawPropertyBranch?.trim();
38670
+ const normalizedPropertyBranch = trimmedPropertyBranch && trimmedPropertyBranch.length > 0 ? trimmedPropertyBranch : void 0;
38671
+ const branchName = normalizedDirectBranch ?? normalizedPropertyBranch;
38672
+ if (normalizedDirectBranch && normalizedPropertyBranch && normalizedDirectBranch !== normalizedPropertyBranch) {
38673
+ const errorPayload = {
38674
+ success: false,
38675
+ action: "trigger_build",
38676
+ error: `Conflicting branch overrides: branchName='${normalizedDirectBranch}' vs properties.teamcity.build.branch='${normalizedPropertyBranch}'.`
38677
+ };
38678
+ return {
38679
+ success: false,
38680
+ error: errorPayload.error,
38681
+ content: [{ type: "text", text: JSON.stringify(errorPayload, null, 2) }]
38682
+ };
38683
+ }
38684
+ const propertyEntries = typed.properties ? Object.entries(typed.properties).map(([name, value]) => ({
38685
+ name,
38686
+ value: name === "teamcity.build.branch" && normalizedPropertyBranch ? normalizedPropertyBranch : value
38687
+ })) : [];
38688
+ const propertiesPayload = propertyEntries.length > 0 ? { property: propertyEntries } : void 0;
38689
+ const buildRequest = {
38690
+ buildType: { id: typed.buildTypeId }
38691
+ };
38692
+ if (branchName) {
38693
+ buildRequest.branchName = branchName;
38694
+ }
38695
+ const commentText = typed.comment?.trim();
38696
+ if (commentText && commentText.length > 0) {
38697
+ buildRequest.comment = { text: commentText };
38698
+ }
38699
+ if (propertiesPayload) {
38700
+ buildRequest.properties = propertiesPayload;
38701
+ }
38702
+ const sendXmlFallback = async (error2) => {
38703
+ const escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
38704
+ const branchPart = branchName ? `<branchName>${escapeXml(branchName)}</branchName>` : "";
38705
+ const commentPart = commentText ? `<comment><text>${escapeXml(commentText)}</text></comment>` : "";
38706
+ const propertiesPart = propertiesPayload ? `<properties>${propertiesPayload.property.map(
38707
+ (prop) => `<property name="${escapeXml(prop.name)}" value="${escapeXml(prop.value)}"/>`
38708
+ ).join("")}</properties>` : "";
38709
+ const xml = `<?xml version="1.0" encoding="UTF-8"?><build><buildType id="${escapeXml(
38710
+ typed.buildTypeId
38711
+ )}"/>${branchPart}${commentPart}${propertiesPart}</build>`;
38712
+ const response = await adapter.modules.buildQueue.addBuildToQueue(
38713
+ false,
38714
+ xml,
38715
+ {
38716
+ headers: { "Content-Type": "application/xml", Accept: "application/json" }
38717
+ }
38572
38718
  );
38719
+ const build = response.data;
38573
38720
  return json({
38574
38721
  success: true,
38575
38722
  action: "trigger_build",
38576
38723
  buildId: String(build.id ?? ""),
38577
38724
  state: build.state ?? void 0,
38578
- status: build.status ?? void 0
38725
+ status: build.status ?? void 0,
38726
+ branchName: build.branchName ?? branchName,
38727
+ fallback: { mode: "xml", reason: error2?.message }
38579
38728
  });
38580
- } catch (e) {
38581
- const branchPart = typed.branchName ? `<branchName>${typed.branchName}</branchName>` : "";
38582
- const commentPart = typed.comment ? `<comment><text>${typed.comment.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</text></comment>` : "";
38583
- const xml = `<?xml version="1.0" encoding="UTF-8"?><build><buildType id="${typed.buildTypeId}"/>${branchPart}${commentPart}</build>`;
38729
+ };
38730
+ try {
38584
38731
  const response = await adapter.modules.buildQueue.addBuildToQueue(
38585
38732
  false,
38586
- xml,
38733
+ buildRequest,
38587
38734
  {
38588
- headers: { "Content-Type": "application/xml", Accept: "application/json" }
38735
+ headers: { "Content-Type": "application/json", Accept: "application/json" }
38589
38736
  }
38590
38737
  );
38591
38738
  const build = response.data;
@@ -38594,8 +38741,11 @@ var DEV_TOOLS = [
38594
38741
  action: "trigger_build",
38595
38742
  buildId: String(build.id ?? ""),
38596
38743
  state: build.state ?? void 0,
38597
- status: build.status ?? void 0
38744
+ status: build.status ?? void 0,
38745
+ branchName: build.branchName ?? branchName
38598
38746
  });
38747
+ } catch (error2) {
38748
+ return sendXmlFallback(error2);
38599
38749
  }
38600
38750
  },
38601
38751
  args