Skip to content
Open
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
35 changes: 26 additions & 9 deletions src/Aspire.Cli/Commands/AgentInitCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -783,17 +783,34 @@ private static async Task<bool> HasExistingAspireSetupAsync(

try
{
foreach (var (path, configureServer) in client.GetMcpConfigPaths(scope, workspaceRoot).Where(static target => target.ConfigureServer))
foreach (var (path, configureServer) in client.GetMcpConfigPaths(scope, workspaceRoot))
{
// Probe a discarded in-memory copy with the client's own update logic, so each client's
// server container and nesting rules apply. Nothing to add means the server already exists.
// This relies on UpdateMcpConfig only mutating the passed JsonObject; files are written
// only when an AgentConfiguration edit is published, and this one never is.
var (config, configFilePath) = await client.ReadMcpConfigAsync(
path, workspaceRoot, scope, new AgentConfiguration(), cancellationToken);
if (!client.UpdateMcpConfig(config, configFilePath, configureServer, scope).Added)
try
{
var (config, configFilePath) = await client.ReadMcpConfigAsync(
path, workspaceRoot, scope, new AgentConfiguration(), cancellationToken);
if (configureServer)
{
// Probe a discarded in-memory copy with the client's own update logic, so each client's
// server container and nesting rules apply. Nothing to add means the server already exists.
// This relies on UpdateMcpConfig only mutating the passed JsonObject; files are written
// only when an AgentConfiguration edit is published, and this one never is.
if (!client.UpdateMcpConfig(config, configFilePath, configureServer, scope).Added)
{
return true;
}
}
// Repair-only targets (such as Copilot's .vscode/mcp.json and .github/copilot/mcp.json) never
// report Added, so probing them would always look configured. They use the VS Code
// {"servers":{"aspire":{...}}} shape, so check for the entry directly.
else if (McpConfigFileHelper.GetServerContainer(config, "servers", configFilePath)?["aspire"] is JsonObject)
{
return true;
}
}
catch (Exception ex) when (IsUnreadableConfig(ex))
{
return true;
// One unreadable file must not hide a valid registration in another.
}
}
}
Expand Down
35 changes: 35 additions & 0 deletions tests/Aspire.Cli.Tests/Commands/AgentInitClientSelectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,41 @@ public async Task AgentInit_NothingSelectedWithExistingAspireSetup_RefreshesHook
Assert.Equal(["claude"], hooks.SelectedClientIds);
}

[Theory]
[InlineData(".vscode/mcp.json", """{"servers":{"aspire":{"type":"stdio","command":"aspire","args":["agent","mcp"]}}}""", false, true)]
[InlineData(".github/copilot/mcp.json", """{"servers":{"aspire":{"type":"stdio","command":"aspire","args":["agent","mcp"]}}}""", false, true)]
[InlineData(".vscode/mcp.json", """{"servers":{"aspire":{"type":"stdio","command":"aspire","args":["agent","mcp"]}}}""", true, true)]
[InlineData(".vscode/mcp.json", """{"servers":{"aspire":null}}""", false, false)]
[InlineData(".vscode/mcp.json", """{"servers":{}}""", false, false)]
[InlineData(".vscode/mcp.json", "{broken", false, false)]
public async Task AgentInit_ProjectScopeRepairOnlyCopilotMcp_RefreshesHook(string relativePath, string content, bool malformedPrimaryConfig, bool expectHook)
{
using var workspace = TemporaryWorkspace.CreateForCli(outputHelper);
var homeDirectory = workspace.CreateDirectory("fake-home");
var executionContext = TestExecutionContextHelper.CreateExecutionContext(workspace.WorkspaceRoot, homeDirectory: homeDirectory);
var hooks = new TestTelemetryHookConfigurator();
var services = CliTestHelper.CreateServiceCollection(workspace, outputHelper, options =>
{
options.CliExecutionContextFactory = _ => executionContext;
options.TelemetryHookConfiguratorFactory = _ => hooks;
});
services.AddSingleton<IEnvironment>(new TestEnvironment());
using var provider = services.BuildServiceProvider();
var mcpPath = Path.Combine(workspace.WorkspaceRoot.FullName, relativePath.Replace('/', Path.DirectorySeparatorChar));
Directory.CreateDirectory(Path.GetDirectoryName(mcpPath)!);
await File.WriteAllTextAsync(mcpPath, content);
if (malformedPrimaryConfig)
{
await File.WriteAllTextAsync(Path.Combine(workspace.WorkspaceRoot.FullName, ".mcp.json"), "{broken");
}

var result = provider.GetRequiredService<RootCommand>().Parse(
"agent init --agents copilot --scope project --skills none --mcp=false");
Assert.Equal(CliExitCodes.Success, await result.InvokeAsync().DefaultTimeout());
Assert.Equal(expectHook ? ["copilot"] : [], hooks.SelectedClientIds);
Assert.Equal(content, await File.ReadAllTextAsync(mcpPath));
}

[Theory]
[InlineData("global", true)]
[InlineData("project", false)]
Expand Down
Loading