fix(forgejo-mcp): convert sync httpx.Client to async AsyncClient

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
This commit is contained in:
Zac Gaetano 2026-03-31 23:17:52 -04:00
parent 39fff1e44a
commit 5f22831106

View file

@ -37,31 +37,31 @@ mcp = FastMCP("forgejo-mcp")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def get_client() -> httpx.Client: def get_client() -> httpx.AsyncClient:
return httpx.Client( return httpx.AsyncClient(
base_url=FORGEJO_URL.rstrip("/"), base_url=FORGEJO_URL.rstrip("/"),
headers={"Authorization": f"token {FORGEJO_TOKEN}"}, headers={"Authorization": f"token {FORGEJO_TOKEN}"},
timeout=30.0, timeout=30.0,
) )
def api_get(endpoint: str, params: dict | None = None) -> Any: async def api_get(endpoint: str, params: dict | None = None) -> Any:
with get_client() as client: async with get_client() as client:
r = client.get(f"/api/v1{endpoint}", params=params or {}) r = await client.get(f"/api/v1{endpoint}", params=params or {})
r.raise_for_status() r.raise_for_status()
return r.json() if r.status_code != 204 else {} return r.json() if r.status_code != 204 else {}
def api_post(endpoint: str, body: dict | None = None) -> Any: async def api_post(endpoint: str, body: dict | None = None) -> Any:
with get_client() as client: async with get_client() as client:
r = client.post(f"/api/v1{endpoint}", json=body or {}) r = await client.post(f"/api/v1{endpoint}", json=body or {})
r.raise_for_status() r.raise_for_status()
return r.json() if r.status_code != 204 else {} return r.json() if r.status_code != 204 else {}
def api_patch(endpoint: str, body: dict | None = None) -> Any: async def api_patch(endpoint: str, body: dict | None = None) -> Any:
with get_client() as client: async with get_client() as client:
r = client.patch(f"/api/v1{endpoint}", json=body or {}) r = await client.patch(f"/api/v1{endpoint}", json=body or {})
r.raise_for_status() r.raise_for_status()
return r.json() if r.status_code != 204 else {} 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() @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. """List repositories for a user or organization.
Args: Args:
@ -88,7 +88,7 @@ def forgejo_list_repositories(owner: str, limit: int = 20, page: int = 1) -> str
@mcp.tool() @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. """Get details about a specific repository.
Args: Args:
@ -109,7 +109,7 @@ def forgejo_get_repository(owner: str, repo: str) -> str:
@mcp.tool() @mcp.tool()
def forgejo_create_repository( async def forgejo_create_repository(
name: str, name: str,
description: str = "", description: str = "",
private: bool = False, private: bool = False,
@ -133,7 +133,7 @@ def forgejo_create_repository(
@mcp.tool() @mcp.tool()
def forgejo_list_issues( async def forgejo_list_issues(
owner: str, owner: str,
repo: str, repo: str,
state: str = "open", state: str = "open",
@ -157,7 +157,7 @@ def forgejo_list_issues(
@mcp.tool() @mcp.tool()
def forgejo_create_issue( async def forgejo_create_issue(
owner: str, owner: str,
repo: str, repo: str,
title: str, title: str,
@ -182,7 +182,7 @@ def forgejo_create_issue(
@mcp.tool() @mcp.tool()
def forgejo_update_issue( async def forgejo_update_issue(
owner: str, owner: str,
repo: str, repo: str,
issue_number: int, issue_number: int,
@ -206,7 +206,7 @@ def forgejo_update_issue(
@mcp.tool() @mcp.tool()
def forgejo_list_pull_requests( async def forgejo_list_pull_requests(
owner: str, owner: str,
repo: str, repo: str,
state: str = "open", state: str = "open",
@ -230,7 +230,7 @@ def forgejo_list_pull_requests(
@mcp.tool() @mcp.tool()
def forgejo_create_pull_request( async def forgejo_create_pull_request(
owner: str, owner: str,
repo: str, repo: str,
title: str, title: str,
@ -258,7 +258,7 @@ def forgejo_create_pull_request(
@mcp.tool() @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. """List branches in a repository.
Args: Args:
@ -275,7 +275,7 @@ def forgejo_list_branches(owner: str, repo: str, limit: int = 20, page: int = 1)
@mcp.tool() @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. """Get file contents from a repository.
Args: Args:
@ -293,7 +293,7 @@ def forgejo_get_file(owner: str, repo: str, path: str, ref: str = "main") -> str
@mcp.tool() @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. """Search for repositories across the Forgejo instance.
Args: Args:
@ -310,7 +310,7 @@ def forgejo_search_repositories(q: str, limit: int = 20, page: int = 1) -> str:
@mcp.tool() @mcp.tool()
def forgejo_get_user(username: str) -> str: async def forgejo_get_user(username: str) -> str:
"""Get user profile information. """Get user profile information.
Args: Args: