Files
cleveragents-core/docs/reference/lsp_stub.md
T
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

6.6 KiB

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:

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:

{
  "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:

{
  "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.