@daghis/teamcity-mcp 2.2.3 → 2.3.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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.3.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.2.3...teamcity-mcp-v2.3.0) (2026-03-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * **tools:** accept buildNumber + buildTypeId across all build tools ([#410](https://github.com/Daghis/teamcity-mcp/issues/410)) ([9aa005d](https://github.com/Daghis/teamcity-mcp/commit/9aa005d01bf6db9e7ac53155680876dc36202da7))
9
+
3
10
  ## [2.2.3](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.2.2...teamcity-mcp-v2.2.3) (2026-03-09)
4
11
 
5
12
 
package/dist/index.js CHANGED
@@ -1205,7 +1205,7 @@ function debug2(message, meta) {
1205
1205
  // package.json
1206
1206
  var package_default = {
1207
1207
  name: "@daghis/teamcity-mcp",
1208
- version: "2.2.3",
1208
+ version: "2.3.0",
1209
1209
  description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
1210
1210
  mcpName: "io.github.Daghis/teamcity",
1211
1211
  main: "dist/index.js",
@@ -38826,6 +38826,55 @@ var TeamCityAPI = class _TeamCityAPI {
38826
38826
  // src/tools.ts
38827
38827
  var isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function";
38828
38828
  var isAxios4042 = (error3) => (0, import_axios36.isAxiosError)(error3) && error3.response?.status === 404;
38829
+ var buildIdentifierSchema = import_zod4.z.object({
38830
+ buildId: import_zod4.z.string().min(1).optional(),
38831
+ buildNumber: import_zod4.z.union([import_zod4.z.string().min(1), import_zod4.z.number().int()]).optional(),
38832
+ buildTypeId: import_zod4.z.string().min(1).optional()
38833
+ }).superRefine((value, ctx) => {
38834
+ if (!value.buildId && value.buildNumber === void 0) {
38835
+ ctx.addIssue({
38836
+ code: import_zod4.z.ZodIssueCode.custom,
38837
+ path: ["buildId"],
38838
+ message: "Either buildId or (buildNumber + buildTypeId) must be provided"
38839
+ });
38840
+ }
38841
+ if (value.buildNumber !== void 0 && !value.buildTypeId) {
38842
+ ctx.addIssue({
38843
+ code: import_zod4.z.ZodIssueCode.custom,
38844
+ path: ["buildTypeId"],
38845
+ message: "buildTypeId is required when querying by buildNumber"
38846
+ });
38847
+ }
38848
+ });
38849
+ function resolveBuildLocator(input) {
38850
+ const trimmedBuildId = typeof input.buildId === "string" ? input.buildId.trim() : void 0;
38851
+ if (trimmedBuildId && trimmedBuildId.length > 0) {
38852
+ return { locator: `id:${trimmedBuildId}`, friendlyId: `ID '${trimmedBuildId}'` };
38853
+ }
38854
+ const buildNumber = input.buildNumber !== void 0 ? String(input.buildNumber).trim() : void 0;
38855
+ const buildTypeId = input.buildTypeId?.trim();
38856
+ if (buildTypeId && buildNumber) {
38857
+ return {
38858
+ locator: `buildType:(id:${buildTypeId}),number:${buildNumber}`,
38859
+ friendlyId: `build type '${buildTypeId}' #${buildNumber}`
38860
+ };
38861
+ }
38862
+ throw new TeamCityAPIError("Unable to resolve build identifier", "INVALID_BUILD_IDENTIFIER");
38863
+ }
38864
+ var buildIdentifierInputProperties = {
38865
+ buildId: { type: "string", description: "Build ID (internal TeamCity ID)" },
38866
+ buildNumber: {
38867
+ oneOf: [
38868
+ { type: "string", description: 'Build number as TeamCity displays it (e.g. "886")' },
38869
+ { type: "number", description: "Numeric build number" }
38870
+ ],
38871
+ description: "Human-readable build number (requires buildTypeId)"
38872
+ },
38873
+ buildTypeId: {
38874
+ type: "string",
38875
+ description: "Build configuration ID (required when using buildNumber)"
38876
+ }
38877
+ };
38829
38878
  var sanitizeFileName = (artifactName) => {
38830
38879
  const base = (0, import_node_path.basename)(artifactName || "artifact");
38831
38880
  const safeBase = base.replace(/[^a-zA-Z0-9._-]/g, "_") || "artifact";
@@ -39316,31 +39365,34 @@ var DEV_TOOLS = [
39316
39365
  inputSchema: {
39317
39366
  type: "object",
39318
39367
  properties: {
39319
- buildId: { type: "string", description: "Build ID" }
39320
- },
39321
- required: ["buildId"]
39368
+ ...buildIdentifierInputProperties
39369
+ }
39322
39370
  },
39323
39371
  handler: async (args) => {
39324
- const schema = import_zod4.z.object({ buildId: import_zod4.z.string().min(1) });
39372
+ const schema = buildIdentifierSchema;
39325
39373
  return runTool(
39326
39374
  "get_build",
39327
39375
  schema,
39328
39376
  async (typed) => {
39329
39377
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
39378
+ const { locator, friendlyId } = resolveBuildLocator(typed);
39330
39379
  try {
39331
- const build2 = await adapter.getBuild(typed.buildId);
39332
- return json(build2);
39380
+ const build = await adapter.getBuild(locator);
39381
+ return json(build);
39333
39382
  } catch (error3) {
39334
39383
  if (!isAxios4042(error3)) throw error3;
39335
39384
  }
39336
- try {
39337
- const qb = await adapter.modules.buildQueue.getQueuedBuild(`id:${typed.buildId}`);
39338
- return json({ ...qb.data, state: "queued" });
39339
- } catch (queueError) {
39340
- if (!isAxios4042(queueError)) throw queueError;
39385
+ if (typed.buildId) {
39386
+ try {
39387
+ const qb = await adapter.modules.buildQueue.getQueuedBuild(`id:${typed.buildId}`);
39388
+ return json({ ...qb.data, state: "queued" });
39389
+ } catch (queueError) {
39390
+ if (!isAxios4042(queueError)) throw queueError;
39391
+ }
39392
+ const build = await adapter.getBuild(locator);
39393
+ return json(build);
39341
39394
  }
39342
- const build = await adapter.getBuild(typed.buildId);
39343
- return json(build);
39395
+ throw new TeamCityNotFoundError(`Build not found for ${friendlyId}`);
39344
39396
  },
39345
39397
  args
39346
39398
  );
@@ -39980,7 +40032,7 @@ var DEV_TOOLS = [
39980
40032
  inputSchema: {
39981
40033
  type: "object",
39982
40034
  properties: {
39983
- buildId: { type: "string", description: "Build ID" },
40035
+ ...buildIdentifierInputProperties,
39984
40036
  pageSize: { type: "number", description: "Items per page (default 100)" },
39985
40037
  maxPages: { type: "number", description: "Max pages to fetch (when all=true)" },
39986
40038
  all: { type: "boolean", description: "Fetch all pages up to maxPages" },
@@ -39988,25 +40040,26 @@ var DEV_TOOLS = [
39988
40040
  type: "string",
39989
40041
  description: "Optional fields selector for server-side projection"
39990
40042
  }
39991
- },
39992
- required: ["buildId"]
40043
+ }
39993
40044
  },
39994
40045
  handler: async (args) => {
39995
- const schema = import_zod4.z.object({
39996
- buildId: import_zod4.z.string().min(1),
39997
- pageSize: import_zod4.z.number().int().min(1).max(1e3).optional(),
39998
- maxPages: import_zod4.z.number().int().min(1).max(1e3).optional(),
39999
- all: import_zod4.z.boolean().optional(),
40000
- fields: import_zod4.z.string().min(1).optional()
40001
- });
40046
+ const schema = buildIdentifierSchema.and(
40047
+ import_zod4.z.object({
40048
+ pageSize: import_zod4.z.number().int().min(1).max(1e3).optional(),
40049
+ maxPages: import_zod4.z.number().int().min(1).max(1e3).optional(),
40050
+ all: import_zod4.z.boolean().optional(),
40051
+ fields: import_zod4.z.string().min(1).optional()
40052
+ })
40053
+ );
40002
40054
  return runTool(
40003
40055
  "list_test_failures",
40004
40056
  schema,
40005
40057
  async (typed) => {
40006
40058
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
40059
+ const { locator: buildLocator } = resolveBuildLocator(typed);
40007
40060
  const pageSize = typed.pageSize ?? 100;
40008
40061
  const baseFetch = async ({ count, start }) => {
40009
- const parts = [`build:(id:${typed.buildId})`, "status:FAILURE"];
40062
+ const parts = [`build:(${buildLocator})`, "status:FAILURE"];
40010
40063
  if (typeof count === "number") parts.push(`count:${count}`);
40011
40064
  if (typeof start === "number") parts.push(`start:${start}`);
40012
40065
  const locator = parts.join(",");
@@ -40930,7 +40983,7 @@ var DEV_TOOLS = [
40930
40983
  inputSchema: {
40931
40984
  type: "object",
40932
40985
  properties: {
40933
- buildId: { type: "string", description: "Build ID" },
40986
+ ...buildIdentifierInputProperties,
40934
40987
  artifactPath: { type: "string", description: "Artifact path or name" },
40935
40988
  encoding: {
40936
40989
  type: "string",
@@ -40947,31 +41000,33 @@ var DEV_TOOLS = [
40947
41000
  description: "Optional absolute path to write streamed content; defaults to a temp file when streaming"
40948
41001
  }
40949
41002
  },
40950
- required: ["buildId", "artifactPath"]
41003
+ required: ["artifactPath"]
40951
41004
  },
40952
41005
  handler: async (args) => {
40953
- const schema = import_zod4.z.object({
40954
- buildId: import_zod4.z.string().min(1),
40955
- artifactPath: import_zod4.z.string().min(1),
40956
- encoding: import_zod4.z.enum(["base64", "text", "stream"]).default("base64"),
40957
- maxSize: import_zod4.z.number().int().positive().optional(),
40958
- outputPath: import_zod4.z.string().min(1).optional()
40959
- });
41006
+ const schema = buildIdentifierSchema.and(
41007
+ import_zod4.z.object({
41008
+ artifactPath: import_zod4.z.string().min(1),
41009
+ encoding: import_zod4.z.enum(["base64", "text", "stream"]).default("base64"),
41010
+ maxSize: import_zod4.z.number().int().positive().optional(),
41011
+ outputPath: import_zod4.z.string().min(1).optional()
41012
+ })
41013
+ );
40960
41014
  return runTool(
40961
41015
  "download_build_artifact",
40962
41016
  schema,
40963
41017
  async (typed) => {
40964
41018
  const encoding = typed.encoding ?? "base64";
40965
41019
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
41020
+ const { locator: buildLocator } = resolveBuildLocator(typed);
40966
41021
  debug2("tools.download_build_artifact.start", {
40967
- buildId: typed.buildId,
41022
+ buildLocator,
40968
41023
  encoding,
40969
41024
  artifactPath: typed.artifactPath,
40970
41025
  maxSize: typed.maxSize,
40971
41026
  outputPath: typed.outputPath
40972
41027
  });
40973
41028
  const manager = new ArtifactManager(adapter);
40974
- const artifact = await manager.downloadArtifact(typed.buildId, typed.artifactPath, {
41029
+ const artifact = await manager.downloadArtifact(buildLocator, typed.artifactPath, {
40975
41030
  encoding,
40976
41031
  maxSize: typed.maxSize
40977
41032
  });
@@ -40990,7 +41045,7 @@ var DEV_TOOLS = [
40990
41045
  inputSchema: {
40991
41046
  type: "object",
40992
41047
  properties: {
40993
- buildId: { type: "string", description: "Build ID" },
41048
+ ...buildIdentifierInputProperties,
40994
41049
  artifactPaths: {
40995
41050
  type: "array",
40996
41051
  description: "Artifact paths or names to download",
@@ -41024,7 +41079,7 @@ var DEV_TOOLS = [
41024
41079
  description: "Optional absolute directory to write streamed artifacts; defaults to temp files when streaming"
41025
41080
  }
41026
41081
  },
41027
- required: ["buildId", "artifactPaths"]
41082
+ required: ["artifactPaths"]
41028
41083
  },
41029
41084
  handler: async (args) => {
41030
41085
  const artifactInputSchema = import_zod4.z.union([
@@ -41035,31 +41090,33 @@ var DEV_TOOLS = [
41035
41090
  downloadUrl: import_zod4.z.string().url().optional()
41036
41091
  })
41037
41092
  ]);
41038
- const schema = import_zod4.z.object({
41039
- buildId: import_zod4.z.string().min(1),
41040
- artifactPaths: import_zod4.z.array(artifactInputSchema).min(1),
41041
- encoding: import_zod4.z.enum(["base64", "text", "stream"]).default("base64"),
41042
- maxSize: import_zod4.z.number().int().positive().optional(),
41043
- outputDir: import_zod4.z.string().min(1).optional().refine((value) => value == null || (0, import_node_path.isAbsolute)(value), {
41044
- message: "outputDir must be an absolute path"
41093
+ const schema = buildIdentifierSchema.and(
41094
+ import_zod4.z.object({
41095
+ artifactPaths: import_zod4.z.array(artifactInputSchema).min(1),
41096
+ encoding: import_zod4.z.enum(["base64", "text", "stream"]).default("base64"),
41097
+ maxSize: import_zod4.z.number().int().positive().optional(),
41098
+ outputDir: import_zod4.z.string().min(1).optional().refine((value) => value == null || (0, import_node_path.isAbsolute)(value), {
41099
+ message: "outputDir must be an absolute path"
41100
+ })
41101
+ }).superRefine((value, ctx) => {
41102
+ if (value.encoding !== "stream" && value.outputDir) {
41103
+ ctx.addIssue({
41104
+ code: import_zod4.z.ZodIssueCode.custom,
41105
+ message: 'outputDir can only be provided when encoding is set to "stream"',
41106
+ path: ["outputDir"]
41107
+ });
41108
+ }
41045
41109
  })
41046
- }).superRefine((value, ctx) => {
41047
- if (value.encoding !== "stream" && value.outputDir) {
41048
- ctx.addIssue({
41049
- code: import_zod4.z.ZodIssueCode.custom,
41050
- message: 'outputDir can only be provided when encoding is set to "stream"',
41051
- path: ["outputDir"]
41052
- });
41053
- }
41054
- });
41110
+ );
41055
41111
  return runTool(
41056
41112
  "download_build_artifacts",
41057
41113
  schema,
41058
41114
  async (typed) => {
41059
41115
  const encoding = typed.encoding ?? "base64";
41060
41116
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
41117
+ const { locator: buildLocator } = resolveBuildLocator(typed);
41061
41118
  const manager = new ArtifactManager(adapter);
41062
- const requests = toNormalizedArtifactRequests(typed.artifactPaths, typed.buildId);
41119
+ const requests = toNormalizedArtifactRequests(typed.artifactPaths, buildLocator);
41063
41120
  const results = [];
41064
41121
  for (const request of requests) {
41065
41122
  try {
@@ -41148,22 +41205,23 @@ var DEV_TOOLS = [
41148
41205
  inputSchema: {
41149
41206
  type: "object",
41150
41207
  properties: {
41151
- buildId: { type: "string", description: "Build ID" },
41208
+ ...buildIdentifierInputProperties,
41152
41209
  testNameId: { type: "string", description: "Test name ID (optional)" }
41153
- },
41154
- required: ["buildId"]
41210
+ }
41155
41211
  },
41156
41212
  handler: async (args) => {
41157
- const schema = import_zod4.z.object({
41158
- buildId: import_zod4.z.string().min(1),
41159
- testNameId: import_zod4.z.string().min(1).optional()
41160
- });
41213
+ const schema = buildIdentifierSchema.and(
41214
+ import_zod4.z.object({
41215
+ testNameId: import_zod4.z.string().min(1).optional()
41216
+ })
41217
+ );
41161
41218
  return runTool(
41162
41219
  "get_test_details",
41163
41220
  schema,
41164
41221
  async (typed) => {
41165
41222
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
41166
- let locator = `build:(id:${typed.buildId})`;
41223
+ const { locator: buildLocator } = resolveBuildLocator(typed);
41224
+ let locator = `build:(${buildLocator})`;
41167
41225
  if (typed.testNameId) locator += `,test:(id:${typed.testNameId})`;
41168
41226
  const response = await adapter.modules.tests.getAllTestOccurrences(locator);
41169
41227
  return json(response.data);
@@ -41178,20 +41236,20 @@ var DEV_TOOLS = [
41178
41236
  inputSchema: {
41179
41237
  type: "object",
41180
41238
  properties: {
41181
- buildId: { type: "string", description: "Build ID to analyze" }
41182
- },
41183
- required: ["buildId"]
41239
+ ...buildIdentifierInputProperties
41240
+ }
41184
41241
  },
41185
41242
  handler: async (args) => {
41186
- const schema = import_zod4.z.object({ buildId: import_zod4.z.string().min(1) });
41243
+ const schema = buildIdentifierSchema;
41187
41244
  return runTool(
41188
41245
  "analyze_build_problems",
41189
41246
  schema,
41190
41247
  async (typed) => {
41191
41248
  const adapter = createAdapterFromTeamCityAPI(TeamCityAPI.getInstance());
41192
- const build = await adapter.getBuild(typed.buildId);
41193
- const problems = await adapter.modules.builds.getBuildProblems(`id:${typed.buildId}`);
41194
- const failures = await adapter.listTestFailures(typed.buildId);
41249
+ const { locator: buildLocator } = resolveBuildLocator(typed);
41250
+ const build = await adapter.getBuild(buildLocator);
41251
+ const problems = await adapter.modules.builds.getBuildProblems(buildLocator);
41252
+ const failures = await adapter.listTestFailures(buildLocator);
41195
41253
  return json({
41196
41254
  buildStatus: build.status,
41197
41255
  statusText: build.statusText,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daghis/teamcity-mcp",
3
- "version": "2.2.3",
3
+ "version": "2.3.0",
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",
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": "2.2.3",
10
+ "version": "2.3.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@daghis/teamcity-mcp",
16
- "version": "2.2.3",
16
+ "version": "2.3.0",
17
17
  "runtimeHint": "npx",
18
18
  "runtimeArguments": [
19
19
  {