{"id":46849505,"url":"https://github.com/pradeepmouli/lspeasy","last_synced_at":"2026-06-29T02:00:58.839Z","repository":{"id":335369742,"uuid":"1145362209","full_name":"pradeepmouli/lspeasy","owner":"pradeepmouli","description":"TypeScript SDK for building Language Server Protocol clients and servers","archived":false,"fork":false,"pushed_at":"2026-06-22T12:05:02.000Z","size":3729,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"develop","last_synced_at":"2026-06-22T14:06:27.405Z","etag":null,"topics":["framework","jsonrpc","language-server-protocol","lsp","typescript"],"latest_commit_sha":null,"homepage":"https://pradeepmouli.github.io/lspeasy/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pradeepmouli.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["pradeepmouli"]}},"created_at":"2026-01-29T18:10:14.000Z","updated_at":"2026-06-22T11:04:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"3523672c-6d96-4408-bc32-961860a4caa3","html_url":"https://github.com/pradeepmouli/lspeasy","commit_stats":null,"previous_names":["pradeepmouli/lspy","pradeepmouli/lspeasy"],"tags_count":24,"template":false,"template_full_name":null,"purl":"pkg:github/pradeepmouli/lspeasy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pradeepmouli%2Flspeasy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pradeepmouli%2Flspeasy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pradeepmouli%2Flspeasy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pradeepmouli%2Flspeasy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pradeepmouli","download_url":"https://codeload.github.com/pradeepmouli/lspeasy/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pradeepmouli%2Flspeasy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34910177,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["framework","jsonrpc","language-server-protocol","lsp","typescript"],"created_at":"2026-03-10T16:20:59.601Z","updated_at":"2026-06-29T02:00:58.827Z","avatar_url":"https://github.com/pradeepmouli.png","language":"TypeScript","funding_links":["https://github.com/sponsors/pradeepmouli"],"categories":[],"sub_categories":[],"readme":"# lspeasy\n\n\u003e A TypeScript SDK for building [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) clients and servers that run anywhere JavaScript runs — Node, browsers, web workers, or VS Code extensions — with a capability-aware, strongly-typed API.\n\n\u003e **⚠️ Pre-1.0 software** — APIs are subject to change between minor versions. Pin to exact versions in production. See the [CHANGELOG](./CHANGELOG.md) for breaking changes between releases.\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.npmjs.com/package/@lspeasy/core\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/@lspeasy/core?style=flat-square\u0026label=%40lspeasy%2Fcore\" alt=\"npm version\" /\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/pradeepmouli/lspeasy/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://img.shields.io/github/actions/workflow/status/pradeepmouli/lspeasy/ci.yml?style=flat-square\" alt=\"ci\" /\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-MIT-blue?style=flat-square\" alt=\"license\" /\u003e\n  \u003cimg src=\"https://img.shields.io/badge/node-%3E%3D20-brightgreen?style=flat-square\" alt=\"node\" /\u003e\n\u003c/p\u003e\n\n📚 **Documentation:** \u003chttps://pradeepmouli.github.io/lspeasy/\u003e\n\n## Overview\n\nThe [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) standardizes how editors and IDEs talk to language tooling — hover, completion, diagnostics, symbol navigation, and dozens of other features all flow over a single JSON-RPC connection. Implementing a server or client against LSP directly is deceptively involved: you need JSON-RPC framing, message validation, cancellation tokens, progress reporting, capability negotiation, the full lifecycle handshake, and correct handling of roughly a hundred request and notification types.\n\n`lspeasy` is a set of small, focused TypeScript packages that wrap all of that in a modern, ESM-first API. Handlers are registered against a typed, capability-aware namespace — `server.textDocument.onHover(...)` — so the editor-facing surface mirrors the spec and advertised capabilities are enforced at both compile time and runtime. Transports are swappable: run the same server over stdio for a classic editor plugin, over a web worker for a browser playground, over WebSockets for a remote tooling backend, or over TCP for diagnostics.\n\nCompared to `vscode-languageserver` (which is tightly coupled to Node and the VS Code extension model), `lspeasy` is runtime-agnostic, tree-shakeable, browser-friendly, and exposes a middleware pipeline for logging, tracing, and request rewriting without monkey-patching the dispatcher.\n\n## Features\n\n- **Capability-aware handler registration** — `server.textDocument.onHover(...)` is only callable after `registerCapabilities({ hoverProvider: true })`; mismatches are caught at both compile time and at the dispatcher.\n- **Full JSON-RPC 2.0 core** — schemas, framing, request/notification/response types, and typed error codes, all validated with Zod.\n- **Swappable transports** — `StdioTransport`, `TcpTransport`, `IpcTransport`, `WebSocketTransport`, `DedicatedWorkerTransport`, `SharedWorkerTransport`; write your own against the `Transport` interface.\n- **Runs anywhere** — Node.js-specific transports live under `@lspeasy/core/node`; the root export is browser-safe so the same code ships to a VS Code extension and a web playground.\n- **Typed client API** — `client.textDocument.hover(...)`, `client.workspace.symbol(...)`, and the full request surface with request/response types pulled from the LSP spec.\n- **Composable middleware** — `composeMiddleware(...)` plus `createScopedMiddleware({ methods, direction })` for logging, tracing, metrics, or request mutation without touching the core dispatcher.\n- **Lifecycle + progress + cancellation** — initialize/initialized/shutdown/exit are handled for you, with built-in partial-result and work-done progress senders and `CancellationToken` support.\n- **Zod-validated messages** — every inbound message is parsed against a schema, so malformed peers surface as typed errors instead of runtime crashes.\n- **Tree-shakeable, ESM-only** — pay for what you import; no CommonJS compatibility shims dragging extra code into browser bundles.\n\n## Install\n\n```bash\n# Build a server\npnpm add @lspeasy/server @lspeasy/core\n\n# Build a client\npnpm add @lspeasy/client @lspeasy/core\n```\n\nRequires **Node.js ≥ 20**. For WebSocket **server** mode or Node \u003c 22.4, also install `ws` (optional peer): `pnpm add ws`.\n\n## Quick Start\n\nA minimal LSP server over stdio that responds to `textDocument/hover`:\n\n```typescript\nimport { LSPServer } from '@lspeasy/server';\nimport { StdioTransport } from '@lspeasy/core/node';\n\nconst server = new LSPServer({ name: 'hello-lsp', version: '0.1.0' });\n\nserver.registerCapabilities({ hoverProvider: true });\n\nserver.textDocument.onHover(async (params) =\u003e ({\n  contents: {\n    kind: 'markdown',\n    value: `**Hovered** line ${params.position.line}, character ${params.position.character}`\n  }\n}));\n\nawait server.listen(new StdioTransport());\n```\n\nWire it into VS Code, Neovim, Helix, or any LSP-aware editor by spawning the script with `--stdio`.\n\n## Usage\n\n### Writing a client\n\n```typescript\nimport { LSPClient } from '@lspeasy/client';\nimport { StdioTransport } from '@lspeasy/core/node';\nimport { spawn } from 'node:child_process';\n\nconst proc = spawn('my-language-server', ['--stdio']);\nconst transport = new StdioTransport({ input: proc.stdout, output: proc.stdin });\n\nconst client = new LSPClient({ name: 'my-client', version: '1.0.0' });\nawait client.connect(transport);\n\nconst hover = await client.textDocument.hover({\n  textDocument: { uri: 'file:///example.ts' },\n  position: { line: 0, character: 6 }\n});\n\nawait client.disconnect();\n```\n\n### Transports\n\n`@lspeasy/core` ships several built-in transports. Import Node-only transports from the `/node` subpath so browser bundles stay clean.\n\n| Transport | Import | Notes |\n|-----------|--------|-------|\n| `StdioTransport` | `@lspeasy/core/node` | stdin/stdout, child processes |\n| `TcpTransport` | `@lspeasy/core/node` | TCP client/server with optional reconnect |\n| `SocketTransport` | `@lspeasy/core/node` | Unix domain socket or TCP; used by the proxy daemon |\n| `IpcTransport` | `@lspeasy/core/node` | Node parent/child IPC |\n| `WebSocketTransport` | `@lspeasy/core` | Native `globalThis.WebSocket` (Node ≥22.4 or browsers) |\n| `DedicatedWorkerTransport` | `@lspeasy/core` | Browser dedicated worker |\n| `SharedWorkerTransport` | `@lspeasy/core` | Browser shared worker |\n\n### Middleware\n\n`@lspeasy/core/middleware` provides a composable pipeline for logging, tracing, or mutating requests on the fly:\n\n```typescript\nimport { composeMiddleware, createScopedMiddleware } from '@lspeasy/core/middleware';\nimport type { Middleware } from '@lspeasy/core/middleware';\n\nconst logging: Middleware = async (ctx, next) =\u003e {\n  console.log(`${ctx.direction} ${ctx.method}`);\n  await next();\n};\n\nconst textDocOnly = createScopedMiddleware(\n  { methods: /^textDocument\\//, direction: 'clientToServer' },\n  async (ctx, next) =\u003e {\n    ctx.metadata.startedAt = Date.now();\n    await next();\n  }\n);\n\nconst middleware = composeMiddleware(logging, textDocOnly);\n```\n\n## How it works\n\nAt the lowest layer, `@lspeasy/core` models JSON-RPC 2.0 messages (request / notification / response / error) as Zod-validated types and handles LSP's Content-Length framing. A `Transport` is just a bidirectional message pipe — everything from stdio to a `SharedWorker` implements the same interface, so the server and client layers are entirely transport-agnostic.\n\n`@lspeasy/server` layers on lifecycle management (initialize / initialized / shutdown / exit), a capability proxy that gates handler registration on advertised capabilities, a message dispatcher that routes to typed handlers, and helpers for work-done progress and partial results. `@lspeasy/client` is the symmetric counterpart: a typed request surface that mirrors the spec and handles correlation, cancellation, and response validation.\n\n## Packages\n\n| Package | Description |\n|---|---|\n| [`@lspeasy/core`](packages/core) | JSON-RPC 2.0, framing, transports, LSP protocol types, middleware pipeline |\n| [`@lspeasy/server`](packages/server) | Server class with lifecycle, capability-aware handler registration, progress/cancellation |\n| [`@lspeasy/client`](packages/client) | Client with typed `textDocument.*` / `workspace.*` request API |\n| [`@lspeasy/middleware`](packages/middleware) | Shared middleware building blocks |\n| [`@lsproxy/cli`](apps/cli) | Language-agnostic CLI (`lspeasy` bin) — builds subcommands at runtime from the server's advertised capabilities |\n| [`@lsproxy/proxy`](apps/proxy) | Per-root Unix socket daemon — holds warm LSP connections so each CLI invocation reconnects in milliseconds |\n\n## CLI\n\n`@lsproxy/cli` is a language-agnostic CLI that connects to any LSP server and exposes\nits capabilities as typed subcommands — hover, rename, formatting, code actions, symbol\nsearch, and more. The command surface is built at runtime from the server's advertised\ncapabilities, so it works out of the box with any LSP server.\n\n```bash\npnpm add -g @lsproxy/cli          # install once\nlspeasy textDocument hover src/foo.ts 12:7\nlspeasy textDocument rename src/foo.ts 12:7 newName\nlspeasy textDocument formatting src/foo.ts\nlspeasy workspace symbol MyClass\nlspeasy call textDocument/semanticTokens/full --params '{\"textDocument\":{\"uri\":\"file:///…\"}}'\n```\n\nPositions are **1-based** `line:col` (editor-style). Write-side commands (`rename`, `formatting`,\ncode actions that produce edits) apply changes to disk automatically; pass `--dry-run` to preview.\n\nServer discovery reads `lsp.json` walking up from `--root`, or use `--server \u003ccmd\u003e` to override.\n\n```json\n{\n  \"lspServers\": {\n    \"typescript\": {\n      \"command\": \"typescript-language-server\",\n      \"args\": [\"--stdio\"],\n      \"fileExtensions\": { \".ts\": \"typescript\", \".tsx\": \"typescriptreact\" }\n    }\n  }\n}\n```\n\nSee [`apps/cli/README.md`](apps/cli/README.md) for the full flag reference.\n\n### Proxy daemon\n\n`@lsproxy/proxy` runs as a background daemon per project root, holding warm LSP server\nconnections in a pool. The CLI connects to it over a Unix domain socket — subsequent\ninvocations skip the `initialize` handshake and respond in milliseconds instead of seconds.\n\n```bash\n# first call — spawns the daemon automatically, ~1-3s\nlspeasy textDocument hover src/foo.ts 1:1\n# subsequent calls — reconnects via socket, \u003c100ms\nlspeasy textDocument hover src/foo.ts 2:5\n\nlspeasy --no-proxy textDocument hover src/foo.ts 1:1   # bypass daemon\n```\n\nThe daemon exits automatically after 30 minutes of idle time.\n\n### Claude Code plugin\n\nThis repo ships a thin Claude Code plugin (`lsp-refactor`) that routes rename, move-file,\nand move-symbol requests through the language server instead of hand edits.\n\n```\n/plugin marketplace add pradeepmouli/lspeasy\n/plugin install lsp-refactor@lspeasy\n```\n\nThe plugin lives in [`.claude-plugin/`](.claude-plugin) and [`skills/lsp-refactor/`](skills/lsp-refactor).\n\n## Related Projects\n\n| Library | Relationship | npm |\n|---|---|---|\n| [rune-langium](https://github.com/pradeepmouli/rune-langium) | DSL toolchain powered by lspeasy's LSP server | [![npm](https://img.shields.io/npm/v/@rune-langium/core?style=flat-square)](https://www.npmjs.com/package/@rune-langium/core) |\n\n## Contributing\n\n```bash\npnpm install\npnpm build\npnpm test\npnpm lint\n```\n\nSee [CONTRIBUTING.md](./CONTRIBUTING.md) for details.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpradeepmouli%2Flspeasy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpradeepmouli%2Flspeasy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpradeepmouli%2Flspeasy/lists"}