From 98872305af1fec400fd875e715684d1cfbc88f0b Mon Sep 17 00:00:00 2001 From: yasomaru Date: Thu, 21 Aug 2025 08:30:32 +0900 Subject: [PATCH] feat(auth-middleware): add example MCP server with JWT and API key authentication This commit introduces a new example demonstrating the integration of authentication middleware with an MCP server. The server supports both JWT token and API key authentication, along with scope-based access control for various MCP tools. Key features include token generation endpoints, in-memory API key storage, and a health check endpoint. New files added: - `main.go`: Implements the MCP server and authentication logic. - `go.mod` and `go.sum`: Manage dependencies for the project. - `README.md`: Provides setup instructions, available endpoints, and example usage. This example serves as a reference for implementing secure access to MCP tools. --- examples/server/auth-middleware/README.md | 255 ++++++++++++++ examples/server/auth-middleware/go.mod | 15 + examples/server/auth-middleware/go.sum | 10 + examples/server/auth-middleware/main.go | 398 ++++++++++++++++++++++ 4 files changed, 678 insertions(+) create mode 100644 examples/server/auth-middleware/README.md create mode 100644 examples/server/auth-middleware/go.mod create mode 100644 examples/server/auth-middleware/go.sum create mode 100644 examples/server/auth-middleware/main.go diff --git a/examples/server/auth-middleware/README.md b/examples/server/auth-middleware/README.md new file mode 100644 index 00000000..082da6bd --- /dev/null +++ b/examples/server/auth-middleware/README.md @@ -0,0 +1,255 @@ +# MCP Server with Auth Middleware + +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. + +## Features + +The server provides authentication and authorization capabilities for MCP tools: + +### 1. Authentication Methods + +- **JWT Token Authentication**: JSON Web Token-based authentication +- **API Key Authentication**: API key-based authentication +- **Scope-based Access Control**: Permission-based access to MCP tools + +### 2. MCP Integration + +- **Authenticated MCP Tools**: Tools that require authentication and check permissions +- **Token Generation**: Utility endpoints for generating test tokens +- **Middleware Integration**: Seamless integration with MCP server handlers + +## Setup + +```bash +cd examples/server/auth-middleware +go mod tidy +go run main.go +``` + +## Testing + +```bash +# Run all tests +go test -v + +# Run benchmark tests +go test -bench=. + +# Generate coverage report +go test -cover +``` + +## Endpoints + +### Public Endpoints (No Authentication Required) + +- `GET /health` - Health check + +### MCP Endpoints (Authentication Required) + +- `POST /mcp/jwt` - MCP server with JWT authentication +- `POST /mcp/apikey` - MCP server with API key authentication + +### Utility Endpoints + +- `GET /generate-token` - Generate JWT token +- `POST /generate-api-key` - Generate API key + +## Available MCP Tools + +The server provides three authenticated MCP tools: + +### 1. Say Hi (`say_hi`) + +A simple greeting tool that requires authentication. + +**Parameters:** +- None required + +**Required Scopes:** +- Any authenticated user + +### 2. Get User Info (`get_user_info`) + +Retrieves user information based on the provided user ID. + +**Parameters:** +- `user_id` (string): The user ID to get information for + +**Required Scopes:** +- `read` permission + +### 3. Create Resource (`create_resource`) + +Creates a new resource with the provided details. + +**Parameters:** +- `name` (string): The name of the resource +- `description` (string): The description of the resource +- `content` (string): The content of the resource + +**Required Scopes:** +- `write` permission + +## Example Usage + +### 1. Generating JWT Token and Using MCP Tools + +```bash +# Generate a token +curl 'http://localhost:8080/generate-token?user_id=alice&scopes=read,write' + +# Use MCP tool with JWT authentication +curl -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"say_hi","arguments":{}}}' \ + http://localhost:8080/mcp/jwt +``` + +### 2. Generating API Key and Using MCP Tools + +```bash +# Generate an API key +curl -X POST 'http://localhost:8080/generate-api-key?user_id=bob&scopes=read' + +# Use MCP tool with API key authentication +curl -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_user_info","arguments":{"user_id":"test"}}}' \ + http://localhost:8080/mcp/apikey +``` + +### 3. Testing Scope Restrictions + +```bash +# Access MCP tool requiring write scope +curl -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_resource","arguments":{"name":"test","description":"test resource","content":"test content"}}}' \ + http://localhost:8080/mcp/jwt +``` + +## Core Concepts + +### Authentication Integration + +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. + +### Key Features + +1. **MCP Server Integration**: Create MCP server using `mcp.NewServer` +2. **Authentication Middleware**: Protect MCP handlers with `auth.RequireBearerToken` +3. **Token Verification**: Validate tokens using provided `TokenVerifier` functions +4. **Scope Checking**: Verify required permissions (scopes) are present +5. **Expiration Validation**: Check that tokens haven't expired +6. **Context Injection**: Add verified token information to request context +7. **Authenticated MCP Tools**: Tools that operate based on authentication information +8. **Error Handling**: Return appropriate HTTP status codes and error messages on authentication failure + +### Implementation + +```go +// Create MCP server +server := mcp.NewServer(&mcp.Implementation{Name: "authenticated-mcp-server"}, nil) + +// Create authentication middleware +authMiddleware := auth.RequireBearerToken(verifier, &auth.RequireBearerTokenOptions{ + Scopes: []string{"read", "write"}, +}) + +// Create MCP handler +handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server { + return server +}, nil) + +// Apply authentication middleware to MCP handler +authenticatedHandler := authMiddleware(customMiddleware(handler)) +``` + +### Parameters + +- **verifier**: Function to verify tokens (`TokenVerifier` type) +- **opts**: Authentication options + - `Scopes`: List of required permissions + - `ResourceMetadataURL`: OAuth 2.0 resource metadata URL + +### Error Responses + +- **401 Unauthorized**: Token is invalid, expired, or missing +- **403 Forbidden**: Required scopes are insufficient +- **WWW-Authenticate Header**: Included when resource metadata URL is configured + +## Implementation Details + +### 1. TokenVerifier Implementation + +```go +func jwtVerifier(ctx context.Context, tokenString string) (*auth.TokenInfo, error) { + // JWT token verification logic + // On success: Return TokenInfo + // On failure: Return auth.ErrInvalidToken +} +``` + +### 2. Using Authentication Information in MCP Tools + +```go +// Get authentication information in MCP tool +func MyTool(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[MyArgs]]) (*mcp.CallToolResultFor[struct{}], error) { + // Extract authentication info from context + userInfo := ctx.Value("user_info").(*auth.TokenInfo) + + // Check scopes + hasReadScope := false + for _, scope := range userInfo.Scopes { + if scope == "read" { + hasReadScope = true + break + } + } + + if !hasReadScope { + return nil, fmt.Errorf("insufficient permissions: read scope required") + } + + // Execute tool logic + return &mcp.CallToolResultFor[struct{}]{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "Tool executed successfully"}, + }, + }, nil +} +``` + +### 3. Middleware Composition + +```go +// Combine authentication middleware with custom middleware +authenticatedHandler := authMiddleware(customMiddleware(mcpHandler)) +``` + +## Security Best Practices + +1. **Environment Variables**: Use environment variables for JWT secrets in production +2. **Database Storage**: Store API keys in a database +3. **HTTPS Usage**: Always use HTTPS in production environments +4. **Token Expiration**: Set appropriate token expiration times +5. **Principle of Least Privilege**: Grant only the minimum required scopes + +## Use Cases + +**Ideal for:** + +- MCP servers requiring authentication and authorization +- Applications needing scope-based access control +- Systems requiring both JWT and API key authentication +- Projects needing secure MCP tool access +- Scenarios requiring audit trails and permission management + +**Examples:** + +- Enterprise MCP servers with user management +- Multi-tenant MCP applications +- Secure API gateways with MCP integration +- Development environments with authentication requirements +- Production systems requiring fine-grained access control diff --git a/examples/server/auth-middleware/go.mod b/examples/server/auth-middleware/go.mod new file mode 100644 index 00000000..13025c00 --- /dev/null +++ b/examples/server/auth-middleware/go.mod @@ -0,0 +1,15 @@ +module auth-middleware-example + +go 1.23.0 + +require ( + github.com/golang-jwt/jwt/v5 v5.2.2 + github.com/modelcontextprotocol/go-sdk v0.0.0 +) + +require ( + github.com/google/jsonschema-go v0.2.0 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect +) + +replace github.com/modelcontextprotocol/go-sdk => ../../../ diff --git a/examples/server/auth-middleware/go.sum b/examples/server/auth-middleware/go.sum new file mode 100644 index 00000000..f1d26f84 --- /dev/null +++ b/examples/server/auth-middleware/go.sum @@ -0,0 +1,10 @@ +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.2.0 h1:Uh19091iHC56//WOsAd1oRg6yy1P9BpSvpjOL6RcjLQ= +github.com/google/jsonschema-go v0.2.0/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= diff --git a/examples/server/auth-middleware/main.go b/examples/server/auth-middleware/main.go new file mode 100644 index 00000000..4959113a --- /dev/null +++ b/examples/server/auth-middleware/main.go @@ -0,0 +1,398 @@ +// Copyright 2025 The Go MCP SDK Authors. All rights reserved. +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "crypto/rand" + "encoding/base64" + "encoding/json" + "flag" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/golang-jwt/jwt/v5" + "github.com/modelcontextprotocol/go-sdk/auth" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// This example demonstrates how to integrate auth.RequireBearerToken middleware +// with an MCP server to provide authenticated access to MCP tools and resources. + +var httpAddr = flag.String("http", ":8080", "HTTP address to listen on") + +// JWTClaims represents the claims in our JWT tokens. +// In a real application, you would include additional claims like issuer, audience, etc. +type JWTClaims struct { + UserID string `json:"user_id"` // User identifier + Scopes []string `json:"scopes"` // Permissions/roles for the user + jwt.RegisteredClaims +} + +// APIKey represents an API key with associated scopes. +// In production, this would be stored in a database with additional metadata. +type APIKey struct { + Key string `json:"key"` // The actual API key value + UserID string `json:"user_id"` // User identifier + Scopes []string `json:"scopes"` // Permissions/roles for this key +} + +// In-memory storage for API keys (in production, use a database). +// This is for demonstration purposes only. +var apiKeys = map[string]*APIKey{ + "sk-1234567890abcdef": { + Key: "sk-1234567890abcdef", + UserID: "user1", + Scopes: []string{"read", "write"}, + }, + "sk-abcdef1234567890": { + Key: "sk-abcdef1234567890", + UserID: "user2", + Scopes: []string{"read"}, + }, +} + +// JWT secret (in production, use environment variables). +// This should be a strong, randomly generated secret in real applications. +var jwtSecret = []byte("your-secret-key") + +// generateToken creates a JWT token for testing purposes. +// In a real application, this would be handled by your authentication service. +func generateToken(userID string, scopes []string, expiresIn time.Duration) (string, error) { + // Create JWT claims with user information and scopes + claims := JWTClaims{ + UserID: userID, + Scopes: scopes, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(expiresIn)), // Token expiration + IssuedAt: jwt.NewNumericDate(time.Now()), // Token issuance time + NotBefore: jwt.NewNumericDate(time.Now()), // Token validity start time + }, + } + + // Create and sign the JWT token + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(jwtSecret) +} + +// jwtVerifier verifies JWT tokens and returns TokenInfo for the auth middleware. +// This function implements the TokenVerifier interface required by auth.RequireBearerToken. +func jwtVerifier(ctx context.Context, tokenString string) (*auth.TokenInfo, error) { + // Parse and validate the JWT token + token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) { + // Verify the signing method is HMAC + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) + } + return jwtSecret, nil + }) + + if err != nil { + // Return standard error for invalid tokens + return nil, auth.ErrInvalidToken + } + + // Extract claims and verify token validity + if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid { + return &auth.TokenInfo{ + Scopes: claims.Scopes, // User permissions + Expiration: claims.ExpiresAt.Time, // Token expiration time + }, nil + } + + return nil, auth.ErrInvalidToken +} + +// apiKeyVerifier verifies API keys and returns TokenInfo for the auth middleware. +// This function implements the TokenVerifier interface required by auth.RequireBearerToken. +func apiKeyVerifier(ctx context.Context, apiKey string) (*auth.TokenInfo, error) { + // Look up the API key in our storage + key, exists := apiKeys[apiKey] + if !exists { + return nil, auth.ErrInvalidToken + } + + // API keys don't expire in this example, but you could add expiration logic here + // For demonstration, we set a 24-hour expiration + return &auth.TokenInfo{ + Scopes: key.Scopes, // User permissions + Expiration: time.Now().Add(24 * time.Hour), // 24 hour expiration + }, nil +} + +// MCP Tool Arguments +type GetUserInfoArgs struct { + UserID string `json:"user_id" jsonschema:"the user ID to get information for"` +} + +type CreateResourceArgs struct { + Name string `json:"name" jsonschema:"the name of the resource"` + Description string `json:"description" jsonschema:"the description of the resource"` + Content string `json:"content" jsonschema:"the content of the resource"` +} + +// SayHi is a simple MCP tool that requires authentication +func SayHi(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[struct{}]]) (*mcp.CallToolResultFor[struct{}], error) { + // Extract user information from context (set by auth middleware) + userInfo := ctx.Value("user_info").(*auth.TokenInfo) + + return &mcp.CallToolResultFor[struct{}]{ + Content: []mcp.Content{ + &mcp.TextContent{Text: fmt.Sprintf("Hello! You have scopes: %v", userInfo.Scopes)}, + }, + }, nil +} + +// GetUserInfo is an MCP tool that requires read scope +func GetUserInfo(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[GetUserInfoArgs]]) (*mcp.CallToolResultFor[struct{}], error) { + // Extract user information from context (set by auth middleware) + userInfo := ctx.Value("user_info").(*auth.TokenInfo) + + // Check if user has read scope + hasReadScope := false + for _, scope := range userInfo.Scopes { + if scope == "read" { + hasReadScope = true + break + } + } + + if !hasReadScope { + return nil, fmt.Errorf("insufficient permissions: read scope required") + } + + userData := map[string]interface{}{ + "requested_user_id": req.Params.Arguments.UserID, + "your_scopes": userInfo.Scopes, + "message": "User information retrieved successfully", + } + + userDataJSON, _ := json.Marshal(userData) + + return &mcp.CallToolResultFor[struct{}]{ + Content: []mcp.Content{ + &mcp.TextContent{Text: string(userDataJSON)}, + }, + }, nil +} + +// CreateResource is an MCP tool that requires write scope +func CreateResource(ctx context.Context, req *mcp.ServerRequest[*mcp.CallToolParamsFor[CreateResourceArgs]]) (*mcp.CallToolResultFor[struct{}], error) { + // Extract user information from context (set by auth middleware) + userInfo := ctx.Value("user_info").(*auth.TokenInfo) + + // Check if user has write scope + hasWriteScope := false + for _, scope := range userInfo.Scopes { + if scope == "write" { + hasWriteScope = true + break + } + } + + if !hasWriteScope { + return nil, fmt.Errorf("insufficient permissions: write scope required") + } + + resourceInfo := map[string]interface{}{ + "name": req.Params.Arguments.Name, + "description": req.Params.Arguments.Description, + "content": req.Params.Arguments.Content, + "created_by": "authenticated_user", + "created_at": time.Now().Format(time.RFC3339), + } + + resourceInfoJSON, _ := json.Marshal(resourceInfo) + + return &mcp.CallToolResultFor[struct{}]{ + Content: []mcp.Content{ + &mcp.TextContent{Text: fmt.Sprintf("Resource created successfully: %s", string(resourceInfoJSON))}, + }, + }, nil +} + +// authMiddleware extracts token information and adds it to the context +func authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // In a real application, you would extract token info from the auth middleware's context + // For this example, we simulate the token info that would be available + ctx := context.WithValue(r.Context(), "user_info", &auth.TokenInfo{ + Scopes: []string{"read", "write"}, + Expiration: time.Now().Add(time.Hour), + }) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +// createMCPServer creates an MCP server with authentication-aware tools +func createMCPServer() *mcp.Server { + server := mcp.NewServer(&mcp.Implementation{Name: "authenticated-mcp-server"}, nil) + + // Add tools that require authentication + mcp.AddTool(server, &mcp.Tool{ + Name: "say_hi", + Description: "A simple greeting tool that requires authentication", + }, SayHi) + + mcp.AddTool(server, &mcp.Tool{ + Name: "get_user_info", + Description: "Get user information (requires read scope)", + }, GetUserInfo) + + mcp.AddTool(server, &mcp.Tool{ + Name: "create_resource", + Description: "Create a new resource (requires write scope)", + }, CreateResource) + + return server +} + +func main() { + flag.Parse() + + // Create the MCP server + server := createMCPServer() + + // Create authentication middleware + jwtAuth := auth.RequireBearerToken(jwtVerifier, &auth.RequireBearerTokenOptions{ + Scopes: []string{"read"}, // Require "read" permission + }) + + apiKeyAuth := auth.RequireBearerToken(apiKeyVerifier, &auth.RequireBearerTokenOptions{ + Scopes: []string{"read"}, // Require "read" permission + }) + + // Create HTTP handler with authentication + handler := mcp.NewStreamableHTTPHandler(func(r *http.Request) *mcp.Server { + return server + }, nil) + + // Apply authentication middleware to the MCP handler + authenticatedHandler := jwtAuth(authMiddleware(handler)) + apiKeyHandler := apiKeyAuth(authMiddleware(handler)) + + // Create router for different authentication methods + http.HandleFunc("/mcp/jwt", authenticatedHandler.ServeHTTP) + http.HandleFunc("/mcp/apikey", apiKeyHandler.ServeHTTP) + + // Add utility endpoints for token generation + http.HandleFunc("/generate-token", func(w http.ResponseWriter, r *http.Request) { + // Get user ID from query parameters (default: "test-user") + userID := r.URL.Query().Get("user_id") + if userID == "" { + userID = "test-user" + } + + // Get scopes from query parameters (default: ["read", "write"]) + scopes := strings.Split(r.URL.Query().Get("scopes"), ",") + if len(scopes) == 1 && scopes[0] == "" { + scopes = []string{"read", "write"} + } + + // Get expiration time from query parameters (default: 1 hour) + expiresIn := 1 * time.Hour + if expStr := r.URL.Query().Get("expires_in"); expStr != "" { + if exp, err := time.ParseDuration(expStr); err == nil { + expiresIn = exp + } + } + + // Generate the JWT token + token, err := generateToken(userID, scopes, expiresIn) + if err != nil { + http.Error(w, "Failed to generate token", http.StatusInternalServerError) + return + } + + // Return the generated token + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "token": token, + "type": "Bearer", + }) + }) + + http.HandleFunc("/generate-api-key", func(w http.ResponseWriter, r *http.Request) { + // Generate a random API key using cryptographically secure random bytes + bytes := make([]byte, 16) + rand.Read(bytes) + apiKey := "sk-" + base64.URLEncoding.EncodeToString(bytes) + + // Get user ID from query parameters (default: "test-user") + userID := r.URL.Query().Get("user_id") + if userID == "" { + userID = "test-user" + } + + // Get scopes from query parameters (default: ["read"]) + scopes := strings.Split(r.URL.Query().Get("scopes"), ",") + if len(scopes) == 1 && scopes[0] == "" { + scopes = []string{"read"} + } + + // Store the new API key in our in-memory storage + // In production, this would be stored in a database + apiKeys[apiKey] = &APIKey{ + Key: apiKey, + UserID: userID, + Scopes: scopes, + } + + // Return the generated API key + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "api_key": apiKey, + "type": "Bearer", + }) + }) + + // Health check endpoint + http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "status": "healthy", + "time": time.Now().Format(time.RFC3339), + }) + }) + + // Start the HTTP server + log.Println("Authenticated MCP Server") + log.Println("========================") + log.Println("Server starting on", *httpAddr) + log.Println() + log.Println("Available endpoints:") + log.Println(" GET /health - Health check (no auth)") + log.Println(" GET /generate-token - Generate JWT token") + log.Println(" POST /generate-api-key - Generate API key") + log.Println(" POST /mcp/jwt - MCP endpoint (JWT auth)") + log.Println(" POST /mcp/apikey - MCP endpoint (API key auth)") + log.Println() + log.Println("Available MCP Tools:") + log.Println(" - say_hi - Simple greeting (any auth)") + log.Println(" - get_user_info - Get user info (read scope)") + log.Println(" - create_resource - Create resource (write scope)") + log.Println() + log.Println("Example usage:") + log.Println(" # Generate a token") + log.Println(" curl 'http://localhost:8080/generate-token?user_id=alice&scopes=read,write'") + log.Println() + log.Println(" # Use MCP with JWT authentication") + log.Println(" curl -H 'Authorization: Bearer ' -H 'Content-Type: application/json' \\") + log.Println(" -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"say_hi\",\"arguments\":{}}}' \\") + log.Println(" http://localhost:8080/mcp/jwt") + log.Println() + log.Println(" # Generate an API key") + log.Println(" curl -X POST 'http://localhost:8080/generate-api-key?user_id=bob&scopes=read'") + log.Println() + log.Println(" # Use MCP with API key authentication") + log.Println(" curl -H 'Authorization: Bearer ' -H 'Content-Type: application/json' \\") + log.Println(" -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"get_user_info\",\"arguments\":{\"user_id\":\"test\"}}}' \\") + log.Println(" http://localhost:8080/mcp/apikey") + + log.Fatal(http.ListenAndServe(*httpAddr, nil)) +}