Skip to content

Commit b62162a

Browse files
committed
Merge branch 'main' of github.com:mark3labs/mcp-go
2 parents 7185224 + 4558b68 commit b62162a

File tree

13 files changed

+809
-140
lines changed

13 files changed

+809
-140
lines changed

.github/workflows/ci.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
branches:
55
- main
66
pull_request:
7+
workflow_dispatch:
8+
79
jobs:
810
test:
911
runs-on: ubuntu-latest
@@ -13,3 +15,21 @@ jobs:
1315
with:
1416
go-version-file: 'go.mod'
1517
- run: go test ./... -race
18+
19+
verify-codegen:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: actions/setup-go@v5
24+
with:
25+
go-version-file: 'go.mod'
26+
- name: Run code generation
27+
run: go generate ./...
28+
- name: Check for uncommitted changes
29+
run: |
30+
if [[ -n $(git status --porcelain) ]]; then
31+
echo "Error: Generated code is not up to date. Please run 'go generate ./...' and commit the changes."
32+
git status
33+
git diff
34+
exit 1
35+
fi

client/inprocess_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ func TestInProcessMCPClient(t *testing.T) {
3636
Type: "text",
3737
Text: "Input parameter: " + request.Params.Arguments["parameter-1"].(string),
3838
},
39+
mcp.AudioContent{
40+
Type: "audio",
41+
Data: "base64-encoded-audio-data",
42+
MIMEType: "audio/wav",
43+
},
3944
},
4045
}, nil
4146
})
@@ -77,6 +82,14 @@ func TestInProcessMCPClient(t *testing.T) {
7782
Text: "Test prompt with arg1: " + request.Params.Arguments["arg1"],
7883
},
7984
},
85+
{
86+
Role: mcp.RoleUser,
87+
Content: mcp.AudioContent{
88+
Type: "audio",
89+
Data: "base64-encoded-audio-data",
90+
MIMEType: "audio/wav",
91+
},
92+
},
8093
},
8194
}, nil
8295
},
@@ -192,8 +205,8 @@ func TestInProcessMCPClient(t *testing.T) {
192205
t.Fatalf("CallTool failed: %v", err)
193206
}
194207

195-
if len(result.Content) != 1 {
196-
t.Errorf("Expected 1 content item, got %d", len(result.Content))
208+
if len(result.Content) != 2 {
209+
t.Errorf("Expected 2 content item, got %d", len(result.Content))
197210
}
198211
})
199212

@@ -359,14 +372,17 @@ func TestInProcessMCPClient(t *testing.T) {
359372

360373
request := mcp.GetPromptRequest{}
361374
request.Params.Name = "test-prompt"
375+
request.Params.Arguments = map[string]string{
376+
"arg1": "arg1 value",
377+
}
362378

363379
result, err := client.GetPrompt(context.Background(), request)
364380
if err != nil {
365381
t.Errorf("GetPrompt failed: %v", err)
366382
}
367383

368-
if len(result.Messages) != 1 {
369-
t.Errorf("Expected 1 message, got %d", len(result.Messages))
384+
if len(result.Messages) != 2 {
385+
t.Errorf("Expected 2 message, got %d", len(result.Messages))
370386
}
371387
})
372388

client/stdio_test.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"log/slog"
88
"os"
99
"os/exec"
10-
"path/filepath"
10+
"runtime"
1111
"sync"
1212
"testing"
1313
"time"
@@ -19,24 +19,41 @@ func compileTestServer(outputPath string) error {
1919
cmd := exec.Command(
2020
"go",
2121
"build",
22+
"-buildmode=pie",
2223
"-o",
2324
outputPath,
2425
"../testdata/mockstdio_server.go",
2526
)
27+
tmpCache, _ := os.MkdirTemp("", "gocache")
28+
cmd.Env = append(os.Environ(), "GOCACHE="+tmpCache)
29+
2630
if output, err := cmd.CombinedOutput(); err != nil {
2731
return fmt.Errorf("compilation failed: %v\nOutput: %s", err, output)
2832
}
33+
// Verify the binary was actually created
2934
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
3035
return fmt.Errorf("mock server binary not found at %s after compilation", outputPath)
3136
}
3237
return nil
3338
}
3439

