Skip to content

Commit 007f93e

Browse files
committed
Merge branch 'main' into oauth-prm
2 parents 3276ff9 + 07b65d7 commit 007f93e

File tree

10 files changed

+871
-28
lines changed

10 files changed

+871
-28
lines changed

README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,12 @@ open-ended discussion. See [CONTRIBUTING.md](/CONTRIBUTING.md) for details.
3333

3434
## Package documentation
3535

36-
The SDK consists of three importable packages:
36+
The SDK consists of two importable packages:
3737

3838
- The
3939
[`github.com/modelcontextprotocol/go-sdk/mcp`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp)
4040
package defines the primary APIs for constructing and using MCP clients and
4141
servers.
42-
- The
43-
[`github.com/modelcontextprotocol/go-sdk/jsonschema`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonschema)
44-
package provides an implementation of [JSON
45-
Schema](https://json-schema.org/), used for MCP tool input and output schema.
4642
- The
4743
[`github.com/modelcontextprotocol/go-sdk/jsonrpc`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/jsonrpc) package is for users implementing
4844
their own transports.

examples/client/loadtest/main.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ var (
3333
workers = flag.Int("workers", 10, "number of concurrent workers")
3434
timeout = flag.Duration("timeout", 1*time.Second, "request timeout")
3535
qps = flag.Int("qps", 100, "tool calls per second, per worker")
36-
v = flag.Bool("v", false, "if set, enable verbose logging of results")
36+
verbose = flag.Bool("v", false, "if set, enable verbose logging")
3737
)
3838

3939
func main() {
@@ -56,8 +56,8 @@ func main() {
5656

5757
parentCtx, cancel := context.WithTimeout(context.Background(), *duration)
5858
defer cancel()
59-
parentCtx, restoreSignal := signal.NotifyContext(parentCtx, os.Interrupt)
60-
defer restoreSignal()
59+
parentCtx, stop := signal.NotifyContext(parentCtx, os.Interrupt)
60+
defer stop()
6161

6262
var (
6363
start = time.Now()
@@ -91,12 +91,12 @@ func main() {
9191
return // test ended
9292
}
9393
failure.Add(1)
94-
if *v {
94+
if *verbose {
9595
log.Printf("FAILURE: %v", err)
9696
}
9797
} else {
9898
success.Add(1)
99-
if *v {
99+
if *verbose {
100100
data, err := json.Marshal(res)
101101
if err != nil {
102102
log.Fatalf("marshalling result: %v", err)
@@ -108,7 +108,7 @@ func main() {
108108
}()
109109
}
110110
wg.Wait()
111-
restoreSignal() // call restore signal (redundantly) here to allow ctrl-c to work again
111+
stop() // restore the interrupt signal
112112

113113
// Print stats.
114114
var (

examples/http/main.go

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,29 +64,29 @@ type GetTimeParams struct {
6464
}
6565

6666
// getTime implements the tool that returns the current time for a given city.
67-
func getTime(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[GetTimeParams]) (*mcp.CallToolResultFor[any], error) {
67+
func getTime(ctx context.Context, req *mcp.CallToolRequest, params *GetTimeParams) (*mcp.CallToolResult, any, error) {
6868
// Define time zones for each city
6969
locations := map[string]string{
7070
"nyc": "America/New_York",
7171
"sf": "America/Los_Angeles",
7272
"boston": "America/New_York",
7373
}
7474

75-
city := params.Arguments.City
75+
city := params.City
7676
if city == "" {
7777
city = "nyc" // Default to NYC
7878
}
7979

8080
// Get the timezone.
8181
tzName, ok := locations[city]
8282
if !ok {
83-
return nil, fmt.Errorf("unknown city: %s", city)
83+
return nil, nil, fmt.Errorf("unknown city: %s", city)
8484
}
8585

8686
// Load the location.
8787
loc, err := time.LoadLocation(tzName)
8888
if err != nil {
89-
return nil, fmt.Errorf("failed to load timezone: %w", err)
89+
return nil, nil, fmt.Errorf("failed to load timezone: %w", err)
9090
}
9191

9292
// Get current time in that location.
@@ -103,11 +103,11 @@ func getTime(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolPar
103103
cityNames[city],
104104
now.Format(time.RFC3339))
105105

106-
return &mcp.CallToolResultFor[any]{
106+
return &mcp.CallToolResult{
107107
Content: []mcp.Content{
108108
&mcp.TextContent{Text: response},
109109
},
110-
}, nil
110+
}, nil, nil
111111
}
112112

113113
func runServer(url string) {
@@ -145,17 +145,14 @@ func runClient(url string) {
145145
// Create the URL for the server.
146146
log.Printf("Connecting to MCP server at %s", url)
147147

148-
// Create a streamable client transport.
149-
transport := mcp.NewStreamableClientTransport(url, nil)
150-
151148
// Create an MCP client.
152149
client := mcp.NewClient(&mcp.Implementation{
153150
Name: "time-client",
154151
Version: "1.0.0",
155152
}, nil)
156153

157154
// Connect to the server.
158-
session, err := client.Connect(ctx, transport)
155+
session, err := client.Connect(ctx, &mcp.StreamableClientTransport{Endpoint: url}, nil)
159156
if err != nil {
160157
log.Fatalf("Failed to connect: %v", err)
161158
}
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# MCP Server with Auth Middleware
2+
3+
This example demonstrates how to integrate the Go MCP SDK's `auth.RequireBearerToken` middleware with an MCP server to provide authenticated access to MCP tools and resources.
4+
5+
## Features
6+
7+
The server provides authentication and authorization capabilities for MCP tools:
8+
9+
### 1. Authentication Methods
10+
11+
- **JWT Token Authentication**: JSON Web Token-based authentication
12+
- **API Key Authentication**: API key-based authentication
13+
- **Scope-based Access Control**: Permission-based access to MCP tools
14+
15+
### 2. MCP Integration
16+
17+
- **Authenticated MCP Tools**: Tools that require authentication and check permissions
18+
- **Token Generation**: Utility endpoints for generating test tokens
19+
- **Middleware Integration**: Seamless integration with MCP server handlers
20+
21+
## Setup
22+
23+
```bash
24+
cd examples/server/auth-middleware
25+
go mod tidy
26+
go run main.go
27+
```
28+
29+
## Testing
30+
31+
```bash
32+
# Run all tests
33+
go test -v
34+
35+
# Run benchmark tests
36+
go test -bench=.
37+
38+
# Generate coverage report
39+
go test -cover
40+
```
41+
42+
## Endpoints
43+
44+
### Public Endpoints (No Authentication Required)
45+
46+
- `GET /health` - Health check
47+
48+
### MCP Endpoints (Authentication Required)
49+
50+
- `POST /mcp/jwt` - MCP server with JWT authentication
51+
- `POST /mcp/apikey` - MCP server with API key authentication
52+
53+
### Utility Endpoints
54+
55+
- `GET /generate-token` - Generate JWT token
56+
- `POST /generate-api-key` - Generate API key
57+
58+
## Available MCP Tools
59+
60+
The server provides three authenticated MCP tools:
61+
62+
### 1. Say Hi (`say_hi`)
63+
64+
A simple greeting tool that requires authentication.
65+
66+
**Parameters:**
67+
- None required
68+
69+
**Required Scopes:**
70+
- Any authenticated user
71+
72+
### 2. Get User Info (`get_user_info`)
73+
74+
Retrieves user information based on the provided user ID.
75+
76+
**Parameters:**
77+
- `user_id` (string): The user ID to get information for
78+
79+
**Required Scopes:**
80+
- `read` permission
81+
82+
### 3. Create Resource (`create_resource`)
83+
84+
Creates a new resource with the provided details.
85+
86+
**Parameters:**
87+
- `name` (string): The name of the resource
88+
- `description` (string): The description of the resource
89+
- `content` (string): The content of the resource
90+
91+
**Required Scopes:**
92+
- `write` permission
93+
94+
## Example Usage
95+
96+
### 1. Generating JWT Token and Using MCP Tools
97+
98+
```bash
99+
# Generate a token
100+
curl 'http://localhost:8080/generate-token?user_id=alice&scopes=read,write'
101+
102+
# Use MCP tool with JWT authentication
103+
curl -H 'Authorization: Bearer <generated_token>' \
104+
-H 'Content-Type: application/json' \
105+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"say_hi","arguments":{}}}' \
106+
http://localhost:8080/mcp/jwt
107+
```
108+
109+
### 2. Generating API Key and Using MCP Tools
110+
111+
```bash
112+
# Generate an API key
113+
curl -X POST 'http://localhost:8080/generate-api-key?user_id=bob&scopes=read'
114+
115+
# Use MCP tool with API key authentication
116+
curl -H 'Authorization: Bearer <generated_api_key>' \
117+
-H 'Content-Type: application/json' \
118+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_user_info","arguments":{"user_id":"test"}}}' \
119+
http://localhost:8080/mcp/apikey
120+
```
121+
122+
### 3. Testing Scope Restrictions
123+
124+
```bash
125+
# Access MCP tool requiring write scope
126+
curl -H 'Authorization: Bearer <token_with_write_scope>' \
127+
-H 'Content-Type: application/json' \
128+
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_resource","arguments":{"name":"test","description":"test resource","content":"test content"}}}' \
129+
http://localhost:8080/mcp/jwt
130+
```
131+
132+
## Core Concepts
133+
134+
### Authentication Integration
135+
136+
This example demonstrates how to integrate `auth.RequireBearerToken` middleware with an MCP server to provide authenticated access. The MCP server operates as an HTTP handler protected by authentication middleware.
137+
138+
### Key Features
139+
140+
1. **MCP Server Integration**: Create MCP server using `mcp.NewServer`
141+
2. **Authentication Middleware**: Protect MCP handlers with `auth.RequireBearerToken`
142+
3. **Token Verification**: Validate tokens using provided `TokenVerifier` functions
143+
4. **Scope Checking**: Verify required permissions (scopes) are present
144+
5. **Expiration Validation**: Check that tokens haven't expired
145+
6. **Context Injection**: Add verified token information to request context
146+
7. **Authenticated MCP Tools**: Tools that operate based on authentication information
147+
8. **Error Handling**: Return appropriate HTTP status codes and error messages on authentication failure
148+
149+
### Implementation
150+
151+
```go
152+
// Create MCP server
153+
server := mcp.NewServer(&mcp.Implementation{Name: "authenticated-mcp-server"}, nil)
154+
155+
// Create authentication middleware
156+
authMiddleware := auth.RequireBearerToken(verifier, &auth.RequireBearerTokenOptions{
157+
Scopes: []string{"read", "write"},
158+
})
159+
160+
// Create MCP handler
161+
handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server {
162+
return server
163+
}, nil)
164+
165+
// Apply authentication middleware to MCP handler
166+
authenticatedHandler := authMiddleware(customMiddleware(handler))
167+
```
168+
169+
### Parameters
170+
171+
- **verifier**: Function to verify tokens (`TokenVerifier` type)
172+
- **opts**: Authentication options
173+
- `Scopes`: List of required permissions
174+
- `ResourceMetadataURL`: OAuth 2.0 resource metadata URL
175+
176+
### Error Responses
177+
178+
- **401 Unauthorized**: Token is invalid, expired, or missing
179+
- **403 Forbidden**: Required scopes are insufficient
180+
- **WWW-Authenticate Header**: Included when resource metadata URL is configured
181+
182+
## Implementation Details
183+
184+
### 1. TokenVerifier Implementation
185+
186+
```go
187+
func jwtVerifier(ctx context.Context, tokenString string) (*auth.TokenInfo, error) {
188+
// JWT token verification logic
189+
// On success: Return TokenInfo
190+
// On failure: Return auth.ErrInvalidToken
191+
}
192+
```
193+
194+
### 2. Using Authentication Information in MCP Tools
195+
196+
```go
197+
// Get authentication information in MCP tool
198+
func MyTool(ctx context.Context, req *mcp.CallToolRequest, args MyArgs) (*mcp.CallToolResult, any, error) {
199+
// Extract authentication info from request
200+
userInfo := req.Extra.TokenInfo
201+
202+
// Check scopes
203+
if !slices.Contains(userInfo.Scopes, "read") {
204+
return nil, nil, fmt.Errorf("insufficient permissions: read scope required")
205+
}
206+
207+
// Execute tool logic
208+
return &mcp.CallToolResult{
209+
Content: []mcp.Content{
210+
&mcp.TextContent{Text: "Tool executed successfully"},
211+
},
212+
}, nil, nil
213+
}
214+
```
215+
216+
### 3. Middleware Composition
217+
218+
```go
219+
// Combine authentication middleware with custom middleware
220+
authenticatedHandler := authMiddleware(customMiddleware(mcpHandler))
221+
```
222+
223+
## Security Best Practices
224+
225+
1. **Environment Variables**: Use environment variables for JWT secrets in production
226+
2. **Database Storage**: Store API keys in a database
227+
3. **HTTPS Usage**: Always use HTTPS in production environments
228+
4. **Token Expiration**: Set appropriate token expiration times
229+
5. **Principle of Least Privilege**: Grant only the minimum required scopes
230+
231+
## Use Cases
232+
233+
**Ideal for:**
234+
235+
- MCP servers requiring authentication and authorization
236+
- Applications needing scope-based access control
237+
- Systems requiring both JWT and API key authentication
238+
- Projects needing secure MCP tool access
239+
- Scenarios requiring audit trails and permission management
240+
241+
**Examples:**
242+
243+
- Enterprise MCP servers with user management
244+
- Multi-tenant MCP applications
245+
- Secure API gateways with MCP integration
246+
- Development environments with authentication requirements
247+
- Production systems requiring fine-grained access control
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module auth-middleware-example
2+
3+
go 1.23.0
4+
5+
require (
6+
github.com/golang-jwt/jwt/v5 v5.2.2
7+
github.com/modelcontextprotocol/go-sdk v0.3.0
8+
)
9+
10+
require (
11+
github.com/google/jsonschema-go v0.2.1-0.20250825175020-748c325cec76 // indirect
12+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
13+
)
14+
15+
replace github.com/modelcontextprotocol/go-sdk => ../../../

0 commit comments

Comments
 (0)