From 5f22831106a2b671f7e53944ed1f04641770559b Mon Sep 17 00:00:00 2001 From: zgaetano Date: Tue, 31 Mar 2026 23:17:52 -0400 Subject: [PATCH] fix(forgejo-mcp): convert sync httpx.Client to async AsyncClient MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All tool functions and API helpers were using synchronous httpx.Client, which blocked FastMCP's async event loop on every tool call — causing 60s timeouts from Claude even though the container appeared healthy. Converted get_client(), api_get(), api_post(), api_patch() and all 12 @mcp.tool() functions to async/await, matching the pattern used by the other working backends (truenas-mcp, homeassistant-mcp). Redeploy with: docker compose up -d --build forgejo-mcp --- forgejo-mcp/forgejo_mcp.py | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/forgejo-mcp/forgejo_mcp.py b/forgejo-mcp/forgejo_mcp.py index 9a3d63a..72ee916 100644 --- a/forgejo-mcp/forgejo_mcp.py +++ b/forgejo-mcp/forgejo_mcp.py @@ -37,31 +37,31 @@ mcp = FastMCP("forgejo-mcp") # --------------------------------------------------------------------------- -def get_client() -> httpx.Client: - return httpx.Client( +def get_client() -> httpx.AsyncClient: + return httpx.AsyncClient( base_url=FORGEJO_URL.rstrip("/"), headers={"Authorization": f"token {FORGEJO_TOKEN}"}, timeout=30.0, ) -def api_get(endpoint: str, params: dict | None = None) -> Any: - with get_client() as client: - r = client.get(f"/api/v1{endpoint}", params=params or {}) +async def api_get(endpoint: str, params: dict | None = None) -> Any: + async with get_client() as client: + r = await client.get(f"/api/v1{endpoint}", params=params or {}) r.raise_for_status() return r.json() if r.status_code != 204 else {} -def api_post(endpoint: str, body: dict | None = None) -> Any: - with get_client() as client: - r = client.post(f"/api/v1{endpoint}", json=body or {}) +async def api_post(endpoint: str, body: dict | None = None) -> Any: + async with get_client() as client: + r = await client.post(f"/api/v1{endpoint}", json=body or {}) r.raise_for_status() return r.json() if r.status_code != 204 else {} -def api_patch(endpoint: str, body: dict | None = None) -> Any: - with get_client() as client: - r = client.patch(f"/api/v1{endpoint}", json=body or {}) +async def api_patch(endpoint: str, body: dict | None = None) -> Any: + async with get_client() as client: + r = await client.patch(f"/api/v1{endpoint}", json=body or {}) r.raise_for_status() return r.json() if r.status_code != 204 else {} @@ -72,7 +72,7 @@ def api_patch(endpoint: str, body: dict | None = None) -> Any: @mcp.tool() -def forgejo_list_repositories(owner: str, limit: int = 20, page: int = 1) -> str: +async def forgejo_list_repositories(owner: str, limit: int = 20, page: int = 1) -> str: """List repositories for a user or organization. Args: @@ -88,7 +88,7 @@ def forgejo_list_repositories(owner: str, limit: int = 20, page: int = 1) -> str @mcp.tool() -def forgejo_get_repository(owner: str, repo: str) -> str: +async def forgejo_get_repository(owner: str, repo: str) -> str: """Get details about a specific repository. Args: @@ -109,7 +109,7 @@ def forgejo_get_repository(owner: str, repo: str) -> str: @mcp.tool() -def forgejo_create_repository( +async def forgejo_create_repository( name: str, description: str = "", private: bool = False, @@ -133,7 +133,7 @@ def forgejo_create_repository( @mcp.tool() -def forgejo_list_issues( +async def forgejo_list_issues( owner: str, repo: str, state: str = "open", @@ -157,7 +157,7 @@ def forgejo_list_issues( @mcp.tool() -def forgejo_create_issue( +async def forgejo_create_issue( owner: str, repo: str, title: str, @@ -182,7 +182,7 @@ def forgejo_create_issue( @mcp.tool() -def forgejo_update_issue( +async def forgejo_update_issue( owner: str, repo: str, issue_number: int, @@ -206,7 +206,7 @@ def forgejo_update_issue( @mcp.tool() -def forgejo_list_pull_requests( +async def forgejo_list_pull_requests( owner: str, repo: str, state: str = "open", @@ -230,7 +230,7 @@ def forgejo_list_pull_requests( @mcp.tool() -def forgejo_create_pull_request( +async def forgejo_create_pull_request( owner: str, repo: str, title: str, @@ -258,7 +258,7 @@ def forgejo_create_pull_request( @mcp.tool() -def forgejo_list_branches(owner: str, repo: str, limit: int = 20, page: int = 1) -> str: +async def forgejo_list_branches(owner: str, repo: str, limit: int = 20, page: int = 1) -> str: """List branches in a repository. Args: @@ -275,7 +275,7 @@ def forgejo_list_branches(owner: str, repo: str, limit: int = 20, page: int = 1) @mcp.tool() -def forgejo_get_file(owner: str, repo: str, path: str, ref: str = "main") -> str: +async def forgejo_get_file(owner: str, repo: str, path: str, ref: str = "main") -> str: """Get file contents from a repository. Args: @@ -293,7 +293,7 @@ def forgejo_get_file(owner: str, repo: str, path: str, ref: str = "main") -> str @mcp.tool() -def forgejo_search_repositories(q: str, limit: int = 20, page: int = 1) -> str: +async def forgejo_search_repositories(q: str, limit: int = 20, page: int = 1) -> str: """Search for repositories across the Forgejo instance. Args: @@ -310,7 +310,7 @@ def forgejo_search_repositories(q: str, limit: int = 20, page: int = 1) -> str: @mcp.tool() -def forgejo_get_user(username: str) -> str: +async def forgejo_get_user(username: str) -> str: """Get user profile information. Args: