summaryrefslogtreecommitdiff
path: root/internal/hexaimcp
diff options
context:
space:
mode:
Diffstat (limited to 'internal/hexaimcp')
-rw-r--r--internal/hexaimcp/run.go75
-rw-r--r--internal/hexaimcp/run_test.go11
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")