// 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" "slices" "strings" "time" "github.com/golang-jwt/jwt/v5" "github.com/modelcontextprotocol/go-sdk/auth" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/oauthex" ) // This example demonstrates how to integrate auth.RequireBearerToken middleware // with an MCP server to provide authenticated access to MCP tools and resources. // Default to loopback to avoid exposing powerful demo endpoints to the local network. // Set -http 0.0.0.0:8080 to intentionally expose it. var httpAddr = flag.String("http", "localhost: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) } // verifyJWT verifies JWT tokens and returns TokenInfo for the auth middleware. // This function implements the TokenVerifier interface required by auth.RequireBearerToken. func verifyJWT(ctx context.Context, tokenString string, _ *http.Request) (*auth.TokenInfo, error) { // Parse and validate the JWT token. token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (any, 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, fmt.Errorf("%w: %v", auth.ErrInvalidToken, err) } // 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, fmt.Errorf("%w: invalid token claims", auth.ErrInvalidToken) } // verifyAPIKey verifies API keys and returns TokenInfo for the auth middleware. // This function implements the TokenVerifier interface required by auth.RequireBearerToken. func verifyAPIKey(ctx context.Context, apiKey string, _ *http.Request) (*auth.TokenInfo, error) { // Look up the API key in our storage. key, exists := apiKeys[apiKey] if !exists { return nil, fmt.Errorf("%w: API key not found", 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.CallToolRequest, args struct{}) (*mcp.CallToolResult, any, error) { // Extract user information from request (v0.3.0+) userInfo := req.Extra.TokenInfo return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: fmt.Sprintf("Hello! You have scopes: %v", userInfo.Scopes)}, }, }, nil, nil } // GetUserInfo is an MCP tool that requires read scope func GetUserInfo(ctx context.Context, req *mcp.CallToolRequest, args getUserInfoArgs) (*mcp.CallToolResult, any, error) { // Extract user information from request (v0.3.0+) userInfo := req.Extra.TokenInfo // Check if user has read scope. if !slices.Contains(userInfo.Scopes, "read") { return nil, nil, fmt.Errorf("insufficient permissions: read scope required") } userData := map[string]any{ "requested_user_id": args.UserID, "your_scopes": userInfo.Scopes, "message": "User information retrieved successfully", } userDataJSON, err := json.Marshal(userData) if err != nil { return nil, nil, fmt.Errorf("failed to marshal user data: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: string(userDataJSON)}, }, }, nil, nil } // CreateResource is an MCP tool that requires write scope func CreateResource(ctx context.Context, req *mcp.CallToolRequest, args createResourceArgs) (*mcp.CallToolResult, any, error) { // Extract user information from request (v0.3.0+) userInfo := req.Extra.TokenInfo // Check if user has write scope. if !slices.Contains(userInfo.Scopes, "write") { return nil, nil, fmt.Errorf("insufficient permissions: write scope required") } resourceInfo := map[string]any{ "name": args.Name, "description": args.Description, "content": args.Content, "created_by": "authenticated_user", "created_at": time.Now().Format(time.RFC3339), } resourceInfoJSON, err := json.Marshal(resourceInfo) if err != nil { return nil, nil, fmt.Errorf("failed to marshal resource info: %w", err) } return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: fmt.Sprintf("Resource created successfully: %s", string(resourceInfoJSON))}, }, }, nil, nil } // 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(verifyJWT, &auth.RequireBearerTokenOptions{ Scopes: []string{"read"}, // Require "read" permission ResourceMetadataURL: "http://localhost:8080/.well-known/oauth-protected-resource", }) apiKeyAuth := auth.RequireBearerToken(verifyAPIKey, &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(handler) apiKeyHandler := apiKeyAuth(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) if _, err := rand.Read(bytes); err != nil { http.Error(w, "Failed to generate random bytes", http.StatusInternalServerError) return } 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), }) }) // OAuth protected resource metadata endpoint. // This endpoint provides OAuth configuration information to clients. // CORS is enabled by default to support cross-origin client discovery. metadata := &oauthex.ProtectedResourceMetadata{ Resource: "http://localhost:8080/mcp/jwt", AuthorizationServers: []string{ "https://auth.example.com/.well-known/openid-configuration", }, ScopesSupported: []string{"read", "write"}, } http.Handle("/.well-known/oauth-protected-resource", auth.ProtectedResourceMetadataHandler(metadata)) // 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 /.well-known/oauth-protected-resource - OAuth resource metadata") 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)) }