claude CLI가 에이전트 작업을 다 하고, 이 확장은 그 CLI가 말을 거는 "IDE 쪽 절반"(네이티브 diff 창 + 컴파일 오류 공유 + 선택 영역 인식)만 구현한다. (저장소: firish/claude_code_vs · C# net48 VSIX · MIT · 1.0.0 = 2026-06-15)이 레포가 무엇을 하는 물건인가.
claude CLI는 이미 코드를 읽고 수정 제안을 만드는 모든 에이전트 작업을 한다. 다만 VS Code·JetBrains에서는 그 결과가 IDE 안에서 네이티브하게 보이는데, Visual Studio에는 그 연결고리가 없었다. 이 확장은 CLI가 사용하는 IDE 통합 프로토콜(로컬 WebSocket + MCP/JSON-RPC)을 Visual Studio 쪽에 똑같이 구현해, 터미널에 복붙하던 흐름을 VS의 진짜 diff 창(Accept/Reject)으로 바꾼다.
한 문장으로: "Claude Code의 두뇌(CLI)는 그대로 두고, Visual Studio라는 새 몸(IDE)에 신경을 연결하는 어댑터"다. 모델 호출도, 에이전트 루프도, 스킬·플러그인도 이 확장은 만들지 않는다 — 그건 전부 CLI가 공짜로 제공한다.
왜 TrendShift에 오르고 개발자들이 주목하는가.
이 레포가 화제가 된 배경에는 실제로 비어 있던 구멍이 있다. Anthropic 공식 이슈 claude-code#15942에서 "Visual Studio(VS Code 아님) 통합이 없다"는 요청이 쌓였는데, 공식 지원을 기다리는 대신 한 개발자가 문서도 없는 프로토콜을 역설계해 직접 메웠다는 점이 인상적이다.
대부분의 CLI 에이전트는 터미널에서 y/n으로 수정을 승인한다. 이 확장은 PreToolUse 훅으로 모든 Edit/Write를 가로채 VS의 네이티브 diff 창 한 곳에서만 승인/거절하게 만든다. 터미널의 중복 프롬프트가 사라지고, "보고 → 승인"이 IDE 안에서 한 번에 끝난다.
Claude가 Visual Studio의 오류 목록(Error List)을 직접 읽는다. C#(Roslyn)과 C++(MSVC) 빌드 오류·경고를 그대로 가져와 고친다. "에러 메시지 복붙" 단계가 통째로 사라진다 — IDE가 이미 아는 것을 AI도 알게 하는 것이 통합의 본질이다.
가장 배울 점. 확장은 모델 호출을 0번 한다. 네트워크 호출도, 에이전트 루프도 없다. 오직 CLI가 드라이브하는 12개 IDE 도구(openDiff, getDiagnostics 등)만 구현한다. "이미 있는 걸 다시 만들지 마라"는 원칙을 코드로 증명한 사례다.
① 프로토콜이 비공개·버전 취약. IDE 통합 프로토콜은 문서가 없고 CLI 업데이트로 깨진 적이 있다(known-good = claude 2.1.173). ② VS 2026 전용. VS 2022 지원은 아직 미검증(로드맵). ③ 비용은 추정치. 패널의 토큰 비용은 하드코딩된 단가 기반 어림값이지 실제 청구가 아니다. 이 확장의 안정성은 결국 Anthropic이 프로토콜을 바꾸지 않느냐에 묶여 있다.
VSIX 확장이 무엇으로, 어떤 제약 위에 만들어졌는가.
이 레포는 순수 C# (.NET) 기반 Visual Studio 확장이다. 외부 의존을 최소화하고 VS SDK가 기본 제공하는 것만 쓴 점이 특징이다. 약 4,600줄 / 37개 C# 파일로, 두 개의 어셈블리(Protocol + VSIX)로 나뉜다.
| 영역 | 기술 | 역할 / 선택 이유 |
|---|---|---|
| 런타임 | .NET Framework 4.8 (net48) | VS의 in-proc 서비스(diff·Roslyn·RDT)를 호스팅하려면 in-proc VSIX여야 함. 신형 out-of-proc 모델로는 불가 |
| 확장 SDK | VSSDK + Community Toolkit | 도구 창·명령·diff 뷰어·에디터 어댑터 접근 |
| 통신 | HttpListener (WebSocket) | 127.0.0.1 전용 로컬 서버. 외부 WS 라이브러리 안 씀 |
| 프로토콜 | MCP over JSON-RPC 2.0 | CLI가 IDE 도구를 호출하는 규약. tools/list·tools/call |
| 직렬화 | Newtonsoft.Json | VS가 바인딩 리디렉션과 함께 기본 제공 |
| 훅 스크립트 | PowerShell (.ps1) | PreToolUse/Stop 훅. 수정 내용 재구성 → /permission 전송 |
| diff 코어 | IVsDifferenceService 등 | VS 네이티브 차이 뷰어 + InfoBar(Accept/Reject) |
| 진단 | Error List (Roslyn·MSVC) | C#·C++ 빌드 오류를 LSP 형태로 변환해 반환 |
| 쓰기 반영 | IVsRunningDocumentTable(RDT) | 열린 에디터 버퍼를 in-place 갱신(리로드 프롬프트 없음) |
이 스택은 "외국 손님(claude CLI)을 위한 전담 통역사 + 안내데스크"다. 손님은 자기 언어(MCP/JSON-RPC)로만 말하는데, 건물(Visual Studio)은 다른 언어를 쓴다. ① 안내데스크(WebSocket 서버)가 손님을 맞고, ② 신분증(auth 토큰)을 확인하고, ③ 손님 요청을 건물 직원이 알아듣는 명령(VS SDK 호출)으로 통역하고, ④ 결과를 다시 손님 말로 돌려준다. 통역사는 스스로 결정(AI 추론)을 하지 않는다 — 오직 전달만 한다.
spike/(콘솔 하네스)에서도 그대로 재사용돼, VS 없이 프로토콜만 빠르게 테스트한다. 의존성 역전의 좋은 예.CLI의 한 번의 요청이 VS diff 창에 닿기까지의 전체 경로.
핵심은 두 개의 채널이다. ① CLI ↔ 확장 사이의 WebSocket(MCP) 채널과, ② 수정을 가로채는 PreToolUse 훅 → HTTP POST /permission 채널. 둘이 합쳐져 "VS diff = 유일한 승인 게이트"가 완성된다.
확장은 시작 시 빈 포트를 잡아 ~/.claude/ide/<port>.lock에 포트·인증토큰·워크스페이스를 적는다. 파일 이름이 곧 포트 번호다. CLI는 이 폴더를 스캔해 "여기 IDE가 있구나"를 발견하고 자동 연결한다. 환경변수(ENABLE_IDE_INTEGRATION, CLAUDE_CODE_SSE_PORT)로 CLI를 띄우면 /ide 명령도 필요 없다.
openDiff 도구는 호출 즉시 응답하지 않는다. 사용자가 Accept/Reject를 누를 때까지 TaskCompletionSource에 응답을 "묶어 두고(park)" 기다린다. UI 핸들러가 나중에 TCS를 완료시켜 응답을 보낸다. 동기적 도구 호출 위에 비동기 사람-개입 게이트를 얹는 깔끔한 기법.
WebSocket 수신 루프는 UI 스레드 밖에서 돈다. 그런데 에디터·솔루션·diff를 만지는 모든 VS SDK 호출은 메인 스레드에서만 안전하다. 그래서 모든 핸들러가 먼저 await ...SwitchToMainThreadAsync(ct)로 갈아탄다. CLAUDE.md가 "이게 1번 버그 원천"이라 못박은 부분.
PreToolUse 훅은 어떤 에러가 나도 항상 allow한다. 브리지를 못 찾거나 통신이 깨져도 CLI를 막지 않는다. "통합이 망가졌다고 도구 자체를 못 쓰게 하지 않는다"는 안전 설계.
CLI는 WebSocket 업그레이드 요청에 Sec-WebSocket-Protocol: mcp 헤더를 실어 보낸다. 서버가 101 응답에서 이 서브프로토콜을 그대로 되돌려주지 않으면, CLI는 인증까지 성공해놓고 initialize 직전에 소켓을 조용히 끊어버린다 — "원인 불명 연결 끊김"처럼 보인다. 문서에 없고, 별도 콘솔 하네스(spike/)로 CLI 2.1.169를 상대로 실험해야 발견되는 함정이다. 이런 비공개 프로토콜은 실험·관찰로만 알 수 있다는 교훈.
37개 C# 파일이 책임별로 어떻게 나뉘는가.
구조에서 읽히는 의도: Protocol(재사용)과 VSIX(VS 종속)의 칼 같은 분리, 도구 하나당 파일 하나(IIdeTool 구현), 그리고 테스트용 spike/를 따로 둬 VS를 띄우지 않고도 프로토콜 회귀를 빠르게 잡는다. demo/의 "의도된 컴파일 에러"는 데모 GIF가 항상 똑같이 재현되도록 만든 장치다.
이 레포를 읽으며 실제로 손에 남기면 좋은 것들.
이 레포의 가장 큰 가치. McpServer.cs는 initialize → tools/list → tools/call로 이어지는 MCP 핸드셰이크를 손으로 짠다. 라이브러리 없이 JSON-RPC 한 프레임을 받아 어떻게 처리하는지 보면 MCP의 실체가 또렷해진다.
// McpServer.HandleAsync — 한 프레임을 받아 메서드별 분기
switch (method)
{
case "initialize": // 클라이언트 protocolVersion을 그대로 에코
return Response(id, BuildInitializeResult(@params));
case "tools/list": // 우리가 가진 12개 IDE 도구 목록 반환
return Response(id, BuildToolsList());
case "tools/call": // 실제 도구 호출 (openDiff·getDiagnostics…)
return Response(id, await BuildToolCallResultAsync(@params, ct));
case "ping": return Response(id, new JObject());
}
배울 핵심: 요청(id 있음) vs 알림(id 없음) 구분, 버전 에코(protocolVersion을 받은 그대로 돌려주기), 반환 포맷(평문 문자열은 그대로, 객체는 JSON 래핑, 에러는 MCP isError 플래그).
"내 PC에서만 도는데 보안이 왜 필요?"의 답이 여기 있다. 서버는 ① 127.0.0.1에만 바인딩(외부 접근 차단), ② HTTP 업그레이드 단계에서 인증 토큰 검증(소켓이 열리기 전에 401로 거절), ③ 토큰은 절대 로그에 안 남김. 로컬 서비스도 같은 PC의 다른 프로세스가 노릴 수 있다는 위협 모델을 보여준다.
// 소켓을 만들기 전에 인증부터 — 미인증 클라이언트는 소켓조차 못 받음
var presented = ctx.Request.Headers[AuthHeader];
if (!string.Equals(presented, _authToken, StringComparison.Ordinal))
{
ctx.Response.StatusCode = 401; // 토큰 값은 로그에 안 찍음
ctx.Response.Close();
return;
}
PowerShell 훅(vs-permission-hook.ps1)은 CLI의 PreToolUse 시점에 끼어들어 수정 내용을 가로챈다. 여기서 배울 디테일: UTF-8 인코딩 함정. stdin·파일·HTTP 본문을 모두 명시적으로 UTF-8로 다루지 않으면 em-dash·스마트 따옴표가 깨져 diff 재구성이 실패한다 — 실무에서 자주 만나는 미묘한 버그.
대부분의 튜토리얼은 문서화된 API를 쓴다. 이 레포는 문서 없는 프로토콜을 콘솔 하네스(spike/)로 실험하며 알아낸 과정 자체가 교재다. CLAUDE.md의 "Gotchas" 섹션을 읽으면, 실험으로 발견한 함정들(서브프로토콜 에코, runningInWindows 플래그, selection_changed 디바운스)이 어떻게 코드 주석으로 박제되는지 볼 수 있다.
이 확장을 쓰거나 빌드하려면 무엇이 필요한가.
| 항목 | 요구사항 | 비고 |
|---|---|---|
| IDE | Visual Studio 2026 | 현재 2026 전용. VS 2022(17.14+)는 매니페스트는 허용하나 미검증 |
| CLI | claude CLI 설치·인증 | known-good = 2.1.173. 확장은 모델 호출을 안 함, CLI 필수 |
| OS | Windows | 로크파일 runningInWindows:true + PowerShell 훅 |
| 진단(C#) | 로드된 프로젝트 | 느슨한 파일(open-folder)은 Error List가 비어 진단 불가 |
| 진단(C++) | "Desktop development with C++" 워크로드 | 없으면 빌드·진단 불가 (demo 가이드 참조) |
| 빌드(소스) | VS 확장 개발 워크로드 + msbuild | msbuild ...ClaudeCodeVS.csproj /t:Rebuild /p:Configuration=Release |
IDE 통합 프로토콜은 비공개라 CLI 패치 한 번에 깨질 수 있다. 저자는 claude --version을 기록하고, spike/로 연결 → mcp__ide__* 도구 노출 → openDiff 발동 → accept/reject 동작 → /permission 응답을 매번 확인하라고 권한다. 이 확장을 쓸 거라면 CLI 자동 업데이트가 통합을 깰 수 있음을 염두에 둬야 한다.
레포를 "읽기"에서 "이해하기"로 바꾸는 난이도별 과제.
VS Code에서 Claude Code를 /ide로 연결한 뒤 ~/.claude/ide/ 폴더의 *.lock 파일을 열어보라. port·authToken·workspaceFolders·ideName 필드가 이 레포의 Lockfile.cs가 만드는 것과 같은 모양임을 확인한다. "IDE 디스커버리"의 실체를 눈으로 본다.
dotnet run --project spike로 콘솔 하네스를 띄우고, 환경변수를 세팅한 claude를 연결한다. 로그에서 initialize → tools/list → ide_connected → closeAllDiffTabs 순서가 흐르는 걸 관찰한다. VS 없이 프로토콜만 보는 가장 빠른 방법.
McpServer.cs를 참고해, 아무 언어로든 127.0.0.1 WebSocket + initialize/tools/list 응답만 하는 더미 서버를 만들어 본다. Sec-WebSocket-Protocol: mcp 에코를 일부러 빼보고, CLI가 정말 조용히 끊는지 재현하면 프로토콜 함정을 체득한다.
C#으로 TaskCompletionSource를 이용해 "도구 호출 → 사용자 입력 대기 → 나중에 응답 완료" 흐름을 구현한다. 콘솔에서 y/n을 받아 TCS를 완료시키는 미니 버전으로 시작해, 사람-개입 게이트가 비동기로 어떻게 동작하는지 익힌다.
ErrorListReader.cs가 VS의 Error List를 LSP 형태([{uri, diagnostics:[]}])로 변환하는 방식을 분석하고, 같은 인터페이스를 다른 IDE(예: Rider, Neovim)의 진단 소스로 채우는 설계를 그려본다. 통합 프로토콜의 "어댑터" 사고법을 연습한다.
이 레포를 온전히 이해하기 위한 4주 코스.
| 주차 | 주제 | 도달 목표 |
|---|---|---|
| 1주차 | JSON-RPC 2.0 + WebSocket 기초 | 요청/응답/알림 구분, 핸드셰이크·서브프로토콜 협상 이해 |
| 2주차 | MCP(Model Context Protocol) | tools/list·tools/call 구조, IDE 통합이 MCP 위에 어떻게 얹히는지 |
| 3주차 | Claude Code 훅 시스템 | PreToolUse/Stop 훅으로 에이전트 흐름에 끼어들기, fail-open 설계 |
| 4주차 | VS SDK / 비동기 스레딩 | UI 스레드 마샬링, TaskCompletionSource, in-proc 서비스 |
| 이후 | 다른 IDE 통합 비교 | VS Code·JetBrains 통합과 대조하며 "프로토콜 어댑터" 패턴 일반화 |
이 레포의 진짜 교훈은 C# 문법이 아니라 "이미 존재하는 시스템에 어떻게 우아하게 끼어드는가"다. 1~2주차(JSON-RPC·MCP)만 잡아도 요즘 폭증하는 MCP 서버·AI 도구 통합을 읽고 만들 수 있다. VS SDK 부분(4주차)은 Visual Studio 확장을 직접 만들 사람만 깊게 파면 된다 — 나머지 사람은 "어댑터 사고법"만 가져가도 충분하다.
레포를 읽다 막히는 용어 빠른 참조.
| MCP | Model Context Protocol. AI 모델/클라이언트가 외부 도구·데이터에 표준 방식으로 접근하는 규약. 이 확장은 IDE 도구를 MCP로 노출한다. |
| JSON-RPC 2.0 | JSON으로 원격 함수 호출을 주고받는 경량 규약. method·params·id 구조. MCP의 전송 기반. |
| VSIX | Visual Studio 확장 프로그램 패키지 형식(.vsix). 더블클릭 또는 마켓플레이스로 설치. |
| in-proc / out-of-proc | 확장이 VS 프로세스 내부에서 도는가(in-proc) 별도 프로세스인가(out-of-proc). diff·Roslyn 등은 in-proc만 호스팅 가능. |
| 로크파일(Lockfile) | ~/.claude/ide/<port>.lock. CLI에게 "IDE가 여기 있다"를 알리는 발견용 파일. 이름이 곧 포트. |
| PreToolUse 훅 | CLI가 도구(Edit/Write 등)를 실행하기 직전에 끼어드는 스크립트. 여기서 수정을 VS diff로 라우팅. |
| single-gate | 승인을 한 곳(VS diff)에서만 받는 설계. 터미널 y/n 중복 제거. |
| getDiagnostics | IDE의 컴파일러 오류/경고를 AI에게 넘기는 도구. 여기선 VS Error List(C#·C++)에서 읽음. |
| openDiff | 제안 수정을 diff로 띄우고 accept/reject를 기다리는 핵심 도구. 응답을 지연(TCS)시킨다. |
| TaskCompletionSource(TCS) | 아직 끝나지 않은 비동기 작업의 결과를 나중에 수동으로 채워 넣는 C# 도구. 사람-개입 대기에 활용. |
| 스레드 마샬링 | 다른 스레드의 작업을 UI(메인) 스레드로 넘겨 안전하게 실행하는 것. SwitchToMainThreadAsync. |
| fail-open | 오류 시 차단하지 않고 허용하는 안전 전략. 통합이 깨져도 CLI를 막지 않음. |
| RDT | IVsRunningDocumentTable. VS에 열려 있는 문서들을 관리. 수정을 리로드 없이 in-place 반영. |
| spike | 본 구현 전 핵심 위험(여기선 비공개 프로토콜)을 검증하는 최소 실험 코드. 회귀 테스트로도 재사용. |