@daghis/teamcity-mcp 2.12.9 → 2.12.11

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
+ ## [2.12.11](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.10...teamcity-mcp-v2.12.11) (2026-10-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **build-log:** keep memory bounded for log chunks, tails and streams ([#549](https://github.com/Daghis/teamcity-mcp/issues/549)) ([7815ab1](https://github.com/Daghis/teamcity-mcp/commit/7815ab10a2b7a395a680d665ca68ebe52a7d629e))
9
+
10
+ ## [2.12.10](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.9...teamcity-mcp-v2.12.10) (2026-10-21)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **security:** bump follow-redirects to 1.16.1 ([#547](https://github.com/Daghis/teamcity-mcp/issues/547)) ([cc52893](https://github.com/Daghis/teamcity-mcp/commit/cc5289336b3db586d054bbbbace74e4fcee9988d))
16
+
3
17
  ## [2.12.9](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.12.8...teamcity-mcp-v2.12.9) (2026-10-20)
4
18
 
5
19
 
package/dist/index.js CHANGED
@@ -68,11 +68,49 @@ async function* sliceLines(source, startLine, endLine) {
68
68
  }
69
69
  }
70
70
  }
71
- var import_stream, isReadableStream, destroyStream, discardStreamBody, toBuffer, sliceStreamLines;
71
+ async function* readLines(source, keep = () => true) {
72
+ const decoder = new import_string_decoder.StringDecoder("utf8");
73
+ let pending = [];
74
+ let started = false;
75
+ let index = 0;
76
+ let afterCarriageReturn = false;
77
+ const append = (piece) => {
78
+ if (piece) {
79
+ started = true;
80
+ if (keep(index)) {
81
+ pending.push(piece);
82
+ }
83
+ }
84
+ };
85
+ for await (const raw of source) {
86
+ let text = decoder.write(toBuffer(raw));
87
+ if (afterCarriageReturn && text.startsWith("\n")) {
88
+ text = text.slice(1);
89
+ }
90
+ afterCarriageReturn = text.endsWith("\r");
91
+ const segments = text.split(LINE_BREAK);
92
+ const unfinished = segments.splice(-1).join("");
93
+ for (const segment of segments) {
94
+ append(segment);
95
+ const line = pending.join("");
96
+ pending = [];
97
+ started = false;
98
+ index += 1;
99
+ yield line;
100
+ }
101
+ append(unfinished);
102
+ }
103
+ append(decoder.end());
104
+ if (started) {
105
+ yield pending.join("");
106
+ }
107
+ }
108
+ var import_stream, import_string_decoder, isReadableStream, destroyStream, discardStreamBody, toBuffer, sliceStreamLines, LINE_BREAK, readLineRange, readLastLines;
72
109
  var init_stream = __esm({
73
110
  "src/teamcity/utils/stream.ts"() {
74
111
  "use strict";
75
112
  import_stream = require("stream");
113
+ import_string_decoder = require("string_decoder");
76
114
  isReadableStream = (value) => typeof value === "object" && value !== null && typeof value.pipe === "function" && typeof value.on === "function";
77
115
  destroyStream = (stream) => {
78
116
  stream.on("error", () => void 0);
@@ -104,6 +142,46 @@ var init_stream = __esm({
104
142
  };
105
143
  return slice;
106
144
  };
145
+ LINE_BREAK = /\r\n|\r|\n/;
146
+ readLineRange = async (source, startLine, lineCount) => {
147
+ const endLine = startLine + lineCount;
148
+ const lines = [];
149
+ let index = 0;
150
+ try {
151
+ const inRange = (line) => line >= startLine && line < endLine;
152
+ for await (const line of readLines(source, inRange)) {
153
+ if (index >= endLine) {
154
+ return { lines, startLine, nextStartLine: endLine };
155
+ }
156
+ if (index >= startLine) {
157
+ lines.push(line);
158
+ }
159
+ index += 1;
160
+ }
161
+ } finally {
162
+ destroyStream(source);
163
+ }
164
+ return { lines, startLine: Math.min(startLine, index), totalLines: index };
165
+ };
166
+ readLastLines = async (source, lineCount) => {
167
+ const window = [];
168
+ let totalLines = 0;
169
+ try {
170
+ for await (const line of readLines(source)) {
171
+ if (lineCount > 0) {
172
+ window[totalLines % lineCount] = line;
173
+ }
174
+ totalLines += 1;
175
+ }
176
+ } finally {
177
+ destroyStream(source);
178
+ }
179
+ if (totalLines <= lineCount) {
180
+ return { lines: window, totalLines };
181
+ }
182
+ const oldest = totalLines % lineCount;
183
+ return { lines: [...window.slice(oldest), ...window.slice(0, oldest)], totalLines };
184
+ };
107
185
  }
108
186
  });
109
187
 
@@ -1310,7 +1388,7 @@ function debug2(message, meta) {
1310
1388
  // package.json
1311
1389
  var package_default = {
1312
1390
  name: "@daghis/teamcity-mcp",
1313
- version: "2.12.9",
1391
+ version: "2.12.11",
1314
1392
  description: "Model Control Protocol server for TeamCity CI/CD integration with AI coding assistants",
1315
1393
  mcpName: "io.github.Daghis/teamcity",
1316
1394
  main: "dist/index.js",
@@ -1415,7 +1493,7 @@ var package_default = {
1415
1493
  },
1416
1494
  axios: "^1.20.0",
1417
1495
  "body-parser": "^2.2.1",
1418
- "follow-redirects": "^1.16.0",
1496
+ "follow-redirects": "^1.16.1",
1419
1497
  "glob@>=10.2.0 <10.5.0": "^10.5.0",
1420
1498
  hono: "^4.12.14",
1421
1499
  "lodash-es": "^4.18.0",
@@ -1427,7 +1505,7 @@ var package_default = {
1427
1505
 
1428
1506
  // src/tools.ts
1429
1507
  var import_node_crypto = require("node:crypto");
1430
- var import_node_fs2 = require("node:fs");
1508
+ var import_node_fs3 = require("node:fs");
1431
1509
  var import_node_os = require("node:os");
1432
1510
  var import_node_path = require("node:path");
1433
1511
  var import_promises = require("node:stream/promises");
@@ -4119,6 +4197,7 @@ function createAdapterFromTeamCityAPI(api, options = {}) {
4119
4197
  triggerBuild: (buildTypeId, branchName, comment) => api.triggerBuild(buildTypeId, branchName, comment),
4120
4198
  getBuildLog: (buildId) => api.getBuildLog(buildId),
4121
4199
  getBuildLogChunk: (buildId, options2) => api.getBuildLogChunk(buildId, options2),
4200
+ getBuildLogTail: (buildId, lineCount) => api.getBuildLogTail(buildId, lineCount),
4122
4201
  listBuildTypes: (projectId) => api.listBuildTypes(projectId),
4123
4202
  getBuildType: (buildTypeId) => api.getBuildType(buildTypeId),
4124
4203
  listTestFailures: (buildId) => api.listTestFailures(buildId),
@@ -4280,6 +4359,75 @@ function sleep(ms) {
4280
4359
  return new Promise((resolve2) => setTimeout(resolve2, ms));
4281
4360
  }
4282
4361
 
4362
+ // src/utils/disk-space.ts
4363
+ var import_node_fs2 = require("node:fs");
4364
+ var import_node_stream = require("node:stream");
4365
+ var DEFAULT_MIN_FREE_DISK_BYTES = 1024 ** 3;
4366
+ var RECHECK_EVERY_BYTES = 64 * 1024 ** 2;
4367
+ var formatBytes = (bytes) => bytes >= 1024 ** 3 ? `${(bytes / 1024 ** 3).toFixed(2)} GiB` : `${(bytes / 1024 ** 2).toFixed(1)} MiB`;
4368
+ var InsufficientDiskSpaceError = class extends Error {
4369
+ constructor(directory, freeBytes, minFreeBytes, requiredBytes) {
4370
+ const need = requiredBytes === void 0 ? "" : `${formatBytes(requiredBytes)} needed, `;
4371
+ super(
4372
+ `Not enough disk space in ${directory}: ${need}${formatBytes(freeBytes)} free, at least ${formatBytes(minFreeBytes)} must stay free`
4373
+ );
4374
+ this.directory = directory;
4375
+ this.freeBytes = freeBytes;
4376
+ this.minFreeBytes = minFreeBytes;
4377
+ this.requiredBytes = requiredBytes;
4378
+ this.name = "InsufficientDiskSpaceError";
4379
+ }
4380
+ directory;
4381
+ freeBytes;
4382
+ minFreeBytes;
4383
+ requiredBytes;
4384
+ };
4385
+ var getFreeDiskBytes = async (directory) => {
4386
+ const stats = await import_node_fs2.promises.statfs(directory);
4387
+ return stats.bavail * stats.bsize;
4388
+ };
4389
+ var assertDiskSpace = async (directory, requiredBytes = 0, minFreeBytes = DEFAULT_MIN_FREE_DISK_BYTES) => {
4390
+ const freeBytes = await getFreeDiskBytes(directory);
4391
+ if (freeBytes - requiredBytes < minFreeBytes) {
4392
+ throw new InsufficientDiskSpaceError(
4393
+ directory,
4394
+ freeBytes,
4395
+ minFreeBytes,
4396
+ requiredBytes > 0 ? requiredBytes : void 0
4397
+ );
4398
+ }
4399
+ return freeBytes;
4400
+ };
4401
+ var createDiskSpaceGuard = (directory, initialFreeBytes, minFreeBytes, recheckEveryBytes = RECHECK_EVERY_BYTES) => {
4402
+ let written = 0;
4403
+ let sinceCheck = 0;
4404
+ let reportedFreeBytes = initialFreeBytes;
4405
+ const estimatedFreeBytes = () => Math.min(reportedFreeBytes, initialFreeBytes - written);
4406
+ return new import_node_stream.Transform({
4407
+ transform(chunk, _encoding, callback) {
4408
+ written += chunk.length;
4409
+ sinceCheck += chunk.length;
4410
+ if (estimatedFreeBytes() < minFreeBytes) {
4411
+ callback(new InsufficientDiskSpaceError(directory, estimatedFreeBytes(), minFreeBytes));
4412
+ return;
4413
+ }
4414
+ if (sinceCheck < recheckEveryBytes) {
4415
+ callback(null, chunk);
4416
+ return;
4417
+ }
4418
+ sinceCheck = 0;
4419
+ getFreeDiskBytes(directory).then((freeBytes) => {
4420
+ reportedFreeBytes = freeBytes;
4421
+ if (estimatedFreeBytes() < minFreeBytes) {
4422
+ callback(new InsufficientDiskSpaceError(directory, estimatedFreeBytes(), minFreeBytes));
4423
+ } else {
4424
+ callback(null, chunk);
4425
+ }
4426
+ }, callback);
4427
+ }
4428
+ });
4429
+ };
4430
+
4283
4431
  // src/utils/list-builds-locator.ts
4284
4432
  var SIMPLE_BRANCH_VALUES = /* @__PURE__ */ new Set([
4285
4433
  "default:true",
@@ -4931,6 +5079,9 @@ axiosRetry.linearDelay = linearDelay;
4931
5079
  axiosRetry.isRetryableError = isRetryableError2;
4932
5080
  var esm_default = axiosRetry;
4933
5081
 
5082
+ // src/api-client.ts
5083
+ var import_stream4 = require("stream");
5084
+
4934
5085
  // src/teamcity/auth.ts
4935
5086
  var import_node_string_decoder = require("node:string_decoder");
4936
5087
  var import_crypto = require("crypto");
@@ -38850,20 +39001,17 @@ var TeamCityAPI = class _TeamCityAPI {
38850
39001
  nextStartLine: lines.length === lineCount ? startLine + lines.length : void 0
38851
39002
  };
38852
39003
  } catch {
38853
- const full = await this.getBuildLog(buildId);
38854
- const allLines = full.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
38855
- if (allLines.length > 0 && allLines[allLines.length - 1] === "") allLines.pop();
38856
- const start = Math.max(0, Math.min(startLine, allLines.length));
38857
- const end = Math.min(allLines.length, start + lineCount);
38858
- const slice = allLines.slice(start, end);
38859
- return {
38860
- lines: slice,
38861
- startLine: start,
38862
- nextStartLine: end < allLines.length ? end : void 0,
38863
- totalLines: allLines.length
38864
- };
39004
+ return readLineRange(await this.streamBuildLog(buildId), startLine, lineCount);
38865
39005
  }
38866
39006
  }
39007
+ async getBuildLogTail(buildId, lineCount) {
39008
+ return readLastLines(await this.streamBuildLog(buildId), lineCount);
39009
+ }
39010
+ async streamBuildLog(buildId) {
39011
+ const response = await this.downloadBuildLog(buildId, { responseType: "stream" });
39012
+ const data = response.data;
39013
+ return isReadableStream(data) ? data : import_stream4.Readable.from([String(data ?? "")]);
39014
+ }
38867
39015
  async listBuildTypes(projectId) {
38868
39016
  const locator = projectId ? `affectedProject:(id:${projectId})` : void 0;
38869
39017
  const response = await this.buildTypes.getAllBuildTypes(locator);
@@ -39113,7 +39261,7 @@ var ensureUniquePath = async (candidate) => {
39113
39261
  const probe = async (attempt) => {
39114
39262
  const next = attempt === 0 ? candidate : `${stem}-${attempt}${ext}`;
39115
39263
  try {
39116
- const handle = await import_node_fs2.promises.open(next, "wx");
39264
+ const handle = await import_node_fs3.promises.open(next, "wx");
39117
39265
  await handle.close();
39118
39266
  return next;
39119
39267
  } catch (error3) {
@@ -39130,7 +39278,7 @@ var resolveStreamOutputPath = async (artifact, options) => {
39130
39278
  const { explicitOutputPath } = options;
39131
39279
  if (typeof explicitOutputPath === "string" && explicitOutputPath.length > 0) {
39132
39280
  const target = explicitOutputPath;
39133
- await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(target), { recursive: true });
39281
+ await import_node_fs3.promises.mkdir((0, import_node_path.dirname)(target), { recursive: true });
39134
39282
  return target;
39135
39283
  }
39136
39284
  if (options.outputDir) {
@@ -39143,17 +39291,17 @@ var resolveStreamOutputPath = async (artifact, options) => {
39143
39291
  if (relativePath.startsWith("..") || (0, import_node_path.isAbsolute)(relativePath)) {
39144
39292
  throw new Error("Resolved artifact path escapes the configured output directory");
39145
39293
  }
39146
- await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(candidate), { recursive: true });
39294
+ await import_node_fs3.promises.mkdir((0, import_node_path.dirname)(candidate), { recursive: true });
39147
39295
  return ensureUniquePath(candidate);
39148
39296
  }
39149
39297
  const tempFilePath = (0, import_node_path.join)((0, import_node_os.tmpdir)(), buildRandomFileName(artifact.name));
39150
- await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(tempFilePath), { recursive: true });
39298
+ await import_node_fs3.promises.mkdir((0, import_node_path.dirname)(tempFilePath), { recursive: true });
39151
39299
  return tempFilePath;
39152
39300
  };
39153
39301
  var writeArtifactStreamToDisk = async (artifact, stream, options) => {
39154
39302
  const targetPath = await resolveStreamOutputPath(artifact, options);
39155
- await (0, import_promises.pipeline)(stream, (0, import_node_fs2.createWriteStream)(targetPath));
39156
- const stats = await import_node_fs2.promises.stat(targetPath);
39303
+ await (0, import_promises.pipeline)(stream, (0, import_node_fs3.createWriteStream)(targetPath));
39304
+ const stats = await import_node_fs3.promises.stat(targetPath);
39157
39305
  return { outputPath: targetPath, bytesWritten: stats.size };
39158
39306
  };
39159
39307
  var buildArtifactPayload = async (artifact, encoding, options) => {
@@ -40241,13 +40389,17 @@ var DEV_TOOLS = [
40241
40389
  tail: { type: "boolean", description: "Tail mode: return last N lines" },
40242
40390
  encoding: {
40243
40391
  type: "string",
40244
- description: "Response encoding: 'text' (default) or 'stream'",
40392
+ description: "Response encoding: 'text' (default) or 'stream'. 'stream' writes the whole log, or the requested line range, to outputPath; logs can be several GB",
40245
40393
  enum: ["text", "stream"],
40246
40394
  default: "text"
40247
40395
  },
40248
40396
  outputPath: {
40249
40397
  type: "string",
40250
40398
  description: "Optional absolute path to write streamed logs; defaults to a temp file when streaming"
40399
+ },
40400
+ minFreeDiskMb: {
40401
+ type: "number",
40402
+ description: "Streaming only: disk space in MiB that must stay free on the target disk, otherwise the download aborts (default 1024, 0 disables the reserve)"
40251
40403
  }
40252
40404
  },
40253
40405
  required: []
@@ -40263,7 +40415,8 @@ var DEV_TOOLS = [
40263
40415
  lineCount: import_zod4.z.coerce.number().int().min(1).max(5e3).optional(),
40264
40416
  tail: import_zod4.z.boolean().optional(),
40265
40417
  encoding: import_zod4.z.enum(["text", "stream"]).default("text"),
40266
- outputPath: import_zod4.z.string().min(1).optional()
40418
+ outputPath: import_zod4.z.string().min(1).optional(),
40419
+ minFreeDiskMb: import_zod4.z.coerce.number().min(0).optional()
40267
40420
  }).superRefine((value, ctx) => {
40268
40421
  if (!value.buildId && typeof value.buildNumber === "undefined") {
40269
40422
  ctx.addIssue({
@@ -40358,12 +40511,11 @@ var DEV_TOOLS = [
40358
40511
  const attemptBuffered = async () => {
40359
40512
  if (typed.tail) {
40360
40513
  const count = typed.lineCount ?? typed.pageSize ?? 500;
40361
- const full = await adapter.getBuildLog(effectiveBuildId);
40362
- const allLines = full.replace(/\r\n/g, "\n").replace(/\r/g, "\n").split("\n");
40363
- if (allLines.length > 0 && allLines[allLines.length - 1] === "") allLines.pop();
40364
- const total = allLines.length;
40365
- const start = Math.max(0, total - count);
40366
- const lines = allLines.slice(start);
40514
+ const { lines, totalLines: total } = await adapter.getBuildLogTail(
40515
+ effectiveBuildId,
40516
+ count
40517
+ );
40518
+ const start = total - lines.length;
40367
40519
  return json({
40368
40520
  lines,
40369
40521
  meta: {
@@ -40406,11 +40558,9 @@ var DEV_TOOLS = [
40406
40558
  const attemptStream = async () => {
40407
40559
  const effectivePageSize = typed.lineCount ?? typed.pageSize ?? 500;
40408
40560
  const startLine = typeof typed.startLine === "number" ? typed.startLine : ((typed.page ?? 1) - 1) * effectivePageSize;
40561
+ const ranged = typed.startLine !== void 0 || typed.page !== void 0 || typed.lineCount !== void 0 || typed.pageSize !== void 0;
40409
40562
  const response = await adapter.downloadBuildLogContent(effectiveBuildId, {
40410
- params: {
40411
- start: startLine,
40412
- count: effectivePageSize
40413
- },
40563
+ params: ranged ? { start: startLine, count: effectivePageSize } : void 0,
40414
40564
  responseType: "stream"
40415
40565
  });
40416
40566
  const stream = response.data;
@@ -40420,9 +40570,34 @@ var DEV_TOOLS = [
40420
40570
  const safeBuildId = effectiveBuildId.replace(/[^a-zA-Z0-9._-]/g, "_") || "build";
40421
40571
  const defaultFileName = `build-log-${safeBuildId}-${startLine}-${(0, import_node_crypto.randomUUID)()}.log`;
40422
40572
  const targetPath = typed.outputPath ?? (0, import_node_path.join)((0, import_node_os.tmpdir)(), defaultFileName);
40423
- await import_node_fs2.promises.mkdir((0, import_node_path.dirname)(targetPath), { recursive: true });
40424
- await (0, import_promises.pipeline)(stream, (0, import_node_fs2.createWriteStream)(targetPath));
40425
- const stats = await import_node_fs2.promises.stat(targetPath);
40573
+ const targetDir = (0, import_node_path.dirname)(targetPath);
40574
+ const contentLength = Number(response.headers?.["content-length"]);
40575
+ const minFreeBytes = typed.minFreeDiskMb === void 0 ? DEFAULT_MIN_FREE_DISK_BYTES : typed.minFreeDiskMb * 1024 ** 2;
40576
+ let freeBytes;
40577
+ try {
40578
+ await import_node_fs3.promises.mkdir(targetDir, { recursive: true });
40579
+ freeBytes = await assertDiskSpace(
40580
+ targetDir,
40581
+ !ranged && Number.isFinite(contentLength) ? contentLength : 0,
40582
+ minFreeBytes
40583
+ );
40584
+ } catch (error3) {
40585
+ stream.destroy();
40586
+ throw error3;
40587
+ }
40588
+ try {
40589
+ await (0, import_promises.pipeline)(
40590
+ stream,
40591
+ createDiskSpaceGuard(targetDir, freeBytes, minFreeBytes),
40592
+ (0, import_node_fs3.createWriteStream)(targetPath)
40593
+ );
40594
+ } catch (error3) {
40595
+ if (error3 instanceof InsufficientDiskSpaceError) {
40596
+ await import_node_fs3.promises.rm(targetPath, { force: true });
40597
+ }
40598
+ throw error3;
40599
+ }
40600
+ const stats = await import_node_fs3.promises.stat(targetPath);
40426
40601
  const page = Math.floor(startLine / effectivePageSize) + 1;
40427
40602
  return json({
40428
40603
  encoding: "stream",
@@ -40432,8 +40607,8 @@ var DEV_TOOLS = [
40432
40607
  buildId: effectiveBuildId,
40433
40608
  buildNumber: typeof typed.buildNumber !== "undefined" ? String(typed.buildNumber) : void 0,
40434
40609
  buildTypeId: typed.buildTypeId,
40435
- page,
40436
- pageSize: effectivePageSize,
40610
+ page: ranged ? page : void 0,
40611
+ pageSize: ranged ? effectivePageSize : void 0,
40437
40612
  startLine
40438
40613
  }
40439
40614
  });
@@ -45117,7 +45292,7 @@ var FULL_MODE_TOOLS = [
45117
45292
  if (typedArgs.privateKeyContent && typedArgs.privateKeyPath) {
45118
45293
  throw new Error("Provide only one of privateKeyContent or privateKeyPath, not both");
45119
45294
  }
45120
- const keyContent = typedArgs.privateKeyPath ? await import_node_fs2.promises.readFile(typedArgs.privateKeyPath, "utf-8") : typedArgs.privateKeyContent;
45295
+ const keyContent = typedArgs.privateKeyPath ? await import_node_fs3.promises.readFile(typedArgs.privateKeyPath, "utf-8") : typedArgs.privateKeyContent;
45121
45296
  const formData = new FormData();
45122
45297
  formData.append("privateKey", new Blob([keyContent]), "key");
45123
45298
  const api = TeamCityAPI.getInstance();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daghis/teamcity-mcp",
3
- "version": "2.12.9",
3
+ "version": "2.12.11",
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",
@@ -105,7 +105,7 @@
105
105
  },
106
106
  "axios": "^1.20.0",
107
107
  "body-parser": "^2.2.1",
108
- "follow-redirects": "^1.16.0",
108
+ "follow-redirects": "^1.16.1",
109
109
  "glob@>=10.2.0 <10.5.0": "^10.5.0",
110
110
  "hono": "^4.12.14",
111
111
  "lodash-es": "^4.18.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": "2.12.9",
10
+ "version": "2.12.11",
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.12.9",
16
+ "version": "2.12.11",
17
17
  "runtimeHint": "npx",
18
18
  "runtimeArguments": [
19
19
  {