@@ -12,6 +12,7 @@ import { sew } from "@/middleware/sew";
1212import { apiHandler } from '@/lib/apiHandler' ;
1313import { env } from '@sourcebot/shared' ;
1414import { hasEntitlement } from '@/lib/entitlements' ;
15+ import { SOURCEBOT_MCP_OAUTH_SCOPE } from '@/ee/features/oauth/constants' ;
1516
1617// On 401, tell MCP clients where to find the OAuth protected resource metadata (RFC 9728)
1718// so they can discover the authorization server and initiate the authorization code flow.
@@ -20,16 +21,31 @@ import { hasEntitlement } from '@/lib/entitlements';
2021// @see : https://datatracker.ietf.org/doc/html/rfc9728
2122async function mcpErrorResponse ( error : ServiceError ) : Promise < Response > {
2223 const response = serviceErrorResponse ( error ) ;
23- if ( error . statusCode === StatusCodes . UNAUTHORIZED && await hasEntitlement ( 'oauth' ) ) {
24- const issuer = env . AUTH_URL . replace ( / \/ $ / , '' ) ;
25- response . headers . set (
26- 'WWW-Authenticate' ,
27- `Bearer realm="Sourcebot", resource_metadata_uri="${ issuer } /.well-known/oauth-protected-resource/api/mcp"`
28- ) ;
24+ if (
25+ ( error . statusCode === StatusCodes . UNAUTHORIZED || error . errorCode === ErrorCode . OAUTH_INSUFFICIENT_SCOPE ) &&
26+ await hasEntitlement ( 'oauth' )
27+ ) {
28+ response . headers . set ( 'WWW-Authenticate' , mcpBearerChallenge ( error ) ) ;
2929 }
3030 return response ;
3131}
3232
33+ function mcpBearerChallenge ( error : ServiceError ) : string {
34+ const issuer = env . AUTH_URL . replace ( / \/ $ / , '' ) ;
35+ const params = [
36+ 'realm="Sourcebot"' ,
37+ `resource_metadata_uri="${ issuer } /.well-known/oauth-protected-resource/api/mcp"` ,
38+ `scope="${ SOURCEBOT_MCP_OAUTH_SCOPE } "` ,
39+ ] ;
40+
41+ if ( error . errorCode === ErrorCode . OAUTH_INSUFFICIENT_SCOPE ) {
42+ params . push ( 'error="insufficient_scope"' ) ;
43+ params . push ( `error_description="${ error . message } "` ) ;
44+ }
45+
46+ return `Bearer ${ params . join ( ', ' ) } ` ;
47+ }
48+
3349// @see : https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#session-management
3450interface McpSession {
3551 server : McpServer ;
@@ -95,7 +111,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
95111 await mcpServer . connect ( transport ) ;
96112
97113 return transport . handleRequest ( request ) ;
98- } )
114+ } , { requiredOAuthScopes : [ SOURCEBOT_MCP_OAUTH_SCOPE ] } )
99115 ) ;
100116
101117 if ( isServiceError ( response ) ) {
@@ -139,7 +155,7 @@ export const DELETE = apiHandler(async (request: NextRequest) => {
139155 }
140156
141157 return session . transport . handleRequest ( request ) ;
142- } )
158+ } , { requiredOAuthScopes : [ SOURCEBOT_MCP_OAUTH_SCOPE ] } )
143159 ) ;
144160
145161 if ( isServiceError ( result ) ) {
0 commit comments