agentek-youtrack-mcp 1.0.0 → 1.0.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.
package/README.md CHANGED
@@ -180,42 +180,29 @@ Choose from multiple registries:
180
180
 
181
181
  #### Docker Hub (Primary)
182
182
  ```bash
183
- # Use the latest stable release
183
+ # Use the latest release
184
184
  docker run --rm \
185
185
  -e YOUTRACK_URL="https://your-instance.youtrack.cloud" \
186
186
  -e YOUTRACK_API_TOKEN="your-token" \
187
187
  windbit/agentek-youtrack-mcp:latest
188
-
189
- # Or use the latest development build
190
- docker run --rm \
191
- -e YOUTRACK_URL="https://your-instance.youtrack.cloud" \
192
- -e YOUTRACK_API_TOKEN="your-token" \
193
- windbit/agentek-youtrack-mcp:1.1.2_wip
194
188
  ```
195
189
 
196
- #### GitHub Container Registry (New)
190
+ #### GitHub Container Registry
197
191
  ```bash
198
- # Use the latest stable release
192
+ # Use the latest release
199
193
  docker run --rm \
200
194
  -e YOUTRACK_URL="https://your-instance.youtrack.cloud" \
201
195
  -e YOUTRACK_API_TOKEN="your-token" \
202
196
  ghcr.io/windbit/agentek-youtrack-mcp:latest
203
-
204
- # Or use the latest development build
205
- docker run --rm \
206
- -e YOUTRACK_URL="https://your-instance.youtrack.cloud" \
207
- -e YOUTRACK_API_TOKEN="your-token" \
208
- ghcr.io/windbit/agentek-youtrack-mcp:1.1.2_wip
209
197
  ```
210
198
 
211
199
  ### Available Docker Tags
212
200
 
213
201
  Both registries provide identical tags:
214
202
 
215
- - `latest` - Latest stable release (currently 1.1.2)
216
- - `1.1.2` - Specific version tags
217
- - `1.1.2_wip` - Work-in-progress builds from main branch
218
- - `pr-<number>` - Pull request builds for testing
203
+ - `latest` - Latest build from the main branch
204
+ - `1.0.0` - Specific release version tags
205
+ - `<commit-sha>` - Exact commit builds
219
206
 
220
207
  *Note: Images are now published to both Docker Hub and GitHub Container Registry simultaneously.*
221
208
 
@@ -232,18 +219,6 @@ npm install -g agentek-youtrack-mcp
232
219
  npx agentek-youtrack-mcp
233
220
  ```
234
221
 
235
- #### GitHub Packages (New)
236
- ```bash
237
- # Configure GitHub registry
238
- npm config set @windbit:registry https://npm.pkg.github.com
239
-
240
- # Install globally
241
- npm install -g @windbit/agentek-youtrack-mcp
242
-
243
- # Or use with npx
244
- npx @windbit/agentek-youtrack-mcp
245
- ```
246
-
247
222
  ## Features
248
223
 
249
224
  - **Issue Management**: Create, read, update, and delete YouTrack issues
@@ -316,17 +291,3 @@ For issues and questions:
316
291
  1. Check the [Issues](https://github.com/windbit/agentek-youtrack-mcp/issues) page
317
292
  2. Review the documentation
318
293
  3. Submit a new issue with detailed information
319
-
320
- ---
321
-
322
- *Latest update: Comprehensive custom fields management with 567 test coverage and clean project organization.*
323
-
324
- ## Version 1.11.1 Released
325
-
326
- 🎉 **MAJOR FEATURE** - Custom Fields Management Support
327
- - ✅ Complete custom fields CRUD operations (create, read, update, delete)
328
- - ✅ Field validation against project schema (all field types supported)
329
- - ✅ Batch update capabilities for performance
330
- - ✅ Comprehensive error handling with detailed messages
331
- - ✅ 567 tests (+68 new tests) with extensive coverage
332
- - ✅ Clean project organization with `automations/` directory
package/README.npm.md CHANGED
@@ -36,24 +36,17 @@ npx agentek-youtrack-mcp --help
36
36
 
37
37
  **Using Docker Hub:**
38
38
  ```bash
39
- # Use the latest stable release
39
+ # Use the latest release
40
40
  docker run --rm \
