A Kotlin Multiplatform implementation of the Language Server Protocol (LSP) and the Debug Adapter Protocol (DAP), modeled after eclipse-lsp4j/lsp4j.
- JSON-RPC 2.0 core — message models, request/response correlation,
Content-Lengthframed transport (shared by LSP and DAP) - LSP model parity with lsp4j — all 365 model classes from lsp4j's
Protocol.xtend, including client/server capabilities, completion, hover, diagnostics, semantic tokens, inlay hints, inline values, notebook documents, and pull diagnostics (LSP 3.18) - LSP services —
LanguageServer,TextDocumentService,WorkspaceService,LanguageClientinterfaces mirroring lsp4j, wired to the wire byLanguageServerLauncher - DAP — debug adapter models (
initialize,setBreakpoints,threads,stackTrace,scopes,variables,evaluate, …),DebugAdapterinterface, andDebugAdapterLauncher - Multiplatform — JVM, Android, and every Kotlin/Native tier-1 target (macOS, iOS, tvOS, watchOS, Linux, Windows); all except JS/Wasm
- Examples — a stdio-based language server and a TCP socket-based language server
| Path | Description |
|---|---|
: (root) |
Library: cn.enaium.lsp (LSP + JSON-RPC) and cn.enaium.lsp.dap (DAP) |
:examples:stdio |
Language server over stdin/stdout |
:examples:socket |
Language server over a TCP socket |
cn.enaium.lsp
├── jsonrpc # JSON-RPC 2.0 core (messages, server, transport, launcher)
├── model # LSP data models (lsp4j parity)
├── dap # Debug Adapter Protocol
│ ├── model # DAP data models
│ ├── DebugAdapter
│ └── DebugAdapterLauncher
├── LanguageServer
├── LanguageServerLauncher
└── LanguageServerServices
- JDK 25 (jvmToolchain)
- Gradle 9.7+ (wrapper included)
The library is not yet published to a Maven repository. Consume it as a local project dependency or wait for publication.
Implement LanguageServer (only the methods you support need real bodies; every service method has a default), then run it over a MessageTransport:
class MyServer : LanguageServer {
override fun initialize(params: InitializeParams): InitializeResult =
InitializeResult(
capabilities = ServerCapabilities(
textDocumentSync = JsonPrimitive(TextDocumentSyncKind.Full),
hoverProvider = JsonPrimitive(true),
),
serverInfo = ServerInfo(name = "my-server", version = "1.0.0"),
)
override fun shutdown(): Any? = null
override fun exit() { Runtime.getRuntime().halt(0) }
override fun textDocumentService(): TextDocumentService? = MyTextDocumentService()
override fun workspaceService(): WorkspaceService? = null
override fun windowService(): WindowService? = null
}
class MyTextDocumentService : TextDocumentService {
override fun hover(params: HoverParams): Hover? =
Hover(contents = JsonPrimitive("Hello from ${params.textDocument.uri}"))
}
fun main() {
// stdio transport: content-length framed messages on stdin/stdout
val transport = StreamMessageTransport(System.`in`, System.out)
val launcher = LanguageServerLauncher(transport, MyServer())
launcher.listen()
}class MyAdapter : DebugAdapter {
override fun initialize(request: InitializeRequestArguments): Capabilities =
Capabilities(supportsConfigurationDoneRequest = true)
override fun launch(args: JsonElement?): JsonElement? = null
override fun threads(): ThreadsResponseBody = ThreadsResponseBody(listOf(Thread(1, "main")))
// ... implement the remaining commands your adapter supports
}
fun main() {
val transport = StreamMessageTransport(System.`in`, System.out)
val launcher = DebugAdapterLauncher(transport, MyAdapter())
launcher.listen()
}MessageTransport is a two-method interface (send / receive); StreamMessageTransport implements the standard Content-Length framing over any InputStream/OutputStream — including sockets, pipes, or WebSocket byte streams. Use the socket example as a reference for a TCP server.
Build and run:
# stdio language server (LSP client integration, e.g. VS Code language client)
./gradlew :examples:stdio:run
# socket language server on port 8080 (pass a port as the first argument)
./gradlew :examples:socket:runBoth examples accept the standard LSP handshake (initialize → requests → shutdown → exit).
./gradlew :jvmTest # JVM tests
./gradlew :macosArm64Test # native (macOS arm64) tests
./gradlew :linuxX64Test # native (Linux x64) testsSimulator-based tests (iOS/tvOS/watchOS simulators) require the corresponding simulator runtime installed via Xcode; they are skipped when no runtime is available. Apple x64 test tasks are disabled (Rosetta not required for compilation, only for running tests).
Dependencies are declared through a Gradle version catalog at
gradle/libs.versions.toml. Android builds need an SDK location; provide it
via ANDROID_HOME or a local local.properties (sdk.dir=...).
The test suite covers JSON-RPC dispatch/correlation, typed launcher round-trips, stream framing, end-to-end pipe communication, and LSP lifecycle wiring.