agentek-youtrack-mcp 1.0.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.
Files changed (44) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +332 -0
  3. package/README.npm.md +436 -0
  4. package/dist/bin/youtrack-mcp.js +158 -0
  5. package/dist/index.js +129 -0
  6. package/dist/python/main.py +74 -0
  7. package/dist/python/requirements.txt +13 -0
  8. package/dist/python/youtrack_mcp/__init__.py +5 -0
  9. package/dist/python/youtrack_mcp/__pycache__/__init__.cpython-311.pyc +0 -0
  10. package/dist/python/youtrack_mcp/__pycache__/version.cpython-311.pyc +0 -0
  11. package/dist/python/youtrack_mcp/api/__init__.py +3 -0
  12. package/dist/python/youtrack_mcp/api/articles.py +317 -0
  13. package/dist/python/youtrack_mcp/api/client.py +466 -0
  14. package/dist/python/youtrack_mcp/api/issues.py +3008 -0
  15. package/dist/python/youtrack_mcp/api/mcp_wrappers.py +304 -0
  16. package/dist/python/youtrack_mcp/api/projects.py +1009 -0
  17. package/dist/python/youtrack_mcp/api/search.py +244 -0
  18. package/dist/python/youtrack_mcp/api/spaces.py +46 -0
  19. package/dist/python/youtrack_mcp/api/users.py +149 -0
  20. package/dist/python/youtrack_mcp/config.py +273 -0
  21. package/dist/python/youtrack_mcp/mcp_server.py +158 -0
  22. package/dist/python/youtrack_mcp/mcp_wrappers.py +324 -0
  23. package/dist/python/youtrack_mcp/tools/__init__.py +8 -0
  24. package/dist/python/youtrack_mcp/tools/articles.py +487 -0
  25. package/dist/python/youtrack_mcp/tools/create_project_tool.py +51 -0
  26. package/dist/python/youtrack_mcp/tools/issues/__init__.py +208 -0
  27. package/dist/python/youtrack_mcp/tools/issues/attachments.py +161 -0
  28. package/dist/python/youtrack_mcp/tools/issues/basic_operations.py +322 -0
  29. package/dist/python/youtrack_mcp/tools/issues/custom_fields.py +365 -0
  30. package/dist/python/youtrack_mcp/tools/issues/dedicated_updates.py +601 -0
  31. package/dist/python/youtrack_mcp/tools/issues/diagnostics.py +363 -0
  32. package/dist/python/youtrack_mcp/tools/issues/linking.py +283 -0
  33. package/dist/python/youtrack_mcp/tools/issues/utilities.py +291 -0
  34. package/dist/python/youtrack_mcp/tools/loader.py +383 -0
  35. package/dist/python/youtrack_mcp/tools/projects.py +826 -0
  36. package/dist/python/youtrack_mcp/tools/projects.py-e +411 -0
  37. package/dist/python/youtrack_mcp/tools/resources.py +744 -0
  38. package/dist/python/youtrack_mcp/tools/search.py +297 -0
  39. package/dist/python/youtrack_mcp/tools/spaces.py +69 -0
  40. package/dist/python/youtrack_mcp/tools/users.py +185 -0
  41. package/dist/python/youtrack_mcp/utils.py +87 -0
  42. package/dist/python/youtrack_mcp/version.py +5 -0
  43. package/package.json +61 -0
  44. package/requirements.txt +13 -0
