or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

client-operations.mdexception-handling.mdfeature-registration.mdindex.mdprogress-reporting.mdprotocol-handling.mdserver-management.mduri-utilities.mdutilities.mdworkspace-management.md

protocol-handling.mddocs/

0

# Protocol and Message Handling

1

2

Low-level protocol handling for JSON-RPC communication, LSP message processing, custom protocol extensions, and message routing with built-in lifecycle management.

3

4

## Capabilities

5

6

### Language Server Protocol

7

8

Core LSP protocol implementation with built-in handlers for standard LSP methods and extensibility for custom protocols.

9

10

```python { .api }

11

class LanguageServerProtocol(JsonRPCProtocol):

12

"""

13

Language Server Protocol implementation with standard LSP handlers.

14

15

Provides built-in handlers for initialize, shutdown, document lifecycle,

16

and workspace operations with extensible architecture for custom features.

17

"""

18

19

def get_message_handler(self) -> Callable: ...

20

21

def lsp_initialize(self, params: InitializeParams) -> InitializeResult:

22

"""Handle LSP initialize request."""

23

24

def lsp_shutdown(self, params: Any) -> None:

25

"""Handle LSP shutdown request."""

26

27

def lsp_exit(self, params: Any) -> None:

28

"""Handle LSP exit notification."""

29

30

def lsp_text_document_did_open(self, params: DidOpenTextDocumentParams) -> None:

31

"""Handle document open notification."""

32

33

def lsp_text_document_did_change(self, params: DidChangeTextDocumentParams) -> None:

34

"""Handle document change notification."""

35

36

def lsp_text_document_did_close(self, params: DidCloseTextDocumentParams) -> None:

37

"""Handle document close notification."""

38

39

def lsp_workspace_did_change_workspace_folders(self, params: DidChangeWorkspaceFoldersParams) -> None:

40

"""Handle workspace folder changes."""

41

42

def lsp_workspace_execute_command(self, params: ExecuteCommandParams) -> Any:

43

"""Handle command execution requests."""

44

```

45

46

### JSON-RPC Protocol

47

48

Base JSON-RPC protocol implementation for message transport, request/response handling, and connection management.

49

50

```python { .api }

51

class JsonRPCProtocol(asyncio.Protocol):

52

"""

53

Base JSON-RPC protocol for message transport and communication.

54

55

Handles connection lifecycle, message parsing, request routing,

56

and response management for JSON-RPC communication.

57

"""

58

59

def connection_made(self, transport: asyncio.Transport) -> None:

60

"""Called when connection is established."""

61

62

def connection_lost(self, exc: Exception) -> None:

63

"""Called when connection is lost."""

64

65

def data_received(self, data: bytes) -> None:

66

"""Process incoming data and parse JSON-RPC messages."""

67

68

def send_request(self, method: str, params: Any = None) -> Future:

69

"""

70

Send JSON-RPC request and return future for response.

71

72

Parameters:

73

- method: str - RPC method name

74

- params: Any - Method parameters

75

76

Returns:

77

Future that resolves to the response

78

"""

79

80

def send_notification(self, method: str, params: Any = None) -> None:

81

"""

82

Send JSON-RPC notification (no response expected).

83

84

Parameters:

85

- method: str - RPC method name

86

- params: Any - Method parameters

87

"""

88

```

89

90

### Protocol Message Types

91

92

Core message type definitions for JSON-RPC communication with structured request/response handling.

93

94

```python { .api }

95

@attrs.define

96

class JsonRPCRequestMessage:

97

"""JSON-RPC request message structure."""

98

id: Union[int, str]

99

method: str

100

params: Any = None

101

jsonrpc: str = "2.0"

102

103

@attrs.define

104

class JsonRPCResponseMessage:

105

"""JSON-RPC response message structure."""

106

id: Union[int, str]

107

result: Any = None

108

error: Any = None

109

jsonrpc: str = "2.0"

110

111

@attrs.define

112

class JsonRPCNotification:

113

"""JSON-RPC notification message structure."""

114

method: str

115

params: Any = None

116

jsonrpc: str = "2.0"

117

```

118

119

### Protocol Utilities

120

121

Utility functions for protocol configuration, message conversion, and type handling.

122

123

```python { .api }

124

def default_converter() -> Converter:

125

"""

126

Create default cattrs converter with LSP-specific hooks.

127

128

Returns:

129

Configured converter for LSP message serialization/deserialization

130

"""

131

132

def _dict_to_object(d: Any) -> Any:

133

"""Convert dictionary to nested object structure."""

134

135

def _params_field_structure_hook(obj: Dict, cls: Type) -> Any:

136

"""Structure hook for handling params field in messages."""

137

138

def _result_field_structure_hook(obj: Dict, cls: Type) -> Any:

139

"""Structure hook for handling result field in messages."""

140

```

141

142

### Server Capabilities

143

144

System for building and managing server capability declarations for LSP initialization.

145

146

