feat: JSON multi-profile runtime config for Gitea MCP (roadmap #10)
Let one MCP server select among named Gitea runtime profiles from a JSON file
instead of editing code or juggling many .env files:
GITEA_MCP_CONFIG=/path/to/gitea-mcp.json
GITEA_MCP_PROFILE=dev
- New gitea_config.py: load/validate the JSON, select the named profile, and
resolve its token by env-var reference. Profiles supply base_url,
profile_name, token_env, owner/repo, allowed/forbidden operations, and audit
label.
- gitea_auth.get_profile() now overlays env over the selected JSON profile:
explicit env vars win, the JSON profile fills only what env leaves unset.
- gitea_auth.get_auth_header() gains a JSON token_env fallback after explicit
env tokens (env still wins).
Security / safety:
- Tokens are referenced by env-var NAME (token_env); an inline "token" is
rejected and never echoed. The value is never stored in or returned as
profile metadata.
- Fail-safe errors: missing file / invalid JSON / unknown or unset selected
profile raise a clear ConfigError that never prints file contents or tokens
(JSONDecodeError context is suppressed so the raw file text can't surface).
- No network calls during config parsing.
- Real config files are gitignored (gitea-mcp*.json), example kept.
Backwards compatible: with GITEA_MCP_CONFIG unset, behaviour is exactly the
prior env-only behaviour (all existing get_profile/get_auth_header tests pass
unchanged).
Docs: README JSON-profiles section + env table rows, .env.example placeholders,
gitea-mcp.example.json.
Tests: tests/test_config.py (22 cases) — env-only, selection, multiple
profiles, env-override precedence, missing file, invalid JSON, missing/unset
profile, inline-token rejection + redaction, and no-network-during-parse.
Refs #10. Note: issue #19 (env-based profiles) was already implemented and
closed; this JSON-file capability is adjacent new scope tracked under the
roadmap rather than reopening #19.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -169,6 +169,8 @@ Recognized environment fields (see [`.env.example`](.env.example) for placeholde
|
||||
| `GITEA_TOKEN_SOURCE` | Optional *name* of the token source (e.g. an env var name). A name only — never the token value. |
|
||||
| `GITEA_BASE_URL` | Optional informational base URL. |
|
||||
| `GITEA_AUDIT_LOG` | Optional path to an audit log file. When set, mutating actions append one redacted JSON record each (profile + authenticated user + outcome). Unset ⇒ auditing off (no records, no extra API calls). |
|
||||
| `GITEA_MCP_CONFIG` | Optional path to a JSON file defining multiple named runtime profiles. Unset ⇒ pure env behaviour. |
|
||||
| `GITEA_MCP_PROFILE` | Name of the profile (from `GITEA_MCP_CONFIG`) to activate for this runtime. |
|
||||
|
||||
Notes:
|
||||
|
||||
@@ -187,6 +189,41 @@ Notes:
|
||||
branch and head SHA where applicable — when `GITEA_AUDIT_LOG` is set. Auditing
|
||||
is off by default and never adds API calls or breaks the action when off.
|
||||
See [`gitea_audit.py`](gitea_audit.py).
|
||||
|
||||
**JSON runtime profiles (roadmap #10).** Instead of juggling many `.env` files,
|
||||
one server can pick a named profile from a JSON file (see
|
||||
[`gitea-mcp.example.json`](gitea-mcp.example.json), loaded by
|
||||
[`gitea_config.py`](gitea_config.py)):
|
||||
|
||||
```bash
|
||||
export GITEA_MCP_CONFIG=/path/to/gitea-mcp.json
|
||||
export GITEA_MCP_PROFILE=dev
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"profiles": {
|
||||
"dev": {
|
||||
"base_url": "https://gitea.dev.example",
|
||||
"profile_name": "gitea-author",
|
||||
"token_env": "GITEA_TOKEN_DEV",
|
||||
"allowed_operations": ["read", "pr.create"],
|
||||
"audit_label": "dev-author"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- **Token by reference only:** a profile names the env var holding its token
|
||||
(`token_env`); an inline `token` is rejected. The value is read from that env
|
||||
var and never stored in or returned as profile metadata.
|
||||
- **Precedence:** explicit env vars (`GITEA_PROFILE_NAME`, `GITEA_BASE_URL`,
|
||||
`GITEA_ALLOWED_OPERATIONS`, `GITEA_TOKEN`, …) **override** the JSON profile;
|
||||
the JSON profile only fills what the environment leaves unset.
|
||||
- **Backwards compatible / fail-safe:** with `GITEA_MCP_CONFIG` unset, behaviour
|
||||
is exactly env-only. A missing file, invalid JSON, or unknown/unset selected
|
||||
profile raises a clear startup error that never prints file contents or
|
||||
tokens. Parsing makes no network calls.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
Reference in New Issue
Block a user