summaryrefslogtreecommitdiff
path: root/internal/mcp/handlers_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-10 19:28:27 +0200
committerPaul Buetow <paul@buetow.org>2026-02-10 19:28:27 +0200
commit5551695f3b0d10c9a22cfacdb10c2cf7bd572421 (patch)
tree282611eacf1fd4c38d54d5cea87decdf2b1cbdb7 /internal/mcp/handlers_test.go
parentec745129258ae800065e302a2a40b54488cbca08 (diff)
Add MCP server implementation with comprehensive test coverage
Implements a full Model Context Protocol (MCP) server for managing and serving prompts to LLM applications. The server provides CRUD operations for prompts with automatic backups and template rendering support. Key additions: - cmd/hexai-mcp-server: Main MCP server binary entrypoint - internal/hexaimcp: Server orchestrator with configuration and setup - internal/mcp: Core MCP protocol implementation (JSON-RPC 2.0) - internal/promptstore: Prompt storage with JSONL backend and automatic backups - Comprehensive test suites achieving 80%+ coverage for all MCP packages - Magefile targets for building and installing the MCP server - Complete documentation for setup, API, prompts, and backups Test coverage: - internal/hexaimcp: 84.3% - internal/mcp: 80.3% - internal/promptstore: 81.2% - Overall project: 81.5% Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/mcp/handlers_test.go')
-rw-r--r--internal/mcp/handlers_test.go955
1 files changed, 955 insertions, 0 deletions
diff --git a/internal/mcp/handlers_test.go b/internal/mcp/handlers_test.go
new file mode 100644
index 0000000..79c567e
--- /dev/null
+++ b/internal/mcp/handlers_test.go
@@ -0,0 +1,955 @@
+// Summary: Tests for MCP prompt management handlers
+package mcp
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "testing"
+ "time"
+
+ "codeberg.org/snonux/hexai/internal/promptstore"
+)
+
+func TestServer_PromptsCreate(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/create request
+ params := CreatePromptRequest{
+ Name: "test_create",
+ Title: "Test Create Prompt",
+ Description: "A test prompt",
+ Arguments: []PromptArgument{
+ {Name: "input", Description: "Test input", Required: true},
+ },
+ Messages: []PromptMessage{
+ {
+ Role: "user",
+ Content: MessageContent{
+ Type: "text",
+ Text: "Test: {{input}}",
+ },
+ },
+ },
+ Tags: []string{"test"},
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 10,
+ Method: "prompts/create",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+
+ // Parse result
+ resultBytes, _ := json.Marshal(resp.Result)
+ var result PromptOperationResult
+ if err := json.Unmarshal(resultBytes, &result); err != nil {
+ t.Fatalf("Unmarshal result error = %v", err)
+ }
+
+ if !result.Success {
+ t.Errorf("Success = false, want true")
+ }
+}
+
+func TestServer_PromptsUpdate(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test_update": {
+ Name: "test_update",
+ Title: "Original Title",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{
+ {
+ Role: "user",
+ Content: promptstore.MessageContent{
+ Type: "text",
+ Text: "Original text",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request
+ params := UpdatePromptRequest{
+ Name: "test_update",
+ Title: "Updated Title",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 11,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+
+ // Parse result
+ resultBytes, _ := json.Marshal(resp.Result)
+ var result PromptOperationResult
+ if err := json.Unmarshal(resultBytes, &result); err != nil {
+ t.Fatalf("Unmarshal result error = %v", err)
+ }
+
+ if !result.Success {
+ t.Errorf("Success = false, want true")
+ }
+}
+
+func TestServer_PromptsDelete(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test_delete": {
+ Name: "test_delete",
+ Title: "To Be Deleted",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{},
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/delete request
+ params := DeletePromptRequest{
+ Name: "test_delete",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 12,
+ Method: "prompts/delete",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+
+ // Parse result
+ resultBytes, _ := json.Marshal(resp.Result)
+ var result PromptOperationResult
+ if err := json.Unmarshal(resultBytes, &result); err != nil {
+ t.Fatalf("Unmarshal result error = %v", err)
+ }
+
+ if !result.Success {
+ t.Errorf("Success = false, want true")
+ }
+}
+
+func TestServer_PromptsCreate_MissingName(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/create request without name
+ params := CreatePromptRequest{
+ Title: "No Name",
+ Messages: []PromptMessage{
+ {Role: "user", Content: MessageContent{Type: "text", Text: "Test"}},
+ },
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 13,
+ Method: "prompts/create",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for missing name")
+ }
+
+ if resp.Error.Code != ErrCodeInvalidParams {
+ t.Errorf("Error code = %d, want %d", resp.Error.Code, ErrCodeInvalidParams)
+ }
+}
+
+// Update mockPromptStore to support Create, Update, Delete
+func (m *mockPromptStore) Create(prompt *promptstore.Prompt) error {
+ if _, exists := m.prompts[prompt.Name]; exists {
+ return fmt.Errorf("prompt already exists: %s", prompt.Name)
+ }
+ m.prompts[prompt.Name] = prompt
+ return nil
+}
+
+func (m *mockPromptStore) Update(prompt *promptstore.Prompt) error {
+ if _, exists := m.prompts[prompt.Name]; !exists {
+ return fmt.Errorf("prompt not found: %s", prompt.Name)
+ }
+ m.prompts[prompt.Name] = prompt
+ return nil
+}
+
+func (m *mockPromptStore) Delete(name string) error {
+ if _, exists := m.prompts[name]; !exists {
+ return fmt.Errorf("prompt not found: %s", name)
+ }
+ delete(m.prompts, name)
+ return nil
+}
+
+func TestServer_PromptsUpdate_NotFound(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request for non-existent prompt
+ params := UpdatePromptRequest{
+ Name: "nonexistent",
+ Title: "Updated Title",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 20,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for non-existent prompt")
+ }
+}
+
+func TestServer_PromptsUpdate_MissingName(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request without name
+ params := UpdatePromptRequest{
+ Title: "Updated Title",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 21,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for missing name")
+ }
+}
+
+func TestServer_PromptsDelete_NotFound(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/delete request for non-existent prompt
+ params := DeletePromptRequest{
+ Name: "nonexistent",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 22,
+ Method: "prompts/delete",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for non-existent prompt")
+ }
+}
+
+func TestServer_PromptsDelete_MissingName(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/delete request without name
+ params := DeletePromptRequest{}
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 23,
+ Method: "prompts/delete",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for missing name")
+ }
+}
+
+func TestServer_PromptsCreate_AlreadyExists(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "existing": {
+ Name: "existing",
+ Title: "Existing Prompt",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{},
+ },
+ },
+ }
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/create request with existing name
+ params := CreatePromptRequest{
+ Name: "existing",
+ Title: "Duplicate",
+ Messages: []PromptMessage{
+ {Role: "user", Content: MessageContent{Type: "text", Text: "Test"}},
+ },
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 24,
+ Method: "prompts/create",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for duplicate prompt")
+ }
+}
+
+func TestServer_PromptsGet_NotFound(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/get request for non-existent prompt
+ params := GetPromptRequest{
+ Name: "nonexistent",
+ Arguments: map[string]string{},
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 25,
+ Method: "prompts/get",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for non-existent prompt")
+ }
+}
+
+func TestServer_PromptsList_WithError(t *testing.T) {
+ store := &mockPromptStore{
+ listFn: func(cursor string, limit int) ([]promptstore.Prompt, string, error) {
+ return nil, "", fmt.Errorf("store error")
+ },
+ }
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/list request
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 26,
+ Method: "prompts/list",
+ Params: json.RawMessage(`{}`),
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error from store")
+ }
+}
+
+func TestServer_PromptsUpdate_WithMessages(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test": {
+ Name: "test",
+ Title: "Original",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{
+ {
+ Role: "user",
+ Content: promptstore.MessageContent{
+ Type: "text",
+ Text: "Original",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request with new messages
+ params := UpdatePromptRequest{
+ Name: "test",
+ Title: "Updated",
+ Messages: []PromptMessage{
+ {
+ Role: "user",
+ Content: MessageContent{
+ Type: "text",
+ Text: "Updated message",
+ },
+ },
+ },
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 27,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+}
+
+func TestServer_PromptsCreate_WithAllFields(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/create request with all fields
+ params := CreatePromptRequest{
+ Name: "complete",
+ Title: "Complete Prompt",
+ Description: "Full description",
+ Arguments: []PromptArgument{
+ {Name: "arg1", Description: "First arg", Required: true},
+ {Name: "arg2", Description: "Second arg", Required: false},
+ },
+ Messages: []PromptMessage{
+ {
+ Role: "user",
+ Content: MessageContent{
+ Type: "text",
+ Text: "Test {{arg1}} and {{arg2}}",
+ },
+ },
+ {
+ Role: "assistant",
+ Content: MessageContent{
+ Type: "text",
+ Text: "Response",
+ },
+ },
+ },
+ Tags: []string{"tag1", "tag2"},
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 28,
+ Method: "prompts/create",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+
+ // Parse result
+ resultBytes, _ := json.Marshal(resp.Result)
+ var result PromptOperationResult
+ if err := json.Unmarshal(resultBytes, &result); err != nil {
+ t.Fatalf("Unmarshal result error = %v", err)
+ }
+
+ if !result.Success {
+ t.Errorf("Success = false, want true")
+ }
+}
+
+func TestServer_PromptsList_WithCursorAndLimit(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test1": {
+ Name: "test1",
+ Title: "Test 1",
+ Created: now,
+ Updated: now,
+ },
+ "test2": {
+ Name: "test2",
+ Title: "Test 2",
+ Created: now,
+ Updated: now,
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/list request with cursor and limit
+ params := map[string]interface{}{
+ "cursor": "test1",
+ "limit": 10,
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 30,
+ Method: "prompts/list",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+}
+
+func TestServer_PromptsUpdate_WithDescription(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test": {
+ Name: "test",
+ Title: "Original",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{
+ {
+ Role: "user",
+ Content: promptstore.MessageContent{
+ Type: "text",
+ Text: "Original",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request with description
+ params := UpdatePromptRequest{
+ Name: "test",
+ Description: "Updated description",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 31,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+}
+
+func TestServer_PromptsUpdate_WithArguments(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test": {
+ Name: "test",
+ Title: "Original",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{
+ {
+ Role: "user",
+ Content: promptstore.MessageContent{
+ Type: "text",
+ Text: "Original",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request with arguments
+ params := UpdatePromptRequest{
+ Name: "test",
+ Arguments: []PromptArgument{
+ {Name: "newarg", Description: "New argument", Required: true},
+ },
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 32,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+}
+
+func TestServer_PromptsUpdate_WithTags(t *testing.T) {
+ now := time.Now()
+ store := &mockPromptStore{
+ prompts: map[string]*promptstore.Prompt{
+ "test": {
+ Name: "test",
+ Title: "Original",
+ Created: now,
+ Updated: now,
+ Messages: []promptstore.PromptMessage{
+ {
+ Role: "user",
+ Content: promptstore.MessageContent{
+ Type: "text",
+ Text: "Original",
+ },
+ },
+ },
+ },
+ },
+ }
+
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/update request with tags
+ params := UpdatePromptRequest{
+ Name: "test",
+ Tags: []string{"newtag1", "newtag2"},
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 33,
+ Method: "prompts/update",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error != nil {
+ t.Fatalf("Error = %v, want nil", resp.Error)
+ }
+}
+
+func TestServer_Run_InvalidJSON(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ inBuf := &bytes.Buffer{}
+ outBuf := &bytes.Buffer{}
+ logger := log.New(io.Discard, "", 0)
+ server := NewServer(inBuf, outBuf, logger, store)
+
+ // Write invalid JSON
+ msg := []byte(`{invalid json}`)
+ header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(msg))
+ inBuf.WriteString(header)
+ inBuf.Write(msg)
+
+ // Run in background
+ done := make(chan error, 1)
+ go func() {
+ done <- server.Run()
+ }()
+
+ // Give time for processing
+ time.Sleep(50 * time.Millisecond)
+
+ // Should have written error response
+ if outBuf.Len() == 0 {
+ t.Error("Expected error response to be written")
+ }
+}
+
+func TestServer_PromptsCreate_MissingMessages(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Initialize server
+ server.mu.Lock()
+ server.initialized = true
+ server.mu.Unlock()
+
+ // Send prompts/create request without messages
+ params := CreatePromptRequest{
+ Name: "test",
+ Title: "Test",
+ }
+ paramsBytes, _ := json.Marshal(params)
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 34,
+ Method: "prompts/create",
+ Params: paramsBytes,
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for missing messages")
+ }
+}
+
+func TestServer_HandleInitialize_InvalidParams(t *testing.T) {
+ store := &mockPromptStore{prompts: make(map[string]*promptstore.Prompt)}
+ server, _, outBuf := createTestServer(t, store)
+
+ // Send initialize request with invalid params
+ req := Request{
+ JSONRPC: "2.0",
+ ID: 35,
+ Method: "initialize",
+ Params: json.RawMessage(`{invalid}`),
+ }
+
+ server.handle(req)
+
+ // Read response
+ resp, err := readResponse(outBuf)
+ if err != nil {
+ t.Fatalf("readResponse() error = %v", err)
+ }
+
+ if resp.Error == nil {
+ t.Fatal("Expected error for invalid params")
+ }
+}