From 370d30e376e9f26ed4eb2219e2e38d3c475409c7 Mon Sep 17 00:00:00 2001 From: Mathias Bergqvist Date: Fri, 1 May 2026 19:41:05 +0200 Subject: [PATCH] feat(ingestion): mount MCP handler at POST /mcp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ingestion server now exposes both REST and MCP on the same port (3300). MCP shares brainDir, pipeline config, and LLM client with the REST handlers — single source of process state. Co-Authored-By: Claude Opus 4.7 (1M context) --- ingestion/cmd/server/main.go | 5 ++++ ingestion/internal/mcp/integration_test.go | 35 ++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 ingestion/internal/mcp/integration_test.go diff --git a/ingestion/cmd/server/main.go b/ingestion/cmd/server/main.go index 4ba6cc4..29409aa 100644 --- a/ingestion/cmd/server/main.go +++ b/ingestion/cmd/server/main.go @@ -12,6 +12,7 @@ import ( "github.com/mathiasbq/hyperguild/ingestion/internal/api" "github.com/mathiasbq/hyperguild/ingestion/internal/llm" + "github.com/mathiasbq/hyperguild/ingestion/internal/mcp" "github.com/mathiasbq/hyperguild/ingestion/internal/pipeline" "github.com/mathiasbq/hyperguild/ingestion/internal/watcher" ) @@ -54,6 +55,8 @@ func main() { h := api.NewHandler(brainDir, logger, pipelineCfg) + mcpSrv := mcp.NewServer(brainDir, &pipelineCfg, llmClient.Complete) + ctx := context.Background() if watchInterval > 0 { watcher.Start(ctx, watcher.Config{ @@ -70,6 +73,7 @@ func main() { mux.HandleFunc("POST /ingest-path", h.IngestPath) mux.HandleFunc("POST /ingest-raw", h.IngestRaw) mux.HandleFunc("POST /backfill-refs", h.BackfillRefs) + mux.Handle("POST /mcp", mcpSrv) addr := ":" + port watchIntervalLog := "disabled" @@ -83,6 +87,7 @@ func main() { "llm_model", llmModel, "chunk_size", chunkSize, "watch_interval", watchIntervalLog, + "mcp_enabled", true, ) if err := http.ListenAndServe(addr, mux); err != nil { logger.Error("server stopped", "err", err) diff --git a/ingestion/internal/mcp/integration_test.go b/ingestion/internal/mcp/integration_test.go new file mode 100644 index 0000000..898c654 --- /dev/null +++ b/ingestion/internal/mcp/integration_test.go @@ -0,0 +1,35 @@ +package mcp_test + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/mathiasbq/hyperguild/ingestion/internal/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMCPMountedHandler(t *testing.T) { + srv := mcp.NewServer(t.TempDir(), nil, nil) + mux := http.NewServeMux() + mux.Handle("POST /mcp", srv) + + ts := httptest.NewServer(mux) + defer ts.Close() + + body, err := json.Marshal(map[string]any{ + "jsonrpc": "2.0", "id": 1, "method": "tools/list", + }) + require.NoError(t, err) + resp, err := http.Post(ts.URL+"/mcp", "application/json", bytes.NewReader(body)) + require.NoError(t, err) + defer resp.Body.Close() + assert.Equal(t, http.StatusOK, resp.StatusCode) + + out, _ := io.ReadAll(resp.Body) + assert.Contains(t, string(out), `"brain_query"`) +}