41
41
  -e YOUTRACK_URL="https://your.youtrack.cloud" \
42
42
  -e YOUTRACK_API_TOKEN="your-token" \
43
43
  windbit/agentek-youtrack-mcp:latest
44
-
45
- # Or use the latest development build
46
- docker run --rm \
47
- -e YOUTRACK_URL="https://your.youtrack.cloud" \
48
- -e YOUTRACK_API_TOKEN="your-token" \
49
- windbit/agentek-youtrack-mcp:1.11.1_wip
50
44
  ```
51
45
 
52
46
  **Available Docker Tags:**
53
- - `latest` - Latest stable release
54
- - `1.11.1` - Specific version tags
55
- - `1.11.1_wip` - Work-in-progress builds from main branch
56
- - `pr-<number>` - Pull request builds for testing
47
+ - `latest` - Latest build from the main branch
48
+ - `1.0.0` - Specific release version tags
49
+ - `<commit-sha>` - Exact commit builds
57
50
 
58
51
  ## Configuration
59
52
 
@@ -121,22 +114,6 @@ Choose your preferred method:
121
114
  }
122
115
  ```
123
116
 
124
- #### Using GitHub Packages npm:
125
- ```json
126
- {
127
- "mcpServers": {
128
- "youtrack": {
129
- "command": "npx",
130
- "args": ["@windbit/agentek-youtrack-mcp"],
131
- "env": {
132
- "YOUTRACK_URL": "https://your.youtrack.cloud",
133
- "YOUTRACK_API_TOKEN": "your-token"
134
- }
135
- }
136
- }
137
- }
138
- ```
139
-
140
117
  ## Features
141
118
 
142
119
  - **Issue Management**: Create, read, update, and delete YouTrack issues
@@ -222,15 +199,6 @@ docker run --rm \
222
199
  ghcr.io/windbit/agentek-youtrack-mcp:latest
223
200
  ```
224
201
 
225
- #### GitHub Packages npm:
226
- ```bash
227
- # Set up GitHub registry (one-time setup)
228
- npm config set @windbit:registry https://npm.pkg.github.com
229
-
230
- # Run with npx
231
- npx @windbit/agentek-youtrack-mcp
232
- ```
233
-
234
202
  ### HTTP Mode
235
203
 
236
204
  #### npm/Node.js:
@@ -266,15 +234,6 @@ docker run --rm -p 8000:8000 \
266
234
  curl http://localhost:8000/api/tools
267
235
  ```
268
236
 
269
- #### GitHub Packages npm:
270
- ```bash
271
- # Start HTTP server
272
- npx @windbit/agentek-youtrack-mcp --http --port 8000
273
-
274
- # Test the server
275
- curl http://localhost:8000/api/tools
276
- ```
277
-
278
237
  ### Development Mode
279
238
 
280
239
  #### npm/Node.js:
@@ -301,12 +260,6 @@ docker run --rm \
301
260
  ghcr.io/windbit/agentek-youtrack-mcp:latest --log-level DEBUG
302
261
  ```
303
262
 
304
- #### GitHub Packages npm:
305
- ```bash
306
- # Run with debug logging
307
- npx @windbit/agentek-youtrack-mcp -- --log-level DEBUG
308
- ```
309
-
310
263
  ## Troubleshooting
311
264
 
312
265
  ### npm/Node.js Issues
@@ -367,23 +320,6 @@ docker login ghcr.io
367
320
  docker pull ghcr.io/windbit/agentek-youtrack-mcp:latest
368
321
  ```
369
322
 
370
- ### GitHub Packages Issues
371
-
372
- #### npm Registry Configuration
373
- ```bash
374
- # If @windbit/agentek-youtrack-mcp is not found, configure the registry
375
- npm config set @windbit:registry https://npm.pkg.github.com
376
-
377
- # You might need authentication for GitHub Packages npm
378
- npm login --registry=https://npm.pkg.github.com
379
- ```
380
-
381
- #### Package Not Found
382
- If GitHub Packages npm shows "package not found":
383
- - The package is published only on releases
384
- - Check if a release has been created
385
- - Fallback to npmjs.org version: `npm install -g agentek-youtrack-mcp`
386
-
387
323
  ### Common Issues (Both Options)
