# 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.