d5b122d4a3
CI / lint (push) Successful in 14s
CI / quality (push) Successful in 21s
CI / security (push) Successful in 36s
CI / build (push) Successful in 36s
CI / typecheck (push) Successful in 39s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m57s
CI / integration_tests (push) Successful in 3m23s
CI / docker (push) Successful in 53s
CI / coverage (push) Successful in 5m58s
CI / benchmark-publish (push) Successful in 19m27s
185 lines
6.6 KiB
Markdown
185 lines
6.6 KiB
Markdown
# LSP Server Stub
|
|
|
|
The CleverAgents LSP server stub provides a minimal Language Server
|
|
Protocol implementation that handles the core lifecycle handshake
|
|
over JSON-RPC stdin/stdout transport with Content-Length header
|
|
framing.
|
|
|
|
## Usage
|
|
|
|
Launch the stub server via the CLI:
|
|
|
|
```bash
|
|
agents lsp serve
|
|
agents lsp serve --log-level debug
|
|
```
|
|
|
|
The server reads JSON-RPC messages from **stdin** and writes
|
|
responses to **stdout**, following the LSP base protocol
|
|
specification.
|
|
|
|
### Options
|
|
|
|
| Flag | Default | Description |
|
|
|----------------|---------|------------------------------------------|
|
|
| `--log-level` | `info` | Logging level: debug, info, warning, error |
|
|
|
|
On startup the server prints a banner with its PID and transport
|
|
details to the console (stderr), then enters the message loop.
|
|
|
|
## Transport Limits
|
|
|
|
| Limit | Value | Description |
|
|
|-----------------------|---------|-----------------------------------------------------|
|
|
| Max Content-Length | 10 MB | Messages exceeding this are rejected (DoS guard) |
|
|
| Max header lines | 32 | Headers with more lines are dropped (DoS guard) |
|
|
|
|
Messages with a `Content-Length` header exceeding 10 MB
|
|
(`MAX_CONTENT_LENGTH`) are skipped with a warning log entry and
|
|
the server continues reading the next message. Up to
|
|
`MAX_CONTENT_LENGTH` bytes of body are consumed and discarded
|
|
in 64 KB chunks so that subsequent messages are not affected by
|
|
a transport desync. The discard is capped to prevent a
|
|
malicious client from blocking the server with an absurdly
|
|
large declared length.
|
|
|
|
Header sections with more than 32 lines (`MAX_HEADER_LINES`)
|
|
are skipped to prevent denial-of-service from adversarial clients
|
|
sending infinite non-empty header lines. Remaining header lines
|
|
are drained up to the blank terminator (with a secondary cap of
|
|
`MAX_HEADER_LINES` drain lines) so the stream stays aligned for
|
|
the next message.
|
|
|
|
Malformed `Content-Length` values (non-integer strings) are
|
|
rejected and remaining headers are drained before resuming.
|
|
|
|
Deeply nested JSON payloads that exceed Python's recursion limit
|
|
are caught and return a JSON-RPC parse error response. This
|
|
prevents a denial-of-service from adversarial clients sending
|
|
pathologically nested structures to exhaust the call stack.
|
|
|
|
Other recoverable transport errors — negative `Content-Length`,
|
|
incomplete message body, and malformed JSON — are also skipped
|
|
(with appropriate warning logs or JSON-RPC parse error responses)
|
|
rather than terminating the server. If the output stream is
|
|
broken (e.g. client disconnected), the server logs a warning and
|
|
terminates gracefully. Only a true EOF (closed stdin), a broken
|
|
output pipe, or an ``exit`` notification stops the event loop.
|
|
|
|
## Supported Methods
|
|
|
|
### `initialize`
|
|
|
|
Responds with a stubbed `InitializeResult` containing:
|
|
|
|
- **serverInfo**: `{ "name": "cleveragents-lsp-stub", "version": "0.1.0" }`
|
|
- **capabilities**: All capabilities from the specification's LSP
|
|
Capability Exposure section are reported as stubs (disabled or
|
|
null). The capability set includes `textDocumentSync`,
|
|
`completionProvider`, `hoverProvider`, `definitionProvider`,
|
|
`referencesProvider`, `documentFormattingProvider`, `renameProvider`,
|
|
`codeActionProvider`, `diagnosticProvider`, `signatureHelpProvider`,
|
|
`documentSymbolProvider`, and `workspaceSymbolProvider`.
|
|
|
|
A second `initialize` request is rejected with `InvalidRequest`
|
|
(-32600) per the LSP specification.
|
|
|
|
### `shutdown`
|
|
|
|
Returns a JSON-RPC success response with `result: null`. Marks the
|
|
server as ready for exit. After `shutdown`, only `exit` is
|
|
accepted — all other requests receive an `InvalidRequest` (-32600)
|
|
error, and notifications are silently ignored, per the LSP
|
|
specification (section 3.16).
|
|
|
|
Like all other requests (except `initialize`), `shutdown` is subject
|
|
to the pre-initialize guard: sending `shutdown` before `initialize`
|
|
returns a `ServerNotInitialized` (-32002) error.
|
|
|
|
### `exit`
|
|
|
|
Terminates the server event loop. If `shutdown` was received first
|
|
the exit code is 0; otherwise it is 1.
|
|
|
|
## Pre-Initialize Requests
|
|
|
|
Requests received **before** the `initialize` handshake (including
|
|
`shutdown`) return a `ServerNotInitialized` (-32002) error, per the
|
|
LSP specification:
|
|
|
|
```json
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"error": {
|
|
"code": -32002,
|
|
"message": "Server not initialized: textDocument/completion"
|
|
}
|
|
}
|
|
```
|
|
|
|
Notifications sent before `initialize` are silently ignored.
|
|
|
|
## Unsupported Methods
|
|
|
|
After `initialize`, all other LSP methods (e.g.
|
|
`textDocument/didOpen`, `textDocument/completion`,
|
|
`textDocument/hover`) return a JSON-RPC `MethodNotFound` error:
|
|
|
|
```json
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"error": {
|
|
"code": -32601,
|
|
"message": "Not implemented: textDocument/completion"
|
|
}
|
|
}
|
|
```
|
|
|
|
Notifications (messages without an `id` field) for unsupported
|
|
methods are silently ignored, per the JSON-RPC specification.
|
|
|
|
## A2A Facade Wiring
|
|
|
|
An `A2aLocalFacade` instance is stored on `LspServer` so that the
|
|
dispatch path can be wired through A2A when server mode lands.
|
|
In the **current stub implementation** the facade is a placeholder —
|
|
all methods are handled by hardcoded stub handlers in
|
|
`_dispatch_stub()`. The facade is **not called** at this time.
|
|
|
|
> **TODO** (M7+): Wire `_dispatch_stub()` through `self._facade`
|
|
> when server mode is implemented.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
stdin → [Content-Length framing] → [JSON-RPC parser]
|
|
↓
|
|
[LspServer._handle_message]
|
|
↓
|
|
[_dispatch_stub (hardcoded)]
|
|
↓
|
|
[Stub handlers]
|
|
↓
|
|
stdout ← [Content-Length framing] ← [JSON-RPC response]
|
|
```
|
|
|
|
> Note: `A2aLocalFacade` is stored but not called in the current
|
|
> stub. The architecture diagram above will change when server
|
|
> mode routes through the facade.
|
|
|
|
## Future Roadmap
|
|
|
|
- **M7**: Wire `textDocument/didOpen` and `textDocument/didClose`
|
|
notifications to the A2A context pipeline.
|
|
- **M8**: Implement `textDocument/completion` via the LspToolAdapter,
|
|
delegating to real language server processes managed by LspRuntime.
|
|
- **M9**: Add `textDocument/diagnostics` push support with
|
|
server-initiated notifications.
|
|
- **M10**: Full LSP feature parity for supported languages
|
|
(Python, TypeScript, Go) with hot-reload of language server
|
|
configurations from the LSP Registry.
|
|
- **Server mode**: Replace stub handlers with real LSP transport
|
|
proxying through the A2A facade in server mode.
|