VS Code's Python extension no longer has to use Microsoft's own type checker. On 21 October 2026 Microsoft announced the Type Server Protocol (TSP), a JSON-RPC protocol that lets Pylance hand type analysis to a separate type server, and Meta's Pyrefly is the first alternative you can switch to. Pyright stays the default; Pylance keeps completions, Quick Info, environment management and the GitHub Copilot integration on top of whichever checker you choose.
This article explains what TSP is and how it differs from the Language Server Protocol, walks through a real TSP session against Pyright's type server, shows how to switch Pylance to Pyrefly, and covers who supports it, who refuses to, and why. It is for Python developers using VS Code, and for anyone building Python tooling who wants structured type information rather than hover text.
| What | JSON-RPC protocol for querying Python type-analysis state; methods prefixed typeServer/ (spec) |
| Current version | 0.4.1 (0.x: a minor version may break compatibility) |
| Needs | Pylance 2026.4.1 or newer; Pyrefly 0.60.0 or newer to use Pyrefly |
| Type servers today | Pyright (default), Pyrefly; a ty implementation is proposed but not merged |
| Announced | 21 October 2026 by Microsoft (Brett Cannon) and the Pyrefly team |
What is the Type Server Protocol (TSP)?
TSP is a protocol for asking a type server about types: what a variable's inferred type is, what was declared, what the context expects, and where an import resolves. The spec describes a server that "maintains the type-analysis state for a workspace" and answers requests for the protocol version, snapshots, Python search paths, import resolution and type queries.
It reuses LSP rather than replacing it. Initialisation goes through the normal LSP initialize and initialized messages, documents are synchronised with textDocument/didOpen and didChange, and positions and ranges use LSP's shapes: zero-based lines and UTF-16 code-unit offsets. What TSP adds is a set of typeServer/* requests whose answers are structured type objects, not the Markdown strings an LSP hover returns.
- VS Code → Pylance: LSP
- Pylance → Pyright: TSP
- Pylance → Pyrefly: TSP
- Pylance → ty: TSP (prototype)
Microsoft's framing is that Pylance and Pyright "were previously tightly coupled", and TSP separates them so Pylance is the VS Code intelligence layer and the checker "becomes an interchangeable component underneath it" (Microsoft).
TSP requests and the snapshot model
There are nine messages in version 0.4.1. Every type query names a construct with an arg that is either a Node (a URI plus a source range) or a Declaration, and carries the snapshot it was computed against.
| Message | Direction | Returns |
|---|---|---|
typeServer/getSupportedProtocolVersion | client → server | semver string |
typeServer/getSnapshot | client → server | snapshot number |
typeServer/snapshotChanged | server → client | notification { old, new } |
typeServer/getComputedType | client → server | Type inferred from code flow |
typeServer/getDeclaredType | client → server | Type written in the source |
typeServer/getExpectedType | client → server | Type the context expects |
typeServer/resolveImport | client → server | document URI or nothing |
typeServer/getPythonSearchPaths | client → server | list of paths |
typeServer/connection | client → server | opens or closes an extra read-only IPC connection |
Snapshots are what make it safe. A snapshot is a non-negative integer that never decreases; type handles are only valid for the snapshot that produced them. Edit a file, change configuration or touch a watched file, and the server bumps the snapshot and sends snapshotChanged. A request that still carries the old number fails with ServerCancelled (-32802), and the client retries with a fresh one. Results are tagged objects with a numeric kind: 3 is a class, 4 a union, 2 a function, 9 a reference to another type by id.
Talk to a type server yourself: a working TSP client
Pyright's type server is on npm as pyright-typeserver and runs over stdio; basedpyright's docs describe it as started by a client, not run interactively. This 60-line client speaks LSP framing over the server's stdin and stdout, opens one file, asks for the computed type of value, then edits the file and replays the old snapshot to show the stale-snapshot error.
- tsp/
- tsp_client.pythe client below
- demo/
- app.pythe file we ask about
- node_modules/
- .bin/
- pyright-typeservernpm install pyright-typeserver
- .bin/
"""Minimal Type Server Protocol client: ask a type server what `value` is."""import json, pathlib, subprocess, sys ROOT = pathlib.Path(sys.argv[1]).resolve()FILE = ROOT / "app.py"SERVER = sys.argv[2:] or ["npx", "pyright-typeserver", "--stdio"] proc = subprocess.Popen(SERVER, stdin=subprocess.PIPE, stdout=subprocess.PIPE)next_id = 0 def send(msg): body = json.dumps({"jsonrpc": "2.0", **msg}).encode() proc.stdin.write(b"Content-Length: %d\r\n\r\n" % len(body) + body) proc.stdin.flush() def read(): length = 0 while (line := proc.stdout.readline().strip()): if line.lower().startswith(b"content-length:"): length = int(line.split(b":")[1]) return json.loads(proc.stdout.read(length)) def request(method, params=None): global next_id next_id += 1 send({"id": next_id, "method": method, "params": params}) while True: msg = read() if msg.get("id") == next_id and "method" not in msg: return msg if msg.get("method") == "typeServer/snapshotChanged": print("<- snapshotChanged", msg["params"]) elif "id" in msg and "method" in msg: # server-to-client request send({"id": msg["id"], "result": None}) def notify(method, params): send({"method": method, "params": params}) uri = FILE.as_uri()request("initialize", {"processId": None, "rootUri": ROOT.as_uri(), "capabilities": {}, "workspaceFolders": [{"uri": ROOT.as_uri(), "name": "demo"}]})notify("initialized", {})notify("textDocument/didOpen", {"textDocument": { "uri": uri, "languageId": "python", "version": 1, "text": FILE.read_text()}}) print("protocol", request("typeServer/getSupportedProtocolVersion")["result"])snap = request("typeServer/getSnapshot")["result"]print("snapshot", snap) # `value` on line 6 (zero-based 5), characters 0-5node = {"uri": uri, "range": {"start": {"line": 5, "character": 0}, "end": {"line": 5, "character": 5}}}t = request("typeServer/getComputedType", {"arg": node, "snapshot": snap})["result"]print("computed type of value:", json.dumps(t)[:300]) # Edit the file: the old snapshot is now stale.notify("textDocument/didChange", {"textDocument": {"uri": uri, "version": 2}, "contentChanges": [{"text": FILE.read_text() + "\nother = 1\n"}]})stale = request("typeServer/getComputedType", {"arg": node, "snapshot": snap})print("stale request:", stale.get("error"))proc.terminate()Here is a real run on 28 October 2026 with Node 22.23.4 and pyright-typeserver 1.1.414 on Linux. The only edit is the long typeshed path, shortened with …; the type is cut at 300 characters by the client itself.
$ npm install pyright-typeserver@1.1.414added 1 package in 3s$ cat demo/app.pydef parse(raw: str | None) -> int | str: if raw is None: return 0 return int(raw) if raw.isdigit() else rawvalue = parse("42")$ python3 tsp_client.py demo node_modules/.bin/pyright-typeserver --stdio<- snapshotChanged {'old': 0, 'new': 1}<- snapshotChanged {'old': 1, 'new': 2}<- snapshotChanged {'old': 2, 'new': 3}<- snapshotChanged {'old': 3, 'new': 4}<- snapshotChanged {'old': 4, 'new': 5}protocol 0.4.1snapshot 5computed type of value: {"id": 0, "kind": 4, "flags": 2, "subTypes": [{"id": 1, "kind": 3, "flags": 2, "declaration": {"kind": 0, "category": 6, "node": {"uri": "file:///…/typeshed-fallback/stdlib/builtins.pyi",<- snapshotChanged {'old': 5, 'new': 6}<- snapshotChanged {'old': 6, 'new': 7}stale request: {'code': -32802, 'message': 'server cancelled'}Three things show up. The server bumped the snapshot five times while it loaded the workspace, so a client should always call getSnapshot after start-up rather than assume 0. The answer for value is a union (kind 4) whose members are classes (kind 3) declared in typeshed's builtins.pyi, which is int | str in machine-readable form. And after the edit, the old snapshot was refused with -32802 exactly as the spec says.
How to use Pyrefly as Pylance's type checker in VS Code
You need Pylance 2026.4.1 or newer and Pyrefly 0.60.0 or newer. Pylance can detect Pyrefly on its own: when a workspace's environment has it installed, or has a pyrefly.toml or a [tool.pyrefly] section in pyproject.toml, it prompts you to switch (Pyrefly).
Install Pyrefly into the project environment
Install PyreflyShell python -m pip install "pyrefly>=0.60.0"Accept the prompt, or pick the diagnostics source yourself
When Pylance asks "Pyrefly was found in your Python environment. Would you like to use it as your source of diagnostics in Visual Studio Code?", choose Switch to Pyrefly. To do it by hand, open Settings and set Python › Analysis: Diagnostics Source to Pylance + Pyrefly. Python › Analysis: Pyrefly Version pins a version, points at a local executable, or stays empty to track the latest release.
Check the status bar
The status bar shows the active diagnostics source next to the interpreter. Click it to switch between Pylance, Pylance + Pyright and Pylance + Pyrefly.
Should you? Pyrefly's own post recommends its standalone extension for most people, since it gives diagnostics, completions, go-to-definition and refactoring straight from Pyrefly's language server. The Pylance route makes sense if your team standardises on Pylance, or you depend on the Copilot integration, which Microsoft says gets type context, code understanding for Copilot Chat and MCP tools for Python codebases through Pylance (Microsoft).
Is Pyrefly faster than Pyright?
On Meta's own benchmark, by a wide margin on large projects. The figures below are from the Pyrefly team's April 2026 comparison: full checks with no cache, five runs after a warm-up, on a MacBook M4 with 64 GB RAM, using Pyrefly 0.60.0, ty 0.0.29, Pyright 1.1.408 and mypy 1.19.1 (mypy in strict mode). It is a vendor benchmark, so treat it as a starting point and time your own codebase.
Source: [Pyrefly: speed and memory comparison](https://pyrefly.org/blog/speed-and-memory-comparison/), 10 April 2026
| Pyright | Pyrefly | ty | mypy | |
|---|---|---|---|---|
| pandas | 144.4 s | 1.9 s | 1.5 s | 13.9 s |
| numpy | 70.9 s | 4.8 s | 30.6 s | 8 s |
| sympy | 79.9 s | 4 s | 1.6 s | 15.5 s |
| sqlalchemy | 8.1 s | 0.5 s | 0.6 s | 4.5 s |
| flask | 1.2 s | 0.2 s | 0.2 s | 2 s |
Whole-project time matters most in CI and on first open. In the editor, the checker works incrementally, so the gap you feel day to day will be smaller; the benchmark does not measure it. On memory, the post gives only one direct comparison: numpy took over 3 GB with Pyright and 1 GB with Pyrefly.
Who supports TSP, and why basedpyright refuses
| Checker | TSP status | Source |
|---|---|---|
| Pyright | Default type server; ships pyright-typeserver | Microsoft |
| Pyrefly | Supported from 0.60.0, co-designed with Microsoft | Pyrefly |
| ty (Astral) | Prototype by the Pylance team; Astral will accept it after clean-up, not yet merged at last update | ruff discussion #23432 |
| basedpyright | Will not support it | basedpyright docs |
| mypy | Not mentioned by any of the announcements | — |
Astral's position is lukewarm but open. In March, Micha Reiser wrote that he did not "see a reason not to support TSP", but that "shipping a stable version of ty remains our top priority" and asked the Pylance team to own the interface (discussion). One open problem is that ty commonly infers intersection types, and TSP has no standard representation for them yet. The Pylance team's prototype benchmarks, where ty was faster in 21 of 28 comparisons, are their figures, not Astral's.
basedpyright, the community fork of Pyright, is the loudest critic. Its docs say TSP "benefits nobody except microsoft", "adds needless complexity to any language server that chooses to support it", and call it "a desperate attempt from microsoft to prevent pylance from becoming obsolete" (basedpyright). The argument is that other checkers do the hard work while the features users see, such as Copilot integration, stay in closed-source, VS Code-only Pylance. On Hacker News the same question came up repeatedly: why a separate protocol rather than a new LSP capability?
The technical answer from the spec is that LSP returns presentation text, while TSP returns types a program can inspect, versioned by snapshot. That is useful outside VS Code too: stub generators, refactoring tools and coding agents all want int | str as data rather than as a hover string. Whether that becomes an open standard or stays a Pylance plug-in interface depends on whether anyone other than Microsoft and Meta implements both sides.
What to do
- To do: On VS Code with Pylance: update to 2026.4.1+, install Pyrefly 0.60.0+ in one project and compare diagnostics with Pyright before switching a team.
- To do: Pin
python.analysis.pyreflyVersiononce you switch, so diagnostics do not change under you with each Pyrefly release. - To do: Keep CI on the standalone checker you chose, so the editor and the pipeline report the same errors.
- To do: Building tooling? Try
pyright-typeserver --stdiowith the client above before scraping hover text from LSP. - To do: On Neovim, Zed or basedpyright: nothing changes for you; TSP is consumed by Pylance today.
Questions people ask about the Type Server Protocol
What is the Type Server Protocol?
TSP is a JSON-RPC protocol, built on LSP conventions, for asking a Python type server for structured type information: computed, declared and expected types, import resolution and search paths. Microsoft and Meta announced it on 21 October 2026.
Is TSP a replacement for LSP?
No. It initialises and synchronises documents through normal LSP messages and adds typeServer/* requests on the same connection. LSP still drives the editor; TSP answers type questions with data rather than display text.
How do I switch Pylance to Pyrefly?
Install Pyrefly 0.60.0 or newer in your environment, update Pylance to 2026.4.1 or newer, and accept the Switch to Pyrefly prompt, or set Python › Analysis: Diagnostics Source to Pylance + Pyrefly in Settings.
Does Pylance still work the same with Pyrefly?
Completions, Quick Info, environment management and Copilot integration stay in Pylance. Diagnostics, the errors and warnings you see, come from the type server you selected.
Does ty support TSP?
Not in a release yet. The Pylance team built a prototype for ty, and Astral said in March 2026 it would accept it after clean-up, but stable ty comes first and intersection types have no TSP representation yet.
Is Pyrefly faster than Pyright?
On the Pyrefly team's own benchmark, full checks were far faster, for example 1.9 s against 144.4 s on pandas. It is a vendor benchmark of whole-project checks, so measure your own codebase.
Can I use TSP outside VS Code?
Yes. pyright-typeserver is on npm and speaks TSP over stdio, so any tool can start it and send requests. Today Pylance is the only editor client the announcements mention.
Sources and further reading
- Type Server Protocol specification, with TypeScript definitions and JSON schema
- Microsoft for Python Developers: Pylance + Pyrefly and the Type Server Protocol (21 October 2026)
- Pyrefly blog: Pyrefly + Pylance in VS Code and speed and memory comparison
- Astral: ruff discussion #23432 on ty and TSP; basedpyright: Pyright type server
pyright-typeserveron npm, Pyright, Language Server Protocol- Hacker News discussion
Discussion
Join the discussion
Sign in with Google, Apple or an email code to comment and reply.