feat(tools): add Visual Studio terminal tools - #106
Merged
Conversation
Adds six MCP tools over the integrated terminal: terminal_run, terminal_create, terminal_list, terminal_show, terminal_close and terminal_close_all. The brokered service plumbing is ordinary compile-time code. IBrokeredServiceContainer, IServiceBroker, ServiceRpcDescriptor and ServiceActivationOptions all come from Microsoft.ServiceHub.Framework, a real NuGet package the VSIX already references. Microsoft.VisualStudio.Terminal.dll is the part that cannot be referenced. It supplies ITerminalService, ProfileConfig and TerminalServiceDescriptors, ships only inside the Visual Studio installation, is absent from NuGet and from the Microsoft.VisualStudio.SDK metapackage, and $(DevEnvDir) is undefined under dotnet build. TerminalInterop loads it by strong name at version 17.0.0.0, which the binding redirect and codeBase entry shipped by both VS 2022 and VS 2026 resolve correctly, and reaches those three types reflectively. Commands run through cmd.exe with /k so the window stays readable. When VsDevCmd.bat can be located from VSAPPIDDIR it is chained in first, so msbuild, vstest.console and dotnet-coverage are on PATH, which is what the original request in #95 needed. Output is deliberately not captured. The terminal is a raw PTY with no exit code and no command boundaries, so inferring success from its text would be unreliable. The tool descriptions say so and point at redirecting to a file and reading it back with document_read. Working directory defaults to the open solution's directory rather than devenv's own, which points into the Visual Studio install.
This was referenced Sep 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resolves #103
Adds six MCP tools over the Visual Studio integrated terminal.
terminal_runterminal_createterminal_listterminal_showterminal_closeterminal_close_allAssembly access turned out better than expected
My comment on #103 assumed the whole brokered-service stack would need reflection. It doesn't.
IBrokeredServiceContainer,IServiceBroker,ServiceRpcDescriptorandServiceActivationOptionsall come fromMicrosoft.ServiceHub.Framework, a real NuGet package the VSIX already pulls in transitively. I verified with a compile probe before designing.Only three types need reflection —
ITerminalService,ProfileConfigandTerminalServiceDescriptors, fromMicrosoft.VisualStudio.Terminal.dll, which is subject to the same constraint documented onTestExplorerInterop: VS-install-only, absent from NuGet and the SDK metapackage, and$(DevEnvDir)undefined underdotnet build.The assembly is loaded by strong name at 17.0.0.0. Both VS 2022 and VS 2026 ship a binding redirect covering
0.0.0.0through the installed version plus acodeBaseentry pointing atCommonExtensions\Microsoft\Terminal, so one version number resolves on both. An already-loaded copy is preferred when present, which avoids depending on binding policy at all once the terminal window has been used.Design notes
Commands run via
ProfileConfig, not a PTY stream.ProfileConfigexposesLocationandArguments, so a command can be launched by creating a terminal configured to run it — no duplex-stream plumbing, noAttachTerminalStreamAsync. Much less machinery than the issue anticipated.Developer environment by default. When
VsDevCmd.batcan be located fromVSAPPIDDIR, it's chained in ahead of the command, somsbuild,vstest.consoleanddotnet-coverageare on PATH. Plaincmd.exewould not have them — and the OpenCover-style use case in #95 is exactly why that matters. Falls back cleanly when the script isn't found, and reports which happened viaDeveloperEnvironment.Working directory defaults to the solution directory, not devenv's own current directory, which points into the Visual Studio install.
Output is deliberately not captured. The terminal is a raw PTY: no exit code, no command boundaries. Rather than return text an agent might misread as success, the tool descriptions state the limitation and point at redirecting to a file and reading it back with
document_read. This is the limitation I flagged on #95 and it is inherent to the API, not to this implementation.Testing
dotnet build -c Releaseclean, 0 warnings. 50/50 tests pass (14 added).TerminalInteroptakes an assembly resolver so tests can supply the test assembly, whose stand-ins are declared in the genuineMicrosoft.VisualStudio.Terminalnamespace. That runs the real path end to end: type resolution, descriptor retrieval, the genericGetProxyAsync<T>invocation including itsValueTaskunwrapping, overload selection, and profile construction.The overload selection is worth calling out —
CreateTerminalAsynchas four overloads, and the three-argument one would silently drop the working directory. The stand-in throws if that overload is picked, so the test fails loudly rather than quietly passing with a wrong working directory.Not covered automatically: an actual VS terminal opening, which needs a live instance.
Security note
terminal_runexecutes commands on the machine. The README now flags that under Settings, next to the Binding Address option, since combining it with a non-localhostbinding exposes command execution to the network.