diff --git a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs index 51cff0d08..637437d52 100644 --- a/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.McpInspector/McpInspectorResourceBuilderExtensions.cs @@ -54,9 +54,7 @@ public static IResourceBuilder AddMcpInspector(this IDistr var packageName = $"@modelcontextprotocol/inspector@{options.InspectorVersion}"; var resourceBuilder = builder.AddResource(new McpInspectorResource(name, packageName)) - .WithNpm(install: true, installArgs: ["-y", $"@modelcontextprotocol/inspector@{options.InspectorVersion}", "--no-save", "--no-package-lock"]) .WithCommand("npx") - .WithArgs(["-y", $"@modelcontextprotocol/inspector@{options.InspectorVersion}"]) .WithCertificateTrustConfiguration(ctx => { if (ctx.Scope == CertificateTrustScope.Append) @@ -320,6 +318,9 @@ private static IResourceBuilder WithInspectorArgs(this IRe ctx.Args.Insert(0, packageName); ctx.Args.Insert(0, "dlx"); break; + case "bunx": + ctx.Args.Insert(0, packageName); + break; default: // npm/npx ctx.Args.Insert(0, packageName); ctx.Args.Insert(0, "-y"); @@ -351,4 +352,16 @@ public static IResourceBuilder WithPnpm(this IResourceBuil return builder.WithCommand("pnpm"); } + + /// + /// Configures the MCP Inspector to use bun as the package manager. + /// + /// The MCP Inspector resource builder. + /// A reference to the . + public static IResourceBuilder WithBun(this IResourceBuilder builder) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.WithCommand("bunx"); + } } diff --git a/src/CommunityToolkit.Aspire.Hosting.McpInspector/README.md b/src/CommunityToolkit.Aspire.Hosting.McpInspector/README.md index c5535941a..523e75683 100644 --- a/src/CommunityToolkit.Aspire.Hosting.McpInspector/README.md +++ b/src/CommunityToolkit.Aspire.Hosting.McpInspector/README.md @@ -27,7 +27,7 @@ You can specify the transport type (`StreamableHttp`) and set which server is th #### Using alternative package managers -By default, the MCP Inspector uses npm/npx. You can configure it to use yarn or pnpm instead by chaining the appropriate method: +By default, the MCP Inspector uses npm/npx. You can configure it to use yarn, pnpm, or bun instead by chaining the appropriate method: ```csharp // Using yarn @@ -39,9 +39,14 @@ var inspector = builder.AddMcpInspector("inspector") var inspector = builder.AddMcpInspector("inspector") .WithPnpm() .WithMcpServer(mcpServer); + +// Using bun +var inspector = builder.AddMcpInspector("inspector") + .WithBun() + .WithMcpServer(mcpServer); ``` -When using yarn or pnpm, the inspector will use `yarn dlx` or `pnpm dlx` respectively to run the MCP Inspector package. +When using yarn, pnpm, or bun, the inspector will use `yarn dlx`, `pnpm dlx`, or `bunx` respectively to run the MCP Inspector package. #### Using options for complex configurations diff --git a/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs b/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs index dd21df777..e9beb88d5 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.McpInspector.Tests/McpInspectorResourceBuilderExtensionsTests.cs @@ -1,6 +1,6 @@ using Aspire.Hosting; using Aspire.Hosting.ApplicationModel; -using Aspire.Hosting.JavaScript; +using Aspire.Hosting.Eventing; namespace CommunityToolkit.Aspire.Hosting.McpInspector.Tests; @@ -517,7 +517,7 @@ public void WithYarnSetsCommand() var appBuilder = DistributedApplication.CreateBuilder(); // Act - var inspector = appBuilder.AddMcpInspector("inspector") + appBuilder.AddMcpInspector("inspector", new McpInspectorOptions()) .WithYarn(); using var app = appBuilder.Build(); @@ -536,7 +536,7 @@ public void WithPnpmSetsCommand() var appBuilder = DistributedApplication.CreateBuilder(); // Act - var inspector = appBuilder.AddMcpInspector("inspector") + appBuilder.AddMcpInspector("inspector", new McpInspectorOptions()) .WithPnpm(); using var app = appBuilder.Build(); @@ -548,6 +548,25 @@ public void WithPnpmSetsCommand() Assert.Equal("pnpm", inspectorResource.Command); } + [Fact] + public void WithBunSetsCommand() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + // Act + appBuilder.AddMcpInspector("inspector", new McpInspectorOptions()) + .WithBun(); + + using var app = appBuilder.Build(); + + // Assert + var appModel = app.Services.GetRequiredService(); + var inspectorResource = Assert.Single(appModel.Resources.OfType()); + + Assert.Equal("bunx", inspectorResource.Command); + } + [Fact] public async Task WithYarnSetsCorrectArguments() { @@ -567,12 +586,9 @@ public async Task WithYarnSetsCorrectArguments() var appModel = app.Services.GetRequiredService(); var inspectorResource = Assert.Single(appModel.Resources.OfType()); - var args = await inspectorResource.GetArgumentValuesAsync(); - var argsList = args.ToList(); + var args = await GetArgsAsync(inspectorResource); - // For yarn, the first arg should be "dlx" - Assert.Equal("dlx", argsList[0]); - Assert.Equal("@modelcontextprotocol/inspector@0.15.0", argsList[1]); + Assert.Equal(new[] { "dlx", "@modelcontextprotocol/inspector@0.15.0" }, args); } [Fact] @@ -594,12 +610,33 @@ public async Task WithPnpmSetsCorrectArguments() var appModel = app.Services.GetRequiredService(); var inspectorResource = Assert.Single(appModel.Resources.OfType()); - var args = await inspectorResource.GetArgumentValuesAsync(); - var argsList = args.ToList(); + var args = await GetArgsAsync(inspectorResource); - // For pnpm, the first arg should be "dlx" - Assert.Equal("dlx", argsList[0]); - Assert.Equal("@modelcontextprotocol/inspector@0.15.0", argsList[1]); + Assert.Equal(new[] { "dlx", "@modelcontextprotocol/inspector@0.15.0" }, args); + } + + [Fact] + public async Task WithBunSetsCorrectArguments() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + // Act + var inspector = appBuilder.AddMcpInspector("inspector", options => + { + options.InspectorVersion = "0.15.0"; + }) + .WithBun(); + + using var app = appBuilder.Build(); + + // Assert + var appModel = app.Services.GetRequiredService(); + var inspectorResource = Assert.Single(appModel.Resources.OfType()); + + var args = await GetArgsAsync(inspectorResource); + + Assert.Equal(new[] { "@modelcontextprotocol/inspector@0.15.0" }, args); } [Fact] @@ -620,12 +657,9 @@ public async Task DefaultNpxUsesCorrectArguments() var appModel = app.Services.GetRequiredService(); var inspectorResource = Assert.Single(appModel.Resources.OfType()); - var args = await inspectorResource.GetArgumentValuesAsync(); - var argsList = args.ToList(); + var args = await GetArgsAsync(inspectorResource); - // For npm/npx, the first arg should be "-y" - Assert.Equal("-y", argsList[0]); - Assert.Equal("@modelcontextprotocol/inspector@0.15.0", argsList[1]); + Assert.Equal(new[] { "-y", "@modelcontextprotocol/inspector@0.15.0" }, args); } [Fact] @@ -648,9 +682,20 @@ public async Task AddMcpInspectorWithOptionsRespectsInspectorVersion() var appModel = app.Services.GetRequiredService(); var inspectorResource = Assert.Single(appModel.Resources.OfType()); - var args = await inspectorResource.GetArgumentValuesAsync(); - var argsList = args.ToList(); + var args = await GetArgsAsync(inspectorResource); + + Assert.Equal("@modelcontextprotocol/inspector@1.2.3", args[1]); + } + + private static async Task> GetArgsAsync(McpInspectorResource resource) + { + CommandLineArgsCallbackContext context = new([]); + + foreach (var annotation in resource.Annotations.OfType()) + { + await annotation.Callback(context); + } - Assert.Equal("@modelcontextprotocol/inspector@1.2.3", argsList[1]); + return context.Args.Select(arg => arg?.ToString() ?? string.Empty).ToArray(); } }