diff options
Diffstat (limited to 'internal/hexaimcp')
| -rw-r--r-- | internal/hexaimcp/run.go | 75 | ||||
| -rw-r--r-- | internal/hexaimcp/run_test.go | 11 |
2 files changed, 78 insertions, 8 deletions
diff --git a/internal/hexaimcp/run.go b/internal/hexaimcp/run.go index 6b28a2a..5f687f1 100644 --- a/internal/hexaimcp/run.go +++ b/internal/hexaimcp/run.go @@ -12,6 +12,7 @@ import ( "codeberg.org/snonux/hexai/internal/appconfig" "codeberg.org/snonux/hexai/internal/mcp" "codeberg.org/snonux/hexai/internal/promptstore" + "codeberg.org/snonux/hexai/internal/slashcommands" ) // ServerRunner interface allows dependency injection for testing. @@ -25,11 +26,12 @@ type ServerFactory func( w io.Writer, logger *log.Logger, store promptstore.PromptStore, + syncer *slashcommands.Syncer, ) ServerRunner // defaultServerFactory is the production server factory. -func defaultServerFactory(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore) ServerRunner { - return mcp.NewServer(r, w, logger, store) +func defaultServerFactory(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer *slashcommands.Syncer) ServerRunner { + return mcp.NewServer(r, w, logger, store, syncer) } // Run starts the MCP server with the given configuration. @@ -76,8 +78,14 @@ func RunWithFactory( return fmt.Errorf("cannot create prompt store: %w", err) } + // Create slash command syncer (optional) + syncer, err := createSyncer(cfg, logger) + if err != nil { + return fmt.Errorf("cannot create syncer: %w", err) + } + // Create and run server - server := factory(stdin, stdout, logger, store) + server := factory(stdin, stdout, logger, store, syncer) if err := server.Run(); err != nil { return fmt.Errorf("server error: %w", err) } @@ -156,3 +164,64 @@ func expandPath(path string) (string, error) { return filepath.Abs(path) } + +// createSyncer creates a slash command syncer from config. +// Returns nil syncer if sync is disabled. +func createSyncer(cfg appconfig.App, logger *log.Logger) (*slashcommands.Syncer, error) { + syncer, err := slashcommands.NewSyncer(cfg) + if err != nil { + return nil, err + } + + if syncer != nil && cfg.MCPSlashCommandSync { + logger.Printf("slash command sync enabled: %s", cfg.MCPSlashCommandDir) + } + + return syncer, nil +} + +// RunBackfill performs a one-time sync of all prompts and exits. +func RunBackfill(logPath, configPath string) error { + logger, err := setupLogger(logPath) + if err != nil { + return fmt.Errorf("cannot setup logger: %w", err) + } + defer func() { + if f, ok := logger.Writer().(*os.File); ok && f != os.Stderr { + f.Close() + } + }() + + logger.Printf("hexai-mcp-server backfill starting") + + cfg := loadConfig(logger, configPath) + + // Force enable sync for backfill + if cfg.MCPSlashCommandDir == "" { + return fmt.Errorf("commands directory not configured (use --slashcommand-dir)") + } + cfg.MCPSlashCommandSync = true + + syncer, err := createSyncer(cfg, logger) + if err != nil { + return fmt.Errorf("cannot create syncer: %w", err) + } + + promptsDir, err := getPromptsDir(cfg) + if err != nil { + return fmt.Errorf("cannot determine prompts directory: %w", err) + } + + store, err := promptstore.NewJSONLStore(promptsDir) + if err != nil { + return fmt.Errorf("cannot create prompt store: %w", err) + } + + logger.Printf("starting backfill sync...") + if err := syncer.SyncAll(store); err != nil { + return fmt.Errorf("backfill failed: %w", err) + } + + logger.Printf("backfill complete") + return nil +} diff --git a/internal/hexaimcp/run_test.go b/internal/hexaimcp/run_test.go index 2adf678..794fa1f 100644 --- a/internal/hexaimcp/run_test.go +++ b/internal/hexaimcp/run_test.go @@ -15,6 +15,7 @@ import ( "codeberg.org/snonux/hexai/internal/appconfig" "codeberg.org/snonux/hexai/internal/mcp" "codeberg.org/snonux/hexai/internal/promptstore" + "codeberg.org/snonux/hexai/internal/slashcommands" ) // mockServerRunner implements ServerRunner for testing @@ -34,8 +35,8 @@ func TestFullProtocolFlow(t *testing.T) { tmpDir := t.TempDir() // Create test server factory - serverFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore) ServerRunner { - return mcp.NewServer(r, w, logger, store) + serverFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer *slashcommands.Syncer) ServerRunner { + return mcp.NewServer(r, w, logger, store, syncer) } // Setup I/O pipes @@ -271,7 +272,7 @@ func TestDefaultServerFactory(t *testing.T) { t.Fatalf("NewJSONLStore() error = %v", err) } - server := defaultServerFactory(inBuf, outBuf, logger, store) + server := defaultServerFactory(inBuf, outBuf, logger, store, nil) if server == nil { t.Fatal("defaultServerFactory() returned nil") } @@ -282,7 +283,7 @@ func TestRun(t *testing.T) { logPath := filepath.Join(tmpDir, "test.log") // Create a mock server factory that returns immediately - mockFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore) ServerRunner { + mockFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer *slashcommands.Syncer) ServerRunner { return &mockServerRunner{ runFunc: func() error { return nil // Exit immediately @@ -315,7 +316,7 @@ func TestRunWithFactory_ServerError(t *testing.T) { logPath := filepath.Join(tmpDir, "test.log") // Create a mock server factory that returns an error - mockFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore) ServerRunner { + mockFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer *slashcommands.Syncer) ServerRunner { return &mockServerRunner{ runFunc: func() error { return fmt.Errorf("mock server error") |
