@daghis/teamcity-mcp 1.10.0 → 1.10.2

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.0"
2
+ ".": "1.10.2"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.2](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.1...teamcity-mcp-v1.10.2) (2025-09-27)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **tools:** clone_build_config uses manager (215) ([b84d1f8](https://github.com/Daghis/teamcity-mcp/commit/b84d1f80a4233783a93dd1e3ede9a83a7cf57171))
9
+ * **tools:** clone_build_config uses manager (215) ([c4cd959](https://github.com/Daghis/teamcity-mcp/commit/c4cd959a9f35052bf95386162316a9ace5599eb6))
10
+
11
+ ## [1.10.1](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.10.0...teamcity-mcp-v1.10.1) (2025-09-27)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * **tools:** support get_build_status buildNumber (209) ([#219](https://github.com/Daghis/teamcity-mcp/issues/219)) ([efb9a00](https://github.com/Daghis/teamcity-mcp/commit/efb9a00ad697335239e7cd87c9436259df27a49c))
17
+
3
18
  ## [1.10.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v1.9.6...teamcity-mcp-v1.10.0) (2025-09-27)
4
19
 
5
20
 
package/dist/index.js CHANGED
@@ -261,17 +261,11 @@ var init_build_status_manager = __esm({
261
261
  buildData = response.data;
262
262
  } else {
263
263
  const locator = this.buildLocator(options);
264
- const response = await this.client.builds.getMultipleBuilds(
264
+ const response = await this.client.builds.getBuild(
265
265
  locator,
266
266
  this.getFieldSelection(options)
267
267
  );
268
- const data = response.data;
269
- if (!Array.isArray(data.build) || data.build.length === 0) {
270
- throw new BuildNotFoundError(
271
- `No build found for number ${options.buildNumber} in ${options.buildTypeId}`
272
- );
273
- }
274
- buildData = data.build[0];
268
+ buildData = response.data;
275
269
  }
276
270
  if (buildData == null) {
277
271
  throw new BuildNotFoundError("Build data is undefined");
@@ -1522,6 +1516,384 @@ var ArtifactManager = class _ArtifactManager {
1522
1516
  }
1523
1517
  };
1524
1518
 
1519
+ // src/teamcity/types/api-responses.ts
1520
+ function isBuildTypeData(data) {
1521
+ return typeof data === "object" && data !== null && ("id" in data || "name" in data || "projectId" in data);
1522
+ }
1523
+ function isVcsRootsResponse(data) {
1524
+ return typeof data === "object" && data !== null && "vcs-root" in data;
1525
+ }
1526
+
1527
+ // src/teamcity/build-configuration-clone-manager.ts
1528
+ var BuildConfigurationCloneManager = class {
1529
+ client;
1530
+ constructor(client) {
1531
+ this.client = client;
1532
+ }
1533
+ /**
1534
+ * Retrieve complete build configuration from TeamCity
1535
+ */
1536
+ async retrieveConfiguration(configId) {
1537
+ try {
1538
+ const response = await this.client.modules.buildTypes.getBuildType(
1539
+ configId,
1540
+ "$long,steps($long),triggers($long),features($long),artifact-dependencies($long),snapshot-dependencies($long),parameters($long),vcs-root-entries($long)"
1541
+ );
1542
+ if (response.data == null || !isBuildTypeData(response.data)) {
1543
+ return null;
1544
+ }
1545
+ const config2 = response.data;
1546
+ let vcsRootId;
1547
+ const vcsRootEntries = config2["vcs-root-entries"];
1548
+ if (vcsRootEntries?.["vcs-root-entry"] && vcsRootEntries["vcs-root-entry"].length > 0) {
1549
+ const firstEntry = vcsRootEntries["vcs-root-entry"][0];
1550
+ if (firstEntry?.["vcs-root"]?.id) {
1551
+ vcsRootId = firstEntry["vcs-root"].id;
1552
+ }
1553
+ }
1554
+ const parameters = {};
1555
+ if (config2.parameters?.property) {
1556
+ for (const param of config2.parameters.property) {
1557
+ if (param.name && param.value) {
1558
+ parameters[param.name] = param.value;
1559
+ }
1560
+ }
1561
+ }
1562
+ const cfgId = config2.id;
1563
+ const cfgName = config2.name;
1564
+ if (!cfgId || !cfgName) {
1565
+ throw new Error("Source configuration missing id or name");
1566
+ }
1567
+ return {
1568
+ id: cfgId,
1569
+ name: cfgName,
1570
+ projectId: config2.projectId ?? config2.project?.id ?? "",
1571
+ description: config2.description,
1572
+ vcsRootId,
1573
+ parameters,
1574
+ templateId: config2.templates?.buildType?.[0]?.id,
1575
+ steps: config2.steps?.step,
1576
+ triggers: config2.triggers?.trigger,
1577
+ features: config2.features?.feature,
1578
+ artifactDependencies: config2["artifact-dependencies"]?.["artifact-dependency"],
1579
+ snapshotDependencies: config2["snapshot-dependencies"]?.["snapshot-dependency"],
1580
+ buildNumberCounter: (() => {
1581
+ const counterProp = config2.settings?.property?.find(
1582
+ (p) => p.name === "buildNumberCounter"
1583
+ );
1584
+ return counterProp?.value ? parseInt(counterProp.value, 10) : void 0;
1585
+ })(),
1586
+ buildNumberFormat: config2.settings?.property?.find(
1587
+ (p) => p.name === "buildNumberPattern"
1588
+ )?.value
1589
+ };
1590
+ } catch (err) {
1591
+ const axiosError = err;
1592
+ if (axiosError.response?.status === 404) {
1593
+ debug("Build configuration not found", { configId });
1594
+ return null;
1595
+ }
1596
+ if (axiosError.response?.status === 403) {
1597
+ throw new Error("Permission denied: No access to source configuration");
1598
+ }
1599
+ throw err;
1600
+ }
1601
+ }
1602
+ /**
1603
+ * Validate target project exists and user has permissions
1604
+ */
1605
+ async validateTargetProject(projectId) {
1606
+ try {
1607
+ const response = await this.client.modules.projects.getProject(projectId, "$short");
1608
+ const id = response.data?.id;
1609
+ const name = response.data?.name;
1610
+ if (id && name) {
1611
+ return { id, name };
1612
+ }
1613
+ return null;
1614
+ } catch (err) {
1615
+ const axiosError = err;
1616
+ if (axiosError.response?.status === 404) {
1617
+ debug("Target project not found", { projectId });
1618
+ return null;
1619
+ }
1620
+ if (axiosError.response?.status === 403) {
1621
+ debug("No permission to access target project", { projectId });
1622
+ return null;
1623
+ }
1624
+ throw err;
1625
+ }
1626
+ }
1627
+ /**
1628
+ * Handle VCS root cloning or reuse
1629
+ */
1630
+ async handleVcsRoot(vcsRootId, handling, targetProjectId) {
1631
+ if (handling === "reuse") {
1632
+ return { id: vcsRootId, name: "Reused VCS Root" };
1633
+ }
1634
+ try {
1635
+ const vcsRootsResponse = await this.client.modules.vcsRoots.getAllVcsRoots(
1636
+ `id:${vcsRootId}`,
1637
+ "$long,vcsRoot($long,properties($long))"
1638
+ );
1639
+ if (vcsRootsResponse.data == null || !isVcsRootsResponse(vcsRootsResponse.data)) {
1640
+ throw new Error("Invalid VCS root response");
1641
+ }
1642
+ const vcsRoots = vcsRootsResponse.data["vcs-root"] ?? [];
1643
+ if (vcsRoots.length === 0) {
1644
+ throw new Error("VCS root not found");
1645
+ }
1646
+ const sourceVcsRoot = vcsRoots[0];
1647
+ if (sourceVcsRoot == null) {
1648
+ throw new Error("VCS root data is invalid");
1649
+ }
1650
+ const clonedVcsRootName = `${sourceVcsRoot.name}_Clone_${Date.now()}`;
1651
+ const clonedVcsRoot = {
1652
+ name: clonedVcsRootName,
1653
+ vcsName: sourceVcsRoot.vcsName,
1654
+ project: {
1655
+ id: targetProjectId
1656
+ },
1657
+ properties: sourceVcsRoot.properties
1658
+ };
1659
+ const createResponse = await this.client.modules.vcsRoots.addVcsRoot(
1660
+ void 0,
1661
+ clonedVcsRoot
1662
+ );
1663
+ const newId = createResponse.data.id;
1664
+ const newName = createResponse.data.name;
1665
+ if (!newId || !newName) {
1666
+ throw new Error("Failed to obtain cloned VCS root id/name");
1667
+ }
1668
+ return { id: newId, name: newName };
1669
+ } catch (err) {
1670
+ error("Failed to clone VCS root", err);
1671
+ throw new Error(`Failed to clone VCS root: ${err.message}`);
1672
+ }
1673
+ }
1674
+ /**
1675
+ * Apply parameter overrides to configuration
1676
+ */
1677
+ async applyParameterOverrides(sourceParameters, overrides) {
1678
+ const mergedParameters = { ...sourceParameters };
1679
+ for (const [key, value] of Object.entries(overrides)) {
1680
+ if (!this.isValidParameterName(key)) {
1681
+ throw new Error(`Invalid parameter name: ${key}`);
1682
+ }
1683
+ mergedParameters[key] = value;
1684
+ }
1685
+ return mergedParameters;
1686
+ }
1687
+ /**
1688
+ * Clone the build configuration
1689
+ */
1690
+ async cloneConfiguration(source, options) {
1691
+ const configId = options.id ?? this.generateBuildConfigId(options.targetProjectId, options.name);
1692
+ const configPayload = {
1693
+ id: configId,
1694
+ name: options.name,
1695
+ project: {
1696
+ id: options.targetProjectId
1697
+ }
1698
+ };
1699
+ if (options.description) {
1700
+ configPayload.description = options.description;
1701
+ }
1702
+ if (source.templateId) {
1703
+ configPayload.templates = {
1704
+ buildType: [{ id: source.templateId }]
1705
+ };
1706
+ }
1707
+ if (options.vcsRootId) {
1708
+ configPayload["vcs-root-entries"] = {
1709
+ "vcs-root-entry": [
1710
+ {
1711
+ "vcs-root": { id: options.vcsRootId },
1712
+ "checkout-rules": ""
1713
+ }
1714
+ ]
1715
+ };
1716
+ }
1717
+ if (source.steps && source.steps.length > 0) {
1718
+ configPayload.steps = {
1719
+ step: this.cloneBuildSteps(source.steps)
1720
+ };
1721
+ }
1722
+ if (source.triggers && source.triggers.length > 0) {
1723
+ configPayload.triggers = {
1724
+ trigger: this.cloneTriggers(source.triggers)
1725
+ };
1726
+ }
1727
+ if (source.features && source.features.length > 0) {
1728
+ configPayload.features = {
1729
+ feature: source.features.map((f) => this.deepCloneConfiguration(f))
1730
+ };
1731
+ }
1732
+ if (source.artifactDependencies && source.artifactDependencies.length > 0) {
1733
+ configPayload["artifact-dependencies"] = {
1734
+ "artifact-dependency": this.updateDependencyReferences(
1735
+ source.artifactDependencies,
1736
+ source.id,
1737
+ configId
1738
+ )
1739
+ };
1740
+ }
1741
+ if (source.snapshotDependencies && source.snapshotDependencies.length > 0) {
1742
+ configPayload["snapshot-dependencies"] = {
1743
+ "snapshot-dependency": this.updateDependencyReferences(
1744
+ source.snapshotDependencies,
1745
+ source.id,
1746
+ configId
1747
+ )
1748
+ };
1749
+ }
1750
+ if (options.parameters && Object.keys(options.parameters).length > 0) {
1751
+ configPayload.parameters = {
1752
+ property: Object.entries(options.parameters).map(([name, value]) => ({
1753
+ name,
1754
+ value
1755
+ }))
1756
+ };
1757
+ }
1758
+ if (options.copyBuildCounter && source.buildNumberCounter) {
1759
+ if (configPayload.settings == null) {
1760
+ configPayload.settings = { property: [] };
1761
+ }
1762
+ configPayload.settings.property?.push({
1763
+ name: "buildNumberCounter",
1764
+ value: source.buildNumberCounter.toString()
1765
+ });
1766
+ }
1767
+ if (source.buildNumberFormat) {
1768
+ if (configPayload.settings == null) {
1769
+ configPayload.settings = { property: [] };
1770
+ }
1771
+ configPayload.settings.property?.push({
1772
+ name: "buildNumberPattern",
1773
+ value: source.buildNumberFormat
1774
+ });
1775
+ }
1776
+ try {
1777
+ const response = await this.client.modules.buildTypes.createBuildType(
1778
+ void 0,
1779
+ this.prepareBuildTypePayload(configPayload)
1780
+ );
1781
+ const teamcityUrl = getTeamCityUrl();
1782
+ const id = response.data.id;
1783
+ const name = response.data.name;
1784
+ if (!id || !name) {
1785
+ throw new Error("Clone response missing id or name");
1786
+ }
1787
+ const result = {
1788
+ id,
1789
+ name,
1790
+ projectId: response.data.projectId ?? options.targetProjectId,
1791
+ description: response.data.description,
1792
+ vcsRootId: options.vcsRootId,
1793
+ parameters: options.parameters,
1794
+ url: `${teamcityUrl}/viewType.html?buildTypeId=${id}`
1795
+ };
1796
+ info("Build configuration cloned", {
1797
+ id: result.id,
1798
+ name: result.name,
1799
+ sourceId: source.id
1800
+ });
1801
+ return result;
1802
+ } catch (err) {
1803
+ const error2 = err;
1804
+ if (error2.response?.status === 409) {
1805
+ throw new Error(`Build configuration already exists with ID: ${configId}`);
1806
+ }
1807
+ if (error2.response?.status === 403) {
1808
+ throw new Error("Permission denied: You need project edit permissions");
1809
+ }
1810
+ if (error2.response?.status === 400) {
1811
+ const message = error2.response?.data?.message ?? "Invalid configuration";
1812
+ throw new Error(`Invalid configuration: ${message}`);
1813
+ }
1814
+ error(
1815
+ "Failed to clone build configuration",
1816
+ error2 instanceof Error ? error2 : new Error(String(error2))
1817
+ );
1818
+ throw error2;
1819
+ }
1820
+ }
1821
+ /**
1822
+ * Normalize cloned payload into the generated BuildType shape expected by the API
1823
+ */
1824
+ prepareBuildTypePayload(payload) {
1825
+ const clone = typeof structuredClone === "function" ? structuredClone(payload) : JSON.parse(JSON.stringify(payload));
1826
+ if (typeof clone.id !== "string" || typeof clone.name !== "string") {
1827
+ throw new Error("Invalid build configuration payload: missing id or name");
1828
+ }
1829
+ if (typeof clone.project?.id !== "string") {
1830
+ throw new Error("Invalid build configuration payload: missing project id");
1831
+ }
1832
+ return clone;
1833
+ }
1834
+ /**
1835
+ * Deep clone configuration object and remove server-generated fields
1836
+ */
1837
+ deepCloneConfiguration(config2) {
1838
+ const cloned = JSON.parse(JSON.stringify(config2));
1839
+ delete cloned.href;
1840
+ delete cloned.webUrl;
1841
+ delete cloned.locator;
1842
+ delete cloned.uuid;
1843
+ delete cloned.links;
1844
+ delete cloned._links;
1845
+ return cloned;
1846
+ }
1847
+ /**
1848
+ * Clone build steps with new IDs
1849
+ */
1850
+ cloneBuildSteps(steps) {
1851
+ return steps.map((step, index) => {
1852
+ const clonedStep = this.deepCloneConfiguration(step);
1853
+ clonedStep.id = `RUNNER_${index + 1}`;
1854
+ return clonedStep;
1855
+ });
1856
+ }
1857
+ /**
1858
+ * Clone triggers with new IDs
1859
+ */
1860
+ cloneTriggers(triggers) {
1861
+ return triggers.map((trigger, index) => {
1862
+ const clonedTrigger = this.deepCloneConfiguration(trigger);
1863
+ clonedTrigger.id = `TRIGGER_${index + 1}`;
1864
+ return clonedTrigger;
1865
+ });
1866
+ }
1867
+ /**
1868
+ * Update internal references in dependencies
1869
+ */
1870
+ updateDependencyReferences(dependencies, oldId, newId) {
1871
+ return dependencies.map((dep) => {
1872
+ const clonedDep = this.deepCloneConfiguration(dep);
1873
+ if (clonedDep.sourceBuildTypeId === oldId) {
1874
+ clonedDep.sourceBuildTypeId = newId;
1875
+ }
1876
+ if (clonedDep.dependsOnBuildTypeId === oldId) {
1877
+ clonedDep.dependsOnBuildTypeId = newId;
1878
+ }
1879
+ return clonedDep;
1880
+ });
1881
+ }
1882
+ /**
1883
+ * Generate a unique build configuration ID
1884
+ */
1885
+ generateBuildConfigId(projectId, name) {
1886
+ const cleanName = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
1887
+ return `${projectId}_${cleanName}`;
1888
+ }
1889
+ /**
1890
+ * Validate parameter name according to TeamCity rules
1891
+ */
1892
+ isValidParameterName(name) {
1893
+ return /^[a-zA-Z0-9._-]+$/.test(name);
1894
+ }
1895
+ };
1896
+
1525
1897
  // src/teamcity/build-configuration-update-manager.ts
1526
1898
  var ARTIFACT_RULES_SETTINGS_FIELD = "settings/artifactRules";
1527
1899
  var ARTIFACT_RULES_LEGACY_FIELD = "artifactRules";
@@ -38262,6 +38634,14 @@ var DEV_TOOLS = [
38262
38634
  type: "object",
38263
38635
  properties: {
38264
38636
  buildId: { type: "string", description: "Build ID" },
38637
+ buildNumber: {
38638
+ type: "string",
38639
+ description: "Human build number (requires buildTypeId when provided)"
38640
+ },
38641
+ buildTypeId: {
38642
+ type: "string",
38643
+ description: "Build configuration identifier (required when using buildNumber)"
38644
+ },
38265
38645
  includeTests: { type: "boolean", description: "Include test summary" },
38266
38646
  includeProblems: { type: "boolean", description: "Include build problems" },
38267
38647
  includeQueueTotals: {
@@ -38272,16 +38652,32 @@ var DEV_TOOLS = [
38272
38652
  type: "boolean",
38273
38653
  description: "Include waitReason for the queued item (extra API call when queued)"
38274
38654
  }
38275
- },
38276
- required: ["buildId"]
38655
+ }
38277
38656
  },
38278
38657
  handler: async (args) => {
38279
38658
  const schema = import_zod4.z.object({
38280
- buildId: import_zod4.z.string().min(1),
38659
+ buildId: import_zod4.z.string().min(1).optional(),
38660
+ buildNumber: import_zod4.z.string().min(1).optional(),
38661
+ buildTypeId: import_zod4.z.string().min(1).optional(),
38281
38662
  includeTests: import_zod4.z.boolean().optional(),
38282
38663
  includeProblems: import_zod4.z.boolean().optional(),
38283
38664
  includeQueueTotals: import_zod4.z.boolean().optional(),
38284
38665
  includeQueueReason: import_zod4.z.boolean().optional()
38666
+ }).superRefine((value, ctx) => {
38667
+ if (!value.buildId && !value.buildNumber) {
38668
+ ctx.addIssue({
38669
+ code: import_zod4.z.ZodIssueCode.custom,
38670
+ path: ["buildId"],
38671
+ message: "Either buildId or buildNumber must be provided"
38672
+ });
38673
+ }
38674
+ if (value.buildNumber && !value.buildTypeId) {
38675
+ ctx.addIssue({
38676
+ code: import_zod4.z.ZodIssueCode.custom,
38677
+ path: ["buildTypeId"],
38678
+ message: "buildTypeId is required when querying by buildNumber"
38679
+ });
38680
+ }
38285
38681
  });
38286
38682
  return runTool(
38287
38683
  "get_build_status",
@@ -38291,6 +38687,8 @@ var DEV_TOOLS = [
38291
38687
  const statusManager = new (await Promise.resolve().then(() => (init_build_status_manager(), build_status_manager_exports))).BuildStatusManager(adapter);
38292
38688
  const result = await statusManager.getBuildStatus({
38293
38689
  buildId: typed.buildId,
38690
+ buildNumber: typed.buildNumber,
38691
+ buildTypeId: typed.buildTypeId,
38294
38692
  includeTests: typed.includeTests,
38295
38693
  includeProblems: typed.includeProblems
38296
38694
  });
@@ -38311,8 +38709,11 @@ var DEV_TOOLS = [
38311
38709
  }
38312
38710
  if (typed.includeQueueReason) {
38313
38711
  try {
38314
- const qb = await adapter.modules.buildQueue.getQueuedBuild(typed.buildId);
38315
- enrich.waitReason = qb.data.waitReason;
38712
+ const targetBuildId = typed.buildId ?? result.buildId;
38713
+ if (targetBuildId) {
38714
+ const qb = await adapter.modules.buildQueue.getQueuedBuild(targetBuildId);
38715
+ enrich.waitReason = qb.data.waitReason;
38716
+ }
38316
38717
  } catch {
38317
38718
  }
38318
38719
  }
@@ -40689,22 +41090,88 @@ var FULL_MODE_TOOLS = [
40689
41090
  sourceBuildTypeId: { type: "string", description: "Source build type ID" },
40690
41091
  name: { type: "string", description: "New build configuration name" },
40691
41092
  id: { type: "string", description: "New build configuration ID" },
40692
- projectId: { type: "string", description: "Target project ID" }
41093
+ projectId: { type: "string", description: "Target project ID" },
41094
+ description: { type: "string", description: "Description for the cloned configuration" },
41095
+ parameters: {
41096
+ type: "object",
41097
+ description: "Optional parameter overrides to apply to the clone",
41098
+ additionalProperties: { type: "string" }
41099
+ },
41100
+ copyBuildCounter: {
41101
+ type: "boolean",
41102
+ description: "Copy the build number counter from the source configuration"
41103
+ }
40693
41104
  },
40694
41105
  required: ["sourceBuildTypeId", "name", "id"]
40695
41106
  },
40696
41107
  handler: async (args) => {
40697
- const typedArgs = args;
40698
- const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
40699
- const source = await adapter.getBuildType(typedArgs.sourceBuildTypeId);
40700
- const buildType = {
40701
- ...source,
40702
- name: typedArgs.name,
40703
- id: typedArgs.id,
40704
- project: { id: typedArgs.projectId ?? source.project?.id ?? "_Root" }
40705
- };
40706
- const response = await adapter.modules.buildTypes.createBuildType(void 0, buildType);
40707
- return json({ success: true, action: "clone_build_config", id: response.data.id });
41108
+ const schema = import_zod4.z.object({
41109
+ sourceBuildTypeId: import_zod4.z.string().min(1),
41110
+ name: import_zod4.z.string().min(1),
41111
+ id: import_zod4.z.string().min(1),
41112
+ projectId: import_zod4.z.string().min(1).optional(),
41113
+ description: import_zod4.z.string().optional(),
41114
+ parameters: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.string()).optional(),
41115
+ copyBuildCounter: import_zod4.z.boolean().optional()
41116
+ }).superRefine((value, ctx) => {
41117
+ if (value.id.trim() === "") {
41118
+ ctx.addIssue({
41119
+ code: import_zod4.z.ZodIssueCode.custom,
41120
+ message: "id must be a non-empty string.",
41121
+ path: ["id"]
41122
+ });
41123
+ }
41124
+ });
41125
+ return runTool(
41126
+ "clone_build_config",
41127
+ schema,
41128
+ async (typedArgs) => {
41129
+ const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
41130
+ const manager = new BuildConfigurationCloneManager(adapter);
41131
+ const source = await manager.retrieveConfiguration(typedArgs.sourceBuildTypeId);
41132
+ if (!source) {
41133
+ return json({
41134
+ success: false,
41135
+ action: "clone_build_config",
41136
+ error: `Source build configuration not found: ${typedArgs.sourceBuildTypeId}`
41137
+ });
41138
+ }
41139
+ const targetProjectId = typedArgs.projectId ?? source.projectId;
41140
+ if (!targetProjectId) {
41141
+ return json({
41142
+ success: false,
41143
+ action: "clone_build_config",
41144
+ error: "projectId is required when the source configuration does not specify a project."
41145
+ });
41146
+ }
41147
+ try {
41148
+ const cloned = await manager.cloneConfiguration(source, {
41149
+ id: typedArgs.id,
41150
+ name: typedArgs.name,
41151
+ targetProjectId,
41152
+ description: typedArgs.description ?? source.description,
41153
+ parameters: typedArgs.parameters,
41154
+ copyBuildCounter: typedArgs.copyBuildCounter
41155
+ });
41156
+ return json({
41157
+ success: true,
41158
+ action: "clone_build_config",
41159
+ id: cloned.id,
41160
+ name: cloned.name,
41161
+ projectId: cloned.projectId,
41162
+ url: cloned.url,
41163
+ description: cloned.description
41164
+ });
41165
+ } catch (error2) {
41166
+ return json({
41167
+ success: false,
41168
+ action: "clone_build_config",
41169
+ error: error2 instanceof Error ? error2.message : "Failed to clone build configuration."
41170
+ });
41171
+ }
41172
+ },
41173
+ args
41174
+ );
40708
41175
  },
40709
41176
  mode: "full"
40710
41177
  },