agentek-youtrack-mcp 1.0.2 → 1.1.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.
@@ -11,7 +11,7 @@ from mcp.server.fastmcp import FastMCP
11
11
 
12
12
  from youtrack_mcp.version import __version__ as APP_VERSION
13
13
  from youtrack_mcp.config import config
14
- from youtrack_mcp.tools.loader import load_all_tools
14
+ from youtrack_mcp.tools.loader import load_all_tools, tool_description
15
15
 
16
16
  # Set up logging
17
17
  logging.basicConfig(
@@ -34,7 +34,7 @@ def create_server(host: str = "0.0.0.0", port: int = 8000) -> FastMCP:
34
34
  # Load and register all tools
35
35
  tools = load_all_tools()
36
36
  for name, func in tools.items():
37
- mcp.add_tool(func, name=name)
37
+ mcp.add_tool(func, name=name, description=tool_description(func))
38
38
 
39
39
  logger.info(f"Registered {len(tools)} tools with FastMCP")
40
40
  return mcp
@@ -183,6 +183,7 @@ class IssuesClient:
183
183
  summary: str,
184
184
  description: Optional[str] = None,
185
185
  additional_fields: Optional[Dict[str, Any]] = None,
186
+ custom_fields: Optional[Dict[str, Any]] = None,
186
187
  ) -> Issue:
