55// The conformance server implements features required for MCP conformance testing.
66// It mirrors the functionality of the TypeScript conformance server at
77// https://github.com/modelcontextprotocol/conformance/blob/main/examples/servers/typescript/everything-server.ts
8+
9+ //go:build mcp_go_client_oauth
10+
811package main
912
1013import (
@@ -16,19 +19,28 @@ import (
1619 "fmt"
1720 "log"
1821 "net/http"
22+ "net/url"
1923 "os"
24+ "strings"
2025 "time"
2126
2227 "github.com/google/jsonschema-go/jsonschema"
28+ "github.com/modelcontextprotocol/go-sdk/auth"
2329 "github.com/modelcontextprotocol/go-sdk/mcp"
30+ "github.com/modelcontextprotocol/go-sdk/oauthex"
2431 "github.com/yosida95/uritemplate/v3"
2532)
2633
2734var (
28- httpAddr = flag .String ("http" , "" , "if set, use streamable HTTP at this address, instead of stdin/stdout" )
35+ httpAddr = flag .String ("http" , "" , "if set, use streamable HTTP at this address, instead of stdin/stdout" )
36+ enableAuth = flag .Bool ("enable_auth" , false , "if set, enable OAuth authorization" )
2937)
3038
31- const watchedResourceURI = "test://watched-resource"
39+ const (
40+ watchedResourceURI = "test://watched-resource"
41+
42+ adminScope = "admin"
43+ )
3244
3345func main () {
3446 flag .Parse ()
@@ -56,11 +68,29 @@ func main() {
5668
5769 // Serve over stdio, or streamable HTTP if -http is set.
5870 if * httpAddr != "" {
59- handler := mcp .NewStreamableHTTPHandler (func (* http.Request ) * mcp.Server {
71+ mux := http .NewServeMux ()
72+ var mcpHandler http.Handler = mcp .NewStreamableHTTPHandler (func (* http.Request ) * mcp.Server {
6073 return server
6174 }, nil )
62- log .Printf ("Conformance server listening at %s" , * httpAddr )
63- log .Fatal (http .ListenAndServe (* httpAddr , handler ))
75+
76+ if * enableAuth {
77+ authServerURL := os .Getenv ("MCP_CONFORMANCE_AUTH_SERVER_URL" )
78+ if authServerURL == "" {
79+ log .Fatal ("MCP_CONFORMANCE_AUTH_SERVER_URL environment variable must be set when --enable-auth is true" )
80+ }
81+
82+ handlePRM (mux , authServerURL )
83+
84+ var err error
85+ mcpHandler , err = addAuthMiddleware (mcpHandler , authServerURL )
86+ if err != nil {
87+ log .Fatalf ("auth middleware: %v" , err )
88+ }
89+ }
90+
91+ mux .Handle ("/" , mcpHandler )
92+ log .Printf ("Conformance server listening at http://%s/mcp" , * httpAddr )
93+ log .Fatal (http .ListenAndServe (* httpAddr , mux ))
6494 } else {
6595 t := & mcp.StdioTransport {}
6696 if err := server .Run (ctx , t ); err != nil {
@@ -722,6 +752,96 @@ func promptWithImageHandler(ctx context.Context, req *mcp.GetPromptRequest) (*mc
722752 }, nil
723753}
724754
755+ // =============================================================================
756+ // Middleware
757+ // =============================================================================
758+
759+ func handlePRM (mux * http.ServeMux , authServerURL string ) {
760+ // Host the resource metadata document.
761+ resourceMetadata := & oauthex.ProtectedResourceMetadata {
762+ Resource : "http://" + * httpAddr ,
763+ AuthorizationServers : []string {authServerURL },
764+ ScopesSupported : []string {adminScope },
765+ }
766+ mux .Handle ("/.well-known/oauth-protected-resource" , auth .ProtectedResourceMetadataHandler (resourceMetadata ))
767+ }
768+
769+ func addAuthMiddleware (handler http.Handler , authServerURL string ) (http.Handler , error ) {
770+
771+ log .Printf ("Fetching authorization server metadata from %s..." , authServerURL )
772+ metadata , err := oauthex .GetAuthServerMeta (context .Background (), authServerURL , http .DefaultClient )
773+ if err != nil {
774+ return nil , fmt .Errorf ("fetch auth server metadata: %v" , err )
775+ }
776+ if metadata .IntrospectionEndpoint == "" {
777+ return nil , fmt .Errorf ("auth server metadata does not contain introspection_endpoint" )
778+ }
779+ log .Printf ("Using introspection endpoint: %s" , metadata .IntrospectionEndpoint )
780+
781+ tokenVerifier := createIntrospectionVerifier (metadata .IntrospectionEndpoint )
782+ verifyAuth := auth .RequireBearerToken (tokenVerifier , & auth.RequireBearerTokenOptions {
783+ ResourceMetadataURL : fmt .Sprintf ("http://%s/.well-known/oauth-protected-resource" , * httpAddr ),
784+ })
785+
786+ return verifyAuth (handler ), nil
787+ }
788+
789+ func createIntrospectionVerifier (introspectionEndpoint string ) auth.TokenVerifier {
790+ return func (ctx context.Context , token string , req * http.Request ) (* auth.TokenInfo , error ) {
791+ data := url.Values {}
792+ data .Set ("token" , token )
793+
794+ req , err := http .NewRequestWithContext (ctx , "POST" , introspectionEndpoint , strings .NewReader (data .Encode ()))
795+ if err != nil {
796+ return nil , fmt .Errorf ("create introspection request: %v" , err )
797+ }
798+ req .Header .Set ("Content-Type" , "application/x-www-form-urlencoded" )
799+ req .Header .Set ("Accept" , "application/json" )
800+
801+ resp , err := http .DefaultClient .Do (req )
802+ if err != nil {
803+ return nil , fmt .Errorf ("introspection request failed: %v" , err )
804+ }
805+ defer resp .Body .Close ()
806+
807+ if resp .StatusCode != http .StatusOK {
808+ return nil , fmt .Errorf ("introspection returned status %d" , resp .StatusCode )
809+ }
810+
811+ var result struct {
812+ Active bool `json:"active"`
813+ Scope string `json:"scope"`
814+ Expiration int64 `json:"exp"`
815+ ClientID string `json:"client_id"`
816+ }
817+ if err := json .NewDecoder (resp .Body ).Decode (& result ); err != nil {
818+ return nil , fmt .Errorf ("decode introspection response: %v" , err )
819+ }
820+
821+ if ! result .Active {
822+ return nil , auth .ErrInvalidToken
823+ }
824+
825+ expiration := time.Time {}
826+ if result .Expiration != 0 {
827+ expiration = time .Unix (result .Expiration , 0 )
828+ }
829+
830+ var scopes []string
831+ if result .Scope != "" {
832+ scopes = strings .Split (result .Scope , " " )
833+ }
834+
835+ return & auth.TokenInfo {
836+ Scopes : scopes ,
837+ Expiration : expiration ,
838+ Extra : map [string ]any {
839+ "client_id" : result .ClientID ,
840+ },
841+ }, nil
842+ }
843+ }
844+
725845// =============================================================================
726846// Server handlers
727847// =============================================================================
0 commit comments