Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 84 additions & 1 deletion docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,90 @@ _ = mcp.NewServer(&mcp.Implementation{Name: "server"}, &mcp.ServerOptions{

### Logging

<!-- TODO -->
MCP servers can send logging messages to MCP clients.
(This form of logging is distinct from server-side logging, where the
server produces logs that remain server-side, for use by server maintainers.)

**Server-side**:
The minimum log level is part of the server state.
For stateful sessions, there is no default log level: no log messages will be sent
until the client calls `SetLevel` (see below).
For stateful sessions, the level defaults to "info".

[`ServerSession.Log`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.Log) is the low-level way for servers to log to clients.
It sends a logging notification to the client if the level of the message
is at least the minimum log level.

For a simpler API, use [`NewLoggingHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#NewLoggingHandler) to obtain a [`slog.Handler`](https://pkg.go.dev/log/slog#Handler).
By setting [`LoggingHandlerOptions.MinInterval`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#LoggingHandlerOptions.MinInterval), the handler can be rate-limited
to avoid spamming clients with too many messages.

Servers always report the logging capability.


**Client-side**:
Set [`ClientOptions.LoggingMessageHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.LoggingMessageHandler) to receive log messages.

Call [`ClientSession.SetLevel`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.SetLevel) to change the log level for a session.

```go
func Example_logging() {
ctx := context.Background()

// Create a server.
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)

// Create a client that displays log messages.
done := make(chan struct{}) // solely for the example
var nmsgs atomic.Int32
c := mcp.NewClient(
&mcp.Implementation{Name: "client", Version: "v0.0.1"},
&mcp.ClientOptions{
LoggingMessageHandler: func(_ context.Context, r *mcp.LoggingMessageRequest) {
m := r.Params.Data.(map[string]any)
fmt.Println(m["msg"], m["value"])
if nmsgs.Add(1) == 2 { // number depends on logger calls below
close(done)
}
},
})

// Connect the server and client.
t1, t2 := mcp.NewInMemoryTransports()
ss, err := s.Connect(ctx, t1, nil)
if err != nil {
log.Fatal(err)
}
defer ss.Close()
cs, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()

// Set the minimum log level to "info".
if err := cs.SetLoggingLevel(ctx, &mcp.SetLoggingLevelParams{Level: "info"}); err != nil {
log.Fatal(err)
}

// Get a slog.Logger for the server session.
logger := slog.New(mcp.NewLoggingHandler(ss, nil))

// Log some things.
logger.Info("info shows up", "value", 1)
logger.Debug("debug doesn't show up", "value", 2)
logger.Warn("warn shows up", "value", 3)

// Wait for them to arrive on the client.
// In a real application, the log messages would appear asynchronously
// while other work was happening.
<-done

// Output:
// info shows up 1
// warn shows up 3
}
```

### Pagination

Expand Down
28 changes: 27 additions & 1 deletion internal/docs/server.src.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,33 @@ requests.

### Logging

<!-- TODO -->
MCP servers can send logging messages to MCP clients.
(This form of logging is distinct from server-side logging, where the
server produces logs that remain server-side, for use by server maintainers.)

**Server-side**:
The minimum log level is part of the server state.
For stateful sessions, there is no default log level: no log messages will be sent
until the client calls `SetLevel` (see below).
For stateful sessions, the level defaults to "info".

[`ServerSession.Log`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ServerSession.Log) is the low-level way for servers to log to clients.
It sends a logging notification to the client if the level of the message
is at least the minimum log level.

For a simpler API, use [`NewLoggingHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#NewLoggingHandler) to obtain a [`slog.Handler`](https://pkg.go.dev/log/slog#Handler).
By setting [`LoggingHandlerOptions.MinInterval`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#LoggingHandlerOptions.MinInterval), the handler can be rate-limited
to avoid spamming clients with too many messages.

Servers always report the logging capability.


**Client-side**:
Set [`ClientOptions.LoggingMessageHandler`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientOptions.LoggingMessageHandler) to receive log messages.

Call [`ClientSession.SetLevel`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#ClientSession.SetLevel) to change the log level for a session.

%include ../../mcp/server_example_test.go logging -

### Pagination

Expand Down
1 change: 1 addition & 0 deletions mcp/logging.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type LoggingHandlerOptions struct {
// The value for the "logger" field of logging notifications.
LoggerName string
// Limits the rate at which log messages are sent.
// Excess messages are dropped.
// If zero, there is no rate limiting.
MinInterval time.Duration
}
Expand Down
63 changes: 63 additions & 0 deletions mcp/server_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"context"
"fmt"
"log"
"log/slog"
"sync/atomic"

"github.com/modelcontextprotocol/go-sdk/mcp"
)
Expand Down Expand Up @@ -83,6 +85,67 @@ func Example_prompts() {

// !-prompts

// !+logging

func Example_logging() {
ctx := context.Background()

// Create a server.
s := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)

// Create a client that displays log messages.
done := make(chan struct{}) // solely for the example
var nmsgs atomic.Int32
c := mcp.NewClient(
&mcp.Implementation{Name: "client", Version: "v0.0.1"},
&mcp.ClientOptions{
LoggingMessageHandler: func(_ context.Context, r *mcp.LoggingMessageRequest) {
m := r.Params.Data.(map[string]any)
fmt.Println(m["msg"], m["value"])
if nmsgs.Add(1) == 2 { // number depends on logger calls below
close(done)
}
},
})

// Connect the server and client.
t1, t2 := mcp.NewInMemoryTransports()
ss, err := s.Connect(ctx, t1, nil)
if err != nil {
log.Fatal(err)
}
defer ss.Close()
cs, err := c.Connect(ctx, t2, nil)
if err != nil {
log.Fatal(err)
}
defer cs.Close()

// Set the minimum log level to "info".
if err := cs.SetLoggingLevel(ctx, &mcp.SetLoggingLevelParams{Level: "info"}); err != nil {
log.Fatal(err)
}

// Get a slog.Logger for the server session.
logger := slog.New(mcp.NewLoggingHandler(ss, nil))

// Log some things.
logger.Info("info shows up", "value", 1)
logger.Debug("debug doesn't show up", "value", 2)
logger.Warn("warn shows up", "value", 3)

// Wait for them to arrive on the client.
// In a real application, the log messages would appear asynchronously
// while other work was happening.
<-done

// Output:
// info shows up 1
// warn shows up 3
}

// !-logging

// !+resources
func Example_resources() {
ctx := context.Background()
Expand Down
Loading