Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

var app = builder.Build();

app.MapMcp();
app.MapMcp("/mcp");

app.Run();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public class McpInspectorResource(string name) : ExecutableResource(name, "npx",
/// <summary>
/// Gets the version of the MCP Inspector.
/// </summary>
public const string InspectorVersion = "0.16.2";
public const string InspectorVersion = "0.16.5";

private readonly List<McpServerMetadata> _mcpServers = [];

Expand All @@ -58,7 +58,7 @@ public class McpInspectorResource(string name) : ExecutableResource(name, "npx",
/// </summary>
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))
{
Expand All @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ public static IResourceBuilder<McpInspectorResource> 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
McpTransportType.Sse => "sse",
#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 };
Expand Down Expand Up @@ -183,18 +183,20 @@ public static IResourceBuilder<McpInspectorResource> AddMcpInspector(this IDistr
/// <param name="mcpServer">The <see cref="IResourceBuilder{T}"/> for the MCP server resource.</param>
/// <param name="isDefault">Indicates whether this MCP server should be considered the default server for the MCP Inspector.</param>
/// <param name="transportType">The transport type to use for the MCP server. Defaults to <see cref="McpTransportType.StreamableHttp"/>.</param>
/// <param name="path">The path to use for MCP communication. Defaults to "/mcp".</param>
/// <returns>A reference to the <see cref="IResourceBuilder{McpInspectorResource}"/> for further configuration.</returns>
public static IResourceBuilder<McpInspectorResource> WithMcpServer<TResource>(
this IResourceBuilder<McpInspectorResource> builder,
IResourceBuilder<TResource> 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;
}

Expand Down Expand Up @@ -222,4 +224,27 @@ private static IResourceBuilder<McpInspectorResource> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ namespace Aspire.Hosting.ApplicationModel;
/// <param name="Name">The name of the server resource.</param>
/// <param name="Endpoint">The endpoint reference for the server resource.</param>
/// <param name="TransportType">The transport type used by the server resource.</param>
public record McpServerMetadata(string Name, EndpointReference Endpoint, McpTransportType TransportType);
/// <param name="Path">The path used for MCP communication.</param>
public record McpServerMetadata(string Name, EndpointReference Endpoint, McpTransportType TransportType, string Path);
Original file line number Diff line number Diff line change
Expand Up @@ -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<Projects.CommunityToolkit_Aspire_Hosting_McpInspector_McpServer>("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<DistributedApplicationModel>();

var inspectorResource = Assert.Single(appModel.Resources.OfType<McpInspectorResource>());
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()
{
Expand Down
Loading