
Company News
Andrew Becherer Joins Socket as Chief Information Security Officer
Socket’s first CISO brings deep experience securing high-growth SaaS companies as open source supply chain threats accelerate.
@ridit/relay
Advanced tools
One port. Any language. Monaco-ready LSP bridge.
@ridit/relay multiplexes any number of language servers over a single WebSocket port using path-based routing. Drop it into any Monaco editor project and get completions, hover, diagnostics, go-to-definition, references, signature help, formatting, and code lenses — wired automatically.
ws://127.0.0.1:9721/python → pylsp / pyright
ws://127.0.0.1:9721/typescript → typescript-language-server
ws://127.0.0.1:9721/rust → rust-analyzer
The standard approach to Monaco + LSP is monaco-languageclient — but it pulls in vscode-languageclient and the full VSCode extension host model just to wire up completions. That's hundreds of kilobytes of VSCode abstractions built for a different runtime, bolted onto Monaco as an afterthought.
Relay uses only the primitives it actually needs — vscode-ws-jsonrpc for WebSocket framing and vscode-languageserver-protocol for LSP message types — then talks directly to Monaco's provider APIs. No extension host, no VSCode shims, no adapter layers.
It was extracted from Meridia — a from-scratch code editor — where we needed LSP that fit the stack instead of fighting it. If you're building an editor or any Monaco-based tool, relay gives you full LSP integration without the overhead.
npm install @ridit/relay
# or
bun add @ridit/relay
Monaco is a peer dependency — install it separately if you haven't:
npm install monaco-editor
The simplest case — command known at startup:
import { Server } from "@ridit/relay";
const server = new Server();
server.register({
languageId: "python",
command: "pyright-langserver",
args: ["--stdio"],
});
server.register({
languageId: "typescript",
command: "typescript-language-server",
args: ["--stdio"],
});
await server.start(9721);
Use resolve when the command must be discovered at runtime — for example, finding a Python interpreter before locating pylsp. resolve is called per connection, so if the language server isn't available the connection fails cleanly without crashing the process.
server.register({
languageId: "python",
resolve: () => {
const python = findPythonInterpreter(); // your own logic
if (!python) return null; // closes the WS with a clean error
const pylsp = findPylsp(python);
if (!pylsp) return null;
return { command: pylsp.command, args: pylsp.args };
},
});
If resolve returns null, the WebSocket is closed with code 1011 and a descriptive message — no process is spawned.
command and resolve are mutually exclusive. command takes priority if both are provided.
import * as monaco from "monaco-editor";
import { Client } from "@ridit/relay";
const editor = monaco.editor.create(document.getElementById("app")!, {
automaticLayout: true,
});
const model = monaco.editor.createModel(
"print('hello')",
"python",
monaco.Uri.file("/workspace/main.py"), // real path required
);
editor.setModel(model);
const client = new Client(monaco);
client.register({ languageId: "python" });
await client.start("/workspace");
Windows: pass the workspace path with backslashes —
"C:\\workspace".
Once client.start() is called, relay registers the following Monaco providers for each language:
| Feature | Provider |
|---|---|
| Completions | registerCompletionItemProvider |
| Hover | registerHoverProvider |
| Diagnostics | setModelMarkers via publishDiagnostics |
| Go to Definition | registerDefinitionProvider |
| Find References | registerReferenceProvider |
| Signature Help | registerSignatureHelpProvider |
| Formatting | registerDocumentFormattingEditProvider |
| Code Lenses | registerCodeLensProvider (with usage counts) |
new Server()server.register(def: LspServerDefinition): this
server.start(port?: number): Promise<void> // default: 9721
server.stop(): Promise<void>
LspServerDefinition
{
languageId: string; // e.g. "python", "typescript"
// Option A — static: command resolved at startup
command?: string; // binary name or full path
args?: string[]; // e.g. ["--stdio"]
env?: Record<string, string>;
// Option B — dynamic: command resolved per connection
resolve?: () => { command: string; args?: string[] } | null;
}
new Client(monaco)client.register(def: LspClientDefinition): this
client.start(workspacePath?: string, port?: number): Promise<void>
client.format_model(model: monaco.editor.ITextModel): Promise<boolean>
client.updateWorkspaceRoot(folderPath: string): Promise<void>
client.dispose(): Promise<void>
LspClientDefinition
{
languageId: string; // must match server registration
extensions?: string[]; // file extensions to match, e.g. ["py", "pyw"]
}
On Windows, npm-installed language servers are .cmd wrappers. Relay resolves these automatically — pass the bare binary name and it will find the right executable:
server.register({
languageId: "python",
command: "pyright-langserver",
args: ["--stdio"],
});
MIT
FAQs
One port. Any language. Monaco-ready LSP bridge.
The npm package @ridit/relay receives a total of 28 weekly downloads. As such, @ridit/relay popularity was classified as not popular.
We found that @ridit/relay demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Socket’s first CISO brings deep experience securing high-growth SaaS companies as open source supply chain threats accelerate.

Company News
Replit is integrating Socket Firewall into its AI-powered development experience to help protect builders from malicious open source packages.

Security News
npm confirmed a tooling bug incorrectly marked several one-character packages as security holders and said it was working on a rollback.