@@ -22,6 +22,10 @@ type mcpBackendConfig struct {
2222 RootAbs string
2323 TargetPaths semsearch.Paths
2424 IndexPath string
25+ StateDirInput string
26+ StateDirExplicit bool
27+ DBInput string
28+ DBExplicit bool
2529 RequestedProvider string
2630 RequestedModel string
2731 RequestedLibPath string
@@ -48,6 +52,9 @@ type mcpBackend struct {
4852 runMu sync.Mutex
4953 mu sync.Mutex
5054
55+ childrenMu sync.Mutex
56+ children map [string ]* mcpBackend
57+
5158 prepared * preparedMCPResources
5259}
5360
@@ -60,10 +67,24 @@ func (b *mcpBackend) Version() string {
6067}
6168
6269func (b * mcpBackend ) Close () error {
70+ b .childrenMu .Lock ()
71+ children := make ([]* mcpBackend , 0 , len (b .children ))
72+ for _ , child := range b .children {
73+ children = append (children , child )
74+ }
75+ b .children = nil
76+ b .childrenMu .Unlock ()
77+
78+ var firstErr error
79+ for _ , child := range children {
80+ if err := child .Close (); err != nil && firstErr == nil {
81+ firstErr = err
82+ }
83+ }
84+
6385 b .mu .Lock ()
6486 defer b .mu .Unlock ()
6587
66- var firstErr error
6788 if b .prepared != nil {
6889 if b .prepared .repo != nil {
6990 if err := b .prepared .repo .Close (); err != nil && firstErr == nil {
@@ -78,7 +99,15 @@ func (b *mcpBackend) Close() error {
7899 return firstErr
79100}
80101
81- func (b * mcpBackend ) WorkspaceStatus (_ context.Context ) (unchmcp.WorkspaceStatusResult , error ) {
102+ func (b * mcpBackend ) WorkspaceStatus (ctx context.Context , params unchmcp.WorkspaceStatusParams ) (unchmcp.WorkspaceStatusResult , error ) {
103+ backend , err := b .backendForDirectory (params .Directory )
104+ if err != nil {
105+ return unchmcp.WorkspaceStatusResult {}, err
106+ }
107+ return backend .workspaceStatus (ctx )
108+ }
109+
110+ func (b * mcpBackend ) workspaceStatus (_ context.Context ) (unchmcp.WorkspaceStatusResult , error ) {
82111 result := unchmcp.WorkspaceStatusResult {
83112 Root : b .cfg .RootAbs ,
84113 StateDir : b .cfg .TargetPaths .LocalDir ,
@@ -137,6 +166,15 @@ func (b *mcpBackend) WorkspaceStatus(_ context.Context) (unchmcp.WorkspaceStatus
137166}
138167
139168func (b * mcpBackend ) SearchCode (ctx context.Context , params unchmcp.SearchCodeParams ) (unchmcp.SearchCodeResult , error ) {
169+ backend , err := b .backendForDirectory (params .Directory )
170+ if err != nil {
171+ return unchmcp.SearchCodeResult {}, err
172+ }
173+ params .Directory = ""
174+ return backend .searchCode (ctx , params )
175+ }
176+
177+ func (b * mcpBackend ) searchCode (ctx context.Context , params unchmcp.SearchCodeParams ) (unchmcp.SearchCodeResult , error ) {
140178 b .runMu .Lock ()
141179 defer b .runMu .Unlock ()
142180
@@ -215,6 +253,15 @@ func (b *mcpBackend) SearchCode(ctx context.Context, params unchmcp.SearchCodePa
215253}
216254
217255func (b * mcpBackend ) IndexRepository (ctx context.Context , params unchmcp.IndexRepositoryParams ) (unchmcp.IndexRepositoryResult , error ) {
256+ backend , err := b .backendForDirectory (params .Directory )
257+ if err != nil {
258+ return unchmcp.IndexRepositoryResult {}, err
259+ }
260+ params .Directory = ""
261+ return backend .indexRepository (ctx , params )
262+ }
263+
264+ func (b * mcpBackend ) indexRepository (ctx context.Context , params unchmcp.IndexRepositoryParams ) (unchmcp.IndexRepositoryResult , error ) {
218265 b .runMu .Lock ()
219266 defer b .runMu .Unlock ()
220267
@@ -313,6 +360,77 @@ func (b *mcpBackend) IndexRepository(ctx context.Context, params unchmcp.IndexRe
313360 }, nil
314361}
315362
363+ func (b * mcpBackend ) backendForDirectory (directory string ) (* mcpBackend , error ) {
364+ rootAbs , ok , err := normalizeMCPDirectory (directory )
365+ if err != nil {
366+ return nil , err
367+ }
368+ if ! ok || rootAbs == b .cfg .RootAbs {
369+ return b , nil
370+ }
371+
372+ b .childrenMu .Lock ()
373+ defer b .childrenMu .Unlock ()
374+
375+ if b .children == nil {
376+ b .children = map [string ]* mcpBackend {}
377+ }
378+ if child , ok := b .children [rootAbs ]; ok {
379+ return child , nil
380+ }
381+
382+ targetPaths , indexPath , _ , err := previewStateTarget (
383+ rootAbs ,
384+ b .cfg .StateDirInput ,
385+ b .cfg .StateDirExplicit ,
386+ b .cfg .DBInput ,
387+ b .cfg .DBExplicit ,
388+ )
389+ if err != nil {
390+ return nil , err
391+ }
392+
393+ child := newMCPBackend (mcpBackendConfig {
394+ RootAbs : rootAbs ,
395+ TargetPaths : targetPaths ,
396+ IndexPath : indexPath ,
397+ StateDirInput : b .cfg .StateDirInput ,
398+ StateDirExplicit : b .cfg .StateDirExplicit ,
399+ DBInput : b .cfg .DBInput ,
400+ DBExplicit : b .cfg .DBExplicit ,
401+ RequestedProvider : b .cfg .RequestedProvider ,
402+ RequestedModel : b .cfg .RequestedModel ,
403+ RequestedLibPath : b .cfg .RequestedLibPath ,
404+ ContextSize : b .cfg .ContextSize ,
405+ Verbose : b .cfg .Verbose ,
406+ })
407+ child .scanner = b .scanner
408+ child .models = b .models
409+ child .runtimes = b .runtimes
410+ b .children [rootAbs ] = child
411+ return child , nil
412+ }
413+
414+ func normalizeMCPDirectory (directory string ) (string , bool , error ) {
415+ clean := strings .TrimSpace (directory )
416+ if clean == "" {
417+ return "" , false , nil
418+ }
419+
420+ rootAbs , err := filepath .Abs (clean )
421+ if err != nil {
422+ return "" , false , fmt .Errorf ("resolve directory: %w" , err )
423+ }
424+ rootAbs = filepath .Clean (rootAbs )
425+
426+ if info , err := os .Stat (rootAbs ); err != nil {
427+ return "" , false , fmt .Errorf ("stat directory: %w" , err )
428+ } else if ! info .IsDir () {
429+ return "" , false , fmt .Errorf ("directory is not a directory: %s" , rootAbs )
430+ }
431+ return rootAbs , true , nil
432+ }
433+
316434func (b * mcpBackend ) ensurePrepared (ctx context.Context ) (* preparedMCPResources , error ) {
317435 b .mu .Lock ()
318436 if b .prepared != nil {
@@ -322,6 +440,10 @@ func (b *mcpBackend) ensurePrepared(ctx context.Context) (*preparedMCPResources,
322440 }
323441 b .mu .Unlock ()
324442
443+ if _ , err := semsearch .PathsForLocalDir (b .cfg .TargetPaths .LocalDir ); err != nil {
444+ return nil , fmt .Errorf ("prepare state directory: %w" , err )
445+ }
446+
325447 preparedEmbedder , err := prepareEmbedder (
326448 ctx ,
327449 nil ,
0 commit comments