diff --git a/ingestion/internal/mcp/handlers.go b/ingestion/internal/mcp/handlers.go index ecf33e4..a72b879 100644 --- a/ingestion/internal/mcp/handlers.go +++ b/ingestion/internal/mcp/handlers.go @@ -105,13 +105,6 @@ func (s *Server) tools() []map[string]any { "target": str("path of target note (must be in a different wing)"), }), }, - { - "name": "brain_index", - "description": "Regenerate _index.md (Map of Content) for one or all wings under brain/wiki/. Auto-called after brain_write with wing+hall.", - "inputSchema": schema([]string{}, map[string]any{ - "wing": str("optional wing to index; if absent, rebuilds every wing"), - }), - }, { "name": "brain_ingest_raw", "description": "Ingest pre-structured pages into the brain wiki, bypassing the LLM extraction step.", diff --git a/ingestion/internal/mcp/server.go b/ingestion/internal/mcp/server.go index fce7876..37fb990 100644 --- a/ingestion/internal/mcp/server.go +++ b/ingestion/internal/mcp/server.go @@ -1,9 +1,14 @@ // Package mcp implements an MCP HTTP handler for the ingestion service. // Exposed tools: brain_query, brain_write, brain_update, brain_get, -// brain_pending, brain_promote, brain_index, brain_tunnel, brain_ingest, +// brain_pending, brain_promote, brain_tunnel, brain_ingest, // brain_ingest_raw, brain_answer, brain_classify, brain_graph, // brain_context, session_log, and capture (the #55 relay tool, registered // only when WithCapture is set). +// +// brain_index dispatches but is deliberately absent from tools/list: it is +// auto-called after every write, so surfacing it as a caller verb is +// cognitive load with no decision behind it (ADR-0012 rule 4, #78). It stays +// callable for maintenance — de-surfacing hides the verb, not the operation. package mcp import ( diff --git a/ingestion/internal/mcp/server_test.go b/ingestion/internal/mcp/server_test.go index da0aa15..16f493f 100644 --- a/ingestion/internal/mcp/server_test.go +++ b/ingestion/internal/mcp/server_test.go @@ -59,11 +59,35 @@ func TestServerToolsList(t *testing.T) { assert.ElementsMatch(t, []string{ "brain_query", "brain_write", "brain_update", "brain_get", "brain_pending", "brain_promote", - "brain_index", "brain_tunnel", + "brain_tunnel", "brain_ingest_raw", "brain_ingest", "brain_answer", "brain_classify", "brain_graph", "brain_context", "session_log", }, names) + + // brain_index is auto-called after every brain_write, so surfacing it as + // a caller verb is cognitive load with no decision behind it (ADR-0012 + // rule 4, #78). The capability stays reachable — see the admin test below. + assert.NotContains(t, names, "brain_index") +} + +func TestBrainIndexStaysCallableForAdminUseAfterDeSurfacing(t *testing.T) { + // De-surfacing hides the verb from the model-facing list; it must not + // remove the operation. A maintenance caller still needs to rebuild a + // Map of Content by hand. + srv := mcp.NewServer(t.TempDir(), nil, nil, nil) + + req := httptest.NewRequest(http.MethodPost, "/mcp", body(t, map[string]any{ + "jsonrpc": "2.0", "id": 3, "method": "tools/call", + "params": map[string]any{"name": "brain_index", "arguments": map[string]any{}}, + })) + rr := httptest.NewRecorder() + srv.ServeHTTP(rr, req) + + require.Equal(t, http.StatusOK, rr.Code) + var resp map[string]any + require.NoError(t, json.Unmarshal(rr.Body.Bytes(), &resp)) + assert.NotContains(t, resp, "error", "brain_index must still dispatch: %v", resp["error"]) } func TestServerNotificationGetsNoBody(t *testing.T) {