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.
- package/LICENSE +22 -0
- package/README.md +332 -0
- package/README.npm.md +436 -0
- package/dist/bin/youtrack-mcp.js +158 -0
- package/dist/index.js +129 -0
- package/dist/python/main.py +74 -0
- package/dist/python/requirements.txt +13 -0
- package/dist/python/youtrack_mcp/__init__.py +5 -0
- package/dist/python/youtrack_mcp/__pycache__/__init__.cpython-311.pyc +0 -0
- package/dist/python/youtrack_mcp/__pycache__/version.cpython-311.pyc +0 -0
- package/dist/python/youtrack_mcp/api/__init__.py +3 -0
- package/dist/python/youtrack_mcp/api/articles.py +317 -0
- package/dist/python/youtrack_mcp/api/client.py +466 -0
- package/dist/python/youtrack_mcp/api/issues.py +3008 -0
- package/dist/python/youtrack_mcp/api/mcp_wrappers.py +304 -0
- package/dist/python/youtrack_mcp/api/projects.py +1009 -0
- package/dist/python/youtrack_mcp/api/search.py +244 -0
- package/dist/python/youtrack_mcp/api/spaces.py +46 -0
- package/dist/python/youtrack_mcp/api/users.py +149 -0
- package/dist/python/youtrack_mcp/config.py +273 -0
- package/dist/python/youtrack_mcp/mcp_server.py +158 -0
- package/dist/python/youtrack_mcp/mcp_wrappers.py +324 -0
- package/dist/python/youtrack_mcp/tools/__init__.py +8 -0
- package/dist/python/youtrack_mcp/tools/articles.py +487 -0
- package/dist/python/youtrack_mcp/tools/create_project_tool.py +51 -0
- package/dist/python/youtrack_mcp/tools/issues/__init__.py +208 -0
- package/dist/python/youtrack_mcp/tools/issues/attachments.py +161 -0
- package/dist/python/youtrack_mcp/tools/issues/basic_operations.py +322 -0
- package/dist/python/youtrack_mcp/tools/issues/custom_fields.py +365 -0
- package/dist/python/youtrack_mcp/tools/issues/dedicated_updates.py +601 -0
- package/dist/python/youtrack_mcp/tools/issues/diagnostics.py +363 -0
- package/dist/python/youtrack_mcp/tools/issues/linking.py +283 -0
- package/dist/python/youtrack_mcp/tools/issues/utilities.py +291 -0
- package/dist/python/youtrack_mcp/tools/loader.py +383 -0
- package/dist/python/youtrack_mcp/tools/projects.py +826 -0
- package/dist/python/youtrack_mcp/tools/projects.py-e +411 -0
- package/dist/python/youtrack_mcp/tools/resources.py +744 -0
- package/dist/python/youtrack_mcp/tools/search.py +297 -0
- package/dist/python/youtrack_mcp/tools/spaces.py +69 -0
- package/dist/python/youtrack_mcp/tools/users.py +185 -0
- package/dist/python/youtrack_mcp/utils.py +87 -0
- package/dist/python/youtrack_mcp/version.py +5 -0
- package/package.json +61 -0
- 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"}
|