Add PUBLIC_BASE_URL and refine OAuth scopes
Some checks failed
docker / lint (push) Has been cancelled
docker / test (push) Has been cancelled
docker / docker-build (push) Has been cancelled
lint / lint (push) Has been cancelled
test / test (push) Has been cancelled

This commit is contained in:
2026-02-25 20:49:08 +01:00
parent 59e1ea53a8
commit c79cc1ab9e
9 changed files with 541 additions and 11 deletions

View File

@@ -23,6 +23,7 @@ cp .env.example .env
|---|---|---|---|
| `MCP_HOST` | No | `127.0.0.1` | Interface to bind to |
| `MCP_PORT` | No | `8080` | Port to listen on |
| `PUBLIC_BASE_URL` | No | empty | Public HTTPS base URL advertised in OAuth metadata (recommended behind reverse proxy) |
| `ALLOW_INSECURE_BIND` | No | `false` | Explicit opt-in required for `0.0.0.0` bind |
| `LOG_LEVEL` | No | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| `STARTUP_VALIDATE_GITEA` | No | `true` | Validate OIDC discovery endpoint at startup |

View File

@@ -44,6 +44,7 @@ Workflows live in `.gitea/workflows/`:
## Production Recommendations
- Place MCP behind TLS reverse proxy.
- Set `PUBLIC_BASE_URL=https://<your-mcp-domain>` so OAuth metadata advertises HTTPS endpoints.
- Restrict inbound traffic to expected clients.
- Persist and monitor audit logs.
- Monitor `/metrics` and auth-failure events.

75
docs/troubleshooting.md Normal file
View File

@@ -0,0 +1,75 @@
# Troubleshooting
## "Internal server error (-32603)" from ChatGPT
**Symptom:** ChatGPT shows `Internal server error` with JSON-RPC error code `-32603` when trying to use Gitea tools.
**Cause:** The OAuth token stored by ChatGPT was issued without Gitea API scopes (e.g. `read:repository`). This happens when the initial authorization request didn't include the correct `scope` parameter. The token passes OIDC validation (openid/profile/email) but gets **403 Forbidden** from Gitea's REST API.
**Fix:**
1. In Gitea: Go to **Settings > Applications > Authorized OAuth2 Applications** and revoke the MCP application.
2. In ChatGPT: Go to **Settings > Connected apps** and disconnect the Gitea integration.
3. Re-authorize: Use the ChatGPT integration again. It will trigger a fresh OAuth flow with the correct scopes (`read:repository`).
**Verification:** Check the server logs for `oauth_auth_summary`. A working token shows:
```
oauth_auth_summary: api_probe=pass login=alice
```
A scopeless token shows:
```
oauth_token_lacks_api_scope: status=403 login=alice
```
## "Gitea rejected the API call" (403)
**Symptom:** Tool calls return 403 with a message about re-authorizing.
**Cause:** Same root cause as above — the OAuth token doesn't have the required Gitea API scopes. The middleware's API scope probe detected this and returned a clear error instead of letting it fail deep in the tool handler.
**Fix:** Same as above — revoke and re-authorize.
## ChatGPT caches stale tokens
**Symptom:** After fixing the OAuth configuration, ChatGPT still sends the old token.
**Cause:** ChatGPT caches access tokens and doesn't automatically re-authenticate when the server configuration changes.
**Fix:**
1. In ChatGPT: **Settings > Connected apps** > disconnect the integration.
2. Start a new conversation and use the integration again — this forces a fresh OAuth flow.
## How OAuth scopes work with Gitea
Gitea's OAuth2/OIDC implementation uses **granular scopes** for API access:
| Scope | Access |
|-------|--------|
| `read:repository` | Read repositories, issues, PRs, files |
| `write:repository` | Create/edit issues, PRs, comments, files |
| `openid` | OIDC identity (login, email) |
When an OAuth application requests authorization, the `scope` parameter in the authorize URL determines what permissions the resulting token has. If only OIDC scopes are requested (e.g. `openid profile email`), the token will validate via the userinfo endpoint but will be rejected by Gitea's REST API with 403.
The MCP server's `openapi-gpt.yaml` file controls which scopes ChatGPT requests. Ensure it includes:
```yaml
scopes:
read:repository: "Read access to Gitea repositories"
write:repository: "Write access to Gitea repositories"
```
## Reading the `oauth_auth_summary` log
Every authenticated request emits a structured log line:
| Field | Description |
|-------|-------------|
| `token_type` | `jwt` or `opaque` |
| `scopes_observed` | Scopes extracted from the token/userinfo |
| `scopes_effective` | Final scopes after implicit grants |
| `api_probe` | `pass`, `fail:403`, `fail:401`, `skip:cached`, `skip:error` |
| `login` | Gitea username |
- `api_probe=pass` — token works for Gitea API calls
- `api_probe=fail:403` — token lacks API scopes, request rejected with re-auth guidance
- `api_probe=skip:cached` — previous probe passed, cached result used
- `api_probe=skip:error` — network error during probe, request allowed to proceed