```python { .api }

147

class ServerCapabilitiesBuilder:

148

"""

149

Builder for constructing server capabilities during initialization.

150

151

Automatically configures capabilities based on registered features

152

and provides manual capability configuration for advanced use cases.

153

"""

154

155

# Capability configuration methods for various LSP features

156

# (specific methods depend on LSP specification)

157

```

158

159

## Usage Examples

160

161

### Custom Protocol Extension

162

163

```python

164

from pygls.protocol import LanguageServerProtocol

165

from pygls.server import LanguageServer

166

167

class CustomProtocol(LanguageServerProtocol):

168

def __init__(self, server, converter):

169

super().__init__(server, converter)

170

self.custom_state = {}

171

172

def lsp_initialize(self, params):

173

# Call parent initialization

174

result = super().lsp_initialize(params)

175

176

# Add custom initialization logic

177

self.custom_state['client_name'] = params.client_info.name if params.client_info else "Unknown"

178

179

# Extend server capabilities

180

result.capabilities.experimental = {

181

"customFeature": True,

182

"version": "1.0.0"

183

}

184

185

return result

186

187

# Add custom message handler

188

@lsp_method("custom/specialRequest")

189

def handle_special_request(self, params):

190

return {

191

"result": "Custom protocol handled",

192

"client": self.custom_state.get('client_name')

193

}

194

195

# Use custom protocol

196

server = LanguageServer(

197

"custom-server",

198

"1.0.0",

199

protocol_cls=CustomProtocol

200

)

201

```

202

203

### Manual Message Sending

204

205

```python

206

from pygls.server import LanguageServer

207

from lsprotocol.types import MessageType

208

209

server = LanguageServer("message-sender", "1.0.0")

210

211

@server.feature(TEXT_DOCUMENT_DID_OPEN)

212

def on_open(params):

213

# Send notification to client

214

server.lsp.send_notification(

215

"window/logMessage",

216

{

217

"type": MessageType.Info,

218

"message": f"Opened document: {params.text_document.uri}"

219

}

220

)

221

222

@server.command("myServer.requestConfiguration")

223

async def request_config(params):

224

# Send request and wait for response

225

try:

226

config_response = await server.lsp.send_request(

227

"workspace/configuration",

228

{

229

"items": [

230

{"section": "myServer.formatting"},

231

{"section": "myServer.linting"}

232

]

233

}

234

)

235

236

return {"configuration": config_response}

237

238

except Exception as e:

239

return {"error": str(e)}

240

```

241

242

### Error Handling and Exceptions

243

244

```python

245

from pygls.exceptions import (

246

JsonRpcException,

247

JsonRpcInternalError,

248

FeatureRequestError

249

)

250

251

class RobustProtocol(LanguageServerProtocol):

252

def lsp_text_document_did_change(self, params):

253

try:

254

# Call parent handler

255

super().lsp_text_document_did_change(params)

256

257

# Custom change processing

258

document = self.workspace.get_document(params.text_document.uri)

259

self.validate_document(document)

260

261

except Exception as e:

262

# Log error but don't propagate to avoid breaking protocol

263

self.server.logger.error(f"Error processing document change: {e}")

264

265

def validate_document(self, document):

266

# Custom validation that might raise exceptions

267

if len(document.source) > 1000000:

268

raise JsonRpcInternalError("Document too large")

269

270

@server.feature(TEXT_DOCUMENT_HOVER)

271

def safe_hover(params):

272

try:

273

# Hover implementation

274

result = generate_hover_content(params)

275

return result

276

277

except FileNotFoundError:

278

# Return None for no hover content

279

return None

280

281

except Exception as e:

282

# Convert to LSP error

283

raise FeatureRequestError(f"Hover failed: {str(e)}")

284

```

285

286

### Connection Monitoring

287

288

```python

289

class MonitoredProtocol(LanguageServerProtocol):

290

def connection_made(self, transport):

291

super().connection_made(transport)

292

self.server.logger.info("Client connected")

293

294

# Setup connection monitoring

295

self.connection_start_time = time.time()

296

self.message_count = 0

297

298

def connection_lost(self, exc):

299

duration = time.time() - self.connection_start_time

300

self.server.logger.info(

301

f"Client disconnected after {duration:.2f}s, "

302

f"processed {self.message_count} messages"

303

)

304

305

super().connection_lost(exc)

306

307

def data_received(self, data):

308

self.message_count += 1

309

super().data_received(data)

310

```

311

312

### Custom Message Converter

313

314

```python

315

from pygls.protocol import default_converter

316

import cattrs

317

318

def create_custom_converter():

319

converter = default_converter()

320

321

# Add custom type conversion

322

converter.register_structure_hook(

323

MyCustomType,

324

lambda obj, cls: MyCustomType(**obj)

325

)

326

327

converter.register_unstructure_hook(

328

MyCustomType,

329

lambda obj: {"custom_field": obj.value}

330

)

331

332

return converter

333

334

# Use custom converter

335

server = LanguageServer(

336

"custom-converter-server",

337

"1.0.0",

338

converter_factory=create_custom_converter

339

)

340

```