feat(ingestion): implement brain_ingest_raw MCP tool
Wraps pipeline.RunRaw directly. Same dry-run semantics as the HTTP /ingest-raw endpoint. Test exercises a single concept page; asserts returned path and that no file is written under dry_run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/mathiasbq/hyperguild/ingestion/internal/api"
|
"github.com/mathiasbq/hyperguild/ingestion/internal/api"
|
||||||
|
"github.com/mathiasbq/hyperguild/ingestion/internal/pipeline"
|
||||||
"github.com/mathiasbq/hyperguild/ingestion/internal/search"
|
"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})
|
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})
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,3 +94,36 @@ func TestBrainWriteAcceptsDoubleDotInName(t *testing.T) {
|
|||||||
_, err := os.Stat(filepath.Join(brainDir, "knowledge", "notes..draft.md"))
|
_, err := os.Stat(filepath.Join(brainDir, "knowledge", "notes..draft.md"))
|
||||||
require.NoError(t, err, "filename with embedded .. should be allowed")
|
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))
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,6 +120,8 @@ func (s *Server) handleCall(ctx context.Context, name string, args json.RawMessa
|
|||||||
return s.brainQuery(ctx, args)
|
return s.brainQuery(ctx, args)
|
||||||
case "brain_write":
|
case "brain_write":
|
||||||
return s.brainWrite(ctx, args)
|
return s.brainWrite(ctx, args)
|
||||||
|
case "brain_ingest_raw":
|
||||||
|
return s.brainIngestRaw(ctx, args)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown tool: %s", name)
|
return nil, fmt.Errorf("unknown tool: %s", name)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user