187
188
  """
188
189
  Create a new issue.
@@ -192,6 +193,9 @@ class IssuesClient:
192
193
  summary: The issue summary
193
194
  description: The issue description
194
195
  additional_fields: Additional fields to set on the issue
196
+ custom_fields: Custom field names/values sent in the same POST as
197
+ the issue itself — projects whose required fields have no
198
+ default value reject a create without them
195
199
 
196
200
  Returns:
197
201
  The created issue data
@@ -209,6 +213,11 @@ class IssuesClient:
209
213
  if description:
210
214
  data["description"] = description
211
215
 
216
+ if custom_fields:
217
+ data.update(
218
+ self._build_custom_fields_payload(custom_fields, project_id)
219
+ )
220
+
212
221
  if additional_fields:
213
222
  data.update(additional_fields)
214
223
 
@@ -129,9 +129,25 @@ class IssueTools:
129
129
  """Search for issues using YouTrack query syntax."""
130
130
  return self.basic_operations.search_issues(query, limit)
131
131
 
132
- def create_issue(self, project: str, summary: str, description: Optional[str] = None) -> str:
133
- """Create a new issue in the specified project."""
134
- return self.basic_operations.create_issue(project, summary, description)
132
+ def create_issue(
133
+ self,
134
+ project: str,
135
+ summary: str,
136
+ description: Optional[str] = None,
137
+ custom_fields: Optional[Dict[str, Any]] = None,
138
+ ) -> str:
139
+ """Create a new issue in the specified project, optionally setting custom fields in the same request.
140
+
141
+ custom_fields maps field names to values and is sent together with the issue itself.
142
+ Projects whose mandatory fields have no default value reject a create without them,
143
+ so pass those fields here instead of updating the issue afterwards.
144
+ Use get_custom_fields / get_available_custom_field_values to discover names and allowed values.
145
+
146
+ Example: create_issue(project="DEMO", summary="Bug in login",
147
+ description="Users cannot log in",
148
+ custom_fields={"Priority": "Critical", "Type": "Bug", "Assignee": "john.doe"})
149
+ """
150
+ return self.basic_operations.create_issue(project, summary, description, custom_fields)
135
151
 
136
152
  def update_issue(self, issue_id: str, summary: Optional[str] = None, description: Optional[str] = None, uses_markdown: Optional[bool] = None, additional_fields: Optional[Dict[str, Any]] = None) -> str:
137
153
  """Update basic issue fields."""
@@ -107,7 +107,8 @@ class BasicOperations:
107
107
  project: The project identifier (e.g., "DEMO", "PROJECT")
108
108
  summary: The issue title/summary
109
109
  description: Optional detailed description of the issue
110
- custom_fields: Optional dictionary of custom field names and values to set on creation (e.g., {"Assignee": "john.doe", "Priority": "Critical"})
110
+ custom_fields: Optional dictionary of custom field names and values, sent in the same API call as the issue
111
+ (required for projects where mandatory fields have no default value)
111
112
 
112
113
  Returns:
113
114
  JSON string with the created issue information
@@ -161,7 +162,7 @@ class BasicOperations:
161
162
  # Call the API client to create the issue
162
163
  try:
163
164
  issue = self.issues_api.create_issue(
164
- project_id, summary, description
165
+ project_id, summary, description, custom_fields=custom_fields
165
166
  )
166
167
 
167
168
  # Check if we got an issue with an ID
@@ -169,14 +170,6 @@ class BasicOperations:
169
170
  # Handle error returned as a dict
170
171
  return format_json_response(issue)
171
172
 
172
- # Apply custom fields if provided
173
- if custom_fields and hasattr(issue, "id") and issue.id:
174
- try:
175
- logger.info(f"Setting custom fields on new issue {issue.id}: {custom_fields}")
176
- self.issues_api.update_issue_custom_fields(issue.id, custom_fields, validate=False)
177
- except Exception as cf_err:
178
- logger.warning(f"Issue created but failed to set custom fields: {cf_err}")
179
-
180
173
  # Try to get full issue details right after creation
181
174
  if hasattr(issue, "id"):
182
175
  try:
@@ -300,12 +293,12 @@ class BasicOperations:
300
293
  }
301
294
  },
302
295
  "create_issue": {
303
- "description": "Create a new issue in YouTrack with automatic project validation. Accepts both project short names (DEMO) and project IDs (0-1). Supports setting custom fields at creation time. Example: create_issue(project='DEMO', summary='Bug in login', description='Users cannot log in', custom_fields={'Assignee': 'john.doe', 'Priority': 'Critical'})",
296
+ "description": "Create a new issue in YouTrack with automatic project validation. Accepts both project short names (DEMO) and project IDs (0-1). Custom fields are sent in the same API call as the issue, so projects with mandatory fields (no default value) can be created in one step. Example: create_issue(project='DEMO', summary='Bug in login', description='Users cannot log in', custom_fields={'Assignee': 'john.doe', 'Priority': 'Critical'})",
304
297
  "parameter_descriptions": {
305
298
  "project": "Project identifier (short name like 'DEMO' or ID like '0-1')",
306
299
  "summary": "Issue title/summary (required)",
307
300
  "description": "Detailed description of the issue (optional)",
308
- "custom_fields": "Optional dictionary of custom field names to values to set on creation (e.g., {'Assignee': 'john.doe', 'Priority': 'Critical', 'Fix versions': ['1.0', '1.1']})"
301
+ "custom_fields": "Optional dictionary of custom field names to values, applied atomically with issue creation (e.g., {'Assignee': 'john.doe', 'Priority': 'Critical', 'Fix versions': ['1.0', '1.1']})"
309
302
  }
310
303
  },
311
304
  "update_issue": {
@@ -325,6 +325,27 @@ def load_all_tools() -> Dict[str, Callable]:
325
325
  return tools
326
326
 
327
327
 
328
+ def tool_description(func: Callable) -> Any:
329
+ """
330
+ Build the description an MCP client sees for a tool.
331
+
332
+ Renders the tool's `tool_definition` (description + parameter_descriptions) into text.
333
+ Returns None when the tool has no definition, so FastMCP falls back to the docstring.
334
+ """
335
+ definition = getattr(func, "tool_definition", None)
336
+ if not definition or not definition.get("description"):
337
+ return None
338
+
339
+ lines = [definition["description"]]
340
+ parameters = definition.get("parameter_descriptions") or {}
341
+ if parameters:
342
+ lines.append("")
343
+ lines.append("Parameters:")
344
+ lines.extend(f"- {name}: {text}" for name, text in parameters.items())
345
+
346
+ return "\n".join(lines)
347
+
348
+
328
349
  def _get_tools_from_class(tool_class: Any) -> Dict[str, Callable]:
329
350
  """
330
351
  Get all tools from a tool class.
@@ -2,4 +2,4 @@
2
2
  YouTrack MCP Server version information.
3
3
  """
4
4
 
5
- __version__ = "1.0.2"
5
+ __version__ = "1.1.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentek-youtrack-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "A Model Context Protocol (MCP) server for JetBrains YouTrack integration",
5
5
  "keywords": [
6
6
  "mcp",