Skip to content

Commit 89bf4a2

Browse files
committed
fix merge conflicts
2 parents 9747156 + 824376e commit 89bf4a2

File tree

21 files changed

+2381
-489
lines changed

21 files changed

+2381
-489
lines changed

README.md

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@
88

99
<strong>A Go implementation of the Model Context Protocol (MCP), enabling seamless integration between LLM applications and external data sources and tools.</strong>
1010

11+
<br>
12+
13+
[![Tutorial](http://img.youtube.com/vi/qoaeYMrXJH0/0.jpg)](http://www.youtube.com/watch?v=qoaeYMrXJH0 "Tutorial")
14+
1115
</div>
1216

1317
```go
1418
package main
1519

1620
import (
1721
"context"
22+
"errors"
1823
"fmt"
1924

2025
"github.com/mark3labs/mcp-go/mcp"
@@ -49,7 +54,7 @@ func main() {
4954
func helloHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
5055
name, ok := request.Params.Arguments["name"].(string)
5156
if !ok {
52-
return mcp.NewToolResultError("name must be a string"), nil
57+
return nil, errors.New("name must be a string")
5358
}
5459

5560
return mcp.NewToolResultText(fmt.Sprintf("Hello, %s!", name)), nil
@@ -103,6 +108,7 @@ package main
103108

104109
import (
105110
"context"
111+
"errors"
106112
"fmt"
107113

108114
"github.com/mark3labs/mcp-go/mcp"
@@ -152,7 +158,7 @@ func main() {
152158
result = x * y
153159
case "divide":
154160
if y == 0 {
155-
return mcp.NewToolResultError("Cannot divide by zero"), nil
161+
return nil, errors.New("Cannot divide by zero")
156162
}
157163
result = x / y
158164
}
@@ -314,7 +320,7 @@ s.AddTool(calculatorTool, func(ctx context.Context, request mcp.CallToolRequest)
314320
result = x * y
315321
case "divide":
316322
if y == 0 {
317-
return mcp.NewToolResultError("Division by zero is not allowed"), nil
323+
return nil, errors.New("Division by zero is not allowed")
318324
}
319325
result = x / y
320326
}
@@ -359,20 +365,20 @@ s.AddTool(httpTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp
359365
req, err = http.NewRequest(method, url, nil)
360366
}
361367
if err != nil {
362-
return mcp.NewToolResultError(fmt.Sprintf("Failed to create request: %v", err)), nil
368+
return nil, fmt.Errorf("Failed to create request: %v", err)
363369
}
364370

365371
client := &http.Client{}
366372
resp, err := client.Do(req)
367373
if err != nil {
368-
return mcp.NewToolResultError(fmt.Sprintf("Request failed: %v", err)), nil
374+
return nil, fmt.Errorf("Request failed: %v", err)
369375
}
370376
defer resp.Body.Close()
371377

372378
// Return response
373379
respBody, err := io.ReadAll(resp.Body)
374380
if err != nil {
375-
return mcp.NewToolResultError(fmt.Sprintf("Failed to read response: %v", err)), nil
381+
return nil, fmt.Errorf("Failed to read response: %v", err)
376382
}
377383

378384
return mcp.NewToolResultText(fmt.Sprintf("Status: %d\nBody: %s", resp.StatusCode, string(respBody))), nil
@@ -503,6 +509,18 @@ Prompts can include:
503509

504510
For examples, see the `examples/` directory.
505511

512+
## Extras
513+
514+
### Request Hooks
515+
516+
Hook into the request lifecycle by creating a `Hooks` object with your
517+
selection among the possible callbacks. This enables telemetry across all
518+
functionality, and observability of various facts, for example the ability
519+
to count improperly-formatted requests, or to log the agent identity during
520+
initialization.
521+
522+
Add the `Hooks` to the server at the time of creation using the
523+
`server.WithHooks` option.
506524

507525
## Contributing
508526

client/sse.go

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,6 @@ import (
1818
"github.com/mark3labs/mcp-go/mcp"
1919
)
2020

21-
type Option func(*SSEMCPClient)
22-
23-
func WithHeaders(headers map[string]string) Option {
24-
return func(sc *SSEMCPClient) {
25-
sc.headers = headers
26-
}
27-
}
28-
29-
func WithSSEReadTimeout(timeout time.Duration) Option {
30-
return func(sc *SSEMCPClient) {
31-
sc.sseReadTimeout = timeout
32-
}
33-
}
34-
3521
// SSEMCPClient implements the MCPClient interface using Server-Sent Events (SSE).
3622
// It maintains a persistent HTTP connection to receive server-pushed events
3723
// while sending requests over regular HTTP POST calls. The client handles
@@ -40,7 +26,6 @@ type SSEMCPClient struct {
4026
baseURL *url.URL
4127
endpoint *url.URL
4228
httpClient *http.Client
43-
headers map[string]string
4429
requestID atomic.Int64
4530
responses map[int64]chan RPCResponse
4631
mu sync.RWMutex
@@ -50,18 +35,33 @@ type SSEMCPClient struct {
5035
notifyMu sync.RWMutex
5136
endpointChan chan struct{}
5237
capabilities mcp.ServerCapabilities
38+
headers map[string]string
5339
sseReadTimeout time.Duration
5440
}
5541

42+
type ClientOption func(*SSEMCPClient)
43+
44+
func WithHeaders(headers map[string]string) ClientOption {
45+
return func(sc *SSEMCPClient) {
46+
sc.headers = headers
47+
}
48+
}
49+
50+
func WithSSEReadTimeout(timeout time.Duration) ClientOption {
51+
return func(sc *SSEMCPClient) {
52+
sc.sseReadTimeout = timeout
53+
}
54+
}
55+
5656
// NewSSEMCPClient creates a new SSE-based MCP client with the given base URL.
5757
// Returns an error if the URL is invalid.
58-
func NewSSEMCPClient(baseURL string, options ...Option) (*SSEMCPClient, error) {
58+
func NewSSEMCPClient(baseURL string, options ...ClientOption) (*SSEMCPClient, error) {
5959
parsedURL, err := url.Parse(baseURL)
6060
if err != nil {
6161
return nil, fmt.Errorf("invalid URL: %w", err)
6262
}
6363

64-
sc := &SSEMCPClient{
64+
smc := &SSEMCPClient{
6565
baseURL: parsedURL,
6666
httpClient: &http.Client{},
6767
responses: make(map[int64]chan RPCResponse),
@@ -72,10 +72,10 @@ func NewSSEMCPClient(baseURL string, options ...Option) (*SSEMCPClient, error) {
7272
}
7373

7474
for _, opt := range options {
75-
opt(sc)
75+
opt(smc)
7676
}
7777

78-
return sc, nil
78+
return smc, nil
7979
}
8080

8181
// Start initiates the SSE connection to the server and waits for the endpoint information.
@@ -125,31 +125,37 @@ func (c *SSEMCPClient) Start(ctx context.Context) error {
125125
func (c *SSEMCPClient) readSSE(reader io.ReadCloser) {
126126
defer reader.Close()
127127

128-
ctx, cancel := context.WithTimeout(context.Background(), c.sseReadTimeout)
129-
defer cancel()
130-
131-
scanner := bufio.NewScanner(reader)
128+
br := bufio.NewReader(reader)
132129
var event, data string
133130

134-
var done bool
135-
for {
136-
if done {
137-
break
138-
}
131+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
132+
defer cancel()
139133

134+
for {
140135
select {
141136
case <-ctx.Done():
142-
if err := ctx.Err(); err != nil {
143-
fmt.Printf("SSE read timed out: %v\n", err)
144-
return
145-
}
137+
return
146138
default:
147-
if !scanner.Scan() {
148-
done = true
149-
break
139+
line, err := br.ReadString('\n')
140+
if err != nil {
141+
if err == io.EOF {
142+
// Process any pending event before exit
143+
if event != "" && data != "" {
144+
c.handleSSEEvent(event, data)
145+
}
146+
break
147+
}
148+
select {
149+
case <-c.done:
150+
return
151+
default:
152+
fmt.Printf("SSE stream error: %v\n", err)
153+
return
154+
}
150155
}
151-
line := scanner.Text()
152156

157+
// Remove only newline markers
158+
line = strings.TrimRight(line, "\r\n")
153159
if line == "" {
154160
// Empty line means end of event
155161
if event != "" && data != "" {
@@ -167,15 +173,6 @@ func (c *SSEMCPClient) readSSE(reader io.ReadCloser) {
167173
}
168174
}
169175
}
170-
171-
if err := scanner.Err(); err != nil {
172-
select {
173-
case <-c.done:
174-
return
175-
default:
176-
fmt.Printf("SSE stream error: %v\n", err)
177-
}
178-
}
179176
}
180177

181178
// handleSSEEvent processes SSE events based on their type.

examples/custom_context/main.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ func makeRequest(ctx context.Context, message, token string) (*response, error)
5656
return nil, err
5757
}
5858
req.Header.Set("Authorization", token)
59-
req.URL.Query().Add("message", message)
59+
query := req.URL.Query()
60+
query.Add("message", message)
61+
req.URL.RawQuery = query.Encode()
6062
resp, err := http.DefaultClient.Do(req)
6163
if err != nil {
6264
return nil, err
@@ -67,7 +69,7 @@ func makeRequest(ctx context.Context, message, token string) (*response, error)
6769
return nil, err
6870
}
6971
var r *response
70-
if err := json.Unmarshal(body, r); err != nil {
72+
if err := json.Unmarshal(body, &r); err != nil {
7173
return nil, err
7274
}
7375
return r, nil

examples/everything/main.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,38 @@ const (
2929
)
3030

3131
func NewMCPServer() *server.MCPServer {
32+
33+
hooks := &server.Hooks{}
34+
35+
hooks.AddBeforeAny(func(id any, method mcp.MCPMethod, message any) {
36+
fmt.Printf("beforeAny: %s, %v, %v\n", method, id, message)
37+
})
38+
hooks.AddOnSuccess(func(id any, method mcp.MCPMethod, message any, result any) {
39+
fmt.Printf("onSuccess: %s, %v, %v, %v\n", method, id, message, result)
40+
})
41+
hooks.AddOnError(func(id any, method mcp.MCPMethod, message any, err error) {
42+
fmt.Printf("onError: %s, %v, %v, %v\n", method, id, message, err)
43+
})
44+
hooks.AddBeforeInitialize(func(id any, message *mcp.InitializeRequest) {
45+
fmt.Printf("beforeInitialize: %v, %v\n", id, message)
46+
})
47+
hooks.AddAfterInitialize(func(id any, message *mcp.InitializeRequest, result *mcp.InitializeResult) {
48+
fmt.Printf("afterInitialize: %v, %v, %v\n", id, message, result)
49+
})
50+
hooks.AddAfterCallTool(func(id any, message *mcp.CallToolRequest, result *mcp.CallToolResult) {
51+
fmt.Printf("afterCallTool: %v, %v, %v\n", id, message, result)
52+
})
53+
hooks.AddBeforeCallTool(func(id any, message *mcp.CallToolRequest) {
54+
fmt.Printf("beforeCallTool: %v, %v\n", id, message)
55+
})
56+
3257
mcpServer := server.NewMCPServer(
3358
"example-servers/everything",
3459
"1.0.0",
3560
server.WithResourceCapabilities(true, true),
3661
server.WithPromptCapabilities(true),
3762
server.WithLogging(),
63+
server.WithHooks(hooks),
3864
)
3965

4066
mcpServer.AddResource(mcp.NewResource("test://static/resource",
@@ -300,6 +326,7 @@ func handleSendNotification(
300326
server := server.ServerFromContext(ctx)
301327

302328
err := server.SendNotificationToClient(
329+
ctx,
303330
"notifications/progress",
304331
map[string]interface{}{
305332
"progress": 10,
@@ -336,6 +363,7 @@ func handleLongRunningOperationTool(
336363
time.Sleep(time.Duration(stepDuration * float64(time.Second)))
337364
if progressToken != nil {
338365
server.SendNotificationToClient(
366+
ctx,
339367
"notifications/progress",
340368
map[string]interface{}{
341369
"progress": i,

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23
55
require (
66
github.com/google/uuid v1.6.0
77
github.com/stretchr/testify v1.9.0
8+
github.com/yosida95/uritemplate/v3 v3.0.2
89
)
910

1011
require (

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
66
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
77
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
88
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
9+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
10+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
911
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1012
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1113
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

mcp/resources.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package mcp
22

3+
import "github.com/yosida95/uritemplate/v3"
4+
35
// ResourceOption is a function that configures a Resource.
46
// It provides a flexible way to set various properties of a Resource using the functional options pattern.
57
type ResourceOption func(*Resource)
@@ -60,7 +62,7 @@ type ResourceTemplateOption func(*ResourceTemplate)
6062
// Options are applied in order, allowing for flexible template configuration.
6163
func NewResourceTemplate(uriTemplate string, name string, opts ...ResourceTemplateOption) ResourceTemplate {
6264
template := ResourceTemplate{
63-
URITemplate: uriTemplate,
65+
URITemplate: &URITemplate{Template: uritemplate.MustNew(uriTemplate)},
6466
Name: name,
6567
}
6668

0 commit comments

Comments
 (0)