@lspeasy/server
v4.0.2
Published
Build LSP servers with simple, typed API
Maintainers
Readme
@lspeasy/server
Build Language Server Protocol (LSP) servers with a simple, type-safe API.
Installation
npm install @lspeasy/server @lspeasy/core
# or
pnpm add @lspeasy/server @lspeasy/core
# or
yarn add @lspeasy/server @lspeasy/coreFor WebSocket server transports, install ws:
pnpm add wsQuick Start
Create a minimal hover server in less than 30 lines:
import { LSPServer, StdioTransport } from '@lspeasy/server';
import type { HoverParams, Hover } from '@lspeasy/server';
// Create server with capabilities (fluent, returns narrowed type)
const server = new LSPServer({
name: 'my-language-server',
version: '1.0.0'
}).registerCapabilities({
hoverProvider: true
});
// Register hover handler via capability-aware namespace
server.textDocument.onHover(async (params) => {
return {
contents: {
kind: 'markdown',
value: `# Hover\nLine ${params.position.line}`
}
};
});
// Start server
const transport = new StdioTransport();
await server.listen(transport);Features
- Type-Safe Handlers: Fully typed request and notification handlers with IntelliSense support
- Automatic Validation: Built-in parameter validation using Zod schemas
- Lifecycle Management: Automatic initialize/shutdown handshake handling
- Cancellation Support: Built-in cancellation token support for long-running operations
- Error Handling: Comprehensive error handling with LSP error codes
- Chainable API: Fluent API with method chaining
Handler Registration
Request Handlers
Request handlers receive parameters, a cancellation token, and return a result:
server.onRequest<'textDocument/completion', CompletionParams, CompletionList>(
'textDocument/completion',
async (params, token, context) => {
// Check for cancellation
if (token.isCancellationRequested) {
return { isIncomplete: false, items: [] };
}
// Return completions
return {
isIncomplete: false,
items: [
{ label: 'function', kind: 3 },
{ label: 'const', kind: 6 }
]
};
}
);Notification Handlers
Notification handlers receive parameters but don't return a value:
server.onNotification('textDocument/didOpen', (params, context) => {
console.log('Document opened:', params.textDocument.uri);
});Method Chaining
Chain multiple handler registrations:
server
.onRequest('textDocument/hover', hoverHandler)
.onRequest('textDocument/completion', completionHandler)
.onNotification('textDocument/didOpen', didOpenHandler)
.onNotification('textDocument/didChange', didChangeHandler);Server Options
interface ServerOptions {
// Server name (sent in initialize response)
name?: string;
// Server version (sent in initialize response)
version?: string;
// Logger instance (defaults to ConsoleLogger)
logger?: Logger;
// Log level (defaults to 'info')
logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error';
// Custom validation error handler
onValidationError?: (error: ZodError, request: RequestContext) => ResponseError;
}Error Handling
Throw ResponseError for custom error codes:
import { ResponseError, JSONRPCErrorCode } from '@lspeasy/server';
server.onRequest('custom/method', async (params) => {
if (!params.valid) {
throw new ResponseError(
JSONRPCErrorCode.InvalidParams,
'Validation failed',
{ details: 'Invalid input' }
);
}
return { success: true };
});Lifecycle
The server automatically handles the LSP lifecycle:
- Initialize: Client sends
initializerequest → Server responds with capabilities - Initialized: Client sends
initializednotification → Server is ready - Running: Server processes requests and notifications
- Shutdown: Client sends
shutdownrequest → Server prepares to exit - Exit: Client sends
exitnotification → Server closes
Graceful Shutdown
// Graceful shutdown (waits for pending requests)
await server.shutdown(5000); // 5 second timeout
// Force close
await server.close();Examples
Check out the examples directory for complete examples:
- minimal-server.ts - Basic hover server
- hover-server.ts - Hover + completion with document tracking
API Reference
Notebook Namespace Helpers
server.notebookDocument.onDidOpen((params) => {
// handle notebook open
});
server.notebookDocument.onDidChange((params) => {
// handle structural/content updates
});Partial Result Sender
import { PartialResultSender } from '@lspeasy/server';
const sender = new PartialResultSender(server);
await sender.send(token, batch);LSPServer
class LSPServer<Capabilities extends Partial<ServerCapabilities> = ServerCapabilities>Methods
onRequest<Method, Params, Result>(method, handler)- Register request handleronNotification<Method, Params>(method, handler)- Register notification handlersetCapabilities(capabilities)- Set server capabilitiesgetCapabilities()- Get current capabilitieslisten(transport)- Start server on transportshutdown(timeout?)- Graceful shutdownclose()- Force close
Handler Types
type RequestHandler<Params, Result> = (
params: Params,
token: CancellationToken,
context: RequestContext
) => Promise<Result> | Result;
type NotificationHandler<Params> = (
params: Params,
context: NotificationContext
) => void | Promise<void>;License
MIT
