← Back to dashboard
visual-studiotoolingfreshReader view (for NotebookLM)

Visual Studio Integration Guide

What is Visual Studio?

The real model

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.

Text
██╗   ██╗██╗███████╗██╗   ██╗ █████╗ ██╗         ███████╗████████╗██╗   ██╗██████╗ ██╗ ██████╗
██║   ██║██║██╔════╝██║   ██║██╔══██╗██║         ██╔════╝╚══██╔══╝██║   ██║██╔══██╗██║██╔═══██╗
██║   ██║██║███████╗██║   ██║███████║██║         ███████╗   ██║   ██║   ██║██║  ██║██║██║   ██║
╚██╗ ██╔╝██║╚════██║██║   ██║██╔══██║██║         ╚════██║   ██║   ██║   ██║██║  ██║██║██║   ██║
 ╚████╔╝ ██║███████║╚██████╔╝██║  ██║███████╗    ███████║   ██║   ╚██████╔╝██████╔╝██║╚██████╔╝
  ╚═══╝  ╚═╝╚══════╝ ╚═════╝ ╚═╝  ╚═╝╚══════╝    ╚══════╝   ╚═╝    ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝

Visual Studio Integration Guide

Focus: Extending the Visual Studio IDE's editor and tooling — building VSIX extensions with the modern out-of-process VisualStudio.Extensibility SDK and the classic VSSDK + MEF editor model (classifiers, adornments, taggers), and driving builds/tests from Claude Code via MSBuild and the dotnet/devenv CLIs.

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.Extensibility SDK is still versioned 17.14.x on NuGet (see the packages table) and targets net8.0, so it installs into VS 2022 17.9+ and VS 2026. Check Help > About or devenv /version before 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:

ModelProcessAPI styleUse it when
VisualStudio.Extensibility SDK (new)Out-of-processAsync, [VisualStudioContribution], hot-reloadNew commands, editor listeners, tool windows, LSP — start here
VSSDK + MEF (classic)In-process (COM)[Export]/[Import], requires VS restartDeep 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


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:

PackagePurposeVersion at review
Microsoft.VisualStudio.Extensibility.SdkCore out-of-process SDK17.14.40608
Microsoft.VisualStudio.Extensibility.BuildMSBuild targets that pack the .vsix17.14.40608
Microsoft.VisualStudio.SDK (classic)Meta-package for VSSDK/MEF editor APIsmatches your VS version (e.g. 17.x)
Microsoft.VSSDK.BuildTools (classic, CI)Restores VsSDK.targets so classic VSIX builds without a full VS install18.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.Extensibility SDK's latest stable NuGet release is 17.14.40608 (the Microsoft.VSSDK.BuildTools classic-CI package has moved to 18.9.x). Pin 17.14.40608 for the SDK — it is current, not stale — and re-check the NuGet link before a release.


Prerequisites

Text
Visual Studio 2022 17.9+  (or Visual Studio 2026)  with the "Visual Studio extension development" workload
.NET 8 SDK

The 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:

XML
<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:

CSHARP
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:

CSHARP
[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

Bash
# 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.vsix

Setup — 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 the Extension with RequiresInProcessHosting = true, and keep the source.extension.vsixmanifest with ExtensionType = VSSDK+VisualStudio.Extensibility. For VS 2022 this variant targets .NET Framework 4.7.2 (not net8.0), because in-process code runs inside devenv.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)

CSHARP
[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)

CSHARP
[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

HookExportReach for it when
ClassifierIClassifierProviderColor/categorize spans of text
TaggerITaggerProviderAttach typed tags (errors, outlining, highlights) to spans
AdornmentAdornmentLayerDefinition + IWpfTextViewCreationListenerDraw WPF visuals over/under text
CompletionIAsyncCompletionSourceProviderCustom IntelliSense
MarginIWpfTextViewMarginProviderAdd a gutter/margin UI strip

MEF components are in-process and lazy — Visual Studio only constructs them when a matching ContentType view 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:

Bash
# 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
JavaScript
// 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, not dotnet build — a classic VSSDK solution that contains a VSIX project won't build with dotnet build even when the projects are SDK-style. Use msbuild directly. (Modern VisualStudio.Extensibility SDK projects can use dotnet build, but msbuild builds 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-latest moved to VS 2026. As of 2026 the windows-latest label maps to Windows Server 2025 with Visual Studio 2026 (18.x); Visual Studio 2022 is no longer on that image. So vs-version: '17.0' on windows-latest no longer resolves. Pick one deliberately: pin runs-on: windows-2022 to keep the VS 2022 (17.x) toolset (shown below — matches the 17.14 SDK line), or stay on windows-latest and bump the pin to vs-version: '18.0' for the VS 2026 toolset. Don't leave a 17.0 pin on windows-latest.

Workflow — .github/workflows/build-vsix.yml

YAML
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: error

Gotcha — 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 on windows-latest/Server 2025) plus the .NET workloads, but a classic VSSDK build also needs the Visual Studio extension development workload (the Microsoft.VsSDK.targets that pack the .vsix). The full VS install usually includes it, but if you hit error MSB4019: The imported project "...Microsoft.VsSDK.targets" was not found, the SDK targets aren't on the runner. Fixes, cheapest first: reference the Microsoft.VSSDK.BuildTools NuGet 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 modern VisualStudio.Extensibility SDK sidesteps this entirely — its .Build package brings the packaging targets in as a normal PackageReference.


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).

SurfaceWhat it does
Completions + next edit suggestionsInline 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 agentRead-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 modeMulti-step edits across solution files, iterating on build errors and running tools. The evolution of Copilot Edits. Required to use MCP servers.
MCP serversAgent 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 CaseApproach
Insert/transform text at caretNew SDK Command + Editor().EditAsync
Syntax highlighting for a custom languageVSSDK IClassifierProvider + ClassificationFormatDefinition
Squiggles / error tagsVSSDK ITaggerProvider<IErrorTag>
Inline visuals (CodeLens-like)VSSDK adornment layer + IWpfTextViewCreationListener
Custom IntelliSenseIAsyncCompletionSourceProvider
Language server integrationNew SDK LSP extension contribution
AI codegen / multi-step edits in the IDEGitHub Copilot agent mode (pick a Claude model) or Claude Code CLI in the terminal
CI build of the .vsixdotnet build / msbuild in GitHub Actions (Windows runner)

Troubleshooting

IssueFix
Extension not loadingCheck the Experimental Instance: devenv /rootSuffix Exp; reset with /resetSettings
MEF component never constructedContentType mismatch — verify the [ContentType] matches the open file's type
.vsix not producedEnsure Microsoft.VisualStudio.Extensibility.Build (or the VSSDK targets) is referenced
Stale extension after rebuildClear 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 CIUse the microsoft/setup-msbuild action or build with dotnet for SDK-style projects
No Ask / Plan / Agent options in Copilot ChatYou'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 .vsix is 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, use execFileSync(cmd, [args]) with array arguments, consistent with the project security convention.