summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/hexaimcp/run.go4
-rw-r--r--internal/hexaimcp/run_test.go7
-rw-r--r--internal/mcp/server.go20
-rw-r--r--internal/slashcommands/syncer.go10
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 {