388
324
 
389
325
  #### Connection Issues
@@ -407,8 +343,7 @@ If you can't connect to YouTrack:
407
343
  - ✅ Need to modify or debug the server
408
344
 
409
345
  **Registry Options:**
410
- - **npmjs.org** (recommended): `agentek-youtrack-mcp` - Better discovery, wider usage
411
- - **GitHub Packages**: `@windbit/agentek-youtrack-mcp` - Integrated with GitHub, requires registry config
346
+ - **npmjs.org**: `agentek-youtrack-mcp`
412
347
 
413
348
  ### Choose **Docker** if you:
414
349
  - ✅ Want isolated, consistent environments
@@ -429,7 +364,6 @@ If you can't connect to YouTrack:
429
364
  - **Docker Hub**: [windbit/agentek-youtrack-mcp](https://hub.docker.com/r/windbit/agentek-youtrack-mcp)
430
365
  - **GitHub Container Registry**: [ghcr.io/windbit/agentek-youtrack-mcp](https://github.com/windbit/agentek-youtrack-mcp/pkgs/container/youtrack-mcp)
431
366
  - **npmjs.org**: [agentek-youtrack-mcp](https://www.npmjs.com/package/agentek-youtrack-mcp)
432
- - **GitHub Packages npm**: [@windbit/agentek-youtrack-mcp](https://github.com/windbit/agentek-youtrack-mcp/pkgs/npm/%40windbit%2Fagentek-youtrack-mcp)
433
367
 
434
368
  ## License
435
369
 
@@ -73,7 +73,7 @@ function showVersion() {
73
73
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
74
74
  console.log(`YouTrack MCP Server v${packageJson.version}`);
75
75
  } catch (error) {
76
- console.log('YouTrack MCP Server v1.11.1');
76
+ console.log('YouTrack MCP Server (version unknown)');
77
77
  }
78
78
  }
79
79
 
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import { spawn } from 'child_process';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { dirname, join } from 'path';
6
- import { existsSync } from 'fs';
6
+ import { existsSync, readFileSync } from 'fs';
7
7
 
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
@@ -115,9 +115,17 @@ class YouTrackMCPServer {
115
115
  * Get server information
116
116
  */
117
117
  getInfo() {
118
+ let version = 'unknown';
119
+ try {
120
+ // package.json sits one level up from this file in both src/ and dist/
121
+ const packageJsonPath = join(__dirname, '..', 'package.json');
122
+ version = JSON.parse(readFileSync(packageJsonPath, 'utf8')).version;
123
+ } catch {
124
+ // leave version as 'unknown' if package.json can't be read
125
+ }
118
126
  return {
119
127
  name: 'YouTrack MCP Server',
120
- version: '1.11.1',
128
+ version,
121
129
  description: 'A Model Context Protocol server for JetBrains YouTrack',
122
130
  pythonPath: this.pythonPath,
123
131
  serverPath: this.serverPath,
@@ -266,6 +266,7 @@ class IssuesClient:
266
266
  issue_id: str,
267
267
  summary: Optional[str] = None,
268
268
  description: Optional[str] = None,
269
+ uses_markdown: Optional[bool] = None,
269
270
  additional_fields: Optional[Dict[str, Any]] = None,
270
271
  ) -> Issue:
271
272
  """
@@ -275,6 +276,9 @@ class IssuesClient:
275
276
  issue_id: The issue ID or readable ID
276
277
  summary: The new issue summary
277
278
  description: The new issue description
279
+ uses_markdown: Whether the description is rendered as Markdown
280
+ (True) or YouTrack Wiki markup (False). Leave as None to keep
281
+ the issue's current setting.
278
282
  additional_fields: Additional fields to update
279
283
 
280
284
  Returns:
@@ -288,6 +292,9 @@ class IssuesClient:
288
292
  if description is not None:
289
293
  data["description"] = description
290
294
 
295
+ if uses_markdown is not None:
296
+ data["usesMarkdown"] = uses_markdown
297
+
291
298
  if additional_fields:
292
299
  data.update(additional_fields)
293
300
 
@@ -224,6 +224,7 @@ class BasicOperations:
224
224
  issue_id: str,
225
225
  summary: Optional[str] = None,
226
226
  description: Optional[str] = None,
227
+ uses_markdown: Optional[bool] = None,
227
228
  additional_fields: Optional[Dict[str, Any]] = None,
228
229
  ) -> str:
229
230
  """
@@ -235,6 +236,9 @@ class BasicOperations:
235
236
  issue_id: The issue identifier (e.g., "DEMO-123", "PROJECT-456")
236
237
  summary: The new issue summary/title (optional)
237
238
  description: The new issue description (optional)
239
+ uses_markdown: Whether the description is rendered as Markdown
240
+ (True) or YouTrack Wiki markup (False); leave as None to keep
241
+ the current setting (optional)
238
242
  additional_fields: Additional fields to update as dict (optional)
239
243
 
240
244
  Returns:
@@ -245,6 +249,7 @@ class BasicOperations:
245
249
  issue_id=issue_id,
246
250
  summary=summary,
247
251
  description=description,
252
+ uses_markdown=uses_markdown,
248
253
  additional_fields=additional_fields,
249
254
  )
250
255
  # Convert Issue object to dict if needed
@@ -309,6 +314,7 @@ class BasicOperations:
309
314
  "issue_id": "Issue identifier like 'DEMO-123' or 'PROJECT-456'",
310
315
  "summary": "New issue summary/title (optional)",
311
316
  "description": "New issue description (optional)",
317
+ "uses_markdown": "Set True to render the description as Markdown, False for YouTrack Wiki markup; omit to keep the current setting (optional)",
312
318
  "additional_fields": "Additional fields to update as dictionary (optional)"
313
319
  }
314
320
  },
@@ -2,4 +2,4 @@
2
2
  YouTrack MCP Server version information.
3
3
  """
4
4
 
5
- __version__ = "1.0.0"
5
+ __version__ = "1.0.2"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentek-youtrack-mcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A Model Context Protocol (MCP) server for JetBrains YouTrack integration",
5
5
  "keywords": [
6
6
  "mcp",
@@ -1,411 +0,0 @@
1
- """
2
- YouTrack Project MCP tools.
3
- """
4
-
5
- import json
6
- import logging
7
- from typing import Any, Dict, List, Optional, Union
8
-
9
- from youtrack_mcp.api.client import YouTrackClient
10
- from youtrack_mcp.api.issues import IssuesClient
11
- from youtrack_mcp.api.projects import ProjectsClient
12
- from youtrack_mcp.mcp_wrappers import sync_wrapper
13
-
14
- logger = logging.getLogger(__name__)
15
-
16
- class ProjectTools:
17
- """Project-related MCP tools."""
18
-
19
- def __init__(self):
20
- """Initialize the project tools."""
21
- self.client = YouTrackClient()
22
- self.projects_api = ProjectsClient(self.client)
23
-
24
- # Also initialize the issues API for fetching issue details
25
- self.issues_api = IssuesClient(self.client)
26
-
27
- @sync_wrapper
28
- def get_projects(self, include_archived: bool = False) -> str:
29
- """
30
- Get a list of all projects.
31
-
32
- FORMAT: get_projects(include_archived=False)
33
-
34
- Args:
35
- include_archived: Whether to include archived projects
36
-
37
- Returns:
38
- JSON string with projects information
39
- """
40
- try:
41
- projects = self.projects_api.get_projects(include_archived=include_archived)
42
-
43
- # Handle both Pydantic models and dictionaries in the response
44
- result = []
45
- for project in projects:
46
- if hasattr(project, 'model_dump'):
47
- result.append(project.model_dump())
48
- else:
49
- result.append(project) # Assume it's already a dict
50
-
51
- return json.dumps(result, indent=2)
52
- except Exception as e:
53
- logger.exception("Error getting projects")
54
- return json.dumps({"error": str(e)})
55
-
56
- @sync_wrapper
57
- def get_project(self, project_id: str = None, project: str = None) -> str:
58
- """
59
- Get information about a specific project.
60
-
61
- FORMAT: get_project(project_id="0-0")
62
-
63
- Args:
64
- project_id: The project ID
65
- project: Alternative parameter name for project_id
66
-
67
- Returns:
68
- JSON string with project information
69
- """
70
- try:
71
- # Use either project_id or project parameter
72
- project_identifier = project_id or project
73
- if not project_identifier:
74
- return json.dumps({"error": "Project ID is required"})
75
-
76
- project_obj = self.projects_api.get_project(project_identifier)
77
-
78
- # Handle both Pydantic models and dictionaries in the response
79
- if hasattr(project_obj, 'model_dump'):
80
- result = project_obj.model_dump()
81
- else:
82
- result = project_obj # Assume it's already a dict
83
-
84
- return json.dumps(result, indent=2)
85
- except Exception as e:
86
- logger.exception(f"Error getting project {project_id or project}")
87
- return json.dumps({"error": str(e)})
88
-
89
- @sync_wrapper
90
- def get_project_by_name(self, project_name: str) -> str:
91
- """
92
- Find a project by its name.
93
-
94
- FORMAT: get_project_by_name(project_name="DEMO")
95
-
96
- Args:
97
- project_name: The project name or short name
98
-
99
- Returns:
100
- JSON string with project information
101
- """
102
- try:
103
- project = self.projects_api.get_project_by_name(project_name)
104
- if project:
105
- # Handle both Pydantic models and dictionaries in the response
106
- if hasattr(project, 'model_dump'):
107
- result = project.model_dump()
108
- else:
109
- result = project # Assume it's already a dict
110
-
111
- return json.dumps(result, indent=2)
112
- else:
113
- return json.dumps({"error": f"Project '{project_name}' not found"})
114
- except Exception as e:
115
- logger.exception(f"Error finding project by name {project_name}")
116
- return json.dumps({"error": str(e)})
117
-
118
- @sync_wrapper
119
- def get_project_issues(self, project_id: str = None, project: str = None, limit: int = 50) -> str:
120
- """
121
- Get issues for a specific project.
122
-
123
- FORMAT: get_project_issues(project_id="0-0", limit=10)
124
-
125
- Args:
126
- project_id: The project ID (e.g., '0-0')
127
- project: Alternative parameter name for project_id
128
- limit: Maximum number of issues to return (default: 50)
129
-
130
- Returns:
131
- JSON string with the issues
132
- """
133
- try:
134
- # Use either project_id or project parameter
135
- project_identifier = project_id or project
136
- if not project_identifier:
137
- return json.dumps({"error": "Project ID is required"})
138
-
139
- # First try with the direct project ID
140
- try:
141
- issues = self.projects_api.get_project_issues(project_identifier, limit)
142
- if issues:
143
- return json.dumps(issues, indent=2)
144
- except Exception as e:
145
- # If that fails, check if it was a non-ID format error
146
- if not str(e).startswith("Project not found"):
147
- logger.exception(f"Error getting issues for project {project_identifier}")
148
- return json.dumps({"error": str(e)})
149
-
150
- # If that failed, try to find project by name
151
- try:
152
- project_obj = self.projects_api.get_project_by_name(project_identifier)
153
- if project_obj:
154
- issues = self.projects_api.get_project_issues(project_obj.id, limit)
155
- return json.dumps(issues, indent=2)
156
- else:
157
- return json.dumps({"error": f"Project not found: {project_identifier}"})
158
- except Exception as e:
159
- logger.exception(f"Error getting issues for project {project_identifier}")
160
- return json.dumps({"error": str(e)})
161
- except Exception as e:
162
- logger.exception(f"Error processing get_project_issues({project_id or project}, {limit})")
163
- return json.dumps({"error": str(e)})
164
-
165
- @sync_wrapper
166
- def get_custom_fields(self, project_id: str = None, project: str = None) -> str:
167
- """
168
- Get custom fields for a project.
169
-
170
- FORMAT: get_custom_fields(project_id="0-0")
171
-
172
- Args:
173
- project_id: The project ID
174
- project: Alternative parameter name for project_id
175
-
176
- Returns:
177
- JSON string with custom fields information
178
- """
179
- try:
180
- # Use either project_id or project parameter
181
- project_identifier = project_id or project
182
- if not project_identifier:
183
- return json.dumps({"error": "Project ID is required"})
184
-
185
- fields = self.projects_api.get_custom_fields(project_identifier)
186
-
187
- # Handle various response formats safely
188
- if fields is None:
189
- return json.dumps([])
190
-
191
- # If it's a dictionary (direct API response)
192
- if isinstance(fields, dict):
193
- return json.dumps(fields, indent=2)
194
-
195
- # If it's a list of objects
196
- try:
197
- result = []
198
- # Try to iterate, but handle errors safely
199
- for field in fields:
200
- if hasattr(field, 'model_dump'):
201
- result.append(field.model_dump())
202
- elif isinstance(field, dict):
203
- result.append(field)
204
- else:
205
- # Last resort: convert to string
206
- result.append(str(field))
207
- return json.dumps(result, indent=2)
208
- except Exception as e:
209
- # If we can't iterate, return the raw string representation
210
- logger.warning(f"Could not process custom fields response: {str(e)}")
211
- return json.dumps({"custom_fields": str(fields)})
212
- except Exception as e:
213
- logger.exception(f"Error getting custom fields for project {project_id or project}")
214
- return json.dumps({"error": str(e)})
215
-
216
- @sync_wrapper
217
- def create_project(self, name: str, short_name: str, lead_id: str, description: Optional[str] = None) -> str:
218
- """
219
- Create a new project with a required leader.
220
-
221
- FORMAT: create_project(name="Project Name", short_name="PROJ", lead_id="1-1", description="Description")
222
-
223
- Args:
224
- name: The name of the project
225
- short_name: The short name of the project (used as prefix for issues)
226
- lead_id: The ID of the user who will be the project leader
227
- description: The project description (optional)
228
-
229
- Returns:
230
- JSON string with the created project information
231
- """
232
- try:
233
- # Check for missing required parameters
234
- if not name:
235
- return json.dumps({"error": "Project name is required"})
236
- if not short_name:
237
- return json.dumps({"error": "Project short name is required"})
238
- if not lead_id:
239
- return json.dumps({"error": "Project leader ID is required"})
240
-
241
- project = self.projects_api.create_project(
242
- name=name,
243
- short_name=short_name,
244
- lead_id=lead_id,
245
- description=description
246
- )
247
-
248
- # Handle both Pydantic models and dictionaries in the response
249
- if hasattr(project, 'model_dump'):
250
- result = project.model_dump()
251
- else:
252
- result = project # Assume it's already a dict
253
-
254
- return json.dumps(result, indent=2)
255
- except Exception as e:
256
- logger.exception(f"Error creating project {name}")
257
- return json.dumps({"error": str(e)})
258
-
259
- @sync_wrapper
260
- def update_project(self, project_id: str = None, project: str = None, name: Optional[str] = None,
261
- description: Optional[str] = None, archived: Optional[bool] = None,
262
- lead_id: Optional[str] = None, short_name: Optional[str] = None) -> str:
263
- """
264
- Update an existing project.
265
-
266
- FORMAT: update_project(project_id="0-0", name="New Name", description="New Description", archived=False, lead_id="1-1", short_name="NEWKEY")
267
-
268
- Args:
269
- project_id: The project ID to update
270
- project: Alternative parameter name for project_id
271
- name: The new name for the project (optional)
272
- description: The new project description (optional)
273
- archived: Whether the project should be archived (optional)
274
- lead_id: The ID of the new project leader (optional)
275
- short_name: The new short name for the project (optional) - used as prefix for issue IDs
276
-
277
- Returns:
278
- JSON string with the updated project information
279
- """
280
- try:
281
- # Use either project_id or project parameter
282
- project_identifier = project_id or project
283
- if not project_identifier:
284
- return json.dumps({"error": "Project ID is required"})
285
-
286
- # First, get the existing project to maintain required fields
287
- try:
288
- existing_project = self.projects_api.get_project(project_identifier)
289
- logger.info(f"Found existing project: {existing_project.name} ({existing_project.id})")
290
-
291
- # Prepare data for direct API call
292
- data = {}
293
-
294
- # Only include parameters that were explicitly provided
295
- if name is not None:
296
- data["name"] = name
297
- if description is not None:
298
- data["description"] = description
299
- if archived is not None:
300
- data["archived"] = archived
301
- if lead_id is not None:
302
- data["leader"] = {"id": lead_id}
303
- if short_name is not None:
304
- data["shortName"] = short_name
305
-
306
- # If no parameters were provided, return current project
307
- if not data:
308
- logger.info("No parameters to update, returning current project")
309
- if hasattr(existing_project, 'model_dump'):
310
- return json.dumps(existing_project.model_dump(), indent=2)
311
- else:
312
- return json.dumps(existing_project, indent=2)
313
-
314
- # Log the data being sent
315
- logger.info(f"Updating project with data: {data}")
316
-
317
- # Make direct API call
318
- try:
319
- # Use the client directly instead of the API method
320
- self.client.post(f"admin/projects/{project_identifier}", data=data)
321
- logger.info("Update API call successful")
322
- except Exception as e:
323
- logger.warning(f"Update API call error: {str(e)}")
324
- # Continue anyway as the update might still have worked
325
-
326
- # Get the updated project data
327
- try:
328
- updated_project = self.projects_api.get_project(project_identifier)
329
- logger.info(f"Retrieved updated project: {updated_project.name}")
330
-
331
- if hasattr(updated_project, 'model_dump'):
332
- return json.dumps(updated_project.model_dump(), indent=2)
333
- else:
334
- return json.dumps(updated_project, indent=2)
335
- except Exception as e:
336
- logger.error(f"Error retrieving updated project: {str(e)}")
337
- return json.dumps({"error": f"Project was updated but could not retrieve the result: {str(e)}"})
338
- except Exception as e:
339
- return json.dumps({"error": f"Could not update project: {str(e)}"})
340
- except Exception as e:
341
- logger.exception(f"Error updating project {project_id or project}")
342
- return json.dumps({"error": str(e)})
343
-
344
- def close(self) -> None:
345
- """Close the API client."""
346
- self.client.close()
347
-
348
- def get_tool_definitions(self) -> Dict[str, Dict[str, Any]]:
349
- """
350
- Get the definitions of all project tools.
351
-
352
- Returns:
353
- Dictionary mapping tool names to their configuration
354
- """
355
- return {
356
- "get_projects": {
357
- "description": "Get a list of all projects.",
358
- "parameter_descriptions": {
359
- "include_archived": "Whether to include archived projects (optional, default: false)"
360
- }
361
- },
362
- "get_project": {
363
- "description": "Get information about a specific project.",
364
- "parameter_descriptions": {
365
- "project_id": "The project ID (e.g., '0-0')",
366
- "project": "Alternative parameter name for project_id"
367
- }
368
- },
369
- "get_project_by_name": {
370
- "description": "Find a project by its name.",
371
- "parameter_descriptions": {
372
- "project_name": "The project name or short name (e.g., 'DEMO')"
373
- }
374
- },
375
- "get_project_issues": {
376
- "description": "Get issues for a specific project.",
377
- "parameter_descriptions": {
378
- "project_id": "The project ID (e.g., '0-0')",
379
- "project": "Alternative parameter name for project_id",
380
- "limit": "Maximum number of issues to return (optional, default: 50)"
381
- }
382
- },
383
- "get_custom_fields": {
384
- "description": "Get custom fields for a project.",
385
- "parameter_descriptions": {
386
- "project_id": "The project ID (e.g., '0-0')",
387
- "project": "Alternative parameter name for project_id"
388
- }
389
- },
390
- "create_project": {
391
- "description": "Create a new project with a required leader.",
392
- "parameter_descriptions": {
393
- "name": "The name of the project",
394
- "short_name": "The short name of the project (used as prefix for issues)",
395
- "lead_id": "The ID of the user who will be the project leader",
396
- "description": "The project description (optional)"
397
- }
398
- },
399
- "update_project": {
400
- "description": "Update an existing project.",
401
- "parameter_descriptions": {
402
- "project_id": "The project ID to update (e.g., '0-0')",
403
- "project": "Alternative parameter name for project_id",
404
- "name": "The new name for the project (optional)",
405
- "description": "The new project description (optional)",
406
- "archived": "Whether the project should be archived (optional)",
407
- "lead_id": "The ID of the new project leader (optional)",
408
- "short_name": "The new short name for the project (optional) - used as prefix for issue IDs"
409
- }
410
- }
411
- }