Spaces:
Runtime error
Runtime error
initial commit
Browse files- mcp/mcp_server.py +80 -3
mcp/mcp_server.py
CHANGED
|
@@ -415,6 +415,9 @@ def create_jira_user_story(epic_key: str, summary: str, description: str,
|
|
| 415 |
"""
|
| 416 |
print(f"[JIRA] Creating user story under {epic_key}: {summary}")
|
| 417 |
|
|
|
|
|
|
|
|
|
|
| 418 |
if use_real_jira():
|
| 419 |
try:
|
| 420 |
jira = get_jira_client()
|
|
@@ -423,13 +426,13 @@ def create_jira_user_story(epic_key: str, summary: str, description: str,
|
|
| 423 |
description_adf = create_adf_description(description)
|
| 424 |
|
| 425 |
story_dict = {
|
| 426 |
-
'project': {'key':
|
| 427 |
'summary': summary,
|
| 428 |
'description': description_adf,
|
| 429 |
'issuetype': {'name': 'Story'},
|
| 430 |
# Link to Epic - field name varies by JIRA instance, usually 'parent' for Next-Gen or 'customfield_XXXXX'
|
| 431 |
# Trying standard 'parent' first for modern JIRA Cloud
|
| 432 |
-
'parent': {'key':
|
| 433 |
}
|
| 434 |
|
| 435 |
new_issue = jira.create_issue(fields=story_dict)
|
|
@@ -471,6 +474,69 @@ def create_jira_user_story(epic_key: str, summary: str, description: str,
|
|
| 471 |
"timestamp": datetime.now().isoformat()
|
| 472 |
}
|
| 473 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
# ===== Gradio Interface =====
|
| 475 |
def create_gradio_interface():
|
| 476 |
"""Create Gradio interface for MCP server"""
|
|
@@ -556,7 +622,16 @@ def create_gradio_interface():
|
|
| 556 |
)
|
| 557 |
|
| 558 |
with gr.Tab("JIRA - Create User Story"):
|
| 559 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 560 |
story_summary = gr.Textbox(label="Story Summary", placeholder="Story title...")
|
| 561 |
story_desc = gr.Textbox(
|
| 562 |
label="Story Description",
|
|
@@ -567,6 +642,8 @@ def create_gradio_interface():
|
|
| 567 |
create_story_btn = gr.Button("Create User Story", variant="primary")
|
| 568 |
story_output = gr.JSON(label="Created Story")
|
| 569 |
|
|
|
|
|
|
|
| 570 |
create_story_btn.click(
|
| 571 |
create_jira_user_story,
|
| 572 |
inputs=[story_epic, story_summary, story_desc, story_points],
|
|
|
|
| 415 |
"""
|
| 416 |
print(f"[JIRA] Creating user story under {epic_key}: {summary}")
|
| 417 |
|
| 418 |
+
# Extract actual key if format is "KEY: Summary"
|
| 419 |
+
actual_epic_key = epic_key.split(':')[0].strip()
|
| 420 |
+
|
| 421 |
if use_real_jira():
|
| 422 |
try:
|
| 423 |
jira = get_jira_client()
|
|
|
|
| 426 |
description_adf = create_adf_description(description)
|
| 427 |
|
| 428 |
story_dict = {
|
| 429 |
+
'project': {'key': actual_epic_key.split('-')[0]},
|
| 430 |
'summary': summary,
|
| 431 |
'description': description_adf,
|
| 432 |
'issuetype': {'name': 'Story'},
|
| 433 |
# Link to Epic - field name varies by JIRA instance, usually 'parent' for Next-Gen or 'customfield_XXXXX'
|
| 434 |
# Trying standard 'parent' first for modern JIRA Cloud
|
| 435 |
+
'parent': {'key': actual_epic_key}
|
| 436 |
}
|
| 437 |
|
| 438 |
new_issue = jira.create_issue(fields=story_dict)
|
|
|
|
| 474 |
"timestamp": datetime.now().isoformat()
|
| 475 |
}
|
| 476 |
|
| 477 |
+
# ===== Helper Functions =====
|
| 478 |
+
def get_available_epics() -> List[str]:
|
| 479 |
+
"""Get list of available epics for dropdown"""
|
| 480 |
+
epics_list = []
|
| 481 |
+
|
| 482 |
+
if use_real_jira():
|
| 483 |
+
try:
|
| 484 |
+
# Use direct REST API call to avoid deprecated GET endpoint
|
| 485 |
+
import requests
|
| 486 |
+
from requests.auth import HTTPBasicAuth
|
| 487 |
+
|
| 488 |
+
# Ensure no trailing slash in base URL
|
| 489 |
+
base_url = config.JIRA_URL.rstrip('/')
|
| 490 |
+
api_url = f"{base_url}/rest/api/3/search"
|
| 491 |
+
|
| 492 |
+
auth = HTTPBasicAuth(config.JIRA_EMAIL, config.JIRA_API_TOKEN)
|
| 493 |
+
headers = {
|
| 494 |
+
"Accept": "application/json",
|
| 495 |
+
"Content-Type": "application/json"
|
| 496 |
+
}
|
| 497 |
+
|
| 498 |
+
# Search for all epics in project
|
| 499 |
+
jql = f'project = "{config.JIRA_PROJECT_KEY}" AND issuetype = Epic ORDER BY created DESC'
|
| 500 |
+
|
| 501 |
+
payload = {
|
| 502 |
+
"jql": jql,
|
| 503 |
+
"maxResults": 20,
|
| 504 |
+
"fields": ["summary"]
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
response = requests.post(api_url, json=payload, headers=headers, auth=auth)
|
| 508 |
+
|
| 509 |
+
# Handle 410 fallback
|
| 510 |
+
if response.status_code == 410:
|
| 511 |
+
api_url = f"{base_url}/rest/api/3/search/jql"
|
| 512 |
+
response = requests.post(api_url, json=payload, headers=headers, auth=auth)
|
| 513 |
+
|
| 514 |
+
if response.ok:
|
| 515 |
+
data = response.json()
|
| 516 |
+
issues = data.get("issues", [])
|
| 517 |
+
if "issues" not in data and isinstance(data, list):
|
| 518 |
+
issues = data
|
| 519 |
+
|
| 520 |
+
for issue in issues:
|
| 521 |
+
key = issue.get("key")
|
| 522 |
+
summary = issue.get("fields", {}).get("summary", "")
|
| 523 |
+
epics_list.append(f"{key}: {summary}")
|
| 524 |
+
except Exception as e:
|
| 525 |
+
print(f"[JIRA] Error fetching epics: {e}")
|
| 526 |
+
else:
|
| 527 |
+
# Mock mode
|
| 528 |
+
for epic in mock_epics:
|
| 529 |
+
epics_list.append(f"{epic['key']}: {epic['summary']}")
|
| 530 |
+
|
| 531 |
+
return epics_list
|
| 532 |
+
|
| 533 |
+
def refresh_epics_dropdown():
|
| 534 |
+
"""Refresh the choices for the epic dropdown"""
|
| 535 |
+
choices = get_available_epics()
|
| 536 |
+
if not choices:
|
| 537 |
+
return gr.Dropdown.update(choices=[], value=None, label="No Epics Found - Please Create an Epic First")
|
| 538 |
+
return gr.Dropdown.update(choices=choices, value=choices[0] if choices else None, label="Select Epic")
|
| 539 |
+
|
| 540 |
# ===== Gradio Interface =====
|
| 541 |
def create_gradio_interface():
|
| 542 |
"""Create Gradio interface for MCP server"""
|
|
|
|
| 622 |
)
|
| 623 |
|
| 624 |
with gr.Tab("JIRA - Create User Story"):
|
| 625 |
+
with gr.Row():
|
| 626 |
+
initial_epics = get_available_epics()
|
| 627 |
+
story_epic = gr.Dropdown(
|
| 628 |
+
choices=initial_epics,
|
| 629 |
+
value=initial_epics[0] if initial_epics else None,
|
| 630 |
+
label="Select Epic" if initial_epics else "No Epics Found - Please Create an Epic First",
|
| 631 |
+
allow_custom_value=True # Allow typing if needed, or strictly selection
|
| 632 |
+
)
|
| 633 |
+
refresh_btn = gr.Button("🔄 Refresh Epics")
|
| 634 |
+
|
| 635 |
story_summary = gr.Textbox(label="Story Summary", placeholder="Story title...")
|
| 636 |
story_desc = gr.Textbox(
|
| 637 |
label="Story Description",
|
|
|
|
| 642 |
create_story_btn = gr.Button("Create User Story", variant="primary")
|
| 643 |
story_output = gr.JSON(label="Created Story")
|
| 644 |
|
| 645 |
+
refresh_btn.click(refresh_epics_dropdown, outputs=[story_epic])
|
| 646 |
+
|
| 647 |
create_story_btn.click(
|
| 648 |
create_jira_user_story,
|
| 649 |
inputs=[story_epic, story_summary, story_desc, story_points],
|