Visual Studio Integration Guide
What is Visual Studio?
Two extensibility worlds — out-of-process SDK (async, hot-reload) over in-process VSSDK/MEF (COM, deep editor hooks).
Start with `Microsoft.VisualStudio.Extensibility.Sdk` (NuGet, net8.0-windows): one `Extension : [VisualStudioContribution]` class is your manifest-in-code, `Command` subclasses run async against `this.Extensibility.Editor().EditAsync(...)`, and the `.Build` package packs the `.vsix` straight from `dotnet build` — F5 hot-loads into the Experimental Instance with no restart. Drop to VSSDK/MEF only for hooks the SDK doesn't expose yet: `IClassifierProvider`, `ITaggerProvider`, `AdornmentLayerDefinition`, gated by `[ContentType]`. Extensions ship as NuGet/VSIX, never npm — so the tech-stack freshness checker can't track them and the frontmatter `packages` list stays empty. Since VS 2026 (version 18.x) GitHub Copilot is built into the IDE — agent mode, a plan agent, MCP servers, and a model picker that includes Claude (Sonnet 4 / Opus 4) plus bring-your-own-key — so per the AI routing policy you'd pick a Claude model there rather than the GPT default. For codeAmani, VS is the .NET/C++/MAUI backend tool; VS Code stays the day-to-day editor for the Next.js + M-Pesa stack, and any VS-only workflow should be documented so it isn't a hidden, bandwidth-heavy prerequisite.
Six Visual Studio editor primitives
One modern SDK, four classic MEF editor hooks, and the VSIX that ships them.
██╗ ██╗██╗███████╗██╗ ██╗ █████╗ ██╗ ███████╗████████╗██╗ ██╗██████╗ ██╗ ██████╗
██║ ██║██║██╔════╝██║ ██║██╔══██╗██║ ██╔════╝╚══██╔══╝██║ ██║██╔══██╗██║██╔═══██╗
██║ ██║██║███████╗██║ ██║███████║██║ ███████╗ ██║ ██║ ██║██║ ██║██║██║ ██║
╚██╗ ██╔╝██║╚════██║██║ ██║██╔══██║██║ ╚════██║ ██║ ██║ ██║██║ ██║██║██║ ██║
╚████╔╝ ██║███████║╚██████╔╝██║ ██║███████╗ ███████║ ██║ ╚██████╔╝██████╔╝██║╚██████╔╝
╚═══╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══════╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝Visual Studio Integration Guide
Focus: Extending the Visual Studio IDE's editor and tooling — building VSIX extensions with the modern out-of-process
VisualStudio.ExtensibilitySDK and the classic VSSDK + MEF editor model (classifiers, adornments, taggers), and driving builds/tests from Claude Code via MSBuild and thedotnet/devenvCLIs.
Overview
Visual Studio is Microsoft's full Windows IDE for .NET, C++, web, and cross-platform mobile development. Unlike VS Code (a separate, lighter editor with a JavaScript extension model), Visual Studio extensions are .NET assemblies packaged as a VSIX and published to the Visual Studio Marketplace.
Versions & channels (2026-08). Visual Studio 2026 (version 18.x) is now the current major release. It renames the update channels: Stable replaces the old Current channel and Insiders replaces Preview — you can run both side by side. Visual Studio 2022 (17.x) reached its final feature minor at 17.14, which stays on the Current channel and is supported for the rest of its 10-year lifecycle to January 2032. Both major versions build the same extensions; the
VisualStudio.ExtensibilitySDK is still versioned 17.14.x on NuGet (see the packages table) and targetsnet8.0, so it installs into VS 2022 17.9+ and VS 2026. CheckHelp > Aboutordevenv /versionbefore scaffolding.
Here's the big picture to keep you oriented — pick your model, scaffold, then let the build tools package the .vsix:
There are two extensibility models, and knowing which you're in saves hours:
| Model | Process | API style | Use it when |
|---|---|---|---|
| VisualStudio.Extensibility SDK (new) | Out-of-process | Async, [VisualStudioContribution], hot-reload | New commands, editor listeners, tool windows, LSP — start here |
| VSSDK + MEF (classic) | In-process (COM) | [Export]/[Import], requires VS restart | Deep editor hooks: IClassifier, adornments, taggers, IntelliSense not yet in the new SDK |
Claude Code's role here is codegen + build orchestration: scaffold the extension classes that match the fetched API, then drive dotnet build / msbuild / dotnet test to compile, package, and validate the .vsix.
Official Documentation
| Resource | URL |
|---|---|
| Extensibility overview | https://learn.microsoft.com/en-us/visualstudio/extensibility/ |
| VisualStudio.Extensibility (new SDK) | https://learn.microsoft.com/en-us/visualstudio/extensibility/visualstudio.extensibility/ |
| Language service & editor extension points | https://learn.microsoft.com/en-us/visualstudio/extensibility/language-service-and-editor-extension-points |
| VSExtensibility SDK (GitHub + samples) | https://github.com/microsoft/VSExtensibility |
| Visual Studio IDE docs | https://learn.microsoft.com/en-us/visualstudio/ide/ |
| GitHub Copilot in Visual Studio (get started) | https://learn.microsoft.com/en-us/visualstudio/ide/visual-studio-github-copilot-get-started |
NuGet Packages (tracked manually — not npm/pypi)
The freshness checker only follows npm and PyPI, so the frontmatter packages list is empty. Track these by hand:
| Package | Purpose | Version at review |
|---|---|---|
Microsoft.VisualStudio.Extensibility.Sdk | Core out-of-process SDK | 17.14.40608 |
Microsoft.VisualStudio.Extensibility.Build | MSBuild targets that pack the .vsix | 17.14.40608 |
Microsoft.VisualStudio.SDK (classic) | Meta-package for VSSDK/MEF editor APIs | matches your VS version (e.g. 17.x) |
Microsoft.VSSDK.BuildTools (classic, CI) | Restores VsSDK.targets so classic VSIX builds without a full VS install | 18.9.x (latest on NuGet) |
Latest versions: https://www.nuget.org/packages/Microsoft.VisualStudio.Extensibility.Sdk
Note — the SDK still tracks 17.14, not the IDE's 18.x. Even though Visual Studio 2026 ships as version 18.x, the modern
VisualStudio.ExtensibilitySDK's latest stable NuGet release is17.14.40608(theMicrosoft.VSSDK.BuildToolsclassic-CI package has moved to18.9.x). Pin17.14.40608for the SDK — it is current, not stale — and re-check the NuGet link before a release.
Setup — Modern SDK (recommended)
Prerequisites
Visual Studio 2022 17.9+ (or Visual Studio 2026) with the "Visual Studio extension development" workload
.NET 8 SDKThe SDK's docs still label it VisualStudio.Extensibility (Preview) and the API surface keeps a small set of experimental members — pin the SDK version and expect occasional breaking changes between minors. It remains Microsoft's "start here" model for new extensions.
Project file
The SDK targets net8.0-windows and pulls two NuGet packages. The .Build package wires the VSIX packaging into dotnet build automatically — no source.extension.vsixmanifest hand-editing:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.14.40608" PrivateAssets="all" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
</ItemGroup>
</Project>Extension entry point
Every extension has one class deriving from Extension, decorated with [VisualStudioContribution]. This is your manifest-in-code:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.Extensibility;
[VisualStudioContribution]
public class InsertGuidExtension : Extension
{
public override ExtensionConfiguration ExtensionConfiguration => new()
{
Metadata = new(
id: "InsertGuid.c5481000-68da-416d-b337-32122a638980",
version: this.ExtensionAssemblyVersion,
publisherName: "codeAmani",
displayName: "Insert Guid Sample Extension",
description: "Inserts a GUID at the caret in the active document."),
};
protected override void InitializeServices(IServiceCollection serviceCollection)
{
base.InitializeServices(serviceCollection);
// Register your own services for DI here.
}
}A command
Commands are Command subclasses, also marked [VisualStudioContribution]. They run async against the out-of-process Extensibility object:
[VisualStudioContribution]
public class InsertGuidCommand : Command
{
public override CommandConfiguration CommandConfiguration => new("%InsertGuid.DisplayName%")
{
Placements = [CommandPlacement.KnownPlacements.ExtensionsMenu],
Icon = new(ImageMoniker.KnownValues.Extension, IconSettings.IconAndText),
};
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken ct)
{
var textView = await context.GetActiveTextViewAsync(ct);
if (textView is null) return;
await this.Extensibility.Editor().EditAsync(batch =>
{
var doc = textView.Document.AsEditable(batch);
doc.Replace(textView.Selection.Extent, Guid.NewGuid().ToString());
}, ct);
}
}Build & run
# From the extension project dir — the .Build package produces the .vsix
dotnet build -c Release
# F5 in Visual Studio launches the VS Experimental Instance with the extension
# hot-loaded (no restart). From CLI, install the packaged VSIX:
"%VsInstallDir%\Common7\IDE\VSIXInstaller.exe" bin\Release\MyExtension.vsixSetup — Classic VSSDK + MEF (deep editor hooks)
When you need an editor hook the new SDK doesn't expose yet — syntax classification, adornments, taggers — use the in-process MEF model. Create a VSIX Project (C# › Extensibility), then add an Editor Classifier item template.
MEF is the wiring: you [Export] a provider and Visual Studio [Import]s it. The editor discovers your component by the exported interface + ContentType.
Middle path — a VSSDK-compatible SDK extension. You no longer have to choose one world wholesale. The VisualStudio.Extensibility Extension with VSSDK Compatibility project template lets a modern SDK extension host classic VSSDK/MEF parts in-process: set
<VssdkCompatibleExtension>true</VssdkCompatibleExtension>, mark theExtensionwithRequiresInProcessHosting = true, and keep thesource.extension.vsixmanifestwithExtensionType = VSSDK+VisualStudio.Extensibility. For VS 2022 this variant targets.NET Framework 4.7.2(notnet8.0), because in-process code runs insidedevenv.exe. Reach for it when you want the SDK's authoring model but still need a deep MEF hook in the same VSIX. See Using the SDK and VSSDK together.
A classifier (colors text)
[Export(typeof(IClassifierProvider))]
[ContentType("text")]
internal class EditorClassifierProvider : IClassifierProvider
{
[Import] internal IClassificationTypeRegistryService ClassificationRegistry { get; set; }
public IClassifier GetClassifier(ITextBuffer buffer) =>
buffer.Properties.GetOrCreateSingletonProperty(
() => new EditorClassifier(ClassificationRegistry));
}
internal class EditorClassifier : IClassifier
{
private readonly IClassificationType _type;
internal EditorClassifier(IClassificationTypeRegistryService registry) =>
_type = registry.GetClassificationType("EditorClassifier");
public IList<ClassificationSpan> GetClassificationSpans(SnapshotSpan span) =>
new List<ClassificationSpan>
{
new(new SnapshotSpan(span.Snapshot, span.Span), _type),
};
public event EventHandler<ClassificationChangedEventArgs> ClassificationChanged;
}The format definition (how the classification looks)
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = "EditorClassifier")]
[Name("EditorClassifier")]
[UserVisible(true)]
internal sealed class EditorClassifierFormat : ClassificationFormatDefinition
{
public EditorClassifierFormat()
{
DisplayName = "EditorClassifier";
BackgroundColor = Colors.BlueViolet;
ForegroundColor = Colors.White;
}
}Editor extension points at a glance
| Hook | Export | Reach for it when |
|---|---|---|
| Classifier | IClassifierProvider | Color/categorize spans of text |
| Tagger | ITaggerProvider | Attach typed tags (errors, outlining, highlights) to spans |
| Adornment | AdornmentLayerDefinition + IWpfTextViewCreationListener | Draw WPF visuals over/under text |
| Completion | IAsyncCompletionSourceProvider | Custom IntelliSense |
| Margin | IWpfTextViewMarginProvider | Add a gutter/margin UI strip |
MEF components are in-process and lazy — Visual Studio only constructs them when a matching
ContentTypeview opens. Keep constructors cheap; do real work on first use.
Driving Visual Studio from Claude Code
Claude Code runs on the CLI, so orchestrate the build tools, not the GUI. Always pass arguments as arrays (execFileSync) — never interpolate paths into a shell string.
You're the codegen-plus-orchestration layer here — here's how a run flows end to end:
# Build a solution (prefer the dotnet CLI for SDK-style projects)
dotnet build MyExtension.sln -c Release
# MSBuild for classic VSSDK projects that aren't SDK-style
msbuild MyExtension.sln /p:Configuration=Release /p:DeployExtension=false
# Run tests
dotnet test --logger "trx;LogFileName=results.trx"
# Locate the active VS install (avoids hard-coded paths)
vswhere -latest -property installationPath// scripts/build-vsix.js — safe argument passing
import { execFileSync } from "node:child_process";
execFileSync("dotnet", ["build", "MyExtension.sln", "-c", "Release"], {
stdio: "inherit",
});CI — build the VSIX
The tables above point to "CI build of the .vsix" but never show the workflow. Here it is. Two facts shape it:
msbuild, notdotnet build— a classic VSSDK solution that contains a VSIX project won't build withdotnet buildeven when the projects are SDK-style. Usemsbuilddirectly. (ModernVisualStudio.ExtensibilitySDK projects can usedotnet build, butmsbuildbuilds both worlds, so the workflow below is the safe default.)DeployExtension=false— on a headless runner there's no local Visual Studio to install into, so suppress the deploy step.
The runner must locate msbuild first. The canonical action is microsoft/setup-msbuild, which runs vswhere and prepends the discovered MSBuild to PATH.
Pin the runner image —
windows-latestmoved to VS 2026. As of 2026 thewindows-latestlabel maps to Windows Server 2025 with Visual Studio 2026 (18.x); Visual Studio 2022 is no longer on that image. Sovs-version: '17.0'onwindows-latestno longer resolves. Pick one deliberately: pinruns-on: windows-2022to keep the VS 2022 (17.x) toolset (shown below — matches the 17.14 SDK line), or stay onwindows-latestand bump the pin tovs-version: '18.0'for the VS 2026 toolset. Don't leave a17.0pin onwindows-latest.
Workflow — .github/workflows/build-vsix.yml
name: Build VSIX
on:
push:
branches: [master]
pull_request:
jobs:
build:
# Pinned: `windows-latest` now = Windows Server 2025 + Visual Studio 2026 (18.x).
# `windows-2022` keeps the VS 2022 (17.x) toolset that matches `vs-version: '17.0'`.
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
# Discovers MSBuild via vswhere and adds it to PATH.
# vs-version pins the toolset (17.0 = VS 2022; use 18.0 on a VS 2026 runner).
- name: Add MSBuild to PATH
uses: microsoft/setup-msbuild@v3
with:
vs-version: '17.0'
# Restore NuGet packages — msbuild -t:Restore avoids a separate nuget.exe.
- name: Restore
run: msbuild MyExtension.sln -t:Restore -p:Configuration=Release
# Build and pack. DeployExtension=false: no local VS to install into.
- name: Build VSIX
run: >-
msbuild MyExtension.sln
-p:Configuration=Release
-p:DeployExtension=false
-m
- name: Upload VSIX
uses: actions/upload-artifact@v4
with:
name: MyExtension-vsix
path: '**/bin/Release/**/*.vsix'
if-no-files-found: errorGotcha — the VS extension build tooling may be missing. The GitHub-hosted Windows images ship a full Visual Studio install (VS 2022 on
windows-2022, VS 2026 onwindows-latest/Server 2025) plus the .NET workloads, but a classic VSSDK build also needs the Visual Studio extension development workload (theMicrosoft.VsSDK.targetsthat pack the.vsix). The full VS install usually includes it, but if you hiterror MSB4019: The imported project "...Microsoft.VsSDK.targets" was not found, the SDK targets aren't on the runner. Fixes, cheapest first: reference theMicrosoft.VSSDK.BuildToolsNuGet package so the targets restore with the project (preferred — keeps the build self-contained); or, for a container/self-hosted runner, add the component via the VS Installer (--add Microsoft.VisualStudio.Workload.VisualStudioExtension). The modernVisualStudio.ExtensibilitySDK sidesteps this entirely — its.Buildpackage brings the packaging targets in as a normalPackageReference.
GitHub Copilot in Visual Studio
Copilot is now a first-class part of the IDE, not an add-on. In Visual Studio 2026 it is built in; in Visual Studio 2022 it ships as the GitHub Copilot optional component in the .NET desktop / other workloads, and agent mode requires 17.14+. Sign in once under Tools > Options > GitHub > Accounts with a GitHub account that has Copilot access (Copilot is a separate GitHub subscription, paid or free).
| Surface | What it does |
|---|---|
| Completions + next edit suggestions | Inline gray-text completions as you type, plus predicted edits to existing code. IntelliSense still takes Tab by default. |
| Ask (Copilot Chat) | Q&A and code examples with no edits applied unless you choose Apply. |
| Plan agent | Read-only exploration that drafts a reviewable implementation plan (saved as markdown under .copilot/plans/) before any edits — hand it off to agent mode to execute. |
| Agent mode | Multi-step edits across solution files, iterating on build errors and running tools. The evolution of Copilot Edits. Required to use MCP servers. |
| MCP servers | Agent mode can call Model Context Protocol tools via the tools icon — configure servers and pick which tools Copilot may use. |
Model picker — including Claude
Copilot Chat has a model picker at the bottom of the chat window. With 17.14 the default model is GPT-4.1 (previously GPT-4o), but the picker exposes an expanded set — Claude Sonnet 4, Claude Opus 4, GPT-5 / GPT-5 mini, Claude Sonnet 3.5, Claude 3.7 (thinking / non-thinking), o3-mini, and Gemini 2.x. Model availability depends on your Copilot plan; for Business/Enterprise an admin enables the models.
Bring your own model (BYOM): in the model picker you can add an API key from Anthropic, OpenAI, or Google and use your own model — but only in the Copilot Chat experience (not completions), and not for Copilot Business/Enterprise seats. Custom-model output comes straight from the provider and may bypass Copilot's responsible-AI filtering.
Safety — agent-mode terminal commands. Agent mode can only touch files in the open solution, but any terminal command it proposes runs with the permissions of the Visual Studio process — it is not sandboxed. Review proposed commands before letting them run.
codeAmani angle
- This matches the AI routing policy. codeAmani's policy puts Anthropic Claude first for reasoning/codegen — so in Copilot Chat, select a Claude model (Sonnet 4 for day-to-day, Opus 4 for hard reasoning) rather than leaving the GPT default. Wire the tech-stack MCP (and others) through agent mode's MCP-servers tools for grounded, in-repo answers.
- Copilot vs. Claude Code. Both live in Visual Studio: Copilot as the native chat/agent surface, and Claude Code as the CLI you run in the Developer PowerShell / integrated terminal (
claude). Use whichever fits the task; they are complementary, not exclusive. - Secrets. A BYOM API key is stored per-user by the IDE — fine for a developer's own Anthropic key, but it is a real secret: never commit it, and never paste a shared/production key into a teammate's IDE. This is the same rule as the rest of the stack — keys stay per-user and server-side, never in the VSIX.
Common Use Cases
| Use Case | Approach |
|---|---|
| Insert/transform text at caret | New SDK Command + Editor().EditAsync |
| Syntax highlighting for a custom language | VSSDK IClassifierProvider + ClassificationFormatDefinition |
| Squiggles / error tags | VSSDK ITaggerProvider<IErrorTag> |
| Inline visuals (CodeLens-like) | VSSDK adornment layer + IWpfTextViewCreationListener |
| Custom IntelliSense | IAsyncCompletionSourceProvider |
| Language server integration | New SDK LSP extension contribution |
| AI codegen / multi-step edits in the IDE | GitHub Copilot agent mode (pick a Claude model) or Claude Code CLI in the terminal |
CI build of the .vsix | dotnet build / msbuild in GitHub Actions (Windows runner) |
Troubleshooting
| Issue | Fix |
|---|---|
| Extension not loading | Check the Experimental Instance: devenv /rootSuffix Exp; reset with /resetSettings |
| MEF component never constructed | ContentType mismatch — verify the [ContentType] matches the open file's type |
.vsix not produced | Ensure Microsoft.VisualStudio.Extensibility.Build (or the VSSDK targets) is referenced |
| Stale extension after rebuild | Clear the Exp cache under %LocalAppData%\Microsoft\VisualStudio\<version>_*Exp\Extensions — 17.0_*Exp for VS 2022, 18.0_*Exp for VS 2026 |
msbuild not found in CI | Use the microsoft/setup-msbuild action or build with dotnet for SDK-style projects |
| No Ask / Plan / Agent options in Copilot Chat | You're below VS 17.14 (check Help > About), or Enable Agent mode is off under Tools > Options > GitHub > Copilot > Copilot Chat |
codeAmani notes
- Secrets: An IDE extension runs on the developer's machine. Never embed API keys (Anthropic, Daraja, Supabase) in extension assemblies — read them from
.env.local/ the OS credential store at runtime. A shipped.vsixis trivially decompiled. - AI routing: Per the routing policy, an extension that calls an LLM should hit Anthropic Claude for reasoning/codegen and OpenAI for structured/function-calling — but proxy through a server-side endpoint, not a key baked into the VSIX.
- Where this fits: Visual Studio is a Windows-first .NET tool. For the codeAmani Next.js/M-Pesa stack the day-to-day editor is VS Code; reach for full Visual Studio when working on .NET/C++ backends, MAUI mobile, or building internal tooling extensions. Most contributors on low-bandwidth African connections will favour VS Code's lighter footprint — document any Visual Studio-only workflow so it isn't a hidden prerequisite.
- Shell safety: When a hook or script invokes
dotnet/msbuild/devenv, useexecFileSync(cmd, [args])with array arguments, consistent with the project security convention.