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
4 changes: 2 additions & 2 deletions src/CodingWithCalvin.MCPServer.Server/Tools/SolutionTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ public async Task<string> GetSolutionInfoAsync()
}

[McpServerTool(Name = "solution_open", Destructive = true, Idempotent = true)]
[Description("Open a solution file in Visual Studio. This will close any currently open solution.")]
[Description("Open a solution file in Visual Studio. This will close any currently open solution. Loading runs asynchronously and this returns as soon as the load has started; use solution_info to check when the solution has finished loading.")]
public async Task<string> OpenSolutionAsync(
[Description("The full absolute path to the solution file (.sln or .slnx). Supports forward slashes (/) or backslashes (\\).")] string path)
{
var success = await _rpcClient.OpenSolutionAsync(path);
return success ? $"Opened solution: {path}" : $"Failed to open solution: {path}";
return success ? $"Opening solution: {path}" : $"Failed to open solution (file not found?): {path}";
}

[McpServerTool(Name = "solution_close", Destructive = true, Idempotent = true)]
Expand Down
39 changes: 29 additions & 10 deletions src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,39 @@ public async Task<bool> OpenSolutionAsync(string path)
{
using var activity = VsixTelemetry.Tracer.StartActivity("OpenSolution");

await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var dte = await GetDteAsync();
var normalizedPath = NormalizePath(path);

try
{
dte.Solution.Open(path);
return true;
}
catch (Exception ex)
if (!File.Exists(normalizedPath))
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
activity?.RecordException(ex);
activity?.SetStatus(ActivityStatusCode.Error, "Solution file not found");
return false;
}

await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var dte = await GetDteAsync();

// EnvDTE's Solution.Open() is synchronous and can take a long time for large
// solutions. Awaiting it here would block this RPC call (and the calling MCP client)
// until the whole solution finishes loading, which can exceed client timeouts and
// surface as "An error occurred invoking 'solution_open'" even though the solution is
// loading normally. Fire the load off without waiting for it to finish; callers can
// poll solution_info to observe when the new solution becomes available.
_ = Task.Run(async () =>
{
using var loadActivity = VsixTelemetry.Tracer.StartActivity("OpenSolution.Load");
try
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
dte.Solution.Open(normalizedPath);
}
catch (Exception ex)
{
loadActivity?.SetStatus(ActivityStatusCode.Error, ex.Message);
loadActivity?.RecordException(ex);
}
});

return true;
}

public async Task CloseSolutionAsync(bool saveFirst = true)
Expand Down
Loading