diff options
| -rw-r--r-- | internal/hexaimcp/run.go | 4 | ||||
| -rw-r--r-- | internal/hexaimcp/run_test.go | 7 | ||||
| -rw-r--r-- | internal/mcp/server.go | 20 | ||||
| -rw-r--r-- | internal/slashcommands/syncer.go | 10 |
4 files changed, 28 insertions, 13 deletions
diff --git a/internal/hexaimcp/run.go b/internal/hexaimcp/run.go index 23faa08..b6fed8c 100644 --- a/internal/hexaimcp/run.go +++ b/internal/hexaimcp/run.go @@ -26,11 +26,11 @@ type ServerFactory func( w io.Writer, logger *log.Logger, store promptstore.PromptStore, - syncer *slashcommands.Syncer, + syncer mcp.SlashCommandSyncer, ) ServerRunner // defaultServerFactory is the production server factory. -func defaultServerFactory(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer *slashcommands.Syncer) ServerRunner { +func defaultServerFactory(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer mcp.SlashCommandSyncer) ServerRunner { return mcp.NewServer(r, w, logger, store, syncer) } diff --git a/internal/hexaimcp/run_test.go b/internal/hexaimcp/run_test.go index 794fa1f..3c3f9d8 100644 --- a/internal/hexaimcp/run_test.go +++ b/internal/hexaimcp/run_test.go @@ -15,7 +15,6 @@ 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 @@ -35,7 +34,7 @@ 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, syncer *slashcommands.Syncer) ServerRunner { + serverFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer mcp.SlashCommandSyncer) ServerRunner { return mcp.NewServer(r, w, logger, store, syncer) } @@ -283,7 +282,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, syncer *slashcommands.Syncer) ServerRunner { + mockFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer mcp.SlashCommandSyncer) ServerRunner { return &mockServerRunner{ runFunc: func() error { return nil // Exit immediately @@ -316,7 +315,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, syncer *slashcommands.Syncer) ServerRunner { + mockFactory := func(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer mcp.SlashCommandSyncer) ServerRunner { return &mockServerRunner{ runFunc: func() error { return fmt.Errorf("mock server error") diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 83f75e8..17df79f 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -13,9 +13,15 @@ import ( "codeberg.org/snonux/hexai/internal" "codeberg.org/snonux/hexai/internal/promptstore" - "codeberg.org/snonux/hexai/internal/slashcommands" ) +// SlashCommandSyncer is the minimal sync contract the MCP server depends on. +type SlashCommandSyncer interface { + SyncCreate(prompt *promptstore.Prompt) error + SyncUpdate(prompt *promptstore.Prompt) error + Delete(promptName string) error +} + // Server implements an MCP server over stdio using JSON-RPC 2.0. // Follows the same pattern as the LSP server with dispatch table and thread safety. type Server struct { @@ -24,7 +30,7 @@ type Server struct { outMu sync.Mutex logger *log.Logger store promptstore.PromptStore - syncer *slashcommands.Syncer + syncer SlashCommandSyncer initialized bool mu sync.RWMutex @@ -34,7 +40,7 @@ type Server struct { // NewServer creates a new MCP server with the given store and I/O streams. // The store provides access to prompts; logger is used for debugging. -func NewServer(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer *slashcommands.Syncer) *Server { +func NewServer(r io.Reader, w io.Writer, logger *log.Logger, store promptstore.PromptStore, syncer SlashCommandSyncer) *Server { s := &Server{ in: bufio.NewReader(r), out: w, @@ -360,7 +366,7 @@ func (s *Server) handlePromptsCreate(req Request) { // Sync to slash commands if enabled if s.syncer != nil { - if err := s.syncer.Sync(prompt, slashcommands.OpCreate); err != nil { + if err := s.syncer.SyncCreate(prompt); err != nil { s.logger.Printf("slash command sync failed: %v", err) } } @@ -467,7 +473,7 @@ func (s *Server) handlePromptsUpdate(req Request) { // Sync to slash commands if enabled if s.syncer != nil { - if err := s.syncer.Sync(existing, slashcommands.OpUpdate); err != nil { + if err := s.syncer.SyncUpdate(existing); err != nil { s.logger.Printf("slash command sync failed: %v", err) } } @@ -742,7 +748,7 @@ func (s *Server) callCreatePromptTool(id any, args map[string]interface{}) { // Sync to slash commands if enabled if s.syncer != nil { - if err := s.syncer.Sync(prompt, slashcommands.OpCreate); err != nil { + if err := s.syncer.SyncCreate(prompt); err != nil { s.logger.Printf("slash command sync failed: %v", err) } } @@ -789,7 +795,7 @@ func (s *Server) callUpdatePromptTool(id any, args map[string]interface{}) { // Sync to slash commands if enabled if s.syncer != nil { - if err := s.syncer.Sync(existing, slashcommands.OpUpdate); err != nil { + if err := s.syncer.SyncUpdate(existing); err != nil { s.logger.Printf("slash command sync failed: %v", err) } } diff --git a/internal/slashcommands/syncer.go b/internal/slashcommands/syncer.go index 91dbf50..8ff9e58 100644 --- a/internal/slashcommands/syncer.go +++ b/internal/slashcommands/syncer.go @@ -79,6 +79,16 @@ func (s *Syncer) Sync(prompt *promptstore.Prompt, op Operation) error { return s.atomicWrite(path, []byte(markdown)) } +// SyncCreate syncs a newly created prompt. +func (s *Syncer) SyncCreate(prompt *promptstore.Prompt) error { + return s.Sync(prompt, OpCreate) +} + +// SyncUpdate syncs an updated prompt. +func (s *Syncer) SyncUpdate(prompt *promptstore.Prompt) error { + return s.Sync(prompt, OpUpdate) +} + // Delete removes hexai-{name}.md file. // Returns nil if file doesn't exist (idempotent). func (s *Syncer) Delete(promptName string) error { |
