diff --git a/src/timmy/mcp_tools.py b/src/timmy/mcp_tools.py index 50b78e6f..ea746798 100644 --- a/src/timmy/mcp_tools.py +++ b/src/timmy/mcp_tools.py @@ -204,15 +204,61 @@ def _bridge_to_work_order(title: str, body: str, category: str) -> None: logger.debug("Work order bridge failed: %s", exc) +async def _ensure_issue_session(): + """Lazily create and connect the module-level MCP issue session. + + Returns the connected ``MCPTools`` instance or raises on failure. + """ + from agno.tools.mcp import MCPTools + + global _issue_session + + if _issue_session is None: + _issue_session = MCPTools( + server_params=_gitea_server_params(), + timeout_seconds=settings.mcp_timeout, + ) + + if not getattr(_issue_session, "_connected", False): + await _issue_session.connect() + _issue_session._connected = True + + return _issue_session + + +def _build_issue_args(title: str, body: str) -> dict: + """Build the ``issue_write`` tool arguments dict. + + Appends the auto-filing signature and parses owner/repo from settings. + """ + full_body = body + if full_body: + full_body += "\n\n" + full_body += "---\n*Auto-filed by Timmy's thinking engine*" + + owner, repo = settings.gitea_repo.split("/", 1) + + return { + "method": "create", + "owner": owner, + "repo": repo, + "title": title, + "body": full_body, + } + + +def _category_from_labels(labels: str) -> str: + """Derive a work-order category from comma-separated label names.""" + label_list = [tag.strip() for tag in labels.split(",") if tag.strip()] if labels else [] + return "bug" if "bug" in label_list else "suggestion" + + async def create_gitea_issue_via_mcp(title: str, body: str = "", labels: str = "") -> str: """File a Gitea issue via the MCP server (standalone, no LLM loop). Used by the thinking engine's ``_maybe_file_issues()`` post-hook. Manages its own MCPTools session with lazy connect + graceful failure. - Uses ``tools.session.call_tool()`` for direct MCP invocation — the - ``MCPTools`` wrapper itself does not expose ``call_tool()``. - Args: title: Issue title. body: Issue body (markdown). @@ -225,46 +271,11 @@ async def create_gitea_issue_via_mcp(title: str, body: str = "", labels: str = " return "Gitea integration is not configured." try: - from agno.tools.mcp import MCPTools + session = await _ensure_issue_session() + args = _build_issue_args(title, body) + result = await session.session.call_tool("issue_write", arguments=args) - global _issue_session - - if _issue_session is None: - _issue_session = MCPTools( - server_params=_gitea_server_params(), - timeout_seconds=settings.mcp_timeout, - ) - - # Ensure connected - if not getattr(_issue_session, "_connected", False): - await _issue_session.connect() - _issue_session._connected = True - - # Append auto-filing signature - full_body = body - if full_body: - full_body += "\n\n" - full_body += "---\n*Auto-filed by Timmy's thinking engine*" - - # Parse owner/repo from settings - owner, repo = settings.gitea_repo.split("/", 1) - - # Build tool arguments — gitea-mcp uses issue_write with method="create" - args = { - "method": "create", - "owner": owner, - "repo": repo, - "title": title, - "body": full_body, - } - - # Call via the underlying MCP session (MCPTools doesn't expose call_tool) - result = await _issue_session.session.call_tool("issue_write", arguments=args) - - # Bridge to local work order - label_list = [tag.strip() for tag in labels.split(",") if tag.strip()] if labels else [] - category = "bug" if "bug" in label_list else "suggestion" - _bridge_to_work_order(title, body, category) + _bridge_to_work_order(title, body, _category_from_labels(labels)) logger.info("Created Gitea issue via MCP: %s", title[:60]) return f"Created issue: {title}\n{result}"