3540
func TestStdioMCPClient(t *testing.T) {
36-
// Compile mock server
37-
mockServerPath := filepath.Join(os.TempDir(), "mockstdio_server")
38-
if err := compileTestServer(mockServerPath); err != nil {
39-
t.Fatalf("Failed to compile mock server: %v", err)
41+
// Create a temporary file for the mock server
42+
tempFile, err := os.CreateTemp("", "mockstdio_server")
43+
if err != nil {
44+
t.Fatalf("Failed to create temp file: %v", err)
45+
}
46+
tempFile.Close()
47+
mockServerPath := tempFile.Name()
48+
49+
// Add .exe suffix on Windows
50+
if runtime.GOOS == "windows" {
51+
os.Remove(mockServerPath) // Remove the empty file first
52+
mockServerPath += ".exe"
53+
}
54+
55+
if compileErr := compileTestServer(mockServerPath); compileErr != nil {
56+
t.Fatalf("Failed to compile mock server: %v", compileErr)
4057
}
4158
defer os.Remove(mockServerPath)
4259

client/transport/stdio_test.go

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"fmt"
77
"os"
88
"os/exec"
9-
"path/filepath"
109
"runtime"
1110
"sync"
1211
"testing"
@@ -19,25 +18,41 @@ func compileTestServer(outputPath string) error {
1918
cmd := exec.Command(
2019
"go",
2120
"build",
21+
"-buildmode=pie",
2222
"-o",
2323
outputPath,
2424
"../../testdata/mockstdio_server.go",
2525
)
26+
tmpCache, _ := os.MkdirTemp("", "gocache")
27+
cmd.Env = append(os.Environ(), "GOCACHE="+tmpCache)
28+
2629
if output, err := cmd.CombinedOutput(); err != nil {
2730
return fmt.Errorf("compilation failed: %v\nOutput: %s", err, output)
2831
}
32+
// Verify the binary was actually created
33+
if _, err := os.Stat(outputPath); os.IsNotExist(err) {
34+
return fmt.Errorf("mock server binary not found at %s after compilation", outputPath)
35+
}
2936
return nil
3037
}
3138

3239
func TestStdio(t *testing.T) {
33-
// Compile mock server
34-
mockServerPath := filepath.Join(os.TempDir(), "mockstdio_server")
40+
// Create a temporary file for the mock server
41+
tempFile, err := os.CreateTemp("", "mockstdio_server")
42+
if err != nil {
43+
t.Fatalf("Failed to create temp file: %v", err)
44+
}
45+
tempFile.Close()
46+
mockServerPath := tempFile.Name()
47+
3548
// Add .exe suffix on Windows
3649
if runtime.GOOS == "windows" {
50+
os.Remove(mockServerPath) // Remove the empty file first
3751
mockServerPath += ".exe"
3852
}
39-
if err := compileTestServer(mockServerPath); err != nil {
40-
t.Fatalf("Failed to compile mock server: %v", err)
53+
54+
if compileErr := compileTestServer(mockServerPath); compileErr != nil {
55+
t.Fatalf("Failed to compile mock server: %v", compileErr)
4156
}
4257
defer os.Remove(mockServerPath)
4358

@@ -48,9 +63,9 @@ func TestStdio(t *testing.T) {
4863
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
4964
defer cancel()
5065

51-
err := stdio.Start(ctx)
52-
if err != nil {
53-
t.Fatalf("Failed to start Stdio transport: %v", err)
66+
startErr := stdio.Start(ctx)
67+
if startErr != nil {
68+
t.Fatalf("Failed to start Stdio transport: %v", startErr)
5469
}
5570
defer stdio.Close()
5671

@@ -307,13 +322,22 @@ func TestStdioErrors(t *testing.T) {
307322
})
308323

309324
t.Run("RequestBeforeStart", func(t *testing.T) {
310-
mockServerPath := filepath.Join(os.TempDir(), "mockstdio_server")
325+
// Create a temporary file for the mock server
326+
tempFile, err := os.CreateTemp("", "mockstdio_server")
327+
if err != nil {
328+
t.Fatalf("Failed to create temp file: %v", err)
329+
}
330+
tempFile.Close()
331+
mockServerPath := tempFile.Name()
332+
311333
// Add .exe suffix on Windows
312334
if runtime.GOOS == "windows" {
335+
os.Remove(mockServerPath) // Remove the empty file first
313336
mockServerPath += ".exe"
314337
}
315-
if err := compileTestServer(mockServerPath); err != nil {
316-
t.Fatalf("Failed to compile mock server: %v", err)
338+
339+
if compileErr := compileTestServer(mockServerPath); compileErr != nil {
340+
t.Fatalf("Failed to compile mock server: %v", compileErr)
317341
}
318342
defer os.Remove(mockServerPath)
319343

@@ -328,23 +352,31 @@ func TestStdioErrors(t *testing.T) {
328352

329353
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
330354
defer cancel()
331-
_, err := uninitiatedStdio.SendRequest(ctx, request)
332-
if err == nil {
355+
_, reqErr := uninitiatedStdio.SendRequest(ctx, request)
356+
if reqErr == nil {
333357
t.Errorf("Expected SendRequest to panic before Start(), but it didn't")
334-
} else if err.Error() != "stdio client not started" {
335-
t.Errorf("Expected error 'stdio client not started', got: %v", err)
358+
} else if reqErr.Error() != "stdio client not started" {
359+
t.Errorf("Expected error 'stdio client not started', got: %v", reqErr)
336360
}
337361
})
338362

339363
t.Run("RequestAfterClose", func(t *testing.T) {
340-
// Compile mock server
341-
mockServerPath := filepath.Join(os.TempDir(), "mockstdio_server")
364+
// Create a temporary file for the mock server
365+
tempFile, err := os.CreateTemp("", "mockstdio_server")
366+
if err != nil {
367+
t.Fatalf("Failed to create temp file: %v", err)
368+
}
369+
tempFile.Close()
370+
mockServerPath := tempFile.Name()
371+
342372
// Add .exe suffix on Windows
343373
if runtime.GOOS == "windows" {
374+
os.Remove(mockServerPath) // Remove the empty file first
344375
mockServerPath += ".exe"
345376
}
346-
if err := compileTestServer(mockServerPath); err != nil {
347-
t.Fatalf("Failed to compile mock server: %v", err)
377+
378+
if compileErr := compileTestServer(mockServerPath); compileErr != nil {
379+
t.Fatalf("Failed to compile mock server: %v", compileErr)
348380
}
349381
defer os.Remove(mockServerPath)
350382

@@ -353,8 +385,8 @@ func TestStdioErrors(t *testing.T) {
353385

354386
// Start the transport
355387
ctx := context.Background()
356-
if err := stdio.Start(ctx); err != nil {
357-
t.Fatalf("Failed to start Stdio transport: %v", err)
388+
if startErr := stdio.Start(ctx); startErr != nil {
389+
t.Fatalf("Failed to start Stdio transport: %v", startErr)
358390
}
359391

360392
// Close the transport - ignore errors like "broken pipe" since the process might exit already
@@ -370,8 +402,8 @@ func TestStdioErrors(t *testing.T) {
370402
Method: "ping",
371403
}
372404

373-
_, err := stdio.SendRequest(ctx, request)
374-
if err == nil {
405+
_, sendErr := stdio.SendRequest(ctx, request)
406+
if sendErr == nil {
375407
t.Errorf("Expected error when sending request after close, got nil")
376408
}
377409
})

mcp/prompts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const (
7878
// resources from the MCP server.
7979
type PromptMessage struct {
8080
Role Role `json:"role"`
81-
Content Content `json:"content"` // Can be TextContent, ImageContent, or EmbeddedResource
81+
Content Content `json:"content"` // Can be TextContent, ImageContent, AudioContent or EmbeddedResource
8282
}
8383

8484
// PromptListChangedNotification is an optional notification from the server

mcp/tools.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type ListToolsResult struct {
3333
// should be reported as an MCP error response.
3434
type CallToolResult struct {
3535
Result
36-
Content []Content `json:"content"` // Can be TextContent, ImageContent, or EmbeddedResource
36+
Content []Content `json:"content"` // Can be TextContent, ImageContent, AudioContent, or EmbeddedResource
3737
// Whether the tool call ended in an error.
3838
//
3939
// If not set, this is assumed to be false (the call was successful).

mcp/types.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ type NotificationParams struct {
132132
}
133133

134134
// MarshalJSON implements custom JSON marshaling
135-
func (p *NotificationParams) MarshalJSON() ([]byte, error) {
135+
func (p NotificationParams) MarshalJSON() ([]byte, error) {
136136
// Create a map to hold all fields
137137
m := make(map[string]interface{})
138138

@@ -656,7 +656,7 @@ type CreateMessageResult struct {
656656
// SamplingMessage describes a message issued to or received from an LLM API.
657657
type SamplingMessage struct {
658658
Role Role `json:"role"`
659-
Content interface{} `json:"content"` // Can be TextContent or ImageContent
659+
Content interface{} `json:"content"` // Can be TextContent, ImageContent or AudioContent
660660
}
661661

662662
type Annotations struct {
@@ -709,6 +709,19 @@ type ImageContent struct {
709709

710710
func (ImageContent) isContent() {}
711711

712+
// AudioContent represents the contents of audio, embedded into a prompt or tool call result.
713+
// It must have Type set to "audio".
714+
type AudioContent struct {
715+
Annotated
716+
Type string `json:"type"` // Must be "audio"
717+
// The base64-encoded audio data.
718+
Data string `json:"data"`
719+
// The MIME type of the audio. Different providers may support different audio types.
720+
MIMEType string `json:"mimeType"`
721+
}
722+
723+
func (AudioContent) isContent() {}
724+
712725
// EmbeddedResource represents the contents of a resource, embedded into a prompt or tool call result.
713726
//
714727
// It is up to the client how best to render embedded resources for the

0 commit comments

Comments
 (0)