""" Jira MCP Server ================ Exposes all Jira Sprint API endpoints as MCP tools. The agent calls these tools natively — no JSON action routing needed. Run this first: python jira_mcp_server.py Then in another terminal run the agent: python jira_agent_mcp.py """ import os import requests from mcp.server.fastmcp import FastMCP from dotenv import load_dotenv load_dotenv() JIRA_API_URL = os.environ.get("JIRA_API_URL", "http://0.0.0.0:8001") mcp = FastMCP("Jira Sprint Manager") def call(method: str, path: str, payload: dict = None) -> dict: url = f"{JIRA_API_URL}{path}" try: if method == "GET": r = requests.get(url, headers={"accept": "application/json"}, timeout=10) else: r = requests.request( method, url, headers={"Content-Type": "application/json", "accept": "application/json"}, json=payload or {}, timeout=10, ) r.raise_for_status() return r.json() except requests.RequestException as e: return {"success": False, "error": str(e)} # ── MCP tools — one per Jira endpoint ──────────────────────────────────────── @mcp.tool() def health_check() -> dict: """Check if the Jira API server is running and healthy.""" return call("GET", "/") @mcp.tool() def get_backlog() -> dict: """ Get all backlog issues — stories and tasks not assigned to any sprint. Use this when the user asks about unassigned work, backlog items, or pending stories. """ return call("GET", "/api/backlog") @mcp.tool() def create_story(name: str, description: str) -> dict: """ Create a new story/task/issue in Jira. Args: name: Short title of the story (e.g. 'Implement login API') description: Detailed description of what needs to be done """ return call("POST", "/api/story", {"name": name, "description": description}) @mcp.tool() def get_active_sprint() -> dict: """ Get the current active sprint with all its issues and their statuses. Use this when the user asks about sprint progress, current work, or what is in the sprint. """ return call("GET", "/api/sprint/active") @mcp.tool() def add_issues_to_sprint(sprint_id: int, issue_keys: list[str]) -> dict: """ Add one or more backlog issues into an active sprint. Args: sprint_id: The numeric ID of the sprint (e.g. 9) issue_keys: List of Jira issue keys to add (e.g. ['SCRUM-17', 'SCRUM-19']) """ return call("POST", f"/api/sprint/{sprint_id}/add-issues", {"issue_keys": issue_keys}) @mcp.tool() def transition_issue(issue_key: str, status_code: str, comment: str = "") -> dict: """ Update the status of a Jira issue. Optionally add a comment explaining the change. Status codes: "1" = To Do "2" = In Progress "3" = Testing "4" = Done Args: issue_key: The Jira issue key (e.g. 'SCRUM-17') status_code: One of "1", "2", "3", "4" comment: Optional comment to add to the issue (e.g. 'All tests passed') """ payload = {"status": status_code} if comment: payload["comment"] = comment return call("POST", f"/api/issue/{issue_key}/transition", payload) @mcp.tool() def sprint_rollover(new_sprint_name: str, add_backlog_to_new_sprint: bool = False) -> dict: """ Close the current active sprint and start a new one. Unfinished issues are automatically carried forward to the new sprint. Args: new_sprint_name: Name for the new sprint (e.g. 'Sprint 2') add_backlog_to_new_sprint: If True, also pull all backlog items into the new sprint """ return call("POST", "/api/sprint/rollover", { "new_sprint_name": new_sprint_name, "add_backlog_to_new_sprint": add_backlog_to_new_sprint, }) # ── run ─────────────────────────────────────────────────────────────────────── if __name__ == "__main__": print("=" * 50) print(" Jira MCP Server starting...") print(f" Jira API : {JIRA_API_URL}") print(" Tools : health_check, get_backlog, create_story,") print(" get_active_sprint, add_issues_to_sprint,") print(" transition_issue, sprint_rollover") print("=" * 50 + "\n") mcp.run(transport="stdio")