diff --git a/.mcp.json b/.mcp.json index ed5498e..c967ef1 100644 --- a/.mcp.json +++ b/.mcp.json @@ -6,6 +6,13 @@ "headers": { "Authorization": "Bearer ${BRAIN_MCP_TOKEN}" } + }, + "gitea": { + "type": "http", + "url": "https://git-mcp.d-ma.be/mcp", + "headers": { + "Authorization": "Bearer ${GITEA_MCP_TOKEN}" + } } } } diff --git a/cmd/hyperguild/README.md b/cmd/hyperguild/README.md index bc73922..0984f5a 100644 --- a/cmd/hyperguild/README.md +++ b/cmd/hyperguild/README.md @@ -112,16 +112,15 @@ Flags: - `--out PATH` — output file (default `./.mcp.json`) - `--force` — overwrite an existing file -Modes: +Modes (all list **brain + gitea** — Gitea is the audit-trail invariant of the +consolidated single harness, #75): -- **cloud** — brain MCP only. Claude Code with no routing. -- **client-local** — brain + routing pod. The `routing` entry points at - `koala:30310/mcp` (the routing pod, deployed in Plan 6). The - `X-Hyperguild-Mode: client-local` header is forward-compat for future - modes; the pod treats absent or unknown values as `client-local`. -- **sovereign** — brain only, with a `_mode_note` explaining that this - mode primarily uses Crush + LiteLLM and the `.mcp.json` is a Claude - Code fallback for emergency offline use. +- **cloud** — brain + gitea MCP. +- **client-local** — brain + gitea MCP. (The former `routing` entry pointing at + `koala:30310/mcp` was removed — the routing pod is iceboxed, #75.) +- **sovereign** — brain + gitea, with a `_mode_note` explaining that this mode + primarily uses Crush + LiteLLM and the `.mcp.json` is a Claude Code fallback + for emergency offline use. ## Environment diff --git a/cmd/hyperguild/mode.go b/cmd/hyperguild/mode.go index 51cd3a9..db8d1e6 100644 --- a/cmd/hyperguild/mode.go +++ b/cmd/hyperguild/mode.go @@ -59,31 +59,40 @@ func runMode(ctx context.Context, args []string, _ io.Reader, stdout, stderr io. return nil } +// giteaMCPURL is the public Gitea MCP endpoint (OAuth via Dex/Authentik). Gitea +// is the audit-trail invariant of the consolidated single harness (#75), so every +// mode lists it as an available connection. +const giteaMCPURL = "https://git-mcp.d-ma.be/mcp" + +func brainEntry(brainURL string) map[string]any { + return map[string]any{ + "url": brainURL + "/mcp", + "description": "Brain MCP — knowledge query, write, ingestion, session log", + } +} + +func giteaEntry() map[string]any { + return map[string]any{ + "url": giteaMCPURL, + "description": "Gitea MCP — issues/PRs/repo ops (audit-trail invariant)", + } +} + func modeCloud(brainURL string) map[string]any { return map[string]any{ "mcpServers": map[string]any{ - "brain": map[string]any{ - "url": brainURL + "/mcp", - "description": "Brain MCP — knowledge query, write, ingestion, session log", - }, + "brain": brainEntry(brainURL), + "gitea": giteaEntry(), }, } } func modeClientLocal(brainURL string) map[string]any { + // The routing pod is iceboxed (#75); the consolidated harness is brain + gitea. return map[string]any{ "mcpServers": map[string]any{ - "brain": map[string]any{ - "url": brainURL + "/mcp", - "description": "Brain MCP — knowledge query, write, ingestion, session log", - }, - "routing": map[string]any{ - "url": "http://koala:30310/mcp", - "description": "Mode 2 routing pod — routes skill calls to LiteLLM/local", - "headers": map[string]any{ - "X-Hyperguild-Mode": "client-local", - }, - }, + "brain": brainEntry(brainURL), + "gitea": giteaEntry(), }, } } @@ -92,10 +101,8 @@ func modeSovereign(brainURL string) map[string]any { return map[string]any{ "_mode_note": "Sovereign mode primarily uses Crush + LiteLLM. This .mcp.json is provided as Claude Code fallback (e.g. emergency offline editing).", "mcpServers": map[string]any{ - "brain": map[string]any{ - "url": brainURL + "/mcp", - "description": "Brain MCP — knowledge query, write, ingestion, session log", - }, + "brain": brainEntry(brainURL), + "gitea": giteaEntry(), }, } } diff --git a/cmd/hyperguild/mode_test.go b/cmd/hyperguild/mode_test.go index 077b2c3..c486c82 100644 --- a/cmd/hyperguild/mode_test.go +++ b/cmd/hyperguild/mode_test.go @@ -35,11 +35,13 @@ func TestRunMode_Cloud_Default(t *testing.T) { servers, ok := got["mcpServers"].(map[string]any) require.True(t, ok, "mcpServers must be a JSON object") assert.Contains(t, servers, "brain") + assert.Contains(t, servers, "gitea") assert.NotContains(t, servers, "routing") assert.NotContains(t, got, "_mode_note") } -func TestRunMode_ClientLocal_HasRoutingEntry(t *testing.T) { +func TestRunMode_ClientLocal_NoRouting_HasGitea(t *testing.T) { + // #75: the routing pod is iceboxed; the consolidated harness is brain + gitea. dir := t.TempDir() outPath := filepath.Join(dir, ".mcp.json") t.Setenv("BRAIN_URL", "http://koala:30330") @@ -51,17 +53,11 @@ func TestRunMode_ClientLocal_HasRoutingEntry(t *testing.T) { got := readJSON(t, outPath) servers := got["mcpServers"].(map[string]any) require.Contains(t, servers, "brain") - require.Contains(t, servers, "routing") - - routing := servers["routing"].(map[string]any) - assert.NotContains(t, routing, "_routing_pending", "placeholder should be removed once Plan 6 ships") - - headers, ok := routing["headers"].(map[string]any) - require.True(t, ok, "routing entry should have headers block") - assert.Equal(t, "client-local", headers["X-Hyperguild-Mode"]) + require.Contains(t, servers, "gitea") + assert.NotContains(t, servers, "routing", "routing pod iceboxed (#75)") } -func TestModeClientLocalHasRoutingHeader(t *testing.T) { +func TestModeGiteaEntryPresentAndWellFormed(t *testing.T) { tmp := t.TempDir() + "/mcp.json" out := &bytes.Buffer{} stderr := &bytes.Buffer{} @@ -73,13 +69,10 @@ func TestModeClientLocalHasRoutingHeader(t *testing.T) { require.NoError(t, json.Unmarshal(body, &doc)) servers := doc["mcpServers"].(map[string]any) - routing := servers["routing"].(map[string]any) - assert.Equal(t, "http://koala:30310/mcp", routing["url"]) - assert.NotContains(t, routing, "_routing_pending", "placeholder should be removed once Plan 6 ships") - - headers, ok := routing["headers"].(map[string]any) - require.True(t, ok, "routing entry should have headers block") - assert.Equal(t, "client-local", headers["X-Hyperguild-Mode"]) + require.NotContains(t, servers, "routing") + gitea, ok := servers["gitea"].(map[string]any) + require.True(t, ok, "gitea entry must be present (audit-trail invariant)") + assert.Equal(t, "https://git-mcp.d-ma.be/mcp", gitea["url"]) } func TestRunMode_Sovereign_HasModeNote(t *testing.T) { @@ -94,6 +87,7 @@ func TestRunMode_Sovereign_HasModeNote(t *testing.T) { assert.Contains(t, got, "_mode_note") servers := got["mcpServers"].(map[string]any) assert.Contains(t, servers, "brain") + assert.Contains(t, servers, "gitea") assert.NotContains(t, servers, "routing") }