@daghis/teamcity-mcp 0.2.0 → 0.2.1

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.
@@ -39,7 +39,27 @@ jobs:
39
39
  - name: Verify package contents
40
40
  run: npm pack --dry-run
41
41
 
42
+ - name: Read package version
43
+ id: pkg
44
+ run: |
45
+ node -e "console.log('version=' + require('./package.json').version)" >> "$GITHUB_OUTPUT"
46
+
47
+ - name: Check if version already exists on npm
48
+ id: exists
49
+ run: |
50
+ set -e
51
+ PKG_NAME=$(node -p -e "require('./package.json').name")
52
+ VERSION=${{ steps.pkg.outputs.version }}
53
+ if npm view "${PKG_NAME}@${VERSION}" version > /dev/null 2>&1; then
54
+ echo "exists=true" >> "$GITHUB_OUTPUT"
55
+ echo "Version ${VERSION} already published for ${PKG_NAME}; skipping publish."
56
+ else
57
+ echo "exists=false" >> "$GITHUB_OUTPUT"
58
+ echo "Version ${VERSION} not found on registry; will publish."
59
+ fi
60
+
42
61
  - name: Publish to npm
62
+ if: steps.exists.outputs.exists == 'false'
43
63
  env:
44
64
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
45
65
  NPM_CONFIG_PROVENANCE: 'true'
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.1](https://github.com/Daghis/teamcity-mcp/compare/v0.2.0...v0.2.1) (2025-09-12)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **mcp:** normalize TeamCity errors and wrap server info/metrics with runTool ([#53](https://github.com/Daghis/teamcity-mcp/issues/53)) ([2c8ab85](https://github.com/Daghis/teamcity-mcp/commit/2c8ab855a85e5faec4216a498c089e3a1a93ed7b))
9
+
3
10
  ## [0.2.0](https://github.com/Daghis/teamcity-mcp/compare/v0.1.2...v0.2.0) (2025-09-11)
4
11
 
5
12
 
package/dist/index.js CHANGED
@@ -11652,49 +11652,8 @@ var require_follow_redirects = __commonJS({
11652
11652
  }
11653
11653
  });
11654
11654
 
11655
- // node_modules/is-retry-allowed/index.js
11656
- var require_is_retry_allowed = __commonJS({
11657
- "node_modules/is-retry-allowed/index.js"(exports2, module2) {
11658
- "use strict";
11659
- var denyList = /* @__PURE__ */ new Set([
11660
- "ENOTFOUND",
11661
- "ENETUNREACH",
11662
- // SSL errors from https://github.com/nodejs/node/blob/fc8e3e2cdc521978351de257030db0076d79e0ab/src/crypto/crypto_common.cc#L301-L328
11663
- "UNABLE_TO_GET_ISSUER_CERT",
11664
- "UNABLE_TO_GET_CRL",
11665
- "UNABLE_TO_DECRYPT_CERT_SIGNATURE",
11666
- "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
11667
- "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
11668
- "CERT_SIGNATURE_FAILURE",
11669
- "CRL_SIGNATURE_FAILURE",
11670
- "CERT_NOT_YET_VALID",
11671
- "CERT_HAS_EXPIRED",
11672
- "CRL_NOT_YET_VALID",
11673
- "CRL_HAS_EXPIRED",
11674
- "ERROR_IN_CERT_NOT_BEFORE_FIELD",
11675
- "ERROR_IN_CERT_NOT_AFTER_FIELD",
11676
- "ERROR_IN_CRL_LAST_UPDATE_FIELD",
11677
- "ERROR_IN_CRL_NEXT_UPDATE_FIELD",
11678
- "OUT_OF_MEM",
11679
- "DEPTH_ZERO_SELF_SIGNED_CERT",
11680
- "SELF_SIGNED_CERT_IN_CHAIN",
11681
- "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
11682
- "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
11683
- "CERT_CHAIN_TOO_LONG",
11684
- "CERT_REVOKED",
11685
- "INVALID_CA",
11686
- "PATH_LENGTH_EXCEEDED",
11687
- "INVALID_PURPOSE",
11688
- "CERT_UNTRUSTED",
11689
- "CERT_REJECTED",
11690
- "HOSTNAME_MISMATCH"
11691
- ]);
11692
- module2.exports = (error2) => !denyList.has(error2 && error2.code);
11693
- }
11694
- });
11695
-
11696
11655
  // src/teamcity/errors.ts
