Skip to content

Commit 228e163

Browse files
authored
Merge branch 'main' into implem
2 parents 31029b6 + bfa5e30 commit 228e163

File tree

15 files changed

+558
-141
lines changed

15 files changed

+558
-141
lines changed

examples/rate-limiting/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ go 1.23.0
55
toolchain go1.24.4
66

77
require (
8-
github.com/modelcontextprotocol/go-sdk v0.0.0-20250625185707-09181c2c2e89
8+
github.com/modelcontextprotocol/go-sdk v0.1.0
99
golang.org/x/time v0.12.0
1010
)

examples/rate-limiting/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
22
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=
3+
github.com/modelcontextprotocol/go-sdk v0.1.0 h1:ItzbFWYNt4EHcUrScX7P8JPASn1FVYb29G773Xkl+IU=
4+
github.com/modelcontextprotocol/go-sdk v0.1.0/go.mod h1:DcXfbr7yl7e35oMpzHfKw2nUYRjhIGS2uou/6tdsTB0=
55
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
66
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
77
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=

examples/rate-limiting/main.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ package main
77
import (
88
"context"
99
"errors"
10+
"log"
11+
"sync"
1012
"time"
1113

1214
"github.com/modelcontextprotocol/go-sdk/mcp"
@@ -43,12 +45,52 @@ func PerMethodRateLimiterMiddleware[S mcp.Session](limiters map[string]*rate.Lim
4345
}
4446
}
4547

48+
// PerSessionRateLimiterMiddleware creates a middleware that applies rate limiting
49+
// on a per-session basis for receiving requests.
50+
func PerSessionRateLimiterMiddleware[S mcp.Session](limit rate.Limit, burst int) mcp.Middleware[S] {
51+
// A map to store limiters, keyed by the session ID.
52+
var (
53+
sessionLimiters = make(map[string]*rate.Limiter)
54+
mu sync.Mutex
55+
)
56+
57+
return func(next mcp.MethodHandler[S]) mcp.MethodHandler[S] {
58+
return func(ctx context.Context, session S, method string, params mcp.Params) (mcp.Result, error) {
59+
// It's possible that session.ID() may be empty at this point in time
60+
// for some transports (e.g., stdio) or until the MCP initialize handshake
61+
// has completed.
62+
sessionID := session.ID()
63+
if sessionID == "" {
64+
// In this situation, you could apply a single global identifier
65+
// if session ID is empty or bypass the rate limiter.
66+
// In this example, we bypass the rate limiter.
67+
log.Printf("Warning: Session ID is empty for method %q. Skipping per-session rate limiting.", method)
68+
return next(ctx, session, method, params) // Skip limiting if ID is unavailable
69+
}
70+
mu.Lock()
71+
limiter, ok := sessionLimiters[sessionID]
72+
if !ok {
73+
limiter = rate.NewLimiter(limit, burst)
74+
sessionLimiters[sessionID] = limiter
75+
}
76+
mu.Unlock()
77+
if !limiter.Allow() {
78+
return nil, errors.New("JSON RPC overloaded")
79+
}
80+
return next(ctx, session, method, params)
81+
}
82+
}
83+
}
84+
4685
func main() {
4786
server := mcp.NewServer("greeter1", "v0.0.1", nil)
4887
server.AddReceivingMiddleware(GlobalRateLimiterMiddleware[*mcp.ServerSession](rate.NewLimiter(rate.Every(time.Second/5), 10)))
4988
server.AddReceivingMiddleware(PerMethodRateLimiterMiddleware[*mcp.ServerSession](map[string]*rate.Limiter{
5089
"callTool": rate.NewLimiter(rate.Every(time.Second), 5), // once a second with a burst up to 5
5190
"listTools": rate.NewLimiter(rate.Every(time.Minute), 20), // once a minute with a burst up to 20
5291
}))
92+
server.AddReceivingMiddleware(PerSessionRateLimiterMiddleware[*mcp.ServerSession](rate.Every(time.Second/5), 10))
5393
// Run Server logic.
94+
log.Println("MCP Server instance created with Middleware (but not running).")
95+
log.Println("This example demonstrates configuration, not live interaction.")
5496
}

jsonrpc/jsonrpc.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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 jsonrpc exposes part of a JSON-RPC v2 implementation
6+
// for use by mcp transport authors.
7+
package jsonrpc
8+
9+
import "github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
10+
11+
type (
12+
// ID is a JSON-RPC request ID.
13+
ID = jsonrpc2.ID
14+
// Message is a JSON-RPC message.
15+
Message = jsonrpc2.Message
16+
// Request is a JSON-RPC request.
17+
Request = jsonrpc2.Request
18+
// Response is a JSON-RPC response.
19+
Response = jsonrpc2.Response
20+
)

