Files
freemo 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
Docs: Updated to A2A and integrating rest standard
2026-03-11 13:19:55 -04:00

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.