fix: surface Gitea auth errors and document the service PAT
Two related issues made the connected MCP server return a bare "Internal server error" for tools that need real Gitea API access (e.g. list_repositories), while public-repo-by-path reads worked: 1. Gitea OIDC access tokens only carry openid/profile/email and cannot call the repository REST API, so pure-OAuth mode fails for most tools. A service PAT (GITEA_TOKEN) is required in practice; per-user permission is still enforced before each call, so this does not weaken authorization. 2. The tool handlers caught GiteaError broadly and re-raised it as RuntimeError. Because GiteaAuthenticationError/GiteaAuthorizationError subclass GiteaError, a clean 401/403 was masked as a generic internal error and the server's re-authorization guidance never fired. Changes: - read_tools.py / repository.py / write_tools.py: re-raise the auth/authz subclasses before the broad GiteaError catch so server.py returns actionable guidance instead of a generic 500. - .env.example + README.md: document GITEA_TOKEN as a least-privilege bot PAT, explain why it's needed and that OAuth remains authoritative, and note that list_repositories is intentionally unavailable in service-PAT mode. - tests: assert tool handlers propagate auth errors unwrapped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,11 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from aegis_gitea_mcp.config import reset_settings
|
||||
from aegis_gitea_mcp.gitea_client import GiteaError
|
||||
from aegis_gitea_mcp.gitea_client import (
|
||||
GiteaAuthenticationError,
|
||||
GiteaAuthorizationError,
|
||||
GiteaError,
|
||||
)
|
||||
from aegis_gitea_mcp.tools.repository import (
|
||||
get_file_contents_tool,
|
||||
get_file_tree_tool,
|
||||
@@ -70,6 +74,23 @@ async def test_list_repositories_tool_failure_mode() -> None:
|
||||
await list_repositories_tool(RepoErrorStub(), {})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("auth_error", [GiteaAuthenticationError, GiteaAuthorizationError])
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_propagates_auth_errors_unwrapped(auth_error: type[GiteaError]) -> None:
|
||||
"""Auth/authz failures must surface as-is, not masked behind RuntimeError.
|
||||
|
||||
The server maps these to actionable re-authorization guidance; wrapping them
|
||||
in RuntimeError would hide that and return a generic internal error instead.
|
||||
"""
|
||||
|
||||
class AuthErrorStub(RepoStub):
|
||||
async def list_repositories(self):
|
||||
raise auth_error("token rejected")
|
||||
|
||||
with pytest.raises(auth_error):
|
||||
await list_repositories_tool(AuthErrorStub(), {})
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_repository_info_tool_success() -> None:
|
||||
"""Repository info tool returns normalized metadata."""
|
||||
|
||||
Reference in New Issue
Block a user