11697
- function isRetryableError2(error2) {
11656
+ function isRetryableError(error2) {
11698
11657
  if (error2 instanceof TeamCityAPIError) {
11699
11658
  if (error2 instanceof TeamCityNetworkError) {
11700
11659
  return true;
@@ -11836,6 +11795,47 @@ var init_errors = __esm({
11836
11795
  }
11837
11796
  });
11838
11797
 
11798
+ // node_modules/is-retry-allowed/index.js
11799
+ var require_is_retry_allowed = __commonJS({
11800
+ "node_modules/is-retry-allowed/index.js"(exports2, module2) {
11801
+ "use strict";
11802
+ var denyList = /* @__PURE__ */ new Set([
11803
+ "ENOTFOUND",
11804
+ "ENETUNREACH",
11805
+ // SSL errors from https://github.com/nodejs/node/blob/fc8e3e2cdc521978351de257030db0076d79e0ab/src/crypto/crypto_common.cc#L301-L328
11806
+ "UNABLE_TO_GET_ISSUER_CERT",
11807
+ "UNABLE_TO_GET_CRL",
11808
+ "UNABLE_TO_DECRYPT_CERT_SIGNATURE",
11809
+ "UNABLE_TO_DECRYPT_CRL_SIGNATURE",
11810
+ "UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY",
11811
+ "CERT_SIGNATURE_FAILURE",
11812
+ "CRL_SIGNATURE_FAILURE",
11813
+ "CERT_NOT_YET_VALID",
11814
+ "CERT_HAS_EXPIRED",
11815
+ "CRL_NOT_YET_VALID",
11816
+ "CRL_HAS_EXPIRED",
11817
+ "ERROR_IN_CERT_NOT_BEFORE_FIELD",
11818
+ "ERROR_IN_CERT_NOT_AFTER_FIELD",
11819
+ "ERROR_IN_CRL_LAST_UPDATE_FIELD",
11820
+ "ERROR_IN_CRL_NEXT_UPDATE_FIELD",
11821
+ "OUT_OF_MEM",
11822
+ "DEPTH_ZERO_SELF_SIGNED_CERT",
11823
+ "SELF_SIGNED_CERT_IN_CHAIN",
11824
+ "UNABLE_TO_GET_ISSUER_CERT_LOCALLY",
11825
+ "UNABLE_TO_VERIFY_LEAF_SIGNATURE",
11826
+ "CERT_CHAIN_TOO_LONG",
11827
+ "CERT_REVOKED",
11828
+ "INVALID_CA",
11829
+ "PATH_LENGTH_EXCEEDED",
11830
+ "INVALID_PURPOSE",
11831
+ "CERT_UNTRUSTED",
11832
+ "CERT_REJECTED",
11833
+ "HOSTNAME_MISMATCH"
11834
+ ]);
11835
+ module2.exports = (error2) => !denyList.has(error2 && error2.code);
11836
+ }
11837
+ });
11838
+
11839
11839
  // src/teamcity/build-status-manager.ts
11840
11840
  var build_status_manager_exports = {};
11841
11841
  __export(build_status_manager_exports, {
@@ -16510,6 +16510,9 @@ function formatError(err, context) {
16510
16510
  };
16511
16511
  }
16512
16512
 
16513
+ // src/middleware/global-error-handler.ts
16514
+ init_errors();
16515
+
16513
16516
  // src/utils/error-logger.ts
16514
16517
  var ErrorLogger = class _ErrorLogger {
16515
16518
  static instance;
@@ -16648,6 +16651,14 @@ var GlobalErrorHandler = class _GlobalErrorHandler {
16648
16651
  * Transform raw errors into structured MCP errors
16649
16652
  */
16650
16653
  transformError(error2, context) {
16654
+ if (error2 instanceof TeamCityAPIError) {
16655
+ return new MCPTeamCityError(
16656
+ this.sanitizeErrorMessage(error2.message),
16657
+ error2.statusCode ?? 500,
16658
+ error2.code,
16659
+ context.requestId
16660
+ );
16661
+ }
16651
16662
  if (error2 instanceof MCPToolError) {
16652
16663
  if (this.options.sanitizeErrors) {
16653
16664
  const sanitizedMessage = this.sanitizeErrorMessage(error2.message);
@@ -16787,20 +16798,20 @@ function isNetworkError(error2) {
16787
16798
  }
16788
16799
  var SAFE_HTTP_METHODS = ["get", "head", "options"];
16789
16800
  var IDEMPOTENT_HTTP_METHODS = SAFE_HTTP_METHODS.concat(["put", "delete"]);
16790
- function isRetryableError(error2) {
16801
+ function isRetryableError2(error2) {
16791
16802
  return error2.code !== "ECONNABORTED" && (!error2.response || error2.response.status === 429 || error2.response.status >= 500 && error2.response.status <= 599);
16792
16803
  }
16793
16804
  function isSafeRequestError(error2) {
16794
16805
  if (!error2.config?.method) {
16795
16806
  return false;
16796
16807
  }
16797
- return isRetryableError(error2) && SAFE_HTTP_METHODS.indexOf(error2.config.method) !== -1;
16808
+ return isRetryableError2(error2) && SAFE_HTTP_METHODS.indexOf(error2.config.method) !== -1;
16798
16809
  }
16799
16810
  function isIdempotentRequestError(error2) {
16800
16811
  if (!error2.config?.method) {
16801
16812
  return false;
16802
16813
  }
16803
- return isRetryableError(error2) && IDEMPOTENT_HTTP_METHODS.indexOf(error2.config.method) !== -1;
16814
+ return isRetryableError2(error2) && IDEMPOTENT_HTTP_METHODS.indexOf(error2.config.method) !== -1;
16804
16815
  }
16805
16816
  function isNetworkOrIdempotentRequestError(error2) {
16806
16817
  return isNetworkError(error2) || isIdempotentRequestError(error2);
@@ -16947,11 +16958,12 @@ axiosRetry.isIdempotentRequestError = isIdempotentRequestError;
16947
16958
  axiosRetry.isNetworkOrIdempotentRequestError = isNetworkOrIdempotentRequestError;
16948
16959
  axiosRetry.exponentialDelay = exponentialDelay;
16949
16960
  axiosRetry.linearDelay = linearDelay;
16950
- axiosRetry.isRetryableError = isRetryableError;
16961
+ axiosRetry.isRetryableError = isRetryableError2;
16951
16962
  var esm_default = axiosRetry;
16952
16963
 
16953
16964
  // src/teamcity/auth.ts
16954
16965
  var import_crypto2 = require("crypto");
16966
+ init_errors();
16955
16967
  function generateRequestId() {
16956
16968
  return (0, import_crypto2.randomUUID)();
16957
16969
  }
@@ -16972,36 +16984,6 @@ function addRequestId(config2) {
16972
16984
  });
16973
16985
  return config2;
16974
16986
  }
16975
- function extractErrorDetails(error2) {
16976
- const requestId = error2.config?.requestId;
16977
- if (error2.response != null) {
16978
- const data = error2.response.data;
16979
- return {
16980
- code: data?.code ?? `HTTP_${error2.response.status}`,
16981
- message: data?.message ?? error2.message,
16982
- details: data?.details ?? JSON.stringify(data),
16983
- requestId,
16984
- statusCode: error2.response.status,
16985
- originalError: error2
16986
- };
16987
- } else if (error2.request != null) {
16988
- return {
16989
- code: "NO_RESPONSE",
16990
- message: "No response received from TeamCity server",
16991
- details: error2.message,
16992
- requestId,
16993
- originalError: error2
16994
- };
16995
- } else {
16996
- return {
16997
- code: "REQUEST_SETUP_ERROR",
16998
- message: "Error setting up the request",
16999
- details: error2.message,
17000
- requestId,
17001
- originalError: error2
17002
- };
17003
- }
17004
- }
17005
16987
  function logResponse(response) {
17006
16988
  const requestId = response.config?.requestId;
17007
16989
  const meta = response.config._tcMeta;
@@ -17018,7 +17000,8 @@ function logResponse(response) {
17018
17000
  return response;
17019
17001
  }
17020
17002
  function logAndTransformError(error2) {
17021
- const teamcityError = extractErrorDetails(error2);
17003
+ const requestId = error2.config?.requestId;
17004
+ const tcError = TeamCityAPIError.fromAxiosError(error2, requestId);
17022
17005
  const meta = error2.config?._tcMeta;
17023
17006
  const duration = meta?.start ? Date.now() - meta.start : void 0;
17024
17007
  const sanitize = (val) => {
@@ -17032,14 +17015,14 @@ function logAndTransformError(error2) {
17032
17015
  }
17033
17016
  };
17034
17017
  error("TeamCity API request failed", void 0, {
17035
- requestId: teamcityError.requestId,
17036
- code: teamcityError.code,
17037
- message: sanitize(teamcityError.message),
17038
- statusCode: teamcityError.statusCode,
17039
- details: sanitize(teamcityError.details),
17018
+ requestId: tcError.requestId,
17019
+ code: tcError.code,
17020
+ message: sanitize(tcError.message),
17021
+ statusCode: tcError.statusCode,
17022
+ details: sanitize(tcError.details),
17040
17023
  duration
17041
17024
  });
17042
- return Promise.reject(teamcityError);
17025
+ return Promise.reject(tcError);
17043
17026
  }
17044
17027
  function validateToken(token) {
17045
17028
  if (!token || token.length === 0) {
@@ -39438,7 +39421,7 @@ var TeamCityAPI = class _TeamCityAPI {
39438
39421
  retryCondition: (error2) => {
39439
39422
  const reqId = error2?.config?.requestId;
39440
39423
  const tcError = TeamCityAPIError.fromAxiosError(error2, reqId);
39441
- return isRetryableError2(tcError);
39424
+ return isRetryableError(tcError);
39442
39425
  }
39443
39426
  });
39444
39427
  this.axiosInstance.interceptors.request.use((config2) => addRequestId(config2));
@@ -40442,9 +40425,16 @@ var DEV_TOOLS = [
40442
40425
  description: "Fetch server metrics (CPU/memory/disk/load) if available",
40443
40426
  inputSchema: { type: "object", properties: {} },
40444
40427
  handler: async (_args) => {
40445
- const api = TeamCityAPI.getInstance();
40446
- const metrics = await api.server.getAllMetrics();
40447
- return json(metrics.data);
40428
+ return runTool(
40429
+ "get_server_metrics",
40430
+ null,
40431
+ async () => {
40432
+ const api = TeamCityAPI.getInstance();
40433
+ const metrics = await api.server.getAllMetrics();
40434
+ return json(metrics.data);
40435
+ },
40436
+ {}
40437
+ );
40448
40438
  },
40449
40439
  mode: "full"
40450
40440
  },
@@ -40453,9 +40443,16 @@ var DEV_TOOLS = [
40453
40443
  description: "Get TeamCity server info (version, build number, state)",
40454
40444
  inputSchema: { type: "object", properties: {} },
40455
40445
  handler: async (_args) => {
40456
- const api = TeamCityAPI.getInstance();
40457
- const info2 = await api.server.getServerInfo();
40458
- return json(info2.data);
40446
+ return runTool(
40447
+ "get_server_info",
40448
+ null,
40449
+ async () => {
40450
+ const api = TeamCityAPI.getInstance();
40451
+ const info2 = await api.server.getServerInfo();
40452
+ return json(info2.data);
40453
+ },
40454
+ {}
40455
+ );
40459
40456
  }
40460
40457
  },
40461
40458
  {