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