@@ -0,0 +1,304 @@
1
+ """
2
+ Wrapper functions for MCP compatibility in the API layer.
3
+ These provide parameter compatibility for MCP function calling.
4
+ """
5
+
6
+ import logging
7
+ from typing import Any, Dict, Optional, Callable, List
8
+
9
+ from youtrack_mcp.mcp_wrappers import sync_wrapper
10
+ from youtrack_mcp.api.client import YouTrackClient
11
+ from youtrack_mcp.api.issues import IssuesClient
12
+ from youtrack_mcp.api.projects import ProjectsClient
13
+ from youtrack_mcp.api.users import UsersClient
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Initialize API clients with error handling
18
+ try:
19
+ client = YouTrackClient()
20
+ issues_api = IssuesClient(client)
21
+ projects_api = ProjectsClient(client)
22
+ users_api = UsersClient(client)
23
+ except Exception as e:
24
+ logger.error(f"Error initializing YouTrack API clients: {str(e)}")
25
+ # Create placeholder clients that will report the error when used
26
+ client = None
27
+ issues_api = None
28
+ projects_api = None
29
+ users_api = None
30
+
31
+ # ================================================
32
+ # Issue-related MCP wrappers
33
+ # ================================================
34
+
35
+
36
+ def get_issue(issue_id: str) -> Dict[str, Any]:
37
+ """
38
+ Get information about a specific issue.
39
+
40
+ Args:
41
+ issue_id: The issue ID or readable ID (e.g., PROJECT-123)
42
+
43
+ Returns:
44
+ Dictionary with issue information
45
+
46
+ Example:
47
+ >>> get_issue(issue_id="DEMO-123")
48
+ """
49
+ logger.info(f"MCP wrapper: get_issue({issue_id})")
50
+
51
+ # Check if API client is available
52
+ if issues_api is None:
53
+ return {
54
+ "error": "YouTrack API client failed to initialize",
55
+ "status": "error",
56
+ }
57
+
58
+ try:
59
+ issue = issues_api.get_issue(issue_id)
60
+ return issue.model_dump() if hasattr(issue, "model_dump") else issue
61
+ except Exception as e:
62
+ logger.exception(f"Error getting issue {issue_id}")
63
+ return {"error": str(e), "status": "error"}
64
+
65
+
66
+ def add_comment(issue_id: str, text: str) -> Dict[str, Any]:
67
+ """
68
+ Add a comment to an issue.
69
+
70
+ Args:
71
+ issue_id: The issue ID or readable ID (e.g., PROJECT-123)
72
+ text: The comment text (supports markdown)
73
+
74
+ Returns:
75
+ Dictionary with the result
76
+
77
+ Example:
78
+ >>> add_comment(issue_id="DEMO-123", text="Fixed in the latest release")
79
+ """
80
+ logger.info(f"MCP wrapper: add_comment({issue_id}, {text[:20]}...)")
81
+
82
+ # Check if API client is available
83
+ if issues_api is None:
84
+ return {
85
+ "error": "YouTrack API client failed to initialize",
86
+ "status": "error",
87
+ }
88
+
89
+ try:
90
+ result = issues_api.add_comment(issue_id, text)
91
+ return result if isinstance(result, dict) else {"status": "success"}
92
+ except Exception as e:
93
+ logger.exception(f"Error adding comment to issue {issue_id}")
94
+ return {"error": str(e), "status": "error"}
95
+
96
+
97
+ def create_issue(
98
+ project: str, summary: str, description: Optional[str] = None
99
+ ) -> Dict[str, Any]:
100
+ """
101
+ Create a new issue in a project.
102
+
103
+ Args:
104
+ project: The project ID or short name (e.g., "DEMO")
105
+ summary: The issue summary
106
+ description: The issue description (optional)
107
+
108
+ Returns:
109
+ Dictionary with the created issue information
110
+
111
+ Example:
112
+ >>> create_issue(project="DEMO", summary="Login button not working", description="Users cannot log in after the latest update")
113
+ """
114
+ logger.info(
115
+ f"MCP wrapper: create_issue({project}, {summary}, {description and description[:20]}...)"
116
+ )
117
+
118
+ # Check if API clients are available
119
+ if issues_api is None or projects_api is None:
120
+ return {
121
+ "error": "YouTrack API client failed to initialize",
122
+ "status": "error",
123
+ }
124
+
125
+ try:
126
+ # Check if project is a project ID or short name
127
+ if project and not project.startswith("0-"):
128
+ # Try to get the project ID from the short name (e.g., "DEMO")
129
+ try:
130
+ project_obj = projects_api.get_project_by_name(project)
131
+ if project_obj:
132
+ logger.info(
133
+ f"Found project {project_obj.name} with ID {project_obj.id}"
134
+ )
135
+ project = project_obj.id
136
+ else:
137
+ logger.warning(f"Project not found: {project}")
138
+ except Exception as e:
139
+ logger.warning(f"Error finding project: {str(e)}")
140
+
141
+ # Ensure we have valid data
142
+ if not project:
143
+ return {"error": "Project is required", "status": "error"}
144
+ if not summary:
145
+ return {"error": "Summary is required", "status": "error"}
146
+
147
+ # Create the issue
148
+ issue = issues_api.create_issue(project, summary, description)
149
+ return issue.model_dump() if hasattr(issue, "model_dump") else issue
150
+ except Exception as e:
151
+ logger.exception(f"Error creating issue in project {project}")
152
+ return {"error": str(e), "status": "error"}
153
+
154
+
155
+ def search_issues(query: str, limit: int = 10) -> List[Dict[str, Any]]:
156
+ """
157
+ Search for issues using YouTrack query language.
158
+
159
+ Args:
160
+ query: The search query (e.g., "project: DEMO #Unresolved")
161
+ limit: Maximum number of issues to return (default: 10)
162
+
163
+ Returns:
164
+ List of matching issues
165
+
166
+ Example:
167
+ >>> search_issues(query="project: DEMO #Unresolved", limit=5)
168
+ """
169
+ logger.info(f"MCP wrapper: search_issues({query}, limit={limit})")
170
+
171
+ # Check if API client is available
172
+ if issues_api is None:
173
+ return [
174
+ {
175
+ "error": "YouTrack API client failed to initialize",
176
+ "status": "error",
177
+ }
178
+ ]
179
+
180
+ try:
181
+ issues = issues_api.search_issues(query, limit)
182
+ # Convert to list of dictionaries
183
+ if isinstance(issues, list):
184
+ return [
185
+ issue.model_dump() if hasattr(issue, "model_dump") else issue
186
+ for issue in issues
187
+ ]
188
+ return []
189
+ except Exception as e:
190
+ logger.exception(f"Error searching for issues with query: {query}")
191
+ return [{"error": str(e), "status": "error"}]
192
+
193
+
194
+ # ================================================
195
+ # Project-related MCP wrappers
196
+ # ================================================
197
+
198
+
199
+ def get_projects(include_archived: bool = False) -> List[Dict[str, Any]]:
200
+ """
201
+ Get a list of all projects.
202
+
203
+ Args:
204
+ include_archived: Whether to include archived projects (default: False)
205
+
206
+ Returns:
207
+ List of projects
208
+
209
+ Example:
210
+ >>> get_projects(include_archived=True)
211
+ """
212
+ logger.info(
213
+ f"MCP wrapper: get_projects(include_archived={include_archived})"
214
+ )
215
+
216
+ # Check if API client is available
217
+ if projects_api is None:
218
+ return [
219
+ {
220
+ "error": "YouTrack API client failed to initialize",
221
+ "status": "error",
222
+ }
223
+ ]
224
+
225
+ try:
226
+ projects = projects_api.get_projects(include_archived)
227
+ # Convert to list of dictionaries
228
+ if isinstance(projects, list):
229
+ return [
230
+ (
231
+ project.model_dump()
232
+ if hasattr(project, "model_dump")
233
+ else project
234
+ )
235
+ for project in projects
236
+ ]
237
+ return []
238
+ except Exception as e:
239
+ logger.exception("Error getting projects")
240
+ return [{"error": str(e), "status": "error"}]
241
+
242
+
243
+ def get_project(project_id: str) -> Dict[str, Any]:
244
+ """
245
+ Get information about a specific project.
246
+
247
+ Args:
248
+ project_id: The project ID or short name
249
+
250
+ Returns:
251
+ Dictionary with project information
252
+
253
+ Example:
254
+ >>> get_project(project_id="DEMO")
255
+ """
256
+ logger.info(f"MCP wrapper: get_project({project_id})")
257
+
258
+ # Check if API client is available
259
+ if projects_api is None:
260
+ return {
261
+ "error": "YouTrack API client failed to initialize",
262
+ "status": "error",
263
+ }
264
+
265
+ try:
266
+ project = projects_api.get_project(project_id)
267
+ return (
268
+ project.model_dump() if hasattr(project, "model_dump") else project
269
+ )
270
+ except Exception as e:
271
+ logger.exception(f"Error getting project {project_id}")
272
+ return {"error": str(e), "status": "error"}
273
+
274
+
275
+ # ================================================
276
+ # User-related MCP wrappers
277
+ # ================================================
278
+
279
+
280
+ def get_current_user() -> Dict[str, Any]:
281
+ """
282
+ Get information about the currently authenticated user.
283
+
284
+ Returns:
285
+ Dictionary with user information
286
+
287
+ Example:
288
+ >>> get_current_user()
289
+ """
290
+ logger.info("MCP wrapper: get_current_user()")
291
+
292
+ # Check if API client is available
293
+ if users_api is None:
294
+ return {
295
+ "error": "YouTrack API client failed to initialize",
296
+ "status": "error",
297
+ }
298
+
299
+ try:
300
+ user = users_api.get_current_user()
301
+ return user.model_dump() if hasattr(user, "model_dump") else user
302
+ except Exception as e:
303
+ logger.exception("Error getting current user")
304
+ return {"error": str(e), "status": "error"}