jsonschema/infer.go

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package jsonschema
99
import (
1010
"fmt"
1111
"reflect"
12+
"regexp"
1213

1314
"github.com/modelcontextprotocol/go-sdk/internal/util"
1415
)
@@ -36,18 +37,24 @@ import (
3637
// - complex numbers
3738
// - unsafe pointers
3839
//
39-
// The types must not have cycles.
40+
// It will return an error if there is a cycle in the types.
41+
//
42+
// For recognizes struct field tags named "jsonschema".
43+
// A jsonschema tag on a field is used as the description for the corresponding property.
44+
// For future compatibility, descriptions must not start with "WORD=", where WORD is a
45+
// sequence of non-whitespace characters.
4046
func For[T any]() (*Schema, error) {
4147
// TODO: consider skipping incompatible fields, instead of failing.
42-
s, err := forType(reflect.TypeFor[T]())
48+
seen := make(map[reflect.Type]bool)
49+
s, err := forType(reflect.TypeFor[T](), seen)
4350
if err != nil {
4451
var z T
4552
return nil, fmt.Errorf("For[%T](): %w", z, err)
4653
}
4754
return s, nil
4855
}
4956

50-
func forType(t reflect.Type) (*Schema, error) {
57+
func forType(t reflect.Type, seen map[reflect.Type]bool) (*Schema, error) {
5158
// Follow pointers: the schema for *T is almost the same as for T, except that
5259
// an explicit JSON "null" is allowed for the pointer.
5360
allowNull := false
@@ -56,6 +63,16 @@ func forType(t reflect.Type) (*Schema, error) {
5663
t = t.Elem()
5764
}
5865

66+
// Check for cycles
67+
// User defined types have a name, so we can skip those that are natively defined
68+
if t.Name() != "" {
69+
if seen[t] {
70+
return nil, fmt.Errorf("cycle detected for type %v", t)
71+
}
72+
seen[t] = true
73+
defer delete(seen, t)
74+
}
75+
5976
var (
6077
s = new(Schema)
6178
err error
@@ -81,14 +98,14 @@ func forType(t reflect.Type) (*Schema, error) {
8198
return nil, fmt.Errorf("unsupported map key type %v", t.Key().Kind())
8299
}
83100
s.Type = "object"
84-
s.AdditionalProperties, err = forType(t.Elem())
101+
s.AdditionalProperties, err = forType(t.Elem(), seen)
85102
if err != nil {
86103
return nil, fmt.Errorf("computing map value schema: %v", err)
87104
}
88105

89106
case reflect.Slice, reflect.Array:
90107
s.Type = "array"
91-
s.Items, err = forType(t.Elem())
108+
s.Items, err = forType(t.Elem(), seen)
92109
if err != nil {
93110
return nil, fmt.Errorf("computing element schema: %v", err)
94111
}
@@ -114,10 +131,20 @@ func forType(t reflect.Type) (*Schema, error) {
114131
if s.Properties == nil {
115132
s.Properties = make(map[string]*Schema)
116133
}
117-
s.Properties[info.Name], err = forType(field.Type)
134+
fs, err := forType(field.Type, seen)
118135
if err != nil {
119136
return nil, err
120137
}
138+
if tag, ok := field.Tag.Lookup("jsonschema"); ok {
139+
if tag == "" {
140+
return nil, fmt.Errorf("empty jsonschema tag on struct field %s.%s", t, field.Name)
141+
}
142+
if disallowedPrefixRegexp.MatchString(tag) {
143+
return nil, fmt.Errorf("tag must not begin with 'WORD=': %q", tag)
144+
}
145+
fs.Description = tag
146+
}
147+
s.Properties[info.Name] = fs
121148
if !info.Settings["omitempty"] && !info.Settings["omitzero"] {
122149
s.Required = append(s.Required, info.Name)
123150
}
@@ -132,3 +159,6 @@ func forType(t reflect.Type) (*Schema, error) {
132159
}
133160
return s, nil
134161
}
162+
163+
// Disallow jsonschema tag values beginning "WORD=", for future expansion.
164+
var disallowedPrefixRegexp = regexp.MustCompile("^[^ \t\n]*=")

0 commit comments

Comments
 (0)