Skip to content

Commit 599a0a6

Browse files
authored
Merge branch 'modelcontextprotocol:main' into resource-link
2 parents 04cc610 + 2facfc6 commit 599a0a6

File tree

13 files changed

+128
-94
lines changed

13 files changed

+128
-94
lines changed

.github/pull_request_template.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
### PR Tips
2+
3+
Typically, PRs should consist of a single commit, and so should generally follow
4+
the [rules for Go commit messages](https://go.dev/wiki/CommitMessage), with the following
5+
changes and additions:
6+
7+
- Markdown is allowed.
8+
9+
- For a pervasive change, use "all" in the title instead of a package name.
10+
11+
- The PR description should provide context (why this change?) and describe the changes
12+
at a high level. Changes that are obvious from the diffs don't need to be mentioned.

design/design.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,10 @@ server.AddReceivingMiddleware(withLogging)
470470
471471
**Differences from mcp-go**: Version 0.26.0 of mcp-go defines 24 server hooks. Each hook consists of a field in the `Hooks` struct, a `Hooks.Add` method, and a type for the hook function. These are rarely used. The most common is `OnError`, which occurs fewer than ten times in open-source code.
472472
473+
#### Rate Limiting
474+
475+
Rate limiting can be configured using middleware. Please see [examples/rate-limiting](<https://github.com/modelcontextprotocol/go-sdk/tree/main/examples/rate-limiting>] for an example on how to implement this.
476+
473477
### Errors
474478
475479
With the exception of tool handler errors, protocol errors are handled transparently as Go errors: errors in server-side feature handlers are propagated as errors from calls from the `ClientSession`, and vice-versa.
@@ -863,7 +867,7 @@ type ServerOptions struct {
863867
}
864868
```
865869
866-
#### Securty Considerations
870+
#### Security Considerations
867871
868872
Implementors of the CompletionHandler MUST adhere to the following security guidelines:
869873
@@ -952,7 +956,7 @@ In addition to the `List` methods, the SDK provides an iterator method for each
952956
953957
# Governance and Community
954958
955-
While the sections above propose an initial implementation of the Go SDK, MCP is evolving rapidly. SDKs need to keep pace, by implementing changes to the spec, fixing bugs, and accomodating new and emerging use-cases. This section proposes how the SDK project can be managed so that it can change safely and transparently.
959+
While the sections above propose an initial implementation of the Go SDK, MCP is evolving rapidly. SDKs need to keep pace, by implementing changes to the spec, fixing bugs, and accommodating new and emerging use-cases. This section proposes how the SDK project can be managed so that it can change safely and transparently.
956960
957961
Initially, the Go SDK repository will be administered by the Go team and Anthropic, and they will be the Approvers (the set of people able to merge PRs to the SDK). The policies here are also intended to satisfy necessary constraints of the Go team's participation in the project.
958962
@@ -980,7 +984,7 @@ A proposal is an issue that proposes a new API for the SDK, or a change to the s
980984
981985
Proposals that are straightforward and uncontroversial may be approved based on GitHub discussion. However, proposals that are deemed to be sufficiently unclear or complicated will be deferred to a regular steering meeting (see below).
982986
983-
This process is similar to the [Go proposal process](https://github.com/golang/proposal), but is necessarily lighter weight to accomodate the greater rate of change expected for the SDK.
987+
This process is similar to the [Go proposal process](https://github.com/golang/proposal), but is necessarily lighter weight to accommodate the greater rate of change expected for the SDK.
984988
985989
### Steering meetings
986990

examples/rate-limiting/go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module github.com/modelcontextprotocol/go-sdk/examples/rate-limiting
2+
3+
go 1.25
4+
5+
require (
6+
github.com/modelcontextprotocol/go-sdk v0.0.0-20250625185707-09181c2c2e89
7+
golang.org/x/time v0.12.0
8+
)

examples/rate-limiting/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
2+
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
3+
github.com/modelcontextprotocol/go-sdk v0.0.0-20250625185707-09181c2c2e89 h1:kUGBYP25FTv3ZRBhLT4iQvtx4FDl7hPkWe3isYrMxyo=
4+
github.com/modelcontextprotocol/go-sdk v0.0.0-20250625185707-09181c2c2e89/go.mod h1:DcXfbr7yl7e35oMpzHfKw2nUYRjhIGS2uou/6tdsTB0=
5+
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
6+
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
7+
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
8+
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=

examples/rate-limiting/main.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
2+
// Use of this source code is governed by an MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package main
6+
7+
import (
8+
"context"
9+
"errors"
10+
"time"
11+
12+
"github.com/modelcontextprotocol/go-sdk/mcp"
13+
"golang.org/x/time/rate"
14+
)
15+
16+
// GlobalRateLimiterMiddleware creates a middleware that applies a global rate limit.
17+
// Every request attempting to pass through will try to acquire a token.
18+
// If a token cannot be acquired immediately, the request will be rejected.
19+
func GlobalRateLimiterMiddleware[S mcp.Session](limiter *rate.Limiter) mcp.Middleware[S] {
20+
return func(next mcp.MethodHandler[S]) mcp.MethodHandler[S] {
21+
return func(ctx context.Context, session S, method string, params mcp.Params) (mcp.Result, error) {
22+
if !limiter.Allow() {
23+
return nil, errors.New("JSON RPC overloaded")
24+
}
25+
return next(ctx, session, method, params)
26+
}
27+
}
28+
}
29+
30+
// PerMethodRateLimiterMiddleware creates a middleware that applies rate limiting
31+
// on a per-method basis.
32+
// Methods not specified in limiters will not be rate limited by this middleware.
33+
func PerMethodRateLimiterMiddleware[S mcp.Session](limiters map[string]*rate.Limiter) mcp.Middleware[S] {
34+
return func(next mcp.MethodHandler[S]) mcp.MethodHandler[S] {
35+
return func(ctx context.Context, session S, method string, params mcp.Params) (mcp.Result, error) {
36+
if limiter, ok := limiters[method]; ok {
37+
if !limiter.Allow() {
38+
return nil, errors.New("JSON RPC overloaded")
39+
}
40+
}
41+
return next(ctx, session, method, params)
42+
}
43+
}
44+
}
45+
46+
func main() {
47+
server := mcp.NewServer("greeter1", "v0.0.1", nil)
48+
server.AddReceivingMiddleware(GlobalRateLimiterMiddleware[*mcp.ServerSession](rate.NewLimiter(rate.Every(time.Second/5), 10)))
49+
server.AddReceivingMiddleware(PerMethodRateLimiterMiddleware[*mcp.ServerSession](map[string]*rate.Limiter{
50+
"callTool": rate.NewLimiter(rate.Every(time.Second), 5), // once a second with a burst up to 5
51+
"listTools": rate.NewLimiter(rate.Every(time.Minute), 20), // once a minute with a burst up to 20
52+
}))
53+
// Run Server logic.
54+
}

jsonschema/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ or as a value of this type:
4040
4141
# Inference
4242
43-
The [For] and [ForType] functions return a [Schema] describing the given Go type.
43+
The [For] function returns a [Schema] describing the given Go type.
4444
The type cannot contain any function or channel types, and any map types must have a string key.
4545
For example, calling For on the above Player type results in this schema:
4646

jsonschema/infer.go

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,39 @@ import (
1515

1616
// For constructs a JSON schema object for the given type argument.
1717
//
18-
// It is a convenience for ForType.
19-
func For[T any]() (*Schema, error) {
20-
return ForType(reflect.TypeFor[T]())
21-
}
22-
23-
// ForType constructs a JSON schema object for the given type.
2418
// It translates Go types into compatible JSON schema types, as follows:
25-
// - strings have schema type "string"
26-
// - bools have schema type "boolean"
27-
// - signed and unsigned integer types have schema type "integer"
28-
// - floating point types have schema type "number"
29-
// - slices and arrays have schema type "array", and a corresponding schema
30-
// for items
31-
// - maps with string key have schema type "object", and corresponding
32-
// schema for additionalProperties
33-
// - structs have schema type "object", and disallow additionalProperties.
19+
// - Strings have schema type "string".
20+
// - Bools have schema type "boolean".
21+
// - Signed and unsigned integer types have schema type "integer".
22+
// - Floating point types have schema type "number".
23+
// - Slices and arrays have schema type "array", and a corresponding schema
24+
// for items.
25+
// - Maps with string key have schema type "object", and corresponding
26+
// schema for additionalProperties.
27+
// - Structs have schema type "object", and disallow additionalProperties.
3428
// Their properties are derived from exported struct fields, using the
35-
// struct field json name. Fields that are marked "omitempty" are
29+
// struct field JSON name. Fields that are marked "omitempty" are
3630
// considered optional; all other fields become required properties.
3731
//
38-
// It returns an error if t contains (possibly recursively) any of the following Go
32+
// For returns an error if t contains (possibly recursively) any of the following Go
3933
// types, as they are incompatible with the JSON schema spec.
4034
// - maps with key other than 'string'
4135
// - function types
4236
// - complex numbers
4337
// - unsafe pointers
4438
//
45-
// The cannot be any cycles in the types.
46-
// TODO(rfindley): we could perhaps just skip these incompatible fields.
47-
func ForType(t reflect.Type) (*Schema, error) {
48-
return typeSchema(t)
39+
// The types must not have cycles.
40+
func For[T any]() (*Schema, error) {
41+
// TODO: consider skipping incompatible fields, instead of failing.
42+
s, err := forType(reflect.TypeFor[T]())
43+
if err != nil {
44+
var z T
45+
return nil, fmt.Errorf("For[%T](): %w", z, err)
46+
}
47+
return s, nil
4948
}
5049

51-
func typeSchema(t reflect.Type) (*Schema, error) {
50+
func forType(t reflect.Type) (*Schema, error) {
5251
// Follow pointers: the schema for *T is almost the same as for T, except that
5352
// an explicit JSON "null" is allowed for the pointer.
5453
allowNull := false
@@ -82,14 +81,14 @@ func typeSchema(t reflect.Type) (*Schema, error) {
8281
return nil, fmt.Errorf("unsupported map key type %v", t.Key().Kind())
8382
}
8483
s.Type = "object"
85-
s.AdditionalProperties, err = typeSchema(t.Elem())
84+
s.AdditionalProperties, err = forType(t.Elem())
8685
if err != nil {
8786
return nil, fmt.Errorf("computing map value schema: %v", err)
8887
}
8988

9089
case reflect.Slice, reflect.Array:
9190
s.Type = "array"
92-
s.Items, err = typeSchema(t.Elem())
91+
s.Items, err = forType(t.Elem())
9392
if err != nil {
9493
return nil, fmt.Errorf("computing element schema: %v", err)
9594
}
@@ -115,7 +114,7 @@ func typeSchema(t reflect.Type) (*Schema, error) {
115114
if s.Properties == nil {
116115
s.Properties = make(map[string]*Schema)
117116
}
118-
s.Properties[info.Name], err = typeSchema(field.Type)
117+
s.Properties[info.Name], err = forType(field.Type)
119118
if err != nil {
120119
return nil, err
121120
}

mcp/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ func (c *Client) RemoveRoots(uris ...string) {
191191
func() bool { return c.roots.remove(uris...) })
192192
}
193193

194-
// changeAndNotifyClient is called when a feature is added or removed.
194+
// changeAndNotify is called when a feature is added or removed.
195195
// It calls change, which should do the work and report whether a change actually occurred.
196196
// If there was a change, it notifies a snapshot of the sessions.
197197
func (c *Client) changeAndNotify(notification string, params Params, change func() bool) {
@@ -347,7 +347,7 @@ func (cs *ClientSession) ListResourceTemplates(ctx context.Context, params *List
347347
return handleSend[*ListResourceTemplatesResult](ctx, cs, methodListResourceTemplates, orZero[Params](params))
348348
}
349349

350-
// ReadResource ask the server to read a resource and return its contents.
350+
// ReadResource asks the server to read a resource and return its contents.
351351
func (cs *ClientSession) ReadResource(ctx context.Context, params *ReadResourceParams) (*ReadResourceResult, error) {
352352
return handleSend[*ReadResourceResult](ctx, cs, methodReadResource, orZero[Params](params))
353353
}

mcp/content_test.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ package mcp_test
77
import (
88
"encoding/json"
99
"fmt"
10-
"log"
1110
"testing"
1211

1312
"github.com/google/go-cmp/cmp"
@@ -112,7 +111,6 @@ func TestContent(t *testing.T) {
112111
t.Errorf("json.Marshal(%v) mismatch (-want +got):\n%s", test.in, diff)
113112
}
114113
result := fmt.Sprintf(`{"content":[%s]}`, string(got))
115-
log.Println(result)
116114
var out mcp.CallToolResult
117115
if err := json.Unmarshal([]byte(result), &out); err != nil {
118116
t.Fatal(err)

mcp/logging.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func (h *LoggingHandler) WithGroup(name string) slog.Handler {
137137
}
138138

139139
// Handle implements [slog.Handler.Handle] by writing the Record to a JSONHandler,
140-
// then calling [ServerSession.LoggingMesssage] with the result.
140+
// then calling [ServerSession.LoggingMessage] with the result.
141141
func (h *LoggingHandler) Handle(ctx context.Context, r slog.Record) error {
142142
err := h.handle(ctx, r)
143143
// TODO(jba): find a way to surface the error.

0 commit comments

Comments
 (0)