Files
Gitea-Tools/docs/gitea-dual-namespace-deployment.md
T

7.5 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
gitea-merger a merger profile merge PRs after approval and verification
gitea-reconciler a reconciler profile close already-landed open PRs after ancestry proof (#304 profile; #310 close tool)

Review and merge are separate workflow roles. A reviewer approval is not merge authorization.

Properties:

  • One process, one credential. Each namespace authenticates as exactly one Gitea identity for its entire lifetime. A session connected to gitea-author can never approve or merge; a session connected to gitea-reviewer cannot 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-merger": {
      "command": "<path-to>/venv/bin/python3",
      "args": ["<path-to>/mcp_server.py"],
      "env": {
        "GITEA_MCP_CONFIG": "<path-to-profiles.json>",
        "GITEA_MCP_PROFILE": "<merger-profile-name>"
      }
    }
  }
}
  • GITEA_MCP_CONFIG — path to the operator-owned profiles config (see gitea-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_context and 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.

  • 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.