summaryrefslogtreecommitdiff
path: root/internal/mcp/server_test.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-02-11 20:12:54 +0200
committerPaul Buetow <paul@buetow.org>2026-02-11 20:12:54 +0200
commit0a218306f8b3381610d219deca10a21406aa08cf (patch)
tree5ccbd55c7fc19234b0bd60668ee679d3cac40ea5 /internal/mcp/server_test.go
parentae38b11a09964e2c291a144c72814559d12d3b96 (diff)
Fix MCP transport to use JSONL instead of Content-Length framing
The MCP stdio protocol uses newline-delimited JSON (JSONL), not LSP-style Content-Length headers. This was discovered during integration testing with Claude Code CLI which could not connect to the server. Also updates docs/mcp-setup.md with correct Claude Code CLI configuration (use `claude mcp add` instead of editing JSON files) and adds integration test runbook for testing against real Claude Code CLI instances. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/mcp/server_test.go')
-rw-r--r--internal/mcp/server_test.go58
1 files changed, 34 insertions, 24 deletions
diff --git a/internal/mcp/server_test.go b/internal/mcp/server_test.go
index 4a14ffc..0c767f2 100644
--- a/internal/mcp/server_test.go
+++ b/internal/mcp/server_test.go
@@ -57,43 +57,34 @@ func createTestServer(t *testing.T, store promptstore.PromptStore) (*Server, *by
return NewServer(inBuf, outBuf, logger, store), inBuf, outBuf
}
+// sendRequest writes a JSON-RPC request as newline-delimited JSON (MCP stdio protocol).
func sendRequest(w io.Writer, req Request) error {
data, err := json.Marshal(req)
if err != nil {
return err
}
- header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(data))
- if _, err := io.WriteString(w, header); err != nil {
+ if _, err := w.Write(data); err != nil {
return err
}
- if _, err := w.Write(data); err != nil {
+ if _, err := io.WriteString(w, "\n"); err != nil {
return err
}
return nil
}
+// readResponse reads a newline-delimited JSON-RPC response (MCP stdio protocol).
func readResponse(r io.Reader) (*Response, error) {
- // Simple read for testing (assumes one message in buffer)
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
- // Find Content-Length
- lines := strings.Split(string(data), "\r\n")
- var contentLength int
- bodyStart := 0
- for i, line := range lines {
- if strings.HasPrefix(line, "Content-Length:") {
- fmt.Sscanf(line, "Content-Length: %d", &contentLength)
- }
- if line == "" {
- bodyStart = i + 1
- break
- }
+ // Parse newline-delimited JSON; take the last non-empty line as the response
+ lines := strings.Split(strings.TrimSpace(string(data)), "\n")
+ if len(lines) == 0 {
+ return nil, fmt.Errorf("no response data")
}
-
- body := strings.Join(lines[bodyStart:], "\r\n")
+ body := lines[len(lines)-1]
var resp Response
if err := json.Unmarshal([]byte(body), &resp); err != nil {
return nil, fmt.Errorf("unmarshal response: %w, body: %s", err, body)
@@ -459,11 +450,9 @@ func TestServer_ReadMessage(t *testing.T) {
logger := log.New(io.Discard, "", 0)
server := NewServer(inBuf, outBuf, logger, store)
- // Write a message with proper framing
- msg := []byte(`{"jsonrpc":"2.0","id":1,"method":"test"}`)
- header := fmt.Sprintf("Content-Length: %d\r\n\r\n", len(msg))
- inBuf.WriteString(header)
- inBuf.Write(msg)
+ // Write a newline-delimited JSON message (MCP stdio protocol)
+ msg := `{"jsonrpc":"2.0","id":1,"method":"test"}`
+ inBuf.WriteString(msg + "\n")
// Read it back
body, err := server.readMessage()
@@ -471,7 +460,28 @@ func TestServer_ReadMessage(t *testing.T) {
t.Fatalf("readMessage() error = %v", err)
}
- if string(body) != string(msg) {
+ if string(body) != msg {
+ t.Errorf("readMessage() = %s, want %s", body, msg)
+ }
+ })
+
+ t.Run("skips empty lines", func(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 empty lines followed by a valid message
+ msg := `{"jsonrpc":"2.0","id":1,"method":"test"}`
+ inBuf.WriteString("\n\n" + msg + "\n")
+
+ body, err := server.readMessage()
+ if err != nil {
+ t.Fatalf("readMessage() error = %v", err)
+ }
+
+ if string(body) != msg {
t.Errorf("readMessage() = %s, want %s", body, msg)
}
})