vscode-jsonrpc

A lightweight JSON-RPC messaging library that carries requests, responses, and notifications between a client and a server over streams, sockets, or IPC.

Library
npm
v9.0.2
1,786stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
94/100Excellent
Development Activity92
Maintenance96
Community88
Maturity60
Momentum40

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
78/100Good
Architecture88
Code Quality78
Innovation72
Learning Curve75

vscode-jsonrpc is the base messaging layer underneath the Language Server Protocol: it implements the JSON-RPC 2.0 wire format and connection lifecycle used to talk between a VSCode language client and a language server, but it works standalone for any process-to-process JSON-RPC channel, not just LSP. A MessageConnection wraps a pair of readers/writers (stdio pipes, sockets, or in Node’s case a child process’s stdout/stdin) and exposes typed request/notification registration, cancellation, and progress reporting on top of raw message framing.

The package ships three entry points — vscode-jsonrpc/node for Node.js process/stream transports, vscode-jsonrpc/browser for Worker-based transports, and a common core shared by both — so the same connection API works whether the two ends are a spawned child process, a Web Worker, or a browser-hosted worker. It is the transport vscode-languageserver and vscode-languageclient (its sibling packages in this same monorepo) are built on, and is also usable directly by anyone building a custom RPC channel between two Node or browser processes.

What You Get

  • A MessageConnection API for registering typed request and notification handlers and sending/receiving JSON-RPC messages
  • StreamMessageReader/StreamMessageWriter for Node.js stream-based transports (stdio, sockets, child process pipes)
  • Cooperative cancellation via CancellationTokenSource, including a shared-array-buffer-backed cancellation strategy for cross-thread cancellation without an extra message round-trip
  • Content-length message framing and encoding/decoding hooks (ContentEncoder/ContentDecoder) for custom compression or transformation of the wire format
  • Separate /node and /browser entry points so the same connection API runs unmodified in a Node.js process or a Web Worker
  • Progress notification support ($/progress) and built-in request cancellation ($/cancelRequest) matching the LSP base protocol

Common Use Cases

  • Implementing the transport layer for a custom Language Server Protocol server or client outside of the higher-level vscode-languageserver/vscode-languageclient packages
  • Building a JSON-RPC channel between a VSCode extension’s main process and a spawned child process or worker
  • Adding request cancellation and progress reporting to an existing stdio-based tool without hand-rolling message framing
  • Bridging a Node.js host process and a browser-hosted extension host over a Web Worker using the shared /browser API
  • Any general-purpose process-to-process RPC channel that needs typed requests/notifications rather than a bespoke ad hoc protocol

Under The Hood

Architecture The package splits into a common core (connection.ts, messages.ts, messageReader.ts, messageWriter.ts, cancellation.ts, linkedMap.ts) and two thin runtime layers, node and browser, that each supply a RAL (runtime abstraction layer) implementation for their environment. createMessageConnection in connection.ts is the central abstraction: it takes any MessageReader/MessageWriter pair, tracks pending requests in a LinkedMap, and dispatches to registered request/notification handlers, with $/cancelRequest and $/progress handled directly inside the connection rather than left to a layer above it. Cancellation has an alternate path — sharedArrayCancellation.ts — that lets a request be cancelled by flipping a byte in a SharedArrayBuffer instead of sending a message, which only makes sense because the transport boundary is already abstracted behind MessageReader/MessageWriter. Swapping node/main.ts for browser/main.ts changes nothing in connection.ts itself, which is the point of the split.

Tech Stack TypeScript throughout, targeting Node.js >=14, compiled via project-referenced tsc -b builds (tsconfig.json/tsconfig.publish.json/tsconfig.watch.json per package) rather than a bundler for the npm output. Browser tests are bundled with webpack and executed against a real browser via Playwright (runBrowserTests.js); the Node test suite runs directly under Mocha. The monorepo’s own tooling (build/bin/all.js) drives per-package compile/lint/test across all six published packages from one root npm run all. Linting is centralized through a shared eslint.config.base.js extended per package, using typescript-eslint and a Microsoft-authored stylistic plugin.

Code Quality Tests exist for both the common protocol logic (general.test.ts, linkedMap.test.ts) and the Node transport (a separate node/test suite), split so the same common-layer tests can also run against the browser transport — but the test surface is comparatively thin relative to the package’s role as a dependency for the entire LSP ecosystem; there’s no visible coverage reporting. Error handling is typed: ResponseError/ErrorCodes model JSON-RPC error responses explicitly rather than throwing bare exceptions, and public APIs are fully typed generics (RequestType0 through RequestType9) rather than any-typed parameter lists. Naming and file layout are consistent across the common/node/browser split, and every source file carries the same Microsoft copyright/license header, suggesting enforced formatting conventions even without a public style guide.

API Design The request/notification API is fully generic over parameter and return types (RequestType0..9, NotificationType0..9), so consumers get compile-time checking of request signatures instead of stringly-typed dispatch — a deliberate ergonomics choice given this package underlies a protocol (LSP) with hundreds of distinct request/notification shapes. Getting started requires minimal boilerplate: wrap a reader/writer pair, call connection.listen(), and register typed handlers, mirrored identically on both client and server sides. Progress and cancellation are exposed as first-class connection methods rather than something each protocol built on top has to reimplement, which keeps downstream packages like vscode-languageserver and vscode-languageclient thin.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search