Skip to content

Commit 9faca53

Browse files
authored
Merge branch 'main' into add-ping
2 parents 993d9f0 + a0e968a commit 9faca53

File tree

15 files changed

+460
-243
lines changed

15 files changed

+460
-243
lines changed

client/stdio.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type StdioMCPClient struct {
2323
cmd *exec.Cmd
2424
stdin io.WriteCloser
2525
stdout *bufio.Reader
26+
stderr io.ReadCloser
2627
requestID atomic.Int64
2728
responses map[int64]chan RPCResponse
2829
mu sync.RWMutex
@@ -58,9 +59,15 @@ func NewStdioMCPClient(
5859
return nil, fmt.Errorf("failed to create stdout pipe: %w", err)
5960
}
6061

62+
stderr, err := cmd.StderrPipe()
63+
if err != nil {
64+
return nil, fmt.Errorf("failed to create stderr pipe: %w", err)
65+
}
66+
6167
client := &StdioMCPClient{
6268
cmd: cmd,
6369
stdin: stdin,
70+
stderr: stderr,
6471
stdout: bufio.NewReader(stdout),
6572
responses: make(map[int64]chan RPCResponse),
6673
done: make(chan struct{}),
@@ -88,9 +95,18 @@ func (c *StdioMCPClient) Close() error {
8895
if err := c.stdin.Close(); err != nil {
8996
return fmt.Errorf("failed to close stdin: %w", err)
9097
}
98+
if err := c.stderr.Close(); err != nil {
99+
return fmt.Errorf("failed to close stderr: %w", err)
100+
}
91101
return c.cmd.Wait()
92102
}
93103

104+
// Stderr returns a reader for the stderr output of the subprocess.
105+
// This can be used to capture error messages or logs from the subprocess.
106+
func (c *StdioMCPClient) Stderr() io.Reader {
107+
return c.stderr
108+
}
109+
94110
// OnNotification registers a handler function to be called when notifications are received.
95111
// Multiple handlers can be registered and will be called in the order they were added.
96112
func (c *StdioMCPClient) OnNotification(

client/stdio_test.go

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ package client
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
7+
"log/slog"
68
"os"
79
"os/exec"
810
"path/filepath"
11+
"sync"
912
"testing"
1013
"time"
1114

@@ -38,7 +41,23 @@ func TestStdioMCPClient(t *testing.T) {
3841
if err != nil {
3942
t.Fatalf("Failed to create client: %v", err)
4043
}
41-
defer client.Close()
44+
var logRecords []map[string]any
45+
var logRecordsMu sync.RWMutex
46+
var wg sync.WaitGroup
47+
wg.Add(1)
48+
go func() {
49+
defer wg.Done()
50+
dec := json.NewDecoder(client.Stderr())
51+
for {
52+
var record map[string]any
53+
if err := dec.Decode(&record); err != nil {
54+
return
55+
}
56+
logRecordsMu.Lock()
57+
logRecords = append(logRecords, record)
58+
logRecordsMu.Unlock()
59+
}
60+
}()
4261

4362
t.Run("Initialize", func(t *testing.T) {
4463
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
@@ -238,4 +257,25 @@ func TestStdioMCPClient(t *testing.T) {
238257
)
239258
}
240259
})
260+
261+
client.Close()
262+
wg.Wait()
263+
264+
t.Run("CheckLogs", func(t *testing.T) {
265+
logRecordsMu.RLock()
266+
defer logRecordsMu.RUnlock()
267+
268+
if len(logRecords) != 1 {
269+
t.Errorf("Expected 1 log record, got %d", len(logRecords))
270+
return
271+
}
272+
273+
msg, ok := logRecords[0][slog.MessageKey].(string)
274+
if !ok {
275+
t.Errorf("Expected log record to have message key")
276+
}
277+
if msg != "launch successful" {
278+
t.Errorf("Expected log message 'launch successful', got '%s'", msg)
279+
}
280+
})
241281
}

examples/everything/main.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,25 @@ func NewMCPServer() *server.MCPServer {
3232

3333
hooks := &server.Hooks{}
3434

35-
hooks.AddBeforeAny(func(id any, method mcp.MCPMethod, message any) {
35+
hooks.AddBeforeAny(func(ctx context.Context, id any, method mcp.MCPMethod, message any) {
3636
fmt.Printf("beforeAny: %s, %v, %v\n", method, id, message)
3737
})
38-
hooks.AddOnSuccess(func(id any, method mcp.MCPMethod, message any, result any) {
38+
hooks.AddOnSuccess(func(ctx context.Context, id any, method mcp.MCPMethod, message any, result any) {
3939
fmt.Printf("onSuccess: %s, %v, %v, %v\n", method, id, message, result)
4040
})
41-
hooks.AddOnError(func(id any, method mcp.MCPMethod, message any, err error) {
41+
hooks.AddOnError(func(ctx context.Context, id any, method mcp.MCPMethod, message any, err error) {
4242
fmt.Printf("onError: %s, %v, %v, %v\n", method, id, message, err)
4343
})
44-
hooks.AddBeforeInitialize(func(id any, message *mcp.InitializeRequest) {
44+
hooks.AddBeforeInitialize(func(ctx context.Context, id any, message *mcp.InitializeRequest) {
4545
fmt.Printf("beforeInitialize: %v, %v\n", id, message)
4646
})
47-
hooks.AddAfterInitialize(func(id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) {
47+
hooks.AddAfterInitialize(func(ctx context.Context, id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) {
4848
fmt.Printf("afterInitialize: %v, %v, %v\n", id, message, result)
4949
})
50-
hooks.AddAfterCallTool(func(id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) {
50+
hooks.AddAfterCallTool(func(ctx context.Context, id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) {
5151
fmt.Printf("afterCallTool: %v, %v, %v\n", id, message, result)
5252
})
53-
hooks.AddBeforeCallTool(func(id any, message *mcp.CallToolRequest) {
53+
hooks.AddBeforeCallTool(func(ctx context.Context, id any, message *mcp.CallToolRequest) {
5454
fmt.Printf("beforeCallTool: %v, %v\n", id, message)
5555
})
5656

mcp/tools.go

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -305,11 +305,7 @@ func WithBoolean(name string, opts ...PropertyOption) ToolOption {
305305
// Remove required from property schema and add to InputSchema.required
306306
if required, ok := schema["required"].(bool); ok && required {
307307
delete(schema, "required")
308-
if t.InputSchema.Required == nil {
309-
t.InputSchema.Required = []string{name}
310-
} else {
311-
t.InputSchema.Required = append(t.InputSchema.Required, name)
312-
}
308+
t.InputSchema.Required = append(t.InputSchema.Required, name)
313309
}
314310

315311
t.InputSchema.Properties[name] = schema
@@ -331,11 +327,7 @@ func WithNumber(name string, opts ...PropertyOption) ToolOption {
331327
// Remove required from property schema and add to InputSchema.required
332328
if required, ok := schema["required"].(bool); ok && required {
333329
delete(schema, "required")
334-
if t.InputSchema.Required == nil {
335-
t.InputSchema.Required = []string{name}
336-
} else {
337-
t.InputSchema.Required = append(t.InputSchema.Required, name)
338-
}
330+
t.InputSchema.Required = append(t.InputSchema.Required, name)
339331
}
340332

341333
t.InputSchema.Properties[name] = schema
@@ -357,11 +349,7 @@ func WithString(name string, opts ...PropertyOption) ToolOption {
357349
// Remove required from property schema and add to InputSchema.required
358350
if required, ok := schema["required"].(bool); ok && required {
359351
delete(schema, "required")
360-
if t.InputSchema.Required == nil {
361-
t.InputSchema.Required = []string{name}
362-
} else {
363-
t.InputSchema.Required = append(t.InputSchema.Required, name)
364-
}
352+
t.InputSchema.Required = append(t.InputSchema.Required, name)
365353
}
366354

367355
t.InputSchema.Properties[name] = schema
@@ -384,11 +372,7 @@ func WithObject(name string, opts ...PropertyOption) ToolOption {
384372
// Remove required from property schema and add to InputSchema.required
385373
if required, ok := schema["required"].(bool); ok && required {
386374
delete(schema, "required")
387-
if t.InputSchema.Required == nil {
388-
t.InputSchema.Required = []string{name}
389-
} else {
390-
t.InputSchema.Required = append(t.InputSchema.Required, name)
391-
}
375+
t.InputSchema.Required = append(t.InputSchema.Required, name)
392376
}
393377

394378
t.InputSchema.Properties[name] = schema
@@ -410,11 +394,7 @@ func WithArray(name string, opts ...PropertyOption) ToolOption {
410394
// Remove required from property schema and add to InputSchema.required
411395
if required, ok := schema["required"].(bool); ok && required {
412396
delete(schema, "required")
413-
if t.InputSchema.Required == nil {
414-
t.InputSchema.Required = []string{name}
415-
} else {
416-
t.InputSchema.Required = append(t.InputSchema.Required, name)
417-
}
397+
t.InputSchema.Required = append(t.InputSchema.Required, name)
418398
}
419399

420400
t.InputSchema.Properties[name] = schema

mcp/utils.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,20 @@ func NewToolResultResource(
252252
}
253253
}
254254

255+
// NewToolResultError creates a new CallToolResult with an error message.
256+
// Any errors that originate from the tool SHOULD be reported inside the result object.
257+
func NewToolResultError(text string) *CallToolResult {
258+
return &CallToolResult{
259+
Content: []Content{
260+
TextContent{
261+
Type: "text",
262+
Text: text,
263+
},
264+
},
265+
IsError: true,
266+
}
267+
}
268+
255269
// NewListResourcesResult creates a new ListResourcesResult
256270
func NewListResourcesResult(
257271
resources []Resource,

0 commit comments

Comments
 (0)