diff --git a/ingestion/internal/mcp/handlers.go b/ingestion/internal/mcp/handlers.go index 55d7998..14ca3ea 100644 --- a/ingestion/internal/mcp/handlers.go +++ b/ingestion/internal/mcp/handlers.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/mathiasbq/hyperguild/ingestion/internal/api" + "github.com/mathiasbq/hyperguild/ingestion/internal/pipeline" "github.com/mathiasbq/hyperguild/ingestion/internal/search" ) @@ -122,3 +123,35 @@ func (s *Server) brainWrite(ctx context.Context, args json.RawMessage) (json.Raw } return json.Marshal(map[string]string{"path": relPath}) } + +type brainIngestRawArgs struct { + Source string `json:"source"` + Pages []pipeline.RawPage `json:"pages"` + DryRun bool `json:"dry_run,omitempty"` +} + +func (s *Server) brainIngestRaw(ctx context.Context, args json.RawMessage) (json.RawMessage, error) { + var a brainIngestRawArgs + if err := json.Unmarshal(args, &a); err != nil { + return nil, fmt.Errorf("parse args: %w", err) + } + if a.Source == "" { + return nil, fmt.Errorf("source is required") + } + if len(a.Pages) == 0 { + return nil, fmt.Errorf("pages must be non-empty") + } + result, err := pipeline.RunRaw(s.brainDir, a.Source, a.Pages, a.DryRun) + if err != nil { + return nil, fmt.Errorf("ingest: %w", err) + } + pages := result.Pages + if pages == nil { + pages = []string{} + } + warnings := result.Warnings + if warnings == nil { + warnings = []string{} + } + return json.Marshal(map[string]any{"pages": pages, "warnings": warnings}) +} diff --git a/ingestion/internal/mcp/handlers_test.go b/ingestion/internal/mcp/handlers_test.go index 0cd3127..4fe352b 100644 --- a/ingestion/internal/mcp/handlers_test.go +++ b/ingestion/internal/mcp/handlers_test.go @@ -94,3 +94,36 @@ func TestBrainWriteAcceptsDoubleDotInName(t *testing.T) { _, err := os.Stat(filepath.Join(brainDir, "knowledge", "notes..draft.md")) require.NoError(t, err, "filename with embedded .. should be allowed") } + +func TestBrainIngestRawDryRun(t *testing.T) { + brainDir := t.TempDir() + require.NoError(t, os.MkdirAll(filepath.Join(brainDir, "wiki", "concepts"), 0o755)) + srv := mcp.NewServer(brainDir, nil, nil) + + resp := toolCall(t, srv, "brain_ingest_raw", map[string]any{ + "source": "test-source", + "dry_run": true, + "pages": []map[string]any{ + { + "title": "Test Concept", + "type": "concept", + "content": "## Definition\nA test concept.", + }, + }, + }) + require.Nil(t, resp["error"]) + result := resp["result"].(map[string]any) + content := result["content"].([]any) + text := content[0].(map[string]any)["text"].(string) + + var parsed struct { + Pages []string `json:"pages"` + } + require.NoError(t, json.Unmarshal([]byte(text), &parsed)) + require.NotEmpty(t, parsed.Pages, "expected at least one page path") + assert.Contains(t, parsed.Pages[0], "wiki/concepts/test-concept.md") + + // dry_run: no file should exist + _, err := os.Stat(filepath.Join(brainDir, "wiki", "concepts", "test-concept.md")) + assert.True(t, os.IsNotExist(err)) +} diff --git a/ingestion/internal/mcp/server.go b/ingestion/internal/mcp/server.go index 56e97af..8e631c9 100644 --- a/ingestion/internal/mcp/server.go +++ b/ingestion/internal/mcp/server.go @@ -120,6 +120,8 @@ func (s *Server) handleCall(ctx context.Context, name string, args json.RawMessa return s.brainQuery(ctx, args) case "brain_write": return s.brainWrite(ctx, args) + case "brain_ingest_raw": + return s.brainIngestRaw(ctx, args) default: return nil, fmt.Errorf("unknown tool: %s", name) }