From 1e4a0202ff6f9e4a72ea05ce5070f65b1d194478 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 28 Aug 2025 02:02:25 +0000 Subject: [PATCH] Loading the config file to MCP inspector so you can inspect right away --- .../Program.cs | 2 +- .../McpInspectorResource.cs | 7 ++-- .../McpInspectorResourceBuilderExtensions.cs | 33 ++++++++++++++++--- .../McpServerMetadata.cs | 3 +- ...InspectorResourceBuilderExtensionsTests.cs | 27 +++++++++++++++ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/examples/mcp-inspector/CommunityToolkit.Aspire.Hosting.McpInspector.McpServer/Program.cs b/examples/mcp-inspector/CommunityToolkit.Aspire.Hosting.McpInspector.McpServer/Program.cs index b69940b63..bf3eb2713 100644 --- a/examples/mcp-inspector/CommunityToolkit.Aspire.Hosting.McpInspector.McpServer/Program.cs +++ b/examples/mcp-inspector/CommunityToolkit.Aspire.Hosting.McpInspector.McpServer/Program.cs @@ -10,7 +10,7 @@ var app = builder.Build(); -app.MapMcp(); +app.MapMcp("/mcp"); app.Run(); diff --git a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResource.cs b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResource.cs index 16d99a33d..89fc6bf94 100644 --- a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResource.cs @@ -37,7 +37,7 @@ public class McpInspectorResource(string name) : ExecutableResource(name, "npx", /// /// Gets the version of the MCP Inspector. /// - public const string InspectorVersion = "0.16.2"; + public const string InspectorVersion = "0.16.5"; private readonly List _mcpServers = []; @@ -58,7 +58,7 @@ public class McpInspectorResource(string name) : ExecutableResource(name, "npx", /// public ParameterResource ProxyTokenParameter { get; set; } = default!; - internal void AddMcpServer(IResourceWithEndpoints mcpServer, bool isDefault, McpTransportType transportType) + internal void AddMcpServer(IResourceWithEndpoints mcpServer, bool isDefault, McpTransportType transportType, string path) { if (_mcpServers.Any(s => s.Name == mcpServer.Name)) { @@ -68,7 +68,8 @@ internal void AddMcpServer(IResourceWithEndpoints mcpServer, bool isDefault, Mcp McpServerMetadata item = new( mcpServer.Name, mcpServer.GetEndpoint("http") ?? throw new InvalidOperationException($"The MCP server {mcpServer.Name} must have a 'http' endpoint defined."), - transportType); + transportType, + path); _mcpServers.Add(item); diff --git a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs index 1ab5405c6..e8fcad33d 100644 --- a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs @@ -70,7 +70,7 @@ public static IResourceBuilder AddMcpInspector(this IDistr var servers = inspectorResource.McpServers.ToDictionary(s => s.Name, s => new { - transport = s.TransportType switch + type = s.TransportType switch { McpTransportType.StreamableHttp => "streamable-http", #pragma warning disable CS0618 @@ -78,7 +78,7 @@ public static IResourceBuilder AddMcpInspector(this IDistr #pragma warning restore CS0618 _ => throw new NotSupportedException($"The transport type {s.TransportType} is not supported.") }, - endpoint = s.Endpoint.Url + url = Combine(s.Endpoint.Url, s.Path), }); var config = new { mcpServers = servers }; @@ -183,18 +183,20 @@ public static IResourceBuilder AddMcpInspector(this IDistr /// The for the MCP server resource. /// Indicates whether this MCP server should be considered the default server for the MCP Inspector. /// The transport type to use for the MCP server. Defaults to . + /// The path to use for MCP communication. Defaults to "/mcp". /// A reference to the for further configuration. public static IResourceBuilder WithMcpServer( this IResourceBuilder builder, IResourceBuilder mcpServer, bool isDefault = true, - McpTransportType transportType = McpTransportType.StreamableHttp) + McpTransportType transportType = McpTransportType.StreamableHttp, + string path = "/mcp") where TResource : IResourceWithEndpoints { ArgumentNullException.ThrowIfNull(mcpServer); ArgumentNullException.ThrowIfNull(builder); - builder.Resource.AddMcpServer(mcpServer.Resource, isDefault, transportType); + builder.Resource.AddMcpServer(mcpServer.Resource, isDefault, transportType, path); return builder; } @@ -222,4 +224,27 @@ private static IResourceBuilder WithDefaultArgs(this IReso ctx.Args.Add(defaultMcpServer?.Name ?? throw new InvalidOperationException("The MCP Inspector resource must have a default MCP server defined.")); }); } + + internal static Uri Combine(string baseUrl, params string[] segments) + { + if (string.IsNullOrEmpty(baseUrl)) + throw new ArgumentException("baseUrl required", nameof(baseUrl)); + + if (segments == null || segments.Length == 0) + return new Uri(baseUrl, UriKind.RelativeOrAbsolute); + + var baseUri = new Uri(baseUrl, UriKind.Absolute); + + // If first segment is absolute URI, return it + if (Uri.IsWellFormedUriString(segments[0], UriKind.Absolute)) + return new Uri(segments[0], UriKind.Absolute); + + var escaped = segments + .Where(s => !string.IsNullOrEmpty(s)) + .Select(s => Uri.EscapeDataString(s.Trim('/'))); + + var relative = string.Join("/", escaped); + + return new Uri(baseUri, relative); + } } diff --git a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpServerMetadata.cs b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpServerMetadata.cs index b7b13866d..47e01c056 100644 --- a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpServerMetadata.cs +++ b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpServerMetadata.cs @@ -6,4 +6,5 @@ namespace Aspire.Hosting.ApplicationModel; /// The name of the server resource. /// The endpoint reference for the server resource. /// The transport type used by the server resource. -public record McpServerMetadata(string Name, EndpointReference Endpoint, McpTransportType TransportType); \ No newline at end of file +/// The path used for MCP communication. +public record McpServerMetadata(string Name, EndpointReference Endpoint, McpTransportType TransportType, string Path); \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs b/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs index 98a1b163d..d3af17914 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs @@ -63,6 +63,33 @@ public void WithMcpServerSpecificTransportTypeAddsServerToResource(McpTransportT Assert.Equal(transportType, inspectorResource.DefaultMcpServer.TransportType); } + [Fact] + public void WithMcpServerCustomPathAddsServerWithCustomPath() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + // Create a mock MCP server resource + var mockServer = appBuilder.AddProject("mcpServer"); + + // Act + var inspector = appBuilder.AddMcpInspector("inspector") + .WithMcpServer(mockServer, isDefault: true, path: "/custom/mcp/path"); + + using var app = appBuilder.Build(); + + // Assert + var appModel = app.Services.GetRequiredService(); + + var inspectorResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("inspector", inspectorResource.Name); + + Assert.Single(inspectorResource.McpServers); + Assert.NotNull(inspectorResource.DefaultMcpServer); + Assert.Equal("mcpServer", inspectorResource.DefaultMcpServer.Name); + Assert.Equal("/custom/mcp/path", inspectorResource.DefaultMcpServer.Path); + } + [Fact] public void WithMultipleMcpServersAddsAllServersToResource() {