pnd-jira-mcp 1.0.0__py3-none-any.whl
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.
- pnd_jira_mcp/__init__.py +28 -0
- pnd_jira_mcp/__main__.py +12 -0
- pnd_jira_mcp/client.py +792 -0
- pnd_jira_mcp/server.py +392 -0
- pnd_jira_mcp/tools.py +295 -0
- pnd_jira_mcp-1.0.0.dist-info/METADATA +226 -0
- pnd_jira_mcp-1.0.0.dist-info/RECORD +10 -0
- pnd_jira_mcp-1.0.0.dist-info/WHEEL +5 -0
- pnd_jira_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- pnd_jira_mcp-1.0.0.dist-info/top_level.txt +1 -0
pnd_jira_mcp/server.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Jira MCP Server Module
|
|
3
|
+
|
|
4
|
+
A standalone MCP (Model Context Protocol) server for Jira integration.
|
|
5
|
+
Can be installed via pip and used independently.
|
|
6
|
+
|
|
7
|
+
Supported MCP Commands:
|
|
8
|
+
- add_comment: Add a comment to a Jira issue
|
|
9
|
+
- transition_issue: Transition an issue to a new status
|
|
10
|
+
- update_fields: Update fields on an issue
|
|
11
|
+
- update_ai_fields: Update AI-related custom fields
|
|
12
|
+
- get_issue: Get issue details
|
|
13
|
+
- get_transitions: Get available transitions
|
|
14
|
+
- search_issues: Search issues using JQL
|
|
15
|
+
- add_label: Add a label to an issue
|
|
16
|
+
- test_connection: Test Jira connection
|
|
17
|
+
- get_boards: Get Jira boards
|
|
18
|
+
- get_sprints: Get sprints for a board
|
|
19
|
+
- get_sprint: Get sprint details
|
|
20
|
+
- get_active_sprint: Get active sprint for a board
|
|
21
|
+
- get_sprint_issues: Get issues in a sprint
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
from typing import Any, Dict, List, Optional
|
|
27
|
+
|
|
28
|
+
from mcp import types
|
|
29
|
+
from mcp.server import Server
|
|
30
|
+
|
|
31
|
+
from .client import JiraClient, JiraConfig
|
|
32
|
+
from .tools import get_jira_tools
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger("pnd_jira_mcp")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def handle_jira_tool(
|
|
38
|
+
name: str,
|
|
39
|
+
arguments: Dict[str, Any],
|
|
40
|
+
jira_client: Optional[JiraClient] = None
|
|
41
|
+
) -> List[types.TextContent]:
|
|
42
|
+
"""
|
|
43
|
+
Handle Jira tool calls.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
name: Tool name
|
|
47
|
+
arguments: Tool arguments
|
|
48
|
+
jira_client: Optional pre-configured JiraClient instance
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of TextContent with results
|
|
52
|
+
"""
|
|
53
|
+
try:
|
|
54
|
+
client = jira_client or JiraClient()
|
|
55
|
+
|
|
56
|
+
if name == "add_comment":
|
|
57
|
+
result_data = client.add_comment(
|
|
58
|
+
issue_key=arguments["issue_key"],
|
|
59
|
+
body=arguments["body"],
|
|
60
|
+
add_qain_label=arguments.get("add_qain_label", True)
|
|
61
|
+
)
|
|
62
|
+
result = {
|
|
63
|
+
"status": "success",
|
|
64
|
+
"message": f"Comment added to {arguments['issue_key']}",
|
|
65
|
+
"comment_id": result_data.get("id")
|
|
66
|
+
}
|
|
67
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
68
|
+
|
|
69
|
+
elif name == "transition_issue":
|
|
70
|
+
success = client.transition_issue(
|
|
71
|
+
issue_key=arguments["issue_key"],
|
|
72
|
+
transition_id=arguments["transition_id"],
|
|
73
|
+
fields=arguments.get("fields"),
|
|
74
|
+
comment=arguments.get("comment")
|
|
75
|
+
)
|
|
76
|
+
result = {
|
|
77
|
+
"status": "success" if success else "failed",
|
|
78
|
+
"message": f"Issue {arguments['issue_key']} transitioned" if success else "Transition failed"
|
|
79
|
+
}
|
|
80
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
81
|
+
|
|
82
|
+
elif name == "update_fields":
|
|
83
|
+
success = client.update_fields(
|
|
84
|
+
issue_key=arguments["issue_key"],
|
|
85
|
+
fields=arguments["fields"]
|
|
86
|
+
)
|
|
87
|
+
result = {
|
|
88
|
+
"status": "success" if success else "failed",
|
|
89
|
+
"message": f"Fields updated on {arguments['issue_key']}" if success else "Update failed"
|
|
90
|
+
}
|
|
91
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
92
|
+
|
|
93
|
+
elif name == "update_ai_fields":
|
|
94
|
+
success = client.update_ai_fields(
|
|
95
|
+
issue_key=arguments["issue_key"],
|
|
96
|
+
ai_used=arguments.get("ai_used", True),
|
|
97
|
+
agent_name=arguments.get("agent_name"),
|
|
98
|
+
efficiency_score=arguments.get("efficiency_score"),
|
|
99
|
+
duration_ms=arguments.get("duration_ms")
|
|
100
|
+
)
|
|
101
|
+
result = {
|
|
102
|
+
"status": "success" if success else "failed",
|
|
103
|
+
"message": f"AI fields updated on {arguments['issue_key']}" if success else "Update failed"
|
|
104
|
+
}
|
|
105
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
106
|
+
|
|
107
|
+
elif name == "get_issue":
|
|
108
|
+
issue = client.get_issue(
|
|
109
|
+
issue_key=arguments["issue_key"],
|
|
110
|
+
fields=arguments.get("fields")
|
|
111
|
+
)
|
|
112
|
+
if issue:
|
|
113
|
+
result = {
|
|
114
|
+
"status": "success",
|
|
115
|
+
"issue": {
|
|
116
|
+
"key": issue.key,
|
|
117
|
+
"id": issue.id,
|
|
118
|
+
"summary": issue.summary,
|
|
119
|
+
"status": issue.status,
|
|
120
|
+
"issue_type": issue.issue_type,
|
|
121
|
+
"description": issue.description,
|
|
122
|
+
"assignee": issue.assignee,
|
|
123
|
+
"priority": issue.priority,
|
|
124
|
+
"labels": issue.labels
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else:
|
|
128
|
+
result = {
|
|
129
|
+
"status": "not_found",
|
|
130
|
+
"message": f"Issue {arguments['issue_key']} not found"
|
|
131
|
+
}
|
|
132
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
133
|
+
|
|
134
|
+
elif name == "get_transitions":
|
|
135
|
+
transitions = client.get_transitions(arguments["issue_key"])
|
|
136
|
+
result = {
|
|
137
|
+
"status": "success",
|
|
138
|
+
"issue_key": arguments["issue_key"],
|
|
139
|
+
"transitions": [
|
|
140
|
+
{"id": t["id"], "name": t["name"]}
|
|
141
|
+
for t in transitions
|
|
142
|
+
]
|
|
143
|
+
}
|
|
144
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
145
|
+
|
|
146
|
+
elif name == "search_issues":
|
|
147
|
+
issues = client.search_issues(
|
|
148
|
+
jql=arguments["jql"],
|
|
149
|
+
max_results=arguments.get("max_results", 50),
|
|
150
|
+
fields=arguments.get("fields")
|
|
151
|
+
)
|
|
152
|
+
result = {
|
|
153
|
+
"status": "success",
|
|
154
|
+
"count": len(issues),
|
|
155
|
+
"issues": [
|
|
156
|
+
{
|
|
157
|
+
"key": issue.key,
|
|
158
|
+
"summary": issue.summary,
|
|
159
|
+
"status": issue.status,
|
|
160
|
+
"issue_type": issue.issue_type
|
|
161
|
+
}
|
|
162
|
+
for issue in issues
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
166
|
+
|
|
167
|
+
elif name == "add_label":
|
|
168
|
+
success = client.add_label(
|
|
169
|
+
issue_key=arguments["issue_key"],
|
|
170
|
+
label=arguments["label"]
|
|
171
|
+
)
|
|
172
|
+
result = {
|
|
173
|
+
"status": "success" if success else "failed",
|
|
174
|
+
"message": f"Label '{arguments['label']}' added to {arguments['issue_key']}" if success else "Failed to add label"
|
|
175
|
+
}
|
|
176
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
177
|
+
|
|
178
|
+
elif name == "test_connection":
|
|
179
|
+
success = client.test_connection()
|
|
180
|
+
result = {
|
|
181
|
+
"status": "success" if success else "failed",
|
|
182
|
+
"message": "Jira connection successful" if success else "Jira connection failed",
|
|
183
|
+
"config": client.config.to_dict() if success else None
|
|
184
|
+
}
|
|
185
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
186
|
+
|
|
187
|
+
elif name == "get_boards":
|
|
188
|
+
boards = client.get_boards(
|
|
189
|
+
project_key=arguments.get("project_key"),
|
|
190
|
+
board_type=arguments.get("board_type")
|
|
191
|
+
)
|
|
192
|
+
result = {
|
|
193
|
+
"status": "success",
|
|
194
|
+
"count": len(boards),
|
|
195
|
+
"boards": [
|
|
196
|
+
{
|
|
197
|
+
"id": board["id"],
|
|
198
|
+
"name": board["name"],
|
|
199
|
+
"type": board.get("type", "unknown")
|
|
200
|
+
}
|
|
201
|
+
for board in boards
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
205
|
+
|
|
206
|
+
elif name == "get_sprints":
|
|
207
|
+
sprints = client.get_sprints(
|
|
208
|
+
board_id=arguments["board_id"],
|
|
209
|
+
state=arguments.get("state"),
|
|
210
|
+
max_results=arguments.get("max_results", 50)
|
|
211
|
+
)
|
|
212
|
+
result = {
|
|
213
|
+
"status": "success",
|
|
214
|
+
"count": len(sprints),
|
|
215
|
+
"sprints": [
|
|
216
|
+
{
|
|
217
|
+
"id": sprint["id"],
|
|
218
|
+
"name": sprint["name"],
|
|
219
|
+
"state": sprint["state"],
|
|
220
|
+
"startDate": sprint.get("startDate"),
|
|
221
|
+
"endDate": sprint.get("endDate"),
|
|
222
|
+
"goal": sprint.get("goal")
|
|
223
|
+
}
|
|
224
|
+
for sprint in sprints
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
228
|
+
|
|
229
|
+
elif name == "get_sprint":
|
|
230
|
+
sprint = client.get_sprint(arguments["sprint_id"])
|
|
231
|
+
if sprint:
|
|
232
|
+
result = {
|
|
233
|
+
"status": "success",
|
|
234
|
+
"sprint": {
|
|
235
|
+
"id": sprint["id"],
|
|
236
|
+
"name": sprint["name"],
|
|
237
|
+
"state": sprint["state"],
|
|
238
|
+
"startDate": sprint.get("startDate"),
|
|
239
|
+
"endDate": sprint.get("endDate"),
|
|
240
|
+
"goal": sprint.get("goal"),
|
|
241
|
+
"originBoardId": sprint.get("originBoardId")
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else:
|
|
245
|
+
result = {
|
|
246
|
+
"status": "not_found",
|
|
247
|
+
"message": f"Sprint {arguments['sprint_id']} not found"
|
|
248
|
+
}
|
|
249
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
250
|
+
|
|
251
|
+
elif name == "get_active_sprint":
|
|
252
|
+
sprint = client.get_active_sprint(arguments["board_id"])
|
|
253
|
+
if sprint:
|
|
254
|
+
result = {
|
|
255
|
+
"status": "success",
|
|
256
|
+
"sprint": {
|
|
257
|
+
"id": sprint["id"],
|
|
258
|
+
"name": sprint["name"],
|
|
259
|
+
"state": sprint["state"],
|
|
260
|
+
"startDate": sprint.get("startDate"),
|
|
261
|
+
"endDate": sprint.get("endDate"),
|
|
262
|
+
"goal": sprint.get("goal")
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else:
|
|
266
|
+
result = {
|
|
267
|
+
"status": "not_found",
|
|
268
|
+
"message": f"No active sprint found for board {arguments['board_id']}"
|
|
269
|
+
}
|
|
270
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
271
|
+
|
|
272
|
+
elif name == "get_sprint_issues":
|
|
273
|
+
issues = client.get_sprint_issues(
|
|
274
|
+
sprint_id=arguments["sprint_id"],
|
|
275
|
+
fields=arguments.get("fields"),
|
|
276
|
+
max_results=arguments.get("max_results", 200)
|
|
277
|
+
)
|
|
278
|
+
result = {
|
|
279
|
+
"status": "success",
|
|
280
|
+
"count": len(issues),
|
|
281
|
+
"issues": [
|
|
282
|
+
{
|
|
283
|
+
"key": issue.key,
|
|
284
|
+
"summary": issue.summary,
|
|
285
|
+
"status": issue.status,
|
|
286
|
+
"issue_type": issue.issue_type,
|
|
287
|
+
"assignee": issue.assignee,
|
|
288
|
+
"priority": issue.priority
|
|
289
|
+
}
|
|
290
|
+
for issue in issues
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
return [types.TextContent(type="text", text=json.dumps(result, indent=2))]
|
|
294
|
+
|
|
295
|
+
else:
|
|
296
|
+
return [types.TextContent(
|
|
297
|
+
type="text",
|
|
298
|
+
text=json.dumps({"status": "error", "message": f"Unknown tool: {name}"})
|
|
299
|
+
)]
|
|
300
|
+
|
|
301
|
+
except Exception as e:
|
|
302
|
+
logger.error(f"Error handling Jira tool {name}: {e}")
|
|
303
|
+
return [types.TextContent(
|
|
304
|
+
type="text",
|
|
305
|
+
text=json.dumps({"status": "error", "message": str(e)})
|
|
306
|
+
)]
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
class JiraMCPServer:
|
|
310
|
+
"""
|
|
311
|
+
Standalone MCP server for Jira operations.
|
|
312
|
+
|
|
313
|
+
Can be run independently without any other dependencies.
|
|
314
|
+
Communicates via stdio for Claude Desktop compatibility.
|
|
315
|
+
|
|
316
|
+
Environment Variables:
|
|
317
|
+
JIRA_BASE_URL: Jira instance URL (e.g., https://your-domain.atlassian.net)
|
|
318
|
+
JIRA_EMAIL: Jira account email
|
|
319
|
+
JIRA_API_TOKEN: Jira API token
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
def __init__(
|
|
323
|
+
self,
|
|
324
|
+
name: str = "jira-mcp",
|
|
325
|
+
config: Optional[JiraConfig] = None
|
|
326
|
+
):
|
|
327
|
+
"""
|
|
328
|
+
Initialize the Jira MCP server.
|
|
329
|
+
|
|
330
|
+
Args:
|
|
331
|
+
name: Server name for MCP identification
|
|
332
|
+
config: Optional JiraConfig for custom configuration
|
|
333
|
+
"""
|
|
334
|
+
self.server = Server(name=name)
|
|
335
|
+
self.config = config
|
|
336
|
+
self._jira_client: Optional[JiraClient] = None
|
|
337
|
+
self._register_tools()
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def jira_client(self) -> JiraClient:
|
|
341
|
+
"""Get or create the Jira client."""
|
|
342
|
+
if self._jira_client is None:
|
|
343
|
+
self._jira_client = JiraClient(config=self.config)
|
|
344
|
+
return self._jira_client
|
|
345
|
+
|
|
346
|
+
def _register_tools(self):
|
|
347
|
+
"""Register all Jira tools."""
|
|
348
|
+
jira_tools = get_jira_tools()
|
|
349
|
+
|
|
350
|
+
@self.server.list_tools()
|
|
351
|
+
async def list_tools() -> List[types.Tool]:
|
|
352
|
+
"""List all Jira tools."""
|
|
353
|
+
return jira_tools
|
|
354
|
+
|
|
355
|
+
@self.server.call_tool()
|
|
356
|
+
async def call_tool(
|
|
357
|
+
name: str,
|
|
358
|
+
arguments: Dict[str, Any]
|
|
359
|
+
) -> List[types.TextContent]:
|
|
360
|
+
"""Handle Jira tool calls."""
|
|
361
|
+
return await handle_jira_tool(name, arguments, self.jira_client)
|
|
362
|
+
|
|
363
|
+
async def run(self):
|
|
364
|
+
"""Run the MCP server via stdio."""
|
|
365
|
+
from mcp.server.stdio import stdio_server
|
|
366
|
+
|
|
367
|
+
async with stdio_server() as (read_stream, write_stream):
|
|
368
|
+
await self.server.run(
|
|
369
|
+
read_stream,
|
|
370
|
+
write_stream,
|
|
371
|
+
self.server.create_initialization_options()
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
async def main():
|
|
376
|
+
"""Main entry point for standalone Jira MCP server."""
|
|
377
|
+
logging.basicConfig(
|
|
378
|
+
level=logging.INFO,
|
|
379
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
380
|
+
)
|
|
381
|
+
server = JiraMCPServer()
|
|
382
|
+
await server.run()
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def main_sync():
|
|
386
|
+
"""Synchronous entry point for CLI."""
|
|
387
|
+
import asyncio
|
|
388
|
+
asyncio.run(main())
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
if __name__ == "__main__":
|
|
392
|
+
main_sync()
|
pnd_jira_mcp/tools.py
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Jira MCP Tools Module
|
|
3
|
+
|
|
4
|
+
Defines the MCP tool specifications for Jira operations.
|
|
5
|
+
These tools are exposed via the Jira MCP server for Claude Desktop/Code integration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from mcp import types
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_jira_tools() -> List[types.Tool]:
|
|
13
|
+
"""
|
|
14
|
+
Get the list of Jira MCP tools.
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
List of MCP Tool definitions for Jira operations.
|
|
18
|
+
"""
|
|
19
|
+
return [
|
|
20
|
+
types.Tool(
|
|
21
|
+
name="add_comment",
|
|
22
|
+
description="Add a comment to a Jira issue. Supports markdown formatting which is converted to Atlassian Document Format (ADF).",
|
|
23
|
+
inputSchema={
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"issue_key": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
29
|
+
},
|
|
30
|
+
"body": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Comment body in markdown format"
|
|
33
|
+
},
|
|
34
|
+
"add_qain_label": {
|
|
35
|
+
"type": "boolean",
|
|
36
|
+
"description": "Whether to add the qAIn label to the issue (default: true)",
|
|
37
|
+
"default": True
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"required": ["issue_key", "body"]
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
types.Tool(
|
|
44
|
+
name="transition_issue",
|
|
45
|
+
description="Transition a Jira issue to a new status. Use get_transitions first to see available transitions.",
|
|
46
|
+
inputSchema={
|
|
47
|
+
"type": "object",
|
|
48
|
+
"properties": {
|
|
49
|
+
"issue_key": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
52
|
+
},
|
|
53
|
+
"transition_id": {
|
|
54
|
+
"type": "string",
|
|
55
|
+
"description": "ID of the transition to perform (get from get_transitions)"
|
|
56
|
+
},
|
|
57
|
+
"comment": {
|
|
58
|
+
"type": "string",
|
|
59
|
+
"description": "Optional comment to add during transition"
|
|
60
|
+
},
|
|
61
|
+
"fields": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"description": "Optional fields to update during transition"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"required": ["issue_key", "transition_id"]
|
|
67
|
+
}
|
|
68
|
+
),
|
|
69
|
+
types.Tool(
|
|
70
|
+
name="update_fields",
|
|
71
|
+
description="Update fields on a Jira issue. Supports both standard and custom fields.",
|
|
72
|
+
inputSchema={
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"issue_key": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
78
|
+
},
|
|
79
|
+
"fields": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"description": "Dictionary of field names/IDs to values"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"required": ["issue_key", "fields"]
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
types.Tool(
|
|
88
|
+
name="update_ai_fields",
|
|
89
|
+
description="Update AI-related custom fields on a Jira issue. Convenience method for updating AI tracking fields.",
|
|
90
|
+
inputSchema={
|
|
91
|
+
"type": "object",
|
|
92
|
+
"properties": {
|
|
93
|
+
"issue_key": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
96
|
+
},
|
|
97
|
+
"ai_used": {
|
|
98
|
+
"type": "boolean",
|
|
99
|
+
"description": "Whether AI was used (default: true)",
|
|
100
|
+
"default": True
|
|
101
|
+
},
|
|
102
|
+
"agent_name": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "Name of the AI agent"
|
|
105
|
+
},
|
|
106
|
+
"efficiency_score": {
|
|
107
|
+
"type": "number",
|
|
108
|
+
"description": "Effectiveness score (0-100)"
|
|
109
|
+
},
|
|
110
|
+
"duration_ms": {
|
|
111
|
+
"type": "number",
|
|
112
|
+
"description": "Duration in milliseconds"
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
"required": ["issue_key"]
|
|
116
|
+
}
|
|
117
|
+
),
|
|
118
|
+
types.Tool(
|
|
119
|
+
name="get_issue",
|
|
120
|
+
description="Get details of a Jira issue by key.",
|
|
121
|
+
inputSchema={
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {
|
|
124
|
+
"issue_key": {
|
|
125
|
+
"type": "string",
|
|
126
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
127
|
+
},
|
|
128
|
+
"fields": {
|
|
129
|
+
"type": "array",
|
|
130
|
+
"items": {"type": "string"},
|
|
131
|
+
"description": "Optional list of specific fields to retrieve"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"required": ["issue_key"]
|
|
135
|
+
}
|
|
136
|
+
),
|
|
137
|
+
types.Tool(
|
|
138
|
+
name="get_transitions",
|
|
139
|
+
description="Get available transitions for a Jira issue. Use this to find valid transition IDs before calling transition_issue.",
|
|
140
|
+
inputSchema={
|
|
141
|
+
"type": "object",
|
|
142
|
+
"properties": {
|
|
143
|
+
"issue_key": {
|
|
144
|
+
"type": "string",
|
|
145
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"required": ["issue_key"]
|
|
149
|
+
}
|
|
150
|
+
),
|
|
151
|
+
types.Tool(
|
|
152
|
+
name="search_issues",
|
|
153
|
+
description="Search for Jira issues using JQL (Jira Query Language).",
|
|
154
|
+
inputSchema={
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": {
|
|
157
|
+
"jql": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"description": "JQL query string (e.g., 'project = PROJ AND status = Open')"
|
|
160
|
+
},
|
|
161
|
+
"max_results": {
|
|
162
|
+
"type": "integer",
|
|
163
|
+
"description": "Maximum number of results to return (default: 50)",
|
|
164
|
+
"default": 50
|
|
165
|
+
},
|
|
166
|
+
"fields": {
|
|
167
|
+
"type": "array",
|
|
168
|
+
"items": {"type": "string"},
|
|
169
|
+
"description": "Optional list of specific fields to retrieve"
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
"required": ["jql"]
|
|
173
|
+
}
|
|
174
|
+
),
|
|
175
|
+
types.Tool(
|
|
176
|
+
name="add_label",
|
|
177
|
+
description="Add a label to a Jira issue.",
|
|
178
|
+
inputSchema={
|
|
179
|
+
"type": "object",
|
|
180
|
+
"properties": {
|
|
181
|
+
"issue_key": {
|
|
182
|
+
"type": "string",
|
|
183
|
+
"description": "Jira issue key (e.g., 'PROJ-123')"
|
|
184
|
+
},
|
|
185
|
+
"label": {
|
|
186
|
+
"type": "string",
|
|
187
|
+
"description": "Label to add to the issue"
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
"required": ["issue_key", "label"]
|
|
191
|
+
}
|
|
192
|
+
),
|
|
193
|
+
types.Tool(
|
|
194
|
+
name="test_connection",
|
|
195
|
+
description="Test the Jira connection and verify credentials.",
|
|
196
|
+
inputSchema={
|
|
197
|
+
"type": "object",
|
|
198
|
+
"properties": {}
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
types.Tool(
|
|
202
|
+
name="get_boards",
|
|
203
|
+
description="Get all Jira boards, optionally filtered by project or type.",
|
|
204
|
+
inputSchema={
|
|
205
|
+
"type": "object",
|
|
206
|
+
"properties": {
|
|
207
|
+
"project_key": {
|
|
208
|
+
"type": "string",
|
|
209
|
+
"description": "Optional project key to filter boards (e.g., 'PROJ')"
|
|
210
|
+
},
|
|
211
|
+
"board_type": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"description": "Optional board type filter",
|
|
214
|
+
"enum": ["scrum", "kanban"]
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
),
|
|
219
|
+
types.Tool(
|
|
220
|
+
name="get_sprints",
|
|
221
|
+
description="Get sprints for a Jira board. Use get_boards first to find the board ID.",
|
|
222
|
+
inputSchema={
|
|
223
|
+
"type": "object",
|
|
224
|
+
"properties": {
|
|
225
|
+
"board_id": {
|
|
226
|
+
"type": "integer",
|
|
227
|
+
"description": "Board ID (get from get_boards)"
|
|
228
|
+
},
|
|
229
|
+
"state": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"description": "Optional sprint state filter",
|
|
232
|
+
"enum": ["active", "closed", "future"]
|
|
233
|
+
},
|
|
234
|
+
"max_results": {
|
|
235
|
+
"type": "integer",
|
|
236
|
+
"description": "Maximum number of results (default: 50)",
|
|
237
|
+
"default": 50
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
"required": ["board_id"]
|
|
241
|
+
}
|
|
242
|
+
),
|
|
243
|
+
types.Tool(
|
|
244
|
+
name="get_sprint",
|
|
245
|
+
description="Get details of a specific sprint by ID.",
|
|
246
|
+
inputSchema={
|
|
247
|
+
"type": "object",
|
|
248
|
+
"properties": {
|
|
249
|
+
"sprint_id": {
|
|
250
|
+
"type": "integer",
|
|
251
|
+
"description": "Sprint ID"
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
"required": ["sprint_id"]
|
|
255
|
+
}
|
|
256
|
+
),
|
|
257
|
+
types.Tool(
|
|
258
|
+
name="get_active_sprint",
|
|
259
|
+
description="Get the currently active sprint for a board.",
|
|
260
|
+
inputSchema={
|
|
261
|
+
"type": "object",
|
|
262
|
+
"properties": {
|
|
263
|
+
"board_id": {
|
|
264
|
+
"type": "integer",
|
|
265
|
+
"description": "Board ID (get from get_boards)"
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"required": ["board_id"]
|
|
269
|
+
}
|
|
270
|
+
),
|
|
271
|
+
types.Tool(
|
|
272
|
+
name="get_sprint_issues",
|
|
273
|
+
description="Get all issues in a sprint.",
|
|
274
|
+
inputSchema={
|
|
275
|
+
"type": "object",
|
|
276
|
+
"properties": {
|
|
277
|
+
"sprint_id": {
|
|
278
|
+
"type": "integer",
|
|
279
|
+
"description": "Sprint ID"
|
|
280
|
+
},
|
|
281
|
+
"fields": {
|
|
282
|
+
"type": "array",
|
|
283
|
+
"items": {"type": "string"},
|
|
284
|
+
"description": "Optional list of specific fields to retrieve"
|
|
285
|
+
},
|
|
286
|
+
"max_results": {
|
|
287
|
+
"type": "integer",
|
|
288
|
+
"description": "Maximum number of results (default: 200)",
|
|
289
|
+
"default": 200
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"required": ["sprint_id"]
|
|
293
|
+
}
|
|
294
|
+
),
|
|
295
|
+
]
|