@daghis/teamcity-mcp 2.3.2 → 2.4.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.4.0](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.3.2...teamcity-mcp-v2.4.0) (2026-03-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * add SSH key management tools for projects ([#407](https://github.com/Daghis/teamcity-mcp/issues/407)) ([#421](https://github.com/Daghis/teamcity-mcp/issues/421)) ([e00955c](https://github.com/Daghis/teamcity-mcp/commit/e00955c1d4c43955a734c806e42bd8ac4296d21d))
9
+
3
10
  ## [2.3.2](https://github.com/Daghis/teamcity-mcp/compare/teamcity-mcp-v2.3.1...teamcity-mcp-v2.3.2) (2026-03-10)
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.3.2",
1208
+ version: "2.4.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",
@@ -4548,6 +4548,13 @@ var globalErrorHandler = GlobalErrorHandler.getInstance();
4548
4548
  function json(data) {
4549
4549
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }], success: true };
4550
4550
  }
4551
+ var SECRET_KEY_PATTERN = /token|authorization|password|privateKey/i;
4552
+ var maskSecrets = (args) => typeof args === "object" && args != null ? Object.fromEntries(
4553
+ Object.entries(args).map(([k, v]) => [
4554
+ k,
4555
+ SECRET_KEY_PATTERN.test(k) ? "***" : v
4556
+ ])
4557
+ ) : {};
4551
4558
  async function runTool(toolName, schema, handler, rawArgs, context) {
4552
4559
  const logger2 = getLogger();
4553
4560
  const reqId = context?.requestId ?? logger2.generateRequestId();
@@ -4559,13 +4566,7 @@ async function runTool(toolName, schema, handler, rawArgs, context) {
4559
4566
  const success = result?.success !== false;
4560
4567
  logger2.logToolExecution(
4561
4568
  toolName,
4562
- // Avoid logging secrets by shallow masking of obvious keys
4563
- typeof args === "object" && args != null ? Object.fromEntries(
4564
- Object.entries(args).map(([k, v]) => [
4565
- k,
4566
- /token|authorization|password/i.test(k) ? "***" : v
4567
- ])
4568
- ) : {},
4569
+ maskSecrets(args),
4569
4570
  { success, error: result?.error },
4570
4571
  duration,
4571
4572
  { requestId: reqId }
@@ -4576,7 +4577,7 @@ async function runTool(toolName, schema, handler, rawArgs, context) {
4576
4577
  const msg = err instanceof Error ? err.message : String(err);
4577
4578
  logger2.logToolExecution(
4578
4579
  toolName,
4579
- typeof rawArgs === "object" && rawArgs != null ? rawArgs : {},
4580
+ maskSecrets(rawArgs),
4580
4581
  { success: false, error: msg },
4581
4582
  duration,
4582
4583
  { requestId: reqId }
@@ -43915,6 +43916,103 @@ var FULL_MODE_TOOLS = [
43915
43916
  );
43916
43917
  },
43917
43918
  mode: "full"
43919
+ },
43920
+ // === SSH Key Management ===
43921
+ {
43922
+ name: "list_project_ssh_keys",
43923
+ description: "List SSH keys configured for a project",
43924
+ inputSchema: {
43925
+ type: "object",
43926
+ properties: {
43927
+ projectId: { type: "string", description: "Project ID" }
43928
+ },
43929
+ required: ["projectId"]
43930
+ },
43931
+ handler: async (args) => {
43932
+ const typedArgs = args;
43933
+ const api = TeamCityAPI.getInstance();
43934
+ const response = await api.http.get(
43935
+ `/app/rest/projects/${encodeURIComponent(typedArgs.projectId)}/sshKeys`,
43936
+ { headers: { Accept: "application/json" } }
43937
+ );
43938
+ return json({
43939
+ success: true,
43940
+ action: "list_project_ssh_keys",
43941
+ projectId: typedArgs.projectId,
43942
+ sshKeys: response.data
43943
+ });
43944
+ },
43945
+ mode: "full"
43946
+ },
43947
+ {
43948
+ name: "upload_project_ssh_key",
43949
+ description: "Upload an SSH key to a project. Provide either privateKeyContent (raw PEM string) or privateKeyPath (path to key file), but not both.",
43950
+ inputSchema: {
43951
+ type: "object",
43952
+ properties: {
43953
+ projectId: { type: "string", description: "Project ID" },
43954
+ keyName: { type: "string", description: "Name for the SSH key" },
43955
+ privateKeyContent: {
43956
+ type: "string",
43957
+ description: "Raw private key content (PEM format)"
43958
+ },
43959
+ privateKeyPath: {
43960
+ type: "string",
43961
+ description: "Path to the private key file"
43962
+ }
43963
+ },
43964
+ required: ["projectId", "keyName"]
43965
+ },
43966
+ handler: async (args) => {
43967
+ const typedArgs = args;
43968
+ if (!typedArgs.privateKeyContent && !typedArgs.privateKeyPath) {
43969
+ throw new Error("Either privateKeyContent or privateKeyPath must be provided");
43970
+ }
43971
+ if (typedArgs.privateKeyContent && typedArgs.privateKeyPath) {
43972
+ throw new Error("Provide only one of privateKeyContent or privateKeyPath, not both");
43973
+ }
43974
+ const keyContent = typedArgs.privateKeyPath ? await import_node_fs2.promises.readFile(typedArgs.privateKeyPath, "utf-8") : typedArgs.privateKeyContent;
43975
+ const formData = new FormData();
43976
+ formData.append("privateKey", new Blob([keyContent]), "key");
43977
+ const api = TeamCityAPI.getInstance();
43978
+ await api.http.post(
43979
+ `/app/rest/projects/${encodeURIComponent(typedArgs.projectId)}/sshKeys?${new URLSearchParams({ name: typedArgs.keyName })}`,
43980
+ formData
43981
+ );
43982
+ return json({
43983
+ success: true,
43984
+ action: "upload_project_ssh_key",
43985
+ projectId: typedArgs.projectId,
43986
+ keyName: typedArgs.keyName
43987
+ });
43988
+ },
43989
+ mode: "full"
43990
+ },
43991
+ {
43992
+ name: "delete_project_ssh_key",
43993
+ description: "Delete an SSH key from a project",
43994
+ inputSchema: {
43995
+ type: "object",
43996
+ properties: {
43997
+ projectId: { type: "string", description: "Project ID" },
43998
+ keyName: { type: "string", description: "Name of the SSH key to delete" }
43999
+ },
44000
+ required: ["projectId", "keyName"]
44001
+ },
44002
+ handler: async (args) => {
44003
+ const typedArgs = args;
44004
+ const api = TeamCityAPI.getInstance();
44005
+ await api.http.delete(
44006
+ `/app/rest/projects/${encodeURIComponent(typedArgs.projectId)}/sshKeys?${new URLSearchParams({ name: typedArgs.keyName })}`
44007
+ );
44008
+ return json({
44009
+ success: true,
44010
+ action: "delete_project_ssh_key",
44011
+ projectId: typedArgs.projectId,
44012
+ keyName: typedArgs.keyName
44013
+ });
44014
+ },
44015
+ mode: "full"
43918
44016
  }
43919
44017
  ];
43920
44018
  function getAvailableTools() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daghis/teamcity-mcp",
3
- "version": "2.3.2",
3
+ "version": "2.4.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.3.2",
10
+ "version": "2.4.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.3.2",
16
+ "version": "2.4.0",
17
17
  "runtimeHint": "npx",
18
18
  "runtimeArguments": [
19
19
  {