feat: support separate Gitea MCP runtime profiles via env config (#19)

Allow the same MCP server to run as separate MCP entries, each with its
own token and profile name, so roles stay task-scoped (the profile is
the role, not the LLM).

- gitea_auth.get_profile(): reads GITEA_PROFILE_NAME,
  GITEA_ALLOWED_OPERATIONS, GITEA_BASE_URL as non-secret metadata.
  Never reads/returns/logs the token.
- gitea_whoami now surfaces the safe profile metadata (name + allowed
  operations) alongside identity; token still never exposed.
- .env.example: placeholder-only template for a runtime profile.
- .gitignore: track .env.example while keeping real .env* ignored.
- README: document multiple env-configured MCP entries.
- tests: profile defaults/parsing, token-never-included, whoami surfaces
  profile without leaking token.

One token + one profile per process. No multi-token switching in a
single runtime. No approve/merge/eligibility workflow. No
Jenkins/Ops/GlitchTip/Release/deploy behavior. No real secrets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 13:21:59 -04:00
parent 28feef3c11
commit e31612027d
6 changed files with 175 additions and 1 deletions
+10 -1
View File
@@ -36,6 +36,7 @@ from gitea_auth import ( # noqa: E402
get_auth_header,
api_request,
repo_api_url,
get_profile,
)
mcp = FastMCP("gitea-tools", instructions=(
@@ -617,7 +618,8 @@ def gitea_whoami(
Returns:
dict with 'authenticated', 'username', 'display_name', 'user_id',
'email', 'server', and 'remote'.
'email', 'server', 'remote', and 'profile' (safe runtime profile
metadata: profile_name + allowed_operations; never the token).
"""
if remote not in REMOTES:
raise ValueError(f"Unknown remote '{remote}'. Choose from: {list(REMOTES)}")
@@ -631,6 +633,9 @@ def gitea_whoami(
f"Could not determine the authenticated Gitea identity for {h}. "
"Verify the configured token is valid for this instance."
)
# Runtime profile metadata is non-secret (name + allowed op categories).
# The token is resolved separately and is never included here.
profile = get_profile()
return {
"authenticated": True,
"username": data.get("login"),
@@ -639,6 +644,10 @@ def gitea_whoami(
"email": data.get("email") or None,
"server": f"https://{h}",
"remote": remote,
"profile": {
"profile_name": profile["profile_name"],
"allowed_operations": profile["allowed_operations"],
},
}