11using Microsoft . AspNetCore . Authentication ;
22using Microsoft . Extensions . Options ;
33using ModelContextProtocol . AspNetCore . Auth ;
4- using ModelContextProtocol . Protocol . Types ;
4+ using ProtectedMCPServer . Tools ;
5+ using System . Net . Http . Headers ;
56using System . Security . Claims ;
67using System . Text . Encodings . Web ;
78
89var builder = WebApplication . CreateBuilder ( args ) ;
910
1011// Configure authentication to use MCP for challenges
11- builder . Services . AddAuthentication ( options =>
12+ builder . Services . AddAuthentication ( options =>
1213{
1314 options . DefaultScheme = "Bearer" ;
1415 options . DefaultChallengeScheme = McpAuthenticationDefaults . AuthenticationScheme ; // Use MCP for challenges
1516} )
1617. AddScheme < AuthenticationSchemeOptions , SimpleAuthHandler > ( "Bearer" , options => { } )
17- . AddMcp ( options => {
18+ . AddMcp ( options =>
19+ {
1820 options . ResourceMetadata . AuthorizationServers . Add ( new Uri ( "https://login.microsoftonline.com/a2213e1c-e51e-4304-9a0d-effe57f31655/v2.0" ) ) ;
1921 options . ResourceMetadata . BearerMethodsSupported . Add ( "header" ) ;
2022 options . ResourceMetadata . ScopesSupported . AddRange ( [ "weather.read" , "weather.write" ] ) ;
3436builder . Services . AddSingleton < ResourceMetadataService > ( ) ;
3537
3638// Configure MCP Server
37- builder . Services . AddMcpServer ( options =>
38- {
39- options . ServerInstructions = "This is an MCP server with OAuth authorization enabled." ;
40-
41- // Configure regular server capabilities like tools, prompts, resources
42- options . Capabilities = new ( )
43- {
44- Tools = new ( )
45- {
46- // Simple Echo tool
47- CallToolHandler = ( request , cancellationToken ) =>
48- {
49- if ( request . Params ? . Name == "echo" )
50- {
51- if ( request . Params . Arguments ? . TryGetValue ( "message" , out var message ) is not true )
52- {
53- throw new Exception ( "It happens." ) ;
54- }
55-
56- return new ValueTask < CallToolResponse > ( new CallToolResponse ( )
57- {
58- Content = [ new Content ( ) { Text = $ "Echo: { message } ", Type = "text" } ]
59- } ) ;
60- }
61-
62- // Protected tool that requires authorization
63- if ( request . Params ? . Name == "protected-data" )
64- {
65- // This tool will only be accessible to authenticated clients
66- return new ValueTask < CallToolResponse > ( new CallToolResponse ( )
67- {
68- Content = [ new Content ( ) { Text = "This is protected data that only authorized clients can access" } ]
69- } ) ;
70- }
71-
72- throw new Exception ( "It happens." ) ;
73- } ,
74-
75- ListToolsHandler = async ( _ , _ ) => new ( )
76- {
77- Tools =
78- [
79- new ( )
80- {
81- Name = "echo" ,
82- Description = "Echoes back the message you send"
83- } ,
84- new ( )
85- {
86- Name = "protected-data" ,
87- Description = "Returns protected data that requires authorization"
88- }
89- ]
90- }
91- }
92- } ;
93- } )
39+ builder . Services . AddMcpServer ( )
40+ . WithTools < WeatherTools > ( )
9441. WithHttpTransport ( ) ;
9542
43+ builder . Services . AddSingleton ( _ =>
44+ {
45+ var client = new HttpClient ( ) { BaseAddress = new Uri ( "https://api.weather.gov" ) } ;
46+ client . DefaultRequestHeaders . UserAgent . Add ( new ProductInfoHeaderValue ( "weather-tool" , "1.0" ) ) ;
47+ return client ;
48+ } ) ;
49+
9650var app = builder . Build ( ) ;
9751
9852app . UseAuthentication ( ) ;
@@ -122,7 +76,7 @@ class SimpleAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
12276 public SimpleAuthHandler (
12377 IOptionsMonitor < AuthenticationSchemeOptions > options ,
12478 ILoggerFactory logger ,
125- UrlEncoder encoder )
79+ UrlEncoder encoder )
12680 : base ( options , logger , encoder )
12781 {
12882 }
@@ -134,37 +88,37 @@ protected override Task<AuthenticateResult> HandleAuthenticateAsync()
13488 {
13589 return Task . FromResult ( AuthenticateResult . Fail ( "Authorization header missing" ) ) ;
13690 }
137-
91+
13892 // Parse the token
13993 var headerValue = authHeader . ToString ( ) ;
14094 if ( ! headerValue . StartsWith ( "Bearer " , StringComparison . OrdinalIgnoreCase ) )
14195 {
14296 return Task . FromResult ( AuthenticateResult . Fail ( "Bearer token missing" ) ) ;
14397 }
144-
98+
14599 var token = headerValue [ "Bearer " . Length ..] . Trim ( ) ;
146-
100+
147101 // Accept any non-empty token for testing purposes
148102 if ( string . IsNullOrEmpty ( token ) )
149103 {
150104 return Task . FromResult ( AuthenticateResult . Fail ( "Token cannot be empty" ) ) ;
151105 }
152-
106+
153107 // Log the received token for debugging
154108 Console . WriteLine ( $ "Received and accepted token: { token } ") ;
155-
109+
156110 // Create a claims identity with required claims
157111 var claims = new [ ]
158112 {
159113 new Claim ( ClaimTypes . Name , "demo_user" ) ,
160114 new Claim ( ClaimTypes . NameIdentifier , "user123" ) ,
161115 new Claim ( "scope" , "weather.read" )
162116 } ;
163-
117+
164118 var identity = new ClaimsIdentity ( claims , "Bearer" ) ;
165119 var principal = new ClaimsPrincipal ( identity ) ;
166120 var ticket = new AuthenticationTicket ( principal , "Bearer" ) ;
167-
121+
168122 return Task . FromResult ( AuthenticateResult . Success ( ticket ) ) ;
169123 }
170124}
0 commit comments