@opticlm/connector 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +499 -0
- package/dist/capabilities-CQHv2shL.d.ts +379 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +6 -0
- package/dist/lsp/index.d.ts +85 -0
- package/dist/lsp/index.js +3 -0
- package/dist/mcp/index.d.ts +175 -0
- package/dist/mcp/index.js +9 -0
- package/dist/resolver-Cg62Av4_.d.ts +129 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 EFLKumo
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
# MCP LSP Driver SDK
|
|
2
|
+
|
|
3
|
+
A TypeScript SDK that bridges Language Server Protocol (LSP) capabilities with the Model Context Protocol (MCP). Designed for IDE plugin developers building AI-assisted coding tools for VS Code, JetBrains, and other editors.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Core Philosophy](#core-philosophy)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [MCP Tools](#mcp-tools)
|
|
11
|
+
- [Tool Callbacks](#tool-callbacks)
|
|
12
|
+
- [MCP Resources](#mcp-resources)
|
|
13
|
+
- [Auto-Complete for File Paths](#auto-complete-for-file-paths)
|
|
14
|
+
- [Subscription and Change Notifications](#subscription-and-change-notifications)
|
|
15
|
+
- [Symbol Resolution](#symbol-resolution)
|
|
16
|
+
- [Pipe IPC (Out-of-Process)](#pipe-ipc-out-of-process)
|
|
17
|
+
- [LSP Client (Built-in)](#lsp-client-built-in)
|
|
18
|
+
- [Requirements](#requirements)
|
|
19
|
+
- [License](#license)
|
|
20
|
+
|
|
21
|
+
## Core Philosophy
|
|
22
|
+
|
|
23
|
+
- **Fuzzy-to-Exact Resolution**: LLMs interact via semantic anchors (`symbolName`, `lineHint`), and the SDK resolves them to precise coordinates
|
|
24
|
+
- **Disk-Based Truth**: All read operations reflect the state of files on disk, ignoring unsaved IDE buffers
|
|
25
|
+
- **High Abstraction**: Beyond LSP, it also provides functionality related to something like dual chains (graph capability) and metadata (frontmatter capability).
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm install @opticlm/connector
|
|
31
|
+
# or
|
|
32
|
+
pnpm add @opticlm/connector
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
Providers are installed one at a time onto an MCP server using `install()` from `@opticlm/connector/mcp`. Each call registers the tools and resources for that specific provider. Providers that depend on file access (definition, references, hierarchy, edit) receive a `fileAccess` option.
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
41
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
42
|
+
import { install } from '@opticlm/connector/mcp'
|
|
43
|
+
import * as fs from 'fs/promises'
|
|
44
|
+
|
|
45
|
+
// 1. Create your MCP server
|
|
46
|
+
const server = new McpServer({
|
|
47
|
+
name: 'my-ide-mcp-server',
|
|
48
|
+
version: '1.0.0'
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// 2. Implement File Access
|
|
52
|
+
const fileAccess = {
|
|
53
|
+
readFile: async (uri: string) => {
|
|
54
|
+
return await fs.readFile(uri, 'utf-8')
|
|
55
|
+
},
|
|
56
|
+
readDirectory: (uri: string) => yourIDE.workspace.readDirectory(uri),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Implement Edit Provider
|
|
60
|
+
const edit = {
|
|
61
|
+
// Show diff in your IDE and get user approval
|
|
62
|
+
previewAndApplyEdits: async (operation) => {
|
|
63
|
+
return await showDiffDialog(operation)
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 4. Implement LSP Capability Providers
|
|
68
|
+
const definition = {
|
|
69
|
+
provideDefinition: async (uri, position) => {
|
|
70
|
+
return await lspClient.getDefinition(uri, position)
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const diagnostics = {
|
|
75
|
+
provideDiagnostics: async (uri) => {
|
|
76
|
+
return await lspClient.getDiagnostics(uri)
|
|
77
|
+
},
|
|
78
|
+
getWorkspaceDiagnostics: async () => {
|
|
79
|
+
return await lspClient.getWorkspaceDiagnostics()
|
|
80
|
+
},
|
|
81
|
+
onDiagnosticsChanged: (callback) => {
|
|
82
|
+
yourIDE.onDiagnosticsChanged((uri) => callback(uri))
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const outline = {
|
|
87
|
+
provideDocumentSymbols: async (uri) => {
|
|
88
|
+
return await lspClient.getDocumentSymbols(uri)
|
|
89
|
+
},
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 5. Install providers onto the server
|
|
93
|
+
// fileAccess is installed first; others receive it as an option when needed
|
|
94
|
+
install(server, fileAccess)
|
|
95
|
+
install(server, edit, { fileAccess })
|
|
96
|
+
install(server, definition, { fileAccess })
|
|
97
|
+
install(server, diagnostics, { fileAccess })
|
|
98
|
+
install(server, outline, { fileAccess })
|
|
99
|
+
|
|
100
|
+
// 6. Connect to transport (you control the server lifecycle)
|
|
101
|
+
const transport = new StdioServerTransport()
|
|
102
|
+
await server.connect(transport)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Each `install()` call is independent — only install the providers your IDE actually supports. The `fileAccess` option is required for providers that read files (edit, definition, references, hierarchy) and is used optionally by others for path auto-complete.
|
|
106
|
+
|
|
107
|
+
## MCP Tools
|
|
108
|
+
|
|
109
|
+
The SDK automatically registers tools based on which providers you install:
|
|
110
|
+
|
|
111
|
+
### `goto_definition`
|
|
112
|
+
|
|
113
|
+
Navigate to the definition of a symbol.
|
|
114
|
+
|
|
115
|
+
### `find_references`
|
|
116
|
+
|
|
117
|
+
Find all references to a symbol.
|
|
118
|
+
|
|
119
|
+
### `call_hierarchy`
|
|
120
|
+
|
|
121
|
+
Get call hierarchy for a function or method.
|
|
122
|
+
|
|
123
|
+
### `apply_edit`
|
|
124
|
+
|
|
125
|
+
Apply a text edit to a file using hashline references (requires user approval).
|
|
126
|
+
|
|
127
|
+
The `files://` resource returns file content in **hashline format** — each line is prefixed with `<line>:<hash>|`, where the hash is a 2-char CRC16 digest of the line's content. To edit a file, reference lines by these hashes. If the file has changed since the last read, the hashes won't match and the edit is rejected, preventing stale overwrites.
|
|
128
|
+
|
|
129
|
+
### `global_find`
|
|
130
|
+
|
|
131
|
+
Search for text across the entire workspace.
|
|
132
|
+
|
|
133
|
+
### `get_link_structure`
|
|
134
|
+
|
|
135
|
+
Get all links in the workspace, showing relationships between documents.
|
|
136
|
+
|
|
137
|
+
### `add_link`
|
|
138
|
+
|
|
139
|
+
Add a link to a document by finding a text pattern and replacing it with a link.
|
|
140
|
+
|
|
141
|
+
### `get_frontmatter_structure`
|
|
142
|
+
|
|
143
|
+
Get frontmatter property values across documents.
|
|
144
|
+
|
|
145
|
+
### `set_frontmatter`
|
|
146
|
+
|
|
147
|
+
Set a frontmatter property on a document.
|
|
148
|
+
|
|
149
|
+
## Tool Callbacks
|
|
150
|
+
|
|
151
|
+
Each provider with tools accepts optional `onInput` and `onOutput` callbacks in its install options. These fire synchronously around each tool invocation — `onInput` before processing, `onOutput` after a successful result (not on errors).
|
|
152
|
+
|
|
153
|
+
Use them for logging, telemetry, or testing:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { install } from '@opticlm/connector/mcp'
|
|
157
|
+
|
|
158
|
+
// EditProvider — apply_edit
|
|
159
|
+
install(server, editProvider, {
|
|
160
|
+
fileAccess,
|
|
161
|
+
onEditInput: (input) => {
|
|
162
|
+
console.log('edit requested:', input.uri, input.description)
|
|
163
|
+
},
|
|
164
|
+
onEditOutput: (output) => {
|
|
165
|
+
console.log('edit result:', output.success, output.message)
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
// DefinitionProvider — goto_definition + goto_type_definition
|
|
170
|
+
install(server, definitionProvider, {
|
|
171
|
+
fileAccess,
|
|
172
|
+
onDefinitionInput: (input) => log('goto_definition', input),
|
|
173
|
+
onDefinitionOutput: (output) => log('goto_definition result', output.snippets.length),
|
|
174
|
+
onTypeDefinitionInput: (input) => log('goto_type_definition', input),
|
|
175
|
+
onTypeDefinitionOutput: (output) => log('goto_type_definition result', output.snippets.length),
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Callback reference
|
|
180
|
+
|
|
181
|
+
| Provider | Tool | Input callback | Output callback |
|
|
182
|
+
|----------|------|----------------|-----------------|
|
|
183
|
+
| `EditProvider` | `apply_edit` | `onEditInput` | `onEditOutput` |
|
|
184
|
+
| `DefinitionProvider` | `goto_definition` | `onDefinitionInput` | `onDefinitionOutput` |
|
|
185
|
+
| `DefinitionProvider` | `goto_type_definition` | `onTypeDefinitionInput` | `onTypeDefinitionOutput` |
|
|
186
|
+
| `ReferencesProvider` | `find_references` | `onReferencesInput` | `onReferencesOutput` |
|
|
187
|
+
| `HierarchyProvider` | `call_hierarchy` | `onCallHierarchyInput` | `onCallHierarchyOutput` |
|
|
188
|
+
| `GlobalFindProvider` | `global_find` | `onGlobalFindInput` | `onGlobalFindOutput` |
|
|
189
|
+
| `GraphProvider` | `get_link_structure` | — | `onLinkStructureOutput` |
|
|
190
|
+
| `GraphProvider` | `add_link` | `onAddLinkInput` | `onAddLinkOutput` |
|
|
191
|
+
| `FrontmatterProvider` | `get_frontmatter_structure` | `onFrontmatterStructureInput` | `onFrontmatterStructureOutput` |
|
|
192
|
+
| `FrontmatterProvider` | `set_frontmatter` | `onSetFrontmatterInput` | `onSetFrontmatterOutput` |
|
|
193
|
+
|
|
194
|
+
`get_link_structure` has no `onInput` since its input schema is empty. Resource-only providers (`FileAccessProvider`, `DiagnosticsProvider`, `OutlineProvider`) have no callbacks.
|
|
195
|
+
|
|
196
|
+
## MCP Resources
|
|
197
|
+
|
|
198
|
+
The SDK automatically registers resources based on which providers you install:
|
|
199
|
+
|
|
200
|
+
### `diagnostics://{path}`
|
|
201
|
+
|
|
202
|
+
Get diagnostics (errors, warnings) for a specific file.
|
|
203
|
+
|
|
204
|
+
**Resource URI Pattern:** `diagnostics://{+path}`
|
|
205
|
+
|
|
206
|
+
**Example:** `diagnostics://src/main.ts`
|
|
207
|
+
|
|
208
|
+
Returns diagnostics formatted as markdown with location, severity, and message information.
|
|
209
|
+
|
|
210
|
+
**Subscription Support:** If your `DiagnosticsProvider` implements `onDiagnosticsChanged`, these resources become subscribable. When diagnostics change, the driver sends resource update notifications.
|
|
211
|
+
|
|
212
|
+
### `diagnostics://workspace`
|
|
213
|
+
|
|
214
|
+
Get diagnostics across the entire workspace.
|
|
215
|
+
|
|
216
|
+
**Resource URI:** `diagnostics://workspace`
|
|
217
|
+
|
|
218
|
+
Only available if your `DiagnosticsProvider` implements the optional `getWorkspaceDiagnostics()` method.
|
|
219
|
+
|
|
220
|
+
Returns workspace diagnostics grouped by file, formatted as markdown.
|
|
221
|
+
|
|
222
|
+
**Subscription Support:** If your `DiagnosticsProvider` implements `onDiagnosticsChanged`, this resource becomes subscribable.
|
|
223
|
+
|
|
224
|
+
### `outline://{path}`
|
|
225
|
+
|
|
226
|
+
Get the document outline (symbol tree) for a file.
|
|
227
|
+
|
|
228
|
+
**Resource URI Pattern:** `outline://{+path}`
|
|
229
|
+
|
|
230
|
+
**Example:** `outline://src/components/Button.tsx`
|
|
231
|
+
|
|
232
|
+
Returns document symbols formatted as a hierarchical markdown outline, including:
|
|
233
|
+
- Symbol names and kinds (class, function, method, etc.)
|
|
234
|
+
- Source locations
|
|
235
|
+
- Nested children (e.g., methods within classes)
|
|
236
|
+
|
|
237
|
+
No subscription support for this resource (read-only).
|
|
238
|
+
|
|
239
|
+
### `files://{path}`
|
|
240
|
+
|
|
241
|
+
For directories: returns directory children (git-ignored files excluded, similar to `ls`). For files: returns content in **hashline format** with optional line range and regex filtering.
|
|
242
|
+
|
|
243
|
+
**Hashline format:** Each line is prefixed with `<line>:<hash>|`, where `<line>` is the 1-based line number and `<hash>` is a 2-char CRC16 hex digest of the line content. For example:
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
1:a3|function hello() {
|
|
247
|
+
2:f1| return "world"
|
|
248
|
+
3:0e|}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
These hashes serve as content-addressed anchors for the `apply_edit` tool — if the file changes between read and edit, the hash mismatch is detected and the edit is safely rejected.
|
|
252
|
+
|
|
253
|
+
**Resource URI Pattern:** `files://{+path}`
|
|
254
|
+
|
|
255
|
+
**Example:** `files://src`, `files://src/index.ts`, `files://src/index.ts#L1-L2`, `files://src/index.ts?pattern=^import`, `files://src/index.ts?pattern=TODO#L10-L50`
|
|
256
|
+
|
|
257
|
+
No subscription support for this resource (read-only).
|
|
258
|
+
|
|
259
|
+
### `outlinks://{path}`
|
|
260
|
+
|
|
261
|
+
Get outgoing links from a specific file.
|
|
262
|
+
|
|
263
|
+
**Resource URI Pattern:** `outlinks://{+path}`
|
|
264
|
+
|
|
265
|
+
**Example:** `outlinks://notes/index.md`
|
|
266
|
+
|
|
267
|
+
Returns a JSON array of links originating from the specified document.
|
|
268
|
+
|
|
269
|
+
No subscription support for this resource (read-only).
|
|
270
|
+
|
|
271
|
+
### `backlinks://{path}`
|
|
272
|
+
|
|
273
|
+
Get incoming links (backlinks) to a specific file.
|
|
274
|
+
|
|
275
|
+
**Resource URI Pattern:** `backlinks://{+path}`
|
|
276
|
+
|
|
277
|
+
**Example:** `backlinks://notes/topic-a.md`
|
|
278
|
+
|
|
279
|
+
Returns a JSON array of links pointing to the specified document.
|
|
280
|
+
|
|
281
|
+
No subscription support for this resource (read-only).
|
|
282
|
+
|
|
283
|
+
### `frontmatter://{path}`
|
|
284
|
+
|
|
285
|
+
Get frontmatter metadata for a specific file.
|
|
286
|
+
|
|
287
|
+
**Resource URI Pattern:** `frontmatter://{+path}`
|
|
288
|
+
|
|
289
|
+
**Example:** `frontmatter://notes/index.md`
|
|
290
|
+
|
|
291
|
+
Returns a JSON object containing all frontmatter properties and values for the document.
|
|
292
|
+
|
|
293
|
+
No subscription support for this resource (read-only).
|
|
294
|
+
|
|
295
|
+
## Auto-Complete for File Paths
|
|
296
|
+
|
|
297
|
+
All resource templates with a `{+path}` variable (`files://`, `diagnostics://`, `outline://`, `outlinks://`, `backlinks://`, `frontmatter://`) support MCP auto-completion. When an MCP client calls `completion/complete` with a partial file path, the SDK uses `readDirectory` from your `FileAccessProvider` to suggest matching entries.
|
|
298
|
+
|
|
299
|
+
- Completion is case-insensitive and splits input into a directory and prefix (e.g., `src/ser` reads `src/` and filters by `ser`)
|
|
300
|
+
- If `readDirectory` fails (e.g., the directory doesn't exist), an empty list is returned
|
|
301
|
+
- Results are capped at 100 items by the MCP SDK
|
|
302
|
+
|
|
303
|
+
This works automatically — no additional configuration is needed.
|
|
304
|
+
|
|
305
|
+
## Subscription and Change Notifications
|
|
306
|
+
|
|
307
|
+
Providers can implement optional `onDiagnosticsChanged` and `onFileChanged` callbacks to make resources subscribable:
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
import { install } from '@opticlm/connector/mcp'
|
|
311
|
+
|
|
312
|
+
install(server, {
|
|
313
|
+
readFile: async (uri) => { /* ... */ },
|
|
314
|
+
readDirectory: async (path) => { /* ... */ },
|
|
315
|
+
onFileChanged: (callback) => {
|
|
316
|
+
yourIDE.onFileChanged((uri) => callback(uri))
|
|
317
|
+
},
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
install(server, {
|
|
321
|
+
provideDiagnostics: async (uri) => { /* ... */ },
|
|
322
|
+
getWorkspaceDiagnostics: async () => { /* ... */ },
|
|
323
|
+
onDiagnosticsChanged: (callback) => {
|
|
324
|
+
yourIDE.onDiagnosticsChanged((uri) => callback(uri))
|
|
325
|
+
},
|
|
326
|
+
})
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
When diagnostics or files change, call the registered callback with the affected file URI. The driver will send MCP resource update notifications to subscribers.
|
|
330
|
+
|
|
331
|
+
## Symbol Resolution
|
|
332
|
+
|
|
333
|
+
The SDK uses a robust algorithm to handle imprecise LLM positioning:
|
|
334
|
+
|
|
335
|
+
1. Target the `lineHint` (converting 1-based to 0-based)
|
|
336
|
+
2. Search for `symbolName` in that line
|
|
337
|
+
3. **Robustness Fallback**: If not found, scan +/- 2 lines (configurable)
|
|
338
|
+
4. Use `orderHint` to select the Nth occurrence if needed
|
|
339
|
+
|
|
340
|
+
Configure the search radius via the `resolverConfig` option:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
install(server, definitionProvider, {
|
|
344
|
+
fileAccess,
|
|
345
|
+
resolverConfig: {
|
|
346
|
+
lineSearchRadius: 5, // Default: 2
|
|
347
|
+
},
|
|
348
|
+
})
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Pipe IPC (Out-of-Process)
|
|
352
|
+
|
|
353
|
+
When the MCP server runs in a separate process from the IDE plugin (e.g., spawned via stdio transport), the Pipe IPC layer lets the two communicate over a named pipe.
|
|
354
|
+
|
|
355
|
+
**IDE plugin side** — expose providers:
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
import { servePipe } from '@opticlm/connector'
|
|
359
|
+
|
|
360
|
+
const server = await servePipe({
|
|
361
|
+
pipeName: 'my-ide-lsp',
|
|
362
|
+
fileAccess: { /* ... */ },
|
|
363
|
+
definition: { /* ... */ },
|
|
364
|
+
diagnostics: {
|
|
365
|
+
provideDiagnostics: async (uri) => { /* ... */ },
|
|
366
|
+
onDiagnosticsChanged: (callback) => { /* ... */ },
|
|
367
|
+
},
|
|
368
|
+
// Add only the providers your IDE supports
|
|
369
|
+
})
|
|
370
|
+
// server.pipePath — the resolved pipe path
|
|
371
|
+
// server.connectionCount — number of connected clients
|
|
372
|
+
// await server.close() — shut down
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**MCP server side** — connect and install proxy providers:
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
379
|
+
import { connectPipe } from '@opticlm/connector'
|
|
380
|
+
import { install } from '@opticlm/connector/mcp'
|
|
381
|
+
|
|
382
|
+
const conn = await connectPipe({
|
|
383
|
+
pipeName: 'my-ide-lsp',
|
|
384
|
+
connectTimeout: 5000, // optional, default 5000ms
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
// conn exposes proxy providers as named fields:
|
|
388
|
+
// conn.fileAccess, conn.definition, conn.diagnostics, etc.
|
|
389
|
+
// conn.availableMethods lists all methods the server exposes
|
|
390
|
+
|
|
391
|
+
const mcpServer = new McpServer({ name: 'my-mcp', version: '1.0.0' })
|
|
392
|
+
|
|
393
|
+
if (conn.fileAccess) install(mcpServer, conn.fileAccess)
|
|
394
|
+
if (conn.definition && conn.fileAccess)
|
|
395
|
+
install(mcpServer, conn.definition, { fileAccess: conn.fileAccess })
|
|
396
|
+
if (conn.diagnostics && conn.fileAccess)
|
|
397
|
+
install(mcpServer, conn.diagnostics, { fileAccess: conn.fileAccess })
|
|
398
|
+
// Install whichever proxy providers are available
|
|
399
|
+
|
|
400
|
+
// When done:
|
|
401
|
+
conn.disconnect()
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The handshake automatically discovers which providers the server exposes and builds typed proxies. Change notifications are forwarded as push notifications to all connected clients. Multiple clients can connect to the same pipe simultaneously.
|
|
405
|
+
|
|
406
|
+
## LSP Client (Built-in)
|
|
407
|
+
|
|
408
|
+
For standalone MCP servers that need to communicate directly with an LSP server (e.g., when not running inside an IDE plugin), `LspClient` spawns a language server process and automatically creates capability providers based on the server's reported capabilities.
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
412
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
413
|
+
import { createLspClient } from '@opticlm/connector'
|
|
414
|
+
import { install } from '@opticlm/connector/mcp'
|
|
415
|
+
import * as fs from 'fs/promises'
|
|
416
|
+
|
|
417
|
+
// 1. Create and start the LSP client
|
|
418
|
+
const lsp = createLspClient({
|
|
419
|
+
command: 'typescript-language-server',
|
|
420
|
+
args: ['--stdio'],
|
|
421
|
+
workspacePath: '/path/to/project',
|
|
422
|
+
readFile: (path) => fs.readFile(path, 'utf-8'),
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
await lsp.start()
|
|
426
|
+
|
|
427
|
+
// 2. Wire providers into MCP
|
|
428
|
+
const server = new McpServer({ name: 'my-mcp', version: '1.0.0' })
|
|
429
|
+
|
|
430
|
+
const fileAccess = {
|
|
431
|
+
readFile: (uri: string) => fs.readFile(uri, 'utf-8'),
|
|
432
|
+
readDirectory: async () => [],
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Providers are automatically created based on server capabilities
|
|
436
|
+
install(server, fileAccess)
|
|
437
|
+
if (lsp.definition) install(server, lsp.definition, { fileAccess })
|
|
438
|
+
if (lsp.references) install(server, lsp.references, { fileAccess })
|
|
439
|
+
if (lsp.hierarchy) install(server, lsp.hierarchy, { fileAccess })
|
|
440
|
+
if (lsp.outline) install(server, lsp.outline, { fileAccess })
|
|
441
|
+
install(server, {
|
|
442
|
+
...lsp.diagnostics,
|
|
443
|
+
onDiagnosticsChanged: lsp.onDiagnosticsChanged,
|
|
444
|
+
}, { fileAccess })
|
|
445
|
+
|
|
446
|
+
const transport = new StdioServerTransport()
|
|
447
|
+
await server.connect(transport)
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### `LspClientOptions`
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
interface LspClientOptions {
|
|
454
|
+
command: string // LSP server command to spawn
|
|
455
|
+
args?: string[] // Command arguments (e.g., ['--stdio'])
|
|
456
|
+
workspacePath: string // Absolute path to the workspace root
|
|
457
|
+
readFile: (path: string) => Promise<string> // File reader for document sync
|
|
458
|
+
env?: Record<string, string> // Additional environment variables
|
|
459
|
+
initializationOptions?: unknown // LSP initializationOptions
|
|
460
|
+
documentIdleTimeout?: number // Auto-close open docs after ms (default: 30000)
|
|
461
|
+
requestTimeout?: number // Timeout for LSP requests in ms (default: 30000)
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### How It Works
|
|
466
|
+
|
|
467
|
+
1. `start()` spawns the LSP server process and performs the initialize/initialized handshake
|
|
468
|
+
2. The server's `ServerCapabilities` response determines which providers are created:
|
|
469
|
+
- `definitionProvider` → `lsp.definition`
|
|
470
|
+
- `referencesProvider` → `lsp.references`
|
|
471
|
+
- `callHierarchyProvider` → `lsp.hierarchy`
|
|
472
|
+
- `documentSymbolProvider` → `lsp.outline`
|
|
473
|
+
- Diagnostics are always available (via `textDocument/publishDiagnostics` notifications)
|
|
474
|
+
3. Providers that the server does not support remain `undefined`
|
|
475
|
+
4. Documents are automatically opened/closed with the server on demand, with an idle timeout for cleanup
|
|
476
|
+
|
|
477
|
+
### Lifecycle
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
const lsp = createLspClient({ /* ... */ })
|
|
481
|
+
|
|
482
|
+
lsp.getState() // 'idle'
|
|
483
|
+
await lsp.start()
|
|
484
|
+
lsp.getState() // 'running'
|
|
485
|
+
|
|
486
|
+
// Use lsp.definition, lsp.references, etc.
|
|
487
|
+
|
|
488
|
+
await lsp.stop()
|
|
489
|
+
lsp.getState() // 'dead'
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
## Requirements
|
|
493
|
+
|
|
494
|
+
- Node.js >= 18.0.0
|
|
495
|
+
- TypeScript >= 5.7.0
|
|
496
|
+
|
|
497
|
+
## License
|
|
498
|
+
|
|
499
|
+
MIT
|