OpenCode로 구성하는 TypeScript LSP 자기수정 루프 — AI가 타입 에러를 스스로 잡는다
AI 코딩 에이전트를 실무에 써보면 어느 순간 같은 벽에 부딪히게 됩니다. 에이전트가 코드를 그럴듯하게 생성해 놓았는데, tsc를 돌리면 타입 에러가 줄줄이 쏟아지는 상황이죠. 그럼 에러 메시지를 복사해서 채팅창에 붙여넣고, 수정된 코드를 받아서 다시 실행하고, 또 에러를 붙여넣고... 저도 처음엔 이 흐름을 당연하게 받아들이다가, 어느 날 문득 '이걸 에이전트가 스스로 처리하게 할 수는 없을까?'라는 생각이 들었습니다. 그때 만난 개념이 LSP 통합 에이전트입니다.
아이디어는 단순합니다. IDE가 타입 에러를 실시간으로 잡아주는 그 메커니즘, 즉 Language Server Protocol(LSP)을 에이전트의 실행 루프 안에 직접 연결하는 것입니다. 에이전트가 파일을 편집하는 순간 LSP 서버가 진단을 내보내고, 그 진단이 다음 LLM 컨텍스트에 자동으로 주입되어 모델이 스스로 에러를 수정하는 **자기수정 루프(self-correction loop)**가 형성됩니다. 이 루프를 별도 인프라 없이 빠르게 구성할 수 있는 도구가 SST 팀이 Go로 만든 오픈소스 터미널 에이전트 OpenCode입니다. LSP 연결을 기본 제공하는 몇 안 되는 에이전트 중 하나로, 30개 이상의 언어 서버를 빌트인으로 내장하고 있습니다.
이 루프가 실제로 얼마나 중요한지는 수치로 확인할 수 있습니다. Anthropic의 2026 Agentic Coding Trends Report에 따르면, SWE-bench Verified 기준으로 동일한 모델이라도 에이전트 스캐폴딩 설계 차이만으로 최대 17문제 격차가 발생합니다. 모델을 바꾸지 않아도 이 정도 차이가 날 수 있다는 점 — 저는 그게 이 설정 하나에 시간을 쏟게 된 이유였습니다.
핵심 개념
AI 에이전트와 LSP: 왜 연결이 필요한가
TypeScript 파일을 VS Code에서 열었을 때 빨간 밑줄이 실시간으로 생기는 건 LSP 덕분입니다. 에디터는 typescript-language-server라는 별도 프로세스와 JSON-RPC(에디터와 언어 서버가 메시지를 주고받는 경량 통신 형식)로 대화하면서 파일 변경 이벤트를 보내고, 서버가 컴파일러 수준의 진단을 돌려줍니다.
문제는 기존 AI 코딩 에이전트 대부분이 이 피드백 채널을 실행 루프 안에 갖고 있지 않다는 점입니다. 에이전트는 텍스트를 생성하고 파일에 쓰지만, 그 결과가 타입 시스템 관점에서 올바른지 즉시 알 방법이 없습니다. 결국 "실행해보고 에러 확인"이라는 느린 사이클이 반복됩니다.
💡 LSP 통합 에이전트란 Language Server Protocol을 AI 에이전트의 실행 루프 안에 연결하여, 파일 편집 직후 컴파일러 수준의 진단(타입 에러, 미정의 참조, 미사용 임포트 등)을 실시간으로 받아 스스로 교정하는 아키텍처를 말합니다.
자기수정 루프의 작동 구조
OpenCode가 구현한 루프는 아래 단계를 에러가 사라질 때까지 반복합니다.
편집 (Edit)
↓
LSP 진단 수집 (150ms 디바운스 + 중복 제거)
↓
LLM 컨텍스트 재구성 (진단 주입)
↓
재수정 (Re-edit)
↓
에러 없음? → 종료
에러 있음? → 다시 편집 (최대 N회 반복 후 중단)150ms 디바운스는 파일 저장 직후 LSP 서버가 안정화될 시간을 주기 위한 처리입니다. 솔직히 저도 이 디테일을 처음엔 그냥 지나쳤는데, 커스텀 파이프라인에서 이 대기를 빼먹으면 불완전한 분석 결과가 컨텍스트에 섞여 노이즈 진단이 꽤 많이 들어옵니다. 최대 반복 횟수 제한도 루프 설계의 중요한 부분입니다 — 이게 없으면 에러 A를 고치면 에러 B가 생기고, B를 고치면 A가 재발하는 순환 상황에서 무한 루프가 생길 수 있습니다.
진단이 LLM 컨텍스트에 주입되는 방식을 실제로 보면 다음과 같습니다. LSP가 돌려주는 원시 진단은 JSON 형태입니다.
{
"uri": "file:///workspace/src/utils.ts",
"diagnostics": [
{
"range": { "start": { "line": 6, "character": 13 } },
"severity": 1,
"code": "TS2339",
"message": "Property 'displayName' does not exist on type 'User'. Did you mean 'name'?"
}
]
}OpenCode는 이것을 에이전트가 읽기 쉬운 자연어 형태로 변환해 다음 프롬프트에 삽입합니다.
[LSP 진단] src/utils.ts 7행
error TS2339: Property 'displayName' does not exist on type 'User'.
Did you mean 'name'?모델은 이 진단을 컨텍스트로 받아 다음 편집에서 user.displayName을 user.name으로 수정합니다. 파일을 다시 저장하면 LSP가 재진단하고, 에러가 없으면 루프가 종료됩니다.
주요 LSP 서버 선택지
| 서버 | 패키지명 | 특징 |
|---|---|---|
| typescript-language-server | typescript-language-server |
TypeScript/JS 공식 LSP 서버 |
| vtsls | @vtsls/language-server |
VS Code TypeScript 서버 기반, 고성능 |
| tsgo | (차세대) | TypeScript Go 재구현, LSP 호환 (실험적) |
lsp: true 한 줄만 설정하면 OpenCode가 파일 확장자에 맞는 서버를 자동으로 골라 기동합니다. .ts나 .tsx 파일에는 TypeScript 서버가, .py 파일에는 Python 서버가 시작되는 방식입니다.
실전 적용
루프가 실제로 하는 일: Before / After
설정으로 들어가기 전에, 루프가 실제로 무엇을 하는지 한 번 보겠습니다. 아래처럼 타입 에러가 있는 코드가 있다고 가정해봅니다.
Before — 에이전트가 생성한 코드 (타입 에러 포함):
interface User {
id: number;
name: string;
}
function getUserLabel(user: User): string {
return user.displayName; // 존재하지 않는 프로퍼티
}LSP가 이 상태를 즉시 감지하고 에이전트 컨텍스트에 다음 진단을 주입합니다.
[LSP 진단] src/utils.ts 7행
error TS2339: Property 'displayName' does not exist on type 'User'.
Did you mean 'name'?에이전트는 이 진단을 보고 다음 편집에서 스스로 수정합니다.
After — 자기수정 루프 한 사이클 후:
function getUserLabel(user: User): string {
return user.name; // LSP 진단을 보고 자동 수정됨
}LSP가 재진단했을 때 에러가 없으면 루프가 종료됩니다. 이 과정이 수작업 없이 에이전트 안에서 자동으로 돌아가는 것이 핵심입니다.
예시 1: lsp: true 한 줄로 자동 구성하기
가장 빠른 시작 방법입니다. 프로젝트 루트에 opencode.json을 만들고 아래 한 줄만 넣어볼 수 있습니다.
{
"$schema": "https://opencode.ai/config.json",
"lsp": true
}이렇게 하면 OpenCode 빌트인 30개 이상의 LSP 서버가 파일 확장자에 따라 자동으로 기동됩니다. TypeScript 프로젝트라면 별도로 서버를 설치하거나 경로를 지정하지 않아도 됩니다.
| 설정 | 동작 |
|---|---|
"lsp": true |
빌트인 서버 전체 자동 활성화 |
.ts/.tsx 파일 접근 시 |
TypeScript LSP 서버 자동 시작 |
| 별도 설치 불필요 | OpenCode가 필요한 서버를 자동 관리 |
⚠️ 주의: 설정이 조용히 실패하는 경우가 있어서, OpenCode 실행 로그에서
typescript-language-server started같은 메시지가 보이는지 확인해보는 것이 좋습니다. 메시지가 없다면 다음 예시처럼 명시적 설정으로 넘어가는 방법이 있습니다.
예시 2: vtsls로 세부 설정하기
임포트 경로 스타일이나 진단 엄격도를 직접 제어하고 싶을 때는 명시적 설정이 유용합니다. 먼저 서버를 전역 설치합니다.
pnpm install -g @vtsls/language-server typescript그 다음 opencode.json에서 아래처럼 구성합니다.
{
"$schema": "https://opencode.ai/config.json",
"lsp": {
"typescript": {
"command": "vtsls",
"args": ["--stdio"],
"initialization": {
"preferences": {
"importModuleSpecifierPreference": "relative"
}
}
}
}
}initialization은 OpenCode 전용 키 이름으로, LSP 표준의 initializationOptions와 구분됩니다. importModuleSpecifierPreference를 "relative"로 설정하면 에이전트가 임포트를 생성할 때 절대 경로 대신 상대 경로를 우선합니다. 팀 컨벤션에 따라 "non-relative"나 "shortest"로 바꿔볼 수도 있습니다.
예시 3: Docker로 격리 환경 구성하기
신뢰하지 않는 레포나 CI 파이프라인에서 LSP를 돌릴 때는 격리 환경을 쓰는 것이 안전합니다. blackwell-systems/agent-lsp 이미지를 활용하면 됩니다.
docker run --rm -i \
-v /your/project:/workspace \
ghcr.io/blackwell-systems/agent-lsp:typescript \
typescript:typescript-language-server,--stdio각 플래그가 하는 일을 짧게 짚으면:
-v /your/project:/workspace— 호스트 프로젝트 디렉토리를 컨테이너 안의/workspace에 마운트--stdio— LSP 서버가 표준 입출력(stdin/stdout)으로 통신하도록 지정
이 방식은 호스트 환경에 전역 패키지를 설치하지 않아도 되고, LSP 서버의 임의 커맨드 실행 위험도 컨테이너 안에 격리할 수 있어 보안상 이점이 있습니다.
💡
agent-lsp는 AI 에이전트가 LSP 서버와 통신할 수 있도록 표준화된 컨테이너 환경을 제공하는 런타임입니다. 공식 README 기준으로 30개 언어를 지원합니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 컴파일 전 에러 제거 | 실행 전에 타입 에러를 스스로 잡아 수작업 피드백 사이클을 대폭 단축 |
| 멀티파일 리팩터링 품질 | 여러 파일에 걸친 리팩터링 시 LSP 피드백 없는 에이전트 대비 유의미한 품질 차이 발생 |
| IDE 수준 코드 지능 | 정의 이동, 참조 추적, 자동완성 등 IDE 기능을 에이전트 루프에 통합 가능 |
| 설정 단순화 | lsp: true 한 줄로 30개 이상의 빌트인 서버가 자동 작동 |
| 하네스 설계가 결과를 좌우 | 동일 모델에서 에이전트 스캐폴딩 차이만으로 벤치마크 17문제 격차 발생 가능 |
벤치마크 수치 출처: Anthropic 2026 Agentic Coding Trends Report, SWE-bench Verified 기준
표만 보면 장밋빛으로 보이지만, 실제로 적용해보면 알아야 할 함정들이 몇 가지 있습니다.
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 모노레포 지원 미흡 | 루트에 package.json + node_modules가 없으면 TypeScript LSP가 활성화되지 않는 버그 (이슈 #16335) |
각 워크스페이스 패키지 디렉토리 안에서 OpenCode 실행, 또는 opencode.json을 패키지별로 분리 배치 |
| 진단 미노출 문제 | 일부 구성에서 read/patch 등 일반 파일 도구 사용 시 TypeScript 진단이 에이전트 컨텍스트에 노출되지 않음 (이슈 #16880) |
OpenCode 내장 편집 도구 사용을 우선, 이슈 해결 버전 업그레이드 추적 |
| 무한 수정 루프 위험 | 에러 A 수정 → 에러 B 발생 → B 수정 → A 재발 순환 가능 | 루프 반복 횟수 상한 설정, 근본 타입 설계 검토 후 재시도 |
| 보안 위험 | opencode.json의 LSP command 배열에 임의 셸 명령 주입 가능 |
신뢰하지 않는 레포의 opencode.json을 열기 전 반드시 내용 확인 |
| 컨텍스트 비용 | 진단이 많아질수록 LLM 컨텍스트 소모 증가 | 진단 필터링(에러만, 경고 제외) 또는 파일 범위 제한 |
실무에서 가장 흔한 실수
-
lsp: true만 켜놓고 서버가 실제로 작동 중인지 확인하지 않는 경우 — OpenCode 실행 로그에서typescript-language-server started같은 메시지가 보이는지 확인해보면 좋습니다. 이 시점에서 메시지가 보이면 루프가 정상 활성화된 겁니다. 조용히 비활성화 상태인 채로 에이전트를 돌리면 LSP 피드백이 없는 기존 방식과 다를 게 없습니다. -
무한 루프를 모델 문제로만 귀결하는 경우 — 에러 A와 에러 B가 순환하는 건 대부분 타입 설계 자체의 문제입니다. 에이전트에게 "에러만 고쳐"를 반복하기보다, 루프가 3회 이상 반복된다면 근본 타입 구조를 재검토해보는 것이 훨씬 효율적입니다.
-
모노레포 환경에서 루트에서 바로 OpenCode를 실행하는 경우 — 현재 알려진 버그로 인해 루트에
node_modules가 없으면 TypeScript LSP가 침묵합니다. 이슈 #16335가 해결되기 전까지는 각 워크스페이스 패키지 디렉토리 안에서 OpenCode를 실행하거나,opencode.json을 패키지별로 분리 배치하는 방법이 잘 작동합니다.
LSAP: 자기수정 루프의 다음 단계
현재 LSP는 에디터용으로 설계된 원자적 연산(파일 열기, 닫기, 편집)을 제공합니다. 에이전트에게 필요한 건 조금 다릅니다 — "이 함수의 모든 사용처를 찾아줘", "이 심볼을 수정하면 어떤 파일들이 영향을 받아?" 같은 고수준 질의를 직접 날릴 수 있는 인터페이스입니다.
**LSAP(Language Server Agent Protocol)**은 바로 이 gap을 메우기 위해 논의 중인 개방형 프로토콜입니다. 정의 탐색, 참조 추적, 편집 영향 범위 분석처럼 에이전트가 코드베이스를 이해하는 데 필요한 연산을 추상화하는 것이 목표입니다. 2026년 현재 활발히 논의 중인 단계로, 이 글에서 다룬 진단 주입 방식이 LSAP에서는 어떻게 달라지는지가 흥미로운 지점입니다.
마치며
처음으로 루프가 돌아가는 걸 직접 봤을 때 — 에이전트가 타입 에러를 스스로 읽고 수정하고 재진단을 받아 종료하는 과정을 지켜보는 건 — 생각보다 꽤 놀라운 경험이었습니다. 이게 되는구나 싶은 그 느낌이요.
LSP를 에이전트 루프 안에 연결하는 것은 모델을 바꾸지 않고도 AI 코딩 품질을 끌어올릴 수 있는 가장 확실한 방법 중 하나입니다.
지금 바로 시작해볼 수 있는 3단계:
-
TypeScript 프로젝트 루트에
opencode.json을 만들고{ "lsp": true }를 추가할 수 있습니다. 이것만으로 빌트인 TypeScript LSP 서버가 자동으로 활성화되며, OpenCode가.ts파일을 편집할 때 자기수정 루프가 작동하기 시작합니다. -
OpenCode 실행 로그에서
typescript-language-server가 정상적으로 기동되었는지 확인해봅니다. 이 메시지가 보이면 LSP 연결이 활성화된 상태입니다. 메시지가 없다면pnpm install -g @vtsls/language-server typescript로 서버를 전역 설치한 뒤 예시 2처럼 명시적 설정으로 전환해볼 수 있습니다. -
에이전트에게 의도적으로 타입 에러가 있는 코드를 수정하도록 요청해봅니다. 진단 메시지가 컨텍스트에 주입되고 에이전트가 스스로 수정하는 과정이 로그에 남습니다. 루프가 한 사이클을 돌 때마다 에러 수가 줄어드는 것을 확인할 수 있을 겁니다.
참고 자료
- Language Server Protocol Integration | sst/opencode | DeepWiki
- LSP Servers | OpenCode 공식 문서
- Config | OpenCode 공식 문서
- TypeScript diagnostics are not surfaced to agent/tool context · Issue #16880
- FEATURE: LSP support for TypeScript monorepos · Issue #16335
- Give Your AI Coding Agent Eyes: How LSP Integrations transform Coding Agents
- LSP — The Protocol Your IDE Uses Every Day (And Now Your AI Agent Does Too)
- GitHub - lsp-client/LSAP: Language Server Agent Protocol
- GitHub - blackwell-systems/agent-lsp
- When "Read This File" Means "Run This Code": LSP Configuration in OpenCode - DEV Community
- OpenCode vs Claude Code (2026): Which AI Coding Agent?
- TypeScript Agent Frameworks in 2026: Loop, Runtime, Sandbox | EveryDev.ai
- Mastering OpenCode — Medium
- 2026 Agentic Coding Trends Report — Anthropic