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
+56
View File
@@ -28,6 +28,7 @@ from mcp_server import ( # noqa: E402
gitea_commit_files,
gitea_whoami,
)
from gitea_auth import get_profile # noqa: E402
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
@@ -514,5 +515,60 @@ class TestWhoami(unittest.TestCase):
gitea_whoami(remote="nope")
# ---------------------------------------------------------------------------
# Runtime profile (env-configured profile metadata) — issue #19
# ---------------------------------------------------------------------------
class TestRuntimeProfile(unittest.TestCase):
def test_defaults_when_unset(self):
with patch.dict(os.environ, {}, clear=True):
p = get_profile()
self.assertEqual(p["profile_name"], "gitea-default")
self.assertEqual(p["allowed_operations"], [])
self.assertIsNone(p["base_url"])
def test_reads_env_metadata(self):
env = {
"GITEA_PROFILE_NAME": "gitea-reviewer",
"GITEA_ALLOWED_OPERATIONS": "read, review , approve",
"GITEA_BASE_URL": "https://gitea.example.invalid",
}
with patch.dict(os.environ, env, clear=True):
p = get_profile()
self.assertEqual(p["profile_name"], "gitea-reviewer")
self.assertEqual(p["allowed_operations"], ["read", "review", "approve"])
self.assertEqual(p["base_url"], "https://gitea.example.invalid")
def test_never_includes_token(self):
env = {
"GITEA_PROFILE_NAME": "gitea-author",
"GITEA_TOKEN": "super-secret-token",
}
with patch.dict(os.environ, env, clear=True):
p = get_profile()
blob = repr(p).lower()
self.assertNotIn("super-secret-token", blob)
self.assertNotIn("token", blob)
@patch("mcp_server.api_request")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_whoami_surfaces_profile_without_token(self, _auth, mock_api):
mock_api.return_value = {"id": 7, "login": "rev"}
env = {
"GITEA_PROFILE_NAME": "gitea-reviewer",
"GITEA_ALLOWED_OPERATIONS": "read,review,approve",
"GITEA_TOKEN": "super-secret-token",
}
with patch.dict(os.environ, env, clear=True):
result = gitea_whoami(remote="prgs")
self.assertEqual(result["profile"]["profile_name"], "gitea-reviewer")
self.assertEqual(
result["profile"]["allowed_operations"], ["read", "review", "approve"]
)
blob = repr(result).lower()
for secret in ("super-secret-token", "token", "authorization", "basic "):
self.assertNotIn(secret, blob)
if __name__ == "__main__":
unittest.main()