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:
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: