55using  System . Net ; 
66using  System . Text ; 
77using  System . Web ; 
8+ using  static  System . Runtime . InteropServices . JavaScript . JSType ; 
89
9- namespace  ProtectedMCPClient ; 
10+ Console . WriteLine ( "Protected MCP Weather Server" ) ; 
11+ Console . WriteLine ( ) ; 
1012
11- class  Program 
13+ var  serverUrl  =  "http://localhost:7071/sse" ; 
14+ var  clientId  =  Environment . GetEnvironmentVariable ( "CLIENT_ID" )  ??  throw  new  Exception ( "The CLIENT_ID environment variable is not set." ) ; 
15+ 
16+ // We can customize a shared HttpClient with a custom handler if desired 
17+ var  sharedHandler  =  new  SocketsHttpHandler 
18+ { 
19+     PooledConnectionLifetime  =  TimeSpan . FromMinutes ( 2 ) , 
20+     PooledConnectionIdleTimeout  =  TimeSpan . FromMinutes ( 1 ) 
21+ } ; 
22+ 
23+ var  httpClient  =  new  HttpClient ( sharedHandler ) ; 
24+ // Create the token provider with our custom HttpClient and authorization URL handler 
25+ var  tokenProvider  =  new  GenericOAuthProvider ( 
26+     new  Uri ( serverUrl ) , 
27+     httpClient , 
28+     null ,  // AuthorizationHelpers will be created automatically 
29+     clientId :  clientId , 
30+     clientSecret :  "" ,  // No secret needed for this client 
31+     redirectUri :  new  Uri ( "http://localhost:1179/callback" ) , 
32+     scopes :  [ $ "api://{ clientId } /weather.read"] , 
33+     logger :  null , 
34+     authorizationUrlHandler :  HandleAuthorizationUrlAsync 
35+ ) ; 
36+ 
37+ Console . WriteLine ( ) ; 
38+ Console . WriteLine ( $ "Connecting to weather server at { serverUrl } ...") ; 
39+ 
40+ try 
1241{ 
13-     static   async   Task   Main ( string [ ]   args ) 
42+     var   transportOptions   =   new   SseClientTransportOptions 
1443    { 
15-         Console . WriteLine ( "Protected MCP Weather Server" ) ; 
16-         Console . WriteLine ( ) ; 
44+         Endpoint  =  new  Uri ( serverUrl ) , 
45+         Name  =  "Secure Weather Client" 
46+     } ; 
1747
18-         var  serverUrl  =  "http://localhost:7071/sse" ; 
19-         var  clientId  =  Environment . GetEnvironmentVariable ( "CLIENT_ID" )  ??  throw  new  Exception ( "The CLIENT_ID environment variable is not set." ) ; 
48+     // Create a transport with authentication support using the correct constructor parameters 
49+     var  transport  =  new  SseClientTransport ( 
50+         transportOptions , 
51+         tokenProvider 
52+     ) ; 
2053
21-         // We can customize a shared HttpClient with a custom handler if desired 
22-         var  sharedHandler  =  new  SocketsHttpHandler 
23-         { 
24-             PooledConnectionLifetime  =  TimeSpan . FromMinutes ( 2 ) , 
25-             PooledConnectionIdleTimeout  =  TimeSpan . FromMinutes ( 1 ) 
26-         } ; 
54+     var  client  =  await  McpClientFactory . CreateAsync ( transport ) ; 
55+ 
56+     var  tools  =  await  client . ListToolsAsync ( ) ; 
57+     if  ( tools . Count  ==  0 ) 
58+     { 
59+         Console . WriteLine ( "No tools available on the server." ) ; 
60+         return ; 
61+     } 
62+ 
63+     Console . WriteLine ( $ "Found { tools . Count }  tools on the server.") ; 
64+     Console . WriteLine ( ) ; 
65+ 
66+     if  ( tools . Any ( t =>  t . Name  ==  "GetAlerts" ) ) 
67+     { 
68+         Console . WriteLine ( "Calling GetAlerts tool..." ) ; 
2769
28-         var  httpClient  =  new  HttpClient ( sharedHandler ) ; 
29-         // Create the token provider with our custom HttpClient and authorization URL handler 
30-         var  tokenProvider  =  new  GenericOAuthProvider ( 
31-             new  Uri ( serverUrl ) , 
32-             httpClient , 
33-             null ,  // AuthorizationHelpers will be created automatically 
34-             clientId :  clientId , 
35-             clientSecret :  "" ,  // No secret needed for this client 
36-             redirectUri :  new  Uri ( "http://localhost:1179/callback" ) , 
37-             scopes :  [ $ "api://{ clientId } /weather.read"] , 
38-             logger :  null , 
39-             authorizationUrlHandler :  HandleAuthorizationUrlAsync 
70+         var  result  =  await  client . CallToolAsync ( 
71+             "GetAlerts" , 
72+             new  Dictionary < string ,  object ? >  {  {  "state" ,  "WA"  }  } 
4073        ) ; 
4174
75+         Console . WriteLine ( "Result: "  +  ( ( TextContentBlock ) result . Content [ 0 ] ) . Text ) ; 
4276        Console . WriteLine ( ) ; 
43-         Console . WriteLine ( $ "Connecting to weather server at { serverUrl } ...") ; 
44- 
45-         try 
46-         { 
47-             var  transportOptions  =  new  SseClientTransportOptions 
48-             { 
49-                 Endpoint  =  new  Uri ( serverUrl ) , 
50-                 Name  =  "Secure Weather Client" 
51-             } ; 
52- 
53-             // Create a transport with authentication support using the correct constructor parameters 
54-             var  transport  =  new  SseClientTransport ( 
55-                 transportOptions , 
56-                 tokenProvider 
57-             ) ; 
58- 
59-             var  client  =  await  McpClientFactory . CreateAsync ( transport ) ; 
60- 
61-             var  tools  =  await  client . ListToolsAsync ( ) ; 
62-             if  ( tools . Count  ==  0 ) 
63-             { 
64-                 Console . WriteLine ( "No tools available on the server." ) ; 
65-                 return ; 
66-             } 
67- 
68-             Console . WriteLine ( $ "Found { tools . Count }  tools on the server.") ; 
69-             Console . WriteLine ( ) ; 
70- 
71-             if  ( tools . Any ( t =>  t . Name  ==  "GetAlerts" ) ) 
72-             { 
73-                 Console . WriteLine ( "Calling GetAlerts tool..." ) ; 
74- 
75-                 var  result  =  await  client . CallToolAsync ( 
76-                     "GetAlerts" , 
77-                     new  Dictionary < string ,  object ? >  {  {  "state" ,  "WA"  }  } 
78-                 ) ; 
79- 
80-                 Console . WriteLine ( "Result: "  +  ( ( TextContentBlock ) result . Content [ 0 ] ) . Text ) ; 
81-                 Console . WriteLine ( ) ; 
82-             } 
83-         } 
84-         catch  ( Exception  ex ) 
85-         { 
86-             Console . WriteLine ( $ "Error: { ex . Message } ") ; 
87-             if  ( ex . InnerException  !=  null ) 
88-             { 
89-                 Console . WriteLine ( $ "Inner error: { ex . InnerException . Message } ") ; 
90-             } 
77+     } 
78+ } 
79+ catch  ( Exception  ex ) 
80+ { 
81+     Console . WriteLine ( $ "Error: { ex . Message } ") ; 
82+     if  ( ex . InnerException  !=  null ) 
83+     { 
84+         Console . WriteLine ( $ "Inner error: { ex . InnerException . Message } ") ; 
85+     } 
9186
9287#if DEBUG 
93-              Console . WriteLine ( $ "Stack trace: { ex . StackTrace } ") ; 
88+     Console . WriteLine ( $ "Stack trace: { ex . StackTrace } ") ; 
9489#endif
95-         } 
96-         Console . WriteLine ( "Press any key to exit..." ) ; 
97-         Console . ReadKey ( ) ; 
98-     } 
90+ } 
91+ Console . WriteLine ( "Press any key to exit..." ) ; 
92+ Console . ReadKey ( ) ; 
93+ 
94+ /// <summary> 
95+ /// Handles the OAuth authorization URL by starting a local HTTP server and opening a browser. 
96+ /// This implementation demonstrates how SDK consumers can provide their own authorization flow. 
97+ /// </summary> 
98+ /// <param name="authorizationUrl">The authorization URL to open in the browser.</param> 
99+ /// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param> 
100+ /// <param name="cancellationToken">The cancellation token.</param> 
101+ /// <returns>The authorization code extracted from the callback, or null if the operation failed.</returns> 
102+ static   async  Task < string ? >  HandleAuthorizationUrlAsync ( Uri  authorizationUrl ,  Uri  redirectUri ,  CancellationToken  cancellationToken ) 
103+ { 
104+     Console . WriteLine ( "Starting OAuth authorization flow..." ) ; 
105+     Console . WriteLine ( $ "Opening browser to: { authorizationUrl } ") ; 
106+ 
107+     var  listenerPrefix  =  redirectUri . GetLeftPart ( UriPartial . Authority ) ; 
108+     if  ( ! listenerPrefix . EndsWith ( "/" ) )  listenerPrefix  +=  "/" ; 
109+ 
110+     using  var  listener  =  new  HttpListener ( ) ; 
111+     listener . Prefixes . Add ( listenerPrefix ) ; 
99112
100-     /// <summary> 
101-     /// Handles the OAuth authorization URL by starting a local HTTP server and opening a browser. 
102-     /// This implementation demonstrates how SDK consumers can provide their own authorization flow. 
103-     /// </summary> 
104-     /// <param name="authorizationUrl">The authorization URL to open in the browser.</param> 
105-     /// <param name="redirectUri">The redirect URI where the authorization code will be sent.</param> 
106-     /// <param name="cancellationToken">The cancellation token.</param> 
107-     /// <returns>The authorization code extracted from the callback, or null if the operation failed.</returns> 
108-     private  static   async  Task < string ? >  HandleAuthorizationUrlAsync ( Uri  authorizationUrl ,  Uri  redirectUri ,  CancellationToken  cancellationToken ) 
113+     try 
109114    { 
110-         Console . WriteLine ( "Starting OAuth authorization flow..." ) ; 
111-         Console . WriteLine ( $ "Opening browser to : { authorizationUrl } ") ; 
115+         listener . Start ( ) ; 
116+         Console . WriteLine ( $ "Listening for OAuth callback on : { listenerPrefix } ") ; 
112117
113-         var  listenerPrefix  =  redirectUri . GetLeftPart ( UriPartial . Authority ) ; 
114-         if  ( ! listenerPrefix . EndsWith ( "/" ) )  listenerPrefix  +=  "/" ; 
118+         OpenBrowser ( authorizationUrl ) ; 
115119
116-         using  var  listener  =  new  HttpListener ( ) ; 
117-         listener . Prefixes . Add ( listenerPrefix ) ; 
120+         var  context  =  await  listener . GetContextAsync ( ) ; 
121+         var  query  =  HttpUtility . ParseQueryString ( context . Request . Url ? . Query  ??  string . Empty ) ; 
122+         var  code  =  query [ "code" ] ; 
123+         var  error  =  query [ "error" ] ; 
118124
119-         try 
120-         { 
121-             listener . Start ( ) ; 
122-             Console . WriteLine ( $ "Listening for OAuth callback on: { listenerPrefix } ") ; 
123- 
124-             OpenBrowser ( authorizationUrl ) ; 
125- 
126-             var  context  =  await  listener . GetContextAsync ( ) ; 
127-             var  query  =  HttpUtility . ParseQueryString ( context . Request . Url ? . Query  ??  string . Empty ) ; 
128-             var  code  =  query [ "code" ] ; 
129-             var  error  =  query [ "error" ] ; 
130- 
131-             string  responseHtml  =  "<html><body><h1>Authentication complete</h1><p>You can close this window now.</p></body></html>" ; 
132-             byte [ ]  buffer  =  Encoding . UTF8 . GetBytes ( responseHtml ) ; 
133-             context . Response . ContentLength64  =  buffer . Length ; 
134-             context . Response . ContentType  =  "text/html" ; 
135-             context . Response . OutputStream . Write ( buffer ,  0 ,  buffer . Length ) ; 
136-             context . Response . Close ( ) ; 
137- 
138-             if  ( ! string . IsNullOrEmpty ( error ) ) 
139-             { 
140-                 Console . WriteLine ( $ "Auth error: { error } ") ; 
141-                 return  null ; 
142-             } 
143- 
144-             if  ( string . IsNullOrEmpty ( code ) ) 
145-             { 
146-                 Console . WriteLine ( "No authorization code received" ) ; 
147-                 return  null ; 
148-             } 
149- 
150-             Console . WriteLine ( "Authorization code received successfully." ) ; 
151-             return  code ; 
152-         } 
153-         catch  ( Exception  ex ) 
125+         string  responseHtml  =  "<html><body><h1>Authentication complete</h1><p>You can close this window now.</p></body></html>" ; 
126+         byte [ ]  buffer  =  Encoding . UTF8 . GetBytes ( responseHtml ) ; 
127+         context . Response . ContentLength64  =  buffer . Length ; 
128+         context . Response . ContentType  =  "text/html" ; 
129+         context . Response . OutputStream . Write ( buffer ,  0 ,  buffer . Length ) ; 
130+         context . Response . Close ( ) ; 
131+ 
132+         if  ( ! string . IsNullOrEmpty ( error ) ) 
154133        { 
155-             Console . WriteLine ( $ "Error getting auth code : { ex . Message } ") ; 
134+             Console . WriteLine ( $ "Auth error : { error } ") ; 
156135            return  null ; 
157136        } 
158-         finally 
137+ 
138+         if  ( string . IsNullOrEmpty ( code ) ) 
159139        { 
160-             if  ( listener . IsListening )  listener . Stop ( ) ; 
140+             Console . WriteLine ( "No authorization code received" ) ; 
141+             return  null ; 
161142        } 
143+ 
144+         Console . WriteLine ( "Authorization code received successfully." ) ; 
145+         return  code ; 
146+     } 
147+     catch  ( Exception  ex ) 
148+     { 
149+         Console . WriteLine ( $ "Error getting auth code: { ex . Message } ") ; 
150+         return  null ; 
151+     } 
152+     finally 
153+     { 
154+         if  ( listener . IsListening )  listener . Stop ( ) ; 
162155    } 
156+ } 
163157
164-     /// <summary> 
165-     /// Opens the specified URL in the default browser. 
166-     /// </summary> 
167-     /// <param name="url">The URL to open.</param> 
168-     private  static   void  OpenBrowser ( Uri  url ) 
158+ /// <summary> 
159+ /// Opens the specified URL in the default browser. 
160+ /// </summary> 
161+ /// <param name="url">The URL to open.</param> 
162+ static   void  OpenBrowser ( Uri  url ) 
163+ { 
164+     try 
169165    { 
170-         try 
171-         { 
172-             var  psi  =  new  ProcessStartInfo 
173-             { 
174-                 FileName  =  url . ToString ( ) , 
175-                 UseShellExecute  =  true 
176-             } ; 
177-             Process . Start ( psi ) ; 
178-         } 
179-         catch  ( Exception  ex ) 
166+         var  psi  =  new  ProcessStartInfo 
180167        { 
181-             Console . WriteLine ( $ "Error opening browser. { ex . Message } ") ; 
182-         } 
168+             FileName  =  url . ToString ( ) , 
169+             UseShellExecute  =  true 
170+         } ; 
171+         Process . Start ( psi ) ; 
172+     } 
173+     catch  ( Exception  ex ) 
174+     { 
175+         Console . WriteLine ( $ "Error opening browser. { ex . Message } ") ; 
183176    } 
184177} 
0 commit comments