코드 에이전트가 파이썬 함수의 인자 타입을 알고 싶을 때 지금은 파일을 읽고 추측하거나 grep을 돌립니다. Microsoft가 공개한 Type Server Protocol(TSP)은 그 질문을 JSON-RPC 요청 하나로 바꿀 수 있는 계약입니다.
Python 개발 블로그의 발표 글은 10월 21일자이고, 작성자는 Brett Cannon입니다. 명세는 Pylance 저장소의 type-server-protocol.md에 있습니다. 이 글은 두 문서를 읽고 구조를 풀어 쓴 것입니다. TSP를 실제로 구현한 서버에 붙여 본 것은 아닙니다.
무엇을 분리하려는 프로토콜인가
발표 글에 따르면 TSP는 LSP를 확장하는 개방형 표준입니다. VS Code 안에서 IDE 지능(자동 완성, 코드 이해, Copilot Chat 연동 등)은 Pylance가 맡고, 그 아래의 타입 검사기는 교체 가능한 정적 분석 부품이 됩니다. 기본 검사기는 계속 Pyright입니다. Pyrefly를 Pylance에 연결하는 것이 이번 발표의 구체적 사례이며, 요구 버전은 Pylance 2026.4.1 이상, Pyrefly 0.60.0 이상입니다. 글은 Copilot 통합과 다른 Pylance 기능이 어떤 검사기를 쓰든 그대로라고 밝힙니다.
즉 TSP는 "AI 에이전트용 API"로 설계되었다고 발표된 것이 아니라, 에디터와 타입 검사기 사이의 계약입니다. 발표 글이 타입 검사를 소프트웨어 에이전트를 위한 정적 의미 분석의 기반이라고 설명하긴 하지만, 에이전트가 바로 이 프로토콜을 호출한다고는 쓰지 않았습니다. 에이전트에 연결할 수 있다는 것은 구조에서 나온 해석입니다.
명세의 뼈대
전송은 JSON-RPC이고, LSP의 Position·Range 같은 데이터 모양을 재사용하며 URI는 문자열입니다. 메서드는 typeServer/ 접두사를 씁니다. 주 연결은 stdio나 Node IPC로 열고, 시작은 표준 LSP initialize/initialized입니다.
| 메서드 | 방향 | 하는 일 |
|---|---|---|
getSupportedProtocolVersion |
클라이언트→서버 | semver 문자열 반환 |
getSnapshot |
클라이언트→서버 | 현재 스냅샷 번호 |
snapshotChanged |
서버→클라이언트 | { old, new } 알림 |
connection |
클라이언트→서버 | 추가 연결 열기·닫기 |
getPythonSearchPaths |
클라이언트→서버 | 검색 경로 목록 |
resolveImport |
클라이언트→서버 | import를 문서 URI로 해석 |
getComputedType / getDeclaredType / getExpectedType |
클라이언트→서버 | 타입 질의 3종 |
타입 질의 세 가지의 차이가 실무에서는 가장 유용합니다. 명세의 예를 그대로 옮기면 이렇습니다.
getComputedType: 코드 흐름 추론이 낸 타입.a가int로 좁혀진 뒤b = a + 1의b는int.getDeclaredType: 코드에 명시된 타입.def foo(a: int | str)의a는int | str.getExpectedType: 문맥이 기대하는 타입.foo(4)의 인자는int | str을 기대.
같은 변수에 대해 세 값이 다를 수 있다는 점이 중요합니다. 에이전트가 "이 인자에 무엇을 넘겨야 하나"를 물을 때는 declared나 expected가, "지금 이 값은 무엇인가"를 물을 때는 computed가 맞습니다.
스냅샷이 계약의 중심이다
스냅샷은 분석 상태의 시점을 가리키는 음이 아닌 정수입니다. 서버는 파일·설정·감시 파일·의존성이 바뀌거나 캐시가 초기화되어 이전 결과가 무효가 될 수 있을 때 번호를 올립니다. 세션 안에서 번호는 줄어들지 않습니다. 낡은 스냅샷으로 온 요청은 -32802(ServerCancelled)로 실패해야 하고, 이것은 클라이언트에게 새 스냅샷으로 다시 시도하라는 신호입니다. 반환된 객체는 별도 언급이 없으면 그 스냅샷 안에서만 유효하며, 타입 id는 응답 안에서만 의미가 있어 다른 응답의 값과 비교하면 안 됩니다.
에이전트에게는 이 규칙이 곧바로 설계 제약이 됩니다. 에이전트가 파일을 편집하는 동안 스냅샷은 계속 바뀌므로, 이전 질의 결과를 캐시해 두었다가 편집 후에 쓰면 틀릴 수 있습니다.
스냅샷 재시도를 모의 서버로 확인하기
아래는 명세의 규칙을 흉내 낸 모의 서버와 클라이언트입니다. Pylance나 Pyrefly가 아니며, 메시지 틀은 LSP식 Content-Length 헤더로 가정했습니다. 명세 페이지는 stdio에서 stdout이 JSON-RPC 전용이라고만 적고 있어, 이 헤더 형식은 제 가정입니다. 직접 실행했고 실제 서버와의 호환성은 확인하지 않았습니다.
STALE = -32802
class MockServer:
def __init__(self): self.snapshot = 0
def edit_file(self): self.snapshot += 1 # 변경이 생기면 스냅샷 번호가 오른다
def handle(self, req):
m, p = req["method"], req.get("params", {})
if m == "typeServer/getSnapshot":
return {"result": self.snapshot}
if p.get("snapshot") != self.snapshot:
return {"error": {"code": STALE, "message": "stale snapshot"}}
if m == "typeServer/getComputedType":
return {"result": {"kind": 3, "id": 1, "flags": 0}}
def get_type_with_retry(server, node):
snap = call(server, 1, "typeServer/getSnapshot")["result"]
for attempt in range(3):
r = call(server, 2, "typeServer/getComputedType", {"arg": node, "snapshot": snap})
if "error" not in r:
return r["result"], attempt
if r["error"]["code"] != STALE:
raise RuntimeError(r)
snap = call(server, 1, "typeServer/getSnapshot")["result"] # 새 스냅샷으로 재시도
raise RuntimeError("too many stale retries")
(call은 요청을 Content-Length 틀로 감싸 서버에 전달하는 보조 함수이며 전체 코드는 증거 폴더에 있습니다.) 파일 편집 뒤에 낡은 스냅샷으로 질의하면 -32802가 오고, 새 스냅샷을 받아 다시 물으면 결과가 옵니다.
old snapshot -> {'error': {'code': -32802, 'message': 'stale snapshot'}}
with retry -> ({'kind': 3, 'id': 1, 'flags': 0}, 0)
다중 연결과 아직 모르는 것들
선택 기능인 다중 연결은 양쪽이 capabilities.experimental.typeServerMultiConnection을 알리고 공통 전송이 있을 때만 켜집니다. 내장 전송은 ipc뿐이고, 추가 연결은 읽기 전용 요청 6개(getSupportedProtocolVersion, getSnapshot, getPythonSearchPaths, resolveImport, 타입 질의 3종)만 실을 수 있으며 이는 TSP 0.4.1부터입니다. 다중 연결은 성능 옵션이어서 서버는 단일 연결에서도 정확히 동작해야 합니다.
기억해 둘 한계가 몇 가지 있습니다.
- 버전이
0.x라서 마이너 버전 변경이 호환성을 깰 수 있다고 명세가 밝힙니다. 0.1.0부터 0.4.1까지 이력이 있고, 다중 연결 기능은experimental키 아래에 있습니다. Type,Declaration,Node,moduleDescriptor의 전체 스키마는 해당 페이지에 없습니다. 권위 있는 정의는typeServerProtocol.ts,tsp.json,tsp.schema.json입니다.- 발표 글에는 이 프로토콜로 코딩 에이전트의 성능이 얼마나 올라가는지에 대한 수치가 없습니다. 제가 측정한 것도 없으므로, 효과는 별도로 검증해야 합니다.
에이전트에 붙여 볼 때의 실험 설계
붙여 볼 가치가 있는지 판단하려면 비교 조건을 먼저 정해야 합니다. 같은 과제 묶음을 두 방식으로 돌립니다.
A: 에이전트가 파일을 읽고 grep으로 타입을 추측한다
B: 에이전트가 타입 질의 도구를 추가로 쓴다 (스냅샷 불일치 시 재시도)
고정: 모델, 하네스, 과제, effort, 시행 횟수(과제당 3회 이상)
기록: 통과율, 타입 오류 재작업 횟수, 도구 호출 수, 비용, 시간
개선이 보이더라도 타입 질의 때문인지, 도구가 하나 늘어 에이전트가 더 신중해졌기 때문인지는 분리해서 봐야 합니다. 이 비교는 Claude Code와 하네스 설정이 결과를 바꾼다는 점과 같은 이유로 중요합니다.
정리
TSP는 타입 검사기를 갈아 끼우기 위한 JSON-RPC 계약이고, 그 중심에는 스냅샷과 -32802 재시도 규칙이 있습니다. 에이전트에 연결하면 추측 대신 질의를 쓸 수 있다는 가능성은 열리지만, 그 효과는 아직 수치로 확인된 바 없습니다. 먼저 작은 과제 묶음으로 위 비교 실험을 해 보는 것이 가장 싼 검증입니다.
글 끝의 유료 홍보 링크는 Claude Code 하네스와 에이전트 작업 흐름을 다루는 강의입니다. Pylance나 TSP 구현을 가르치는 강의가 아닙니다.