자동완성, 정의로 이동, 참조 찾기, 진단 메시지는 이제 당연한 개발 환경처럼 느껴집니다. 하지만 이 기능을 모든 에디터와 모든 언어에 각각 구현해야 한다면 이야기가 달라집니다.
예를 들어 에디터가 10개이고 언어가 20개라면 조합은 200개입니다. 각 에디터마다 Go, Rust, TypeScript, Python 지원을 따로 만들고 유지해야 합니다. 이것이 LSP 이전 개발 도구 생태계의 전형적인 문제였습니다.
Language Server Protocol, 즉 LSP는 이 문제를 에디터와 언어 사이의 표준 프로토콜로 풀었습니다.
LSP가 해결한 문제
LSP의 핵심은 단순합니다. 에디터는 LSP 클라이언트가 되고, 언어별 분석 도구는 Language Server가 됩니다. 둘은 JSON-RPC 기반 프로토콜로 대화합니다.
이 구조가 생기면 각 에디터는 모든 언어를 직접 알 필요가 없습니다. LSP 클라이언트만 구현하면 됩니다. 각 언어도 모든 에디터를 직접 알 필요가 없습니다. Language Server 하나를 만들면 여러 에디터에서 같은 언어 기능을 쓸 수 있습니다.
즉, M개의 에디터와 N개의 언어가 있을 때 M×N개의 구현이 아니라 M+N개의 구현으로 줄어듭니다.
클라이언트와 서버의 역할
LSP에서 클라이언트는 보통 VS Code, Neovim, Emacs, Zed 같은 에디터입니다. 서버는 rust-analyzer, gopls, pyright, tsserver 같은 언어 지능 도구입니다.
클라이언트는 사용자가 파일을 열고 수정하고 저장하는 이벤트를 서버에 전달합니다. 서버는 해당 언어의 문법, 타입, 프로젝트 구조를 분석해서 필요한 응답을 돌려줍니다.
대표적인 흐름은 다음과 같습니다.
- 에디터가 서버 프로세스를 시작합니다.
initialize요청으로 클라이언트와 서버의 기능을 협상합니다.- 파일이 열리면
textDocument/didOpen알림을 보냅니다. - 파일이 바뀌면
textDocument/didChange알림을 보냅니다. - 서버는 오류와 경고를
textDocument/publishDiagnostics로 보냅니다. - 사용자가 자동완성이나 정의 이동을 요청하면 클라이언트가 별도 요청을 보냅니다.
이 구조 덕분에 언어 서버는 에디터 UI와 분리된 독립 프로세스로 동작합니다. 무거운 분석 작업을 별도 프로세스에서 처리할 수 있고, 같은 서버를 여러 편집 환경에서 재사용할 수 있습니다.
LSP는 기능 목록이 아니라 계약입니다
LSP를 단순히 자동완성 프로토콜로 이해하면 부족합니다. LSP는 에디터와 언어 서버가 서로 무엇을 지원하는지 협상하는 계약입니다.
어떤 서버는 코드 액션을 지원하고, 어떤 서버는 시맨틱 토큰을 지원합니다. 어떤 클라이언트는 워크스페이스 폴더 변경을 보낼 수 있고, 어떤 클라이언트는 일부 기능만 구현합니다. 초기화 단계에서 이런 기능을 capability로 주고받기 때문에 서로 다른 도구가 같은 프로토콜 안에서 동작할 수 있습니다.
이 점이 중요합니다. LSP는 모든 언어 기능을 완전히 동일하게 만드는 표준이 아닙니다. 서로 다른 도구가 최소한의 공통 언어로 통신할 수 있게 하는 표준입니다.
AI 코딩 도구에도 LSP가 중요한 이유
AI 코딩 도구가 코드베이스를 이해하려면 단순 문자열 검색만으로는 부족합니다. 함수 정의가 어디 있는지, 타입이 무엇인지, 어떤 진단이 발생했는지, 심볼이 어디에서 참조되는지 알아야 합니다.
이 정보는 이미 Language Server가 잘 만들고 있습니다. 그래서 AI 도구도 LSP 정보를 활용하면 단순한 텍스트 생성에서 벗어나 더 의미론적인 작업을 할 수 있습니다.
물론 LSP가 AI의 모든 문제를 해결하지는 않습니다. LSP는 코드의 구조와 언어 지능을 제공합니다. 제품 요구사항, 런타임 데이터, 배포 환경, 사용자 의도는 여전히 별도의 맥락으로 제공해야 합니다.
피해야 할 오해
LSP를 도입하면 개발자 경험이 자동으로 좋아진다고 생각하기 쉽습니다. 하지만 실제로는 서버 품질과 프로젝트 설정이 중요합니다.
예를 들어 TypeScript 프로젝트에서 tsconfig.json이 잘못되어 있으면 서버는 잘못된 프로젝트 경계를 분석합니다. Go 프로젝트에서 모듈 경로가 꼬이면 gopls도 정확한 정보를 주기 어렵습니다. Rust 프로젝트에서 매크로나 빌드 스크립트가 무거우면 rust-analyzer의 응답성이 떨어질 수 있습니다.
LSP는 표준 통신 방식이지, 프로젝트 설정 오류까지 대신 고쳐주는 도구는 아닙니다.
정리
LSP의 가치는 자동완성 하나에 있지 않습니다. 에디터와 언어 도구 사이의 결합을 줄이고, 언어 지능을 재사용 가능한 서버로 분리했다는 점에 있습니다.
개발 도구를 고를 때도 이 관점이 유용합니다. 어떤 에디터가 마음에 드는지와 별개로, 내가 쓰는 언어의 Language Server가 성숙한지, 프로젝트 설정을 제대로 읽는지, 진단과 코드 액션이 안정적인지를 봐야 합니다.
LSP는 개발 환경의 보이지 않는 인프라입니다. 잘 동작할 때는 티가 나지 않지만, 무너지면 생산성 전체가 흔들립니다.
참고
Read more
DMARC, 이메일 도메인을 피싱에서 지키는 최소한의 정책
SPF와 DKIM만으로 부족한 이유, DMARC가 인증 실패 메일을 어떻게 처리하고 보고서를 통해 운영 상태를 보여주는지 정리합니다.
EKS IRSA, Pod에 AWS 권한을 나눠주는 현실적인 방법
EKS에서 IRSA가 어떤 보안 문제를 해결하는지, OIDC와 ServiceAccount를 통해 Pod 단위 IAM 권한이 연결되는 방식을 정리합니다.
Next.js standalone은 Docker 이미지를 어떻게 줄이는가
Next.js standalone output이 어떤 파일을 만들고, Docker 배포에서 public과 static 자산을 왜 따로 복사해야 하는지 정리합니다.