@@ -787,6 +787,213 @@ app.get("/config", originValidationMiddleware, authMiddleware, (req, res) => {
787787 }
788788} ) ;
789789
790+ // OAuth Proxy Endpoints - for routing OAuth requests through the proxy to avoid CORS issues
791+ app . use ( express . json ( ) ) ; // Ensure JSON body parsing is enabled
792+
793+ /**
794+ * Proxy endpoint for OAuth Authorization Server Metadata Discovery
795+ * GET /oauth/metadata?authServerUrl=<url>
796+ */
797+ app . get (
798+ "/oauth/metadata" ,
799+ originValidationMiddleware ,
800+ authMiddleware ,
801+ async ( req , res ) => {
802+ try {
803+ const authServerUrl = req . query . authServerUrl as string ;
804+ if ( ! authServerUrl ) {
805+ res
806+ . status ( 400 )
807+ . json ( { error : "authServerUrl query parameter is required" } ) ;
808+ return ;
809+ }
810+
811+ console . log ( `OAuth metadata discovery for: ${ authServerUrl } ` ) ;
812+
813+ // Append the well-known path to the authServerUrl
814+ // Remove trailing slash if present, then append the well-known path
815+ const baseUrl = authServerUrl . endsWith ( "/" )
816+ ? authServerUrl . slice ( 0 , - 1 )
817+ : authServerUrl ;
818+ const metadataUrl = `${ baseUrl } /.well-known/oauth-authorization-server` ;
819+ console . log ( `Fetching metadata from: ${ metadataUrl } ` ) ;
820+
821+ const response = await fetch ( metadataUrl ) ;
822+
823+ if ( ! response . ok ) {
824+ const errorText = await response . text ( ) ;
825+ res . status ( response . status ) . json ( {
826+ error : `Failed to fetch OAuth metadata: ${ response . statusText } ` ,
827+ details : errorText ,
828+ } ) ;
829+ return ;
830+ }
831+
832+ const metadata = await response . json ( ) ;
833+ res . json ( metadata ) ;
834+ } catch ( error ) {
835+ console . error ( "Error in /oauth/metadata route:" , error ) ;
836+ res . status ( 500 ) . json ( {
837+ error : error instanceof Error ? error . message : String ( error ) ,
838+ } ) ;
839+ }
840+ } ,
841+ ) ;
842+
843+ /**
844+ * Proxy endpoint for OAuth Protected Resource Metadata Discovery
845+ * GET /oauth/resource-metadata?serverUrl=<url>
846+ */
847+ app . get (
848+ "/oauth/resource-metadata" ,
849+ originValidationMiddleware ,
850+ authMiddleware ,
851+ async ( req , res ) => {
852+ try {
853+ const serverUrl = req . query . serverUrl as string ;
854+ if ( ! serverUrl ) {
855+ res
856+ . status ( 400 )
857+ . json ( { error : "serverUrl query parameter is required" } ) ;
858+ return ;
859+ }
860+
861+ console . log ( `OAuth resource metadata discovery for: ${ serverUrl } ` ) ;
862+
863+ // For resource metadata, use the origin (protocol + host + port) only
864+ // This is per RFC 8414 - resource metadata is at the origin root
865+ const url = new URL ( serverUrl ) ;
866+ const metadataUrl = `${ url . origin } /.well-known/oauth-protected-resource` ;
867+ console . log ( `Fetching resource metadata from: ${ metadataUrl } ` ) ;
868+
869+ const response = await fetch ( metadataUrl ) ;
870+
871+ if ( ! response . ok ) {
872+ const errorText = await response . text ( ) ;
873+ res . status ( response . status ) . json ( {
874+ error : `Failed to fetch resource metadata: ${ response . statusText } ` ,
875+ details : errorText ,
876+ } ) ;
877+ return ;
878+ }
879+
880+ const metadata = await response . json ( ) ;
881+ res . json ( metadata ) ;
882+ } catch ( error ) {
883+ console . error ( "Error in /oauth/resource-metadata route:" , error ) ;
884+ res . status ( 500 ) . json ( {
885+ error : error instanceof Error ? error . message : String ( error ) ,
886+ } ) ;
887+ }
888+ } ,
889+ ) ;
890+
891+ /**
892+ * Proxy endpoint for OAuth Dynamic Client Registration (DCR)
893+ * POST /oauth/register
894+ * Body: { registrationEndpoint: string, clientMetadata: object }
895+ */
896+ app . post (
897+ "/oauth/register" ,
898+ originValidationMiddleware ,
899+ authMiddleware ,
900+ async ( req , res ) => {
901+ try {
902+ const { registrationEndpoint, clientMetadata } = req . body ;
903+
904+ if ( ! registrationEndpoint || ! clientMetadata ) {
905+ res . status ( 400 ) . json ( {
906+ error :
907+ "registrationEndpoint and clientMetadata are required in request body" ,
908+ } ) ;
909+ return ;
910+ }
911+
912+ console . log ( `OAuth client registration at: ${ registrationEndpoint } ` ) ;
913+
914+ const response = await fetch ( registrationEndpoint , {
915+ method : "POST" ,
916+ headers : {
917+ "Content-Type" : "application/json" ,
918+ Accept : "application/json" ,
919+ } ,
920+ body : JSON . stringify ( clientMetadata ) ,
921+ } ) ;
922+
923+ if ( ! response . ok ) {
924+ const errorText = await response . text ( ) ;
925+ res . status ( response . status ) . json ( {
926+ error : `Failed to register client: ${ response . statusText } ` ,
927+ details : errorText ,
928+ } ) ;
929+ return ;
930+ }
931+
932+ const clientInformation = await response . json ( ) ;
933+ res . json ( clientInformation ) ;
934+ } catch ( error ) {
935+ console . error ( "Error in /oauth/register route:" , error ) ;
936+ res . status ( 500 ) . json ( {
937+ error : error instanceof Error ? error . message : String ( error ) ,
938+ } ) ;
939+ }
940+ } ,
941+ ) ;
942+
943+ /**
944+ * Proxy endpoint for OAuth Token Exchange
945+ * POST /oauth/token
946+ * Body: { tokenEndpoint: string, params: object }
947+ */
948+ app . post (
949+ "/oauth/token" ,
950+ originValidationMiddleware ,
951+ authMiddleware ,
952+ async ( req , res ) => {
953+ try {
954+ const { tokenEndpoint, params } = req . body ;
955+
956+ if ( ! tokenEndpoint || ! params ) {
957+ res . status ( 400 ) . json ( {
958+ error : "tokenEndpoint and params are required in request body" ,
959+ } ) ;
960+ return ;
961+ }
962+
963+ console . log ( `OAuth token exchange at: ${ tokenEndpoint } ` ) ;
964+
965+ // Convert params object to URLSearchParams for form encoding
966+ const formBody = new URLSearchParams ( params as Record < string , string > ) ;
967+
968+ const response = await fetch ( tokenEndpoint , {
969+ method : "POST" ,
970+ headers : {
971+ "Content-Type" : "application/x-www-form-urlencoded" ,
972+ Accept : "application/json" ,
973+ } ,
974+ body : formBody . toString ( ) ,
975+ } ) ;
976+
977+ if ( ! response . ok ) {
978+ const errorText = await response . text ( ) ;
979+ res . status ( response . status ) . json ( {
980+ error : `Failed to exchange token: ${ response . statusText } ` ,
981+ details : errorText ,
982+ } ) ;
983+ return ;
984+ }
985+
986+ const tokens = await response . json ( ) ;
987+ res . json ( tokens ) ;
988+ } catch ( error ) {
989+ console . error ( "Error in /oauth/token route:" , error ) ;
990+ res . status ( 500 ) . json ( {
991+ error : error instanceof Error ? error . message : String ( error ) ,
992+ } ) ;
993+ }
994+ } ,
995+ ) ;
996+
790997const PORT = parseInt (
791998 process . env . SERVER_PORT || DEFAULT_MCP_PROXY_LISTEN_PORT ,
792999 10 ,
0 commit comments