1
1
using Aspire . Hosting . ApplicationModel ;
2
+ using Microsoft . Extensions . DependencyInjection ;
2
3
3
4
namespace Aspire . Hosting ;
4
5
@@ -15,16 +16,17 @@ public static class McpInspectorResourceBuilderExtensions
15
16
/// <param name="clientPort">The port for the client application. Defaults to 6274.</param>
16
17
/// <param name="serverPort">The port for the server proxy application. Defaults to 6277.</param>
17
18
/// <param name="inspectorVersion">The version of the Inspector app to use</param>
18
- public static IResourceBuilder < McpInspectorResource > AddMcpInspector ( this IDistributedApplicationBuilder builder , [ ResourceName ] string name , int clientPort = 6274 , int serverPort = 6277 , string inspectorVersion = McpInspectorResource . InspectorVersion )
19
+ /// <param name="proxyToken">The parameter used to provide the proxy authentication token for the MCP Inspector resource. If <see langword="null"/> a random token will be generated.</param>
20
+ public static IResourceBuilder < McpInspectorResource > AddMcpInspector ( this IDistributedApplicationBuilder builder , [ ResourceName ] string name , int clientPort = 6274 , int serverPort = 6277 , string inspectorVersion = McpInspectorResource . InspectorVersion , IResourceBuilder < ParameterResource > ? proxyToken = null )
19
21
{
22
+ var proxyTokenParameter = proxyToken ? . Resource ?? ParameterResourceBuilderExtensions . CreateDefaultPasswordParameter ( builder , $ "{ name } -proxyToken") ;
23
+
20
24
var resource = builder . AddResource ( new McpInspectorResource ( name ) )
21
25
. WithArgs ( [ "-y" , $ "@modelcontextprotocol/inspector@{ inspectorVersion } "] )
22
26
. ExcludeFromManifest ( )
23
27
. WithHttpEndpoint ( isProxied : false , port : clientPort , env : "CLIENT_PORT" , name : McpInspectorResource . ClientEndpointName )
24
28
. WithHttpEndpoint ( isProxied : false , port : serverPort , env : "SERVER_PORT" , name : McpInspectorResource . ServerProxyEndpointName )
25
- . WithEnvironment ( "DANGEROUSLY_OMIT_AUTH" , "true" )
26
29
. WithHttpHealthCheck ( "/" , endpointName : McpInspectorResource . ClientEndpointName )
27
- . WithHttpHealthCheck ( "/config" , endpointName : McpInspectorResource . ServerProxyEndpointName )
28
30
. WithUrlForEndpoint ( McpInspectorResource . ClientEndpointName , annotation =>
29
31
{
30
32
annotation . DisplayText = "Client" ;
@@ -34,8 +36,46 @@ public static IResourceBuilder<McpInspectorResource> AddMcpInspector(this IDistr
34
36
{
35
37
annotation . DisplayText = "Server Proxy" ;
36
38
annotation . DisplayOrder = 1 ;
39
+ annotation . DisplayLocation = UrlDisplayLocation . DetailsOnly ;
40
+ } )
41
+ . WithUrls ( async context =>
42
+ {
43
+ var token = await proxyTokenParameter . GetValueAsync ( CancellationToken . None ) ;
44
+
45
+ foreach ( var url in context . Urls )
46
+ {
47
+ if ( url . Endpoint is not null )
48
+ {
49
+ var uriBuilder = new UriBuilder ( url . Url ) ;
50
+ uriBuilder . Query = $ "MCP_PROXY_AUTH_TOKEN={ Uri . EscapeDataString ( token ! ) } ";
51
+ url . Url = uriBuilder . ToString ( ) ;
52
+ }
53
+ }
37
54
} ) ;
38
55
56
+ resource . Resource . ProxyTokenParameter = proxyTokenParameter ;
57
+
58
+ // Add authenticated health check for server proxy /config endpoint
59
+ var healthCheckKey = $ "{ name } _proxy_config_check";
60
+ builder . Services . AddHealthChecks ( ) . AddUrlGroup ( options =>
61
+ {
62
+ var serverProxyEndpoint = resource . GetEndpoint ( McpInspectorResource . ServerProxyEndpointName ) ;
63
+ var uri = serverProxyEndpoint . Url ;
64
+ if ( uri is null )
65
+ {
66
+ throw new DistributedApplicationException ( "The MCP Inspector 'server-proxy' endpoint URL is not set. Ensure that the resource has been allocated before the health check is executed." ) ;
67
+ }
68
+
69
+ var healthCheckUri = new Uri ( new Uri ( uri ) , "/config" ) ;
70
+ options . AddUri ( healthCheckUri , async setup =>
71
+ {
72
+ var token = await proxyTokenParameter . GetValueAsync ( CancellationToken . None ) ;
73
+ setup . AddCustomHeader ( "X-MCP-Proxy-Auth" , $ "Bearer { token } ") ;
74
+ } ) ;
75
+ } , healthCheckKey ) ;
76
+
77
+ resource . WithHealthCheck ( healthCheckKey ) ;
78
+
39
79
builder . Eventing . Subscribe < BeforeResourceStartedEvent > ( resource . Resource , async ( @event , ct ) =>
40
80
{
41
81
if ( @event . Resource is not McpInspectorResource inspectorResource )
@@ -80,6 +120,7 @@ public static IResourceBuilder<McpInspectorResource> AddMcpInspector(this IDistr
80
120
ctx . EnvironmentVariables [ "MCP_PROXY_FULL_ADDRESS" ] = serverProxyEndpoint . Url ;
81
121
ctx . EnvironmentVariables [ "CLIENT_PORT" ] = clientEndpoint . TargetPort ? . ToString ( ) ?? throw new InvalidOperationException ( "The MCP Inspector 'client' endpoint must have a target port defined." ) ;
82
122
ctx . EnvironmentVariables [ "SERVER_PORT" ] = serverProxyEndpoint . TargetPort ? . ToString ( ) ?? throw new InvalidOperationException ( "The MCP Inspector 'server-proxy' endpoint must have a target port defined." ) ;
123
+ ctx . EnvironmentVariables [ "MCP_PROXY_AUTH_TOKEN" ] = proxyTokenParameter ;
83
124
} )
84
125
. WithArgs ( ctx =>
85
126
{
@@ -90,7 +131,6 @@ public static IResourceBuilder<McpInspectorResource> AddMcpInspector(this IDistr
90
131
throw new InvalidOperationException ( "No default MCP server has been configured for the MCP Inspector resource, yet servers have been provided." ) ;
91
132
}
92
133
93
-
94
134
if ( defaultMcpServer is null && inspectorResource . McpServers . Count == 0 )
95
135
{
96
136
return ;
0 commit comments