Introduce a dedicated reconciler role with gitea_reconcile_already_landed_pr, which closes open PRs only after live fetch and ancestor proof against a fresh target branch. Document the gitea-reconciler namespace and extend task routing.
7.1 KiB
Static Dual-Namespace Gitea MCP Deployment
Purpose
This document (tracked as issue #143) records the deployment model accepted in issue #139: run the Gitea MCP server as two static, per-role namespaces — one authoring, one reviewing — instead of switching profiles inside a running server or routing through a dispatcher. It explains what to configure, why this model was chosen, and what to expect from MCP clients.
This is the deployment companion to
gitea-execution-profiles.md (the profile
model) and llm-workflow-runbooks.md (the
workflows run on top of it).
The model
Run two independent MCP server instances of the same gitea-mcp code, each
launched with exactly one static execution profile:
| Namespace (MCP server name) | Profile (role) | Typical use |
|---|---|---|
gitea-author |
an author profile | implement issues, push branches, open PRs, comment |
gitea-reviewer |
a reviewer profile | review, approve/request changes, merge |
gitea-reconciler |
a reconciler profile | close already-landed open PRs after ancestry proof (#310) |
Properties:
- One process, one credential. Each namespace authenticates as exactly
one Gitea identity for its entire lifetime. A session connected to
gitea-authorcan never approve or merge; a session connected togitea-reviewercannot push branches or commit unless explicitly configured. runtime_switching_supported: false. The running server never changes identity. Choosing a role means choosing which namespace to connect to, not asking the server to become someone else.- Roles are profiles, not LLMs. Per the profile model, the LLM is not the role — the profile is. The same LLM session may author under one namespace and (in a separate session) review under the other.
Rejected alternatives — and why
Both alternatives below were considered in the #139 discussion and are
rejected for now; dynamic in-process profile switching is
not enabled in this deployment model. (The runtime can support it
behind an explicit allow_runtime_switching: true config opt-in — see
gitea-execution-profiles.md — but this
model deliberately leaves it off, so namespaces report
runtime_switching_supported: false.)
- Dynamic profile switching (one server,
gitea_activate_profile-style role changes at runtime): rejected because a single process would hold, or be able to obtain, both credentials; "which identity am I?" becomes mutable state that injected instructions could target; and audit attribution blurs when one process acts as multiple identities. - Dispatcher / router front door (one entry point that forwards each call to a role-appropriate backend): rejected because it concentrates every credential behind one surface and re-creates the same escalation problem with extra moving parts.
Why the static dual-namespace model wins:
- Clearer audit. Every audit record from a namespace maps to one
identity and one
audit_label; there is no in-process identity history to reconstruct. - Less credential concentration. No process ever holds more than one token. Compromise or prompt-injection of one session bounds the blast radius to that role's allowed operations.
- Simpler two-party review boundary. Author and reviewer are different authenticated identities in different processes; self-review/self-merge checks stay structural, not behavioral. Note that namespaces alone do not provide two-party review — one agent driving both namespaces in one session still defeats it. Keep authoring and reviewing in separate sessions.
- Safer fail-closed behavior. Each server validates its single profile at startup and on every gated call; anything unknown, ambiguous, or unresolved refuses. There is no "switch succeeded but half-applied" state to reason about.
Client setup
Each namespace is the same server binary launched with its own environment. Configuration is by reference only: environment variables name a config file and a profile entry; tokens stay in the operator's keychain/secret store and never appear in client config, tool output, or this document.
Conceptual client registration (names and variables only — adapt the launch syntax to the client):
{
"mcpServers": {
"gitea-author": {
"command": "<path-to>/venv/bin/python3",
"args": ["<path-to>/mcp_server.py"],
"env": {
"GITEA_MCP_CONFIG": "<path-to-profiles.json>",
"GITEA_MCP_PROFILE": "<author-profile-name>"
}
},
"gitea-reviewer": {
"command": "<path-to>/venv/bin/python3",
"args": ["<path-to>/mcp_server.py"],
"env": {
"GITEA_MCP_CONFIG": "<path-to-profiles.json>",
"GITEA_MCP_PROFILE": "<reviewer-profile-name>"
}
}
}
}
GITEA_MCP_CONFIG— path to the operator-owned profiles config (seegitea-execution-profiles.md). The file is operator-owned; LLM sessions must never rewrite it.GITEA_MCP_PROFILE— the profile entry this namespace runs as. Exactly one per namespace; never both.- Verify after connecting: call
gitea_whoami/gitea_get_runtime_contextand confirm the authenticated identity and allowed operations match the namespace's role before doing any work.
"Auth unsupported" in some clients is normal
Some MCP clients display an "Auth unsupported" (or similar) status for
custom/local stdio servers. That message refers to the client↔server MCP
authentication handshake, which local servers do not use — it does not
mean Gitea authentication failed. Gitea credentials are resolved by the
server itself from the configured profile. Trust gitea_whoami, not the
client's connection badge.
Reconnect / reload after changes
The server reads its code and profile config once, at process start. A long-running namespace does not see later changes, so after any of:
- editing the profiles config (e.g. granting/removing an operation),
- merging server code that changes operation gating or tool surfaces,
- rotating the credential a profile references,
the operator must reload the affected namespace — restart the server or
use the client's MCP reconnect action (e.g. /mcp in Claude Code) — before
the change takes effect. Symptoms of a stale namespace include gated calls
failing closed with operation-normalization errors even though the live
config is correct. Fail-closed is the intended behavior here: a stale
server refuses rather than guesses. Reconnect and re-verify with
gitea_whoami.
Related documents
gitea-execution-profiles.md— the profile model, reference profiles (gitea-author,gitea-reviewer), operation naming, and safety rules.llm-workflow-runbooks.md— the author and reviewer workflows run on top of these namespaces.safety-model.md— fail-closed and gating principles.- Issue #139 — the discussion and decision this document records.