@gesslar/lpc-mcp 0.1.2
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/.github/dependabot.yml +11 -0
- package/.github/workflows/ci.yml +33 -0
- package/README.md +326 -0
- package/UNLICENSE.txt +24 -0
- package/eslint.config.js +167 -0
- package/package.json +43 -0
- package/src/index.js +382 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Giddyup
|
|
2
|
+
permissions:
|
|
3
|
+
contents: read
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: [main]
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
cowyboysounds:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
node-version: [20.x, 22.x]
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- name: Checkout code
|
|
21
|
+
uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- name: Setup Node.js ${{ matrix.node-version }}
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version: ${{ matrix.node-version }}
|
|
27
|
+
cache: "npm"
|
|
28
|
+
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: npm ci
|
|
31
|
+
|
|
32
|
+
- name: Run ESLint
|
|
33
|
+
run: npm run lint
|
package/README.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# LPC MCP Server
|
|
2
|
+
|
|
3
|
+
**Bring LPC development into 2025** with AI-powered code intelligence.
|
|
4
|
+
|
|
5
|
+
This MCP (Model Context Protocol) server wraps the [jlchmura/lpc-language-server](https://github.com/jlchmura/lpc-language-server) and exposes it to AI assistants, enabling natural language queries about your LPC codebase with real language server-powered understanding.
|
|
6
|
+
|
|
7
|
+
## What This Enables
|
|
8
|
+
|
|
9
|
+
✨ **AI assistants can now:**
|
|
10
|
+
|
|
11
|
+
- Understand your LPC code structure through the language server
|
|
12
|
+
- Get real documentation from hover information
|
|
13
|
+
- Jump to definitions and find references
|
|
14
|
+
- Answer natural language questions about your mudlib
|
|
15
|
+
- Trace inheritance chains and function calls
|
|
16
|
+
- Explain complex code patterns
|
|
17
|
+
|
|
18
|
+
All through conversation, powered by actual code intelligence instead of pattern matching.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- 🔍 **`lpc_hover`**: Get documentation/hover information for symbols
|
|
23
|
+
- 🎯 **`lpc_definition`**: Jump to definition of symbols
|
|
24
|
+
- 🔗 **`lpc_references`**: Find all references to a symbol
|
|
25
|
+
- 🐛 **`lpc_diagnostics`**: Get real-time errors, warnings, and hints from the language server
|
|
26
|
+
- 📁 **Workspace-aware**: Reads your `lpc-config.json` for proper symbol resolution
|
|
27
|
+
- 🚀 **Fast**: Direct JSON-RPC communication with the language server
|
|
28
|
+
|
|
29
|
+
## Prerequisites
|
|
30
|
+
|
|
31
|
+
### 1. Install Node.js
|
|
32
|
+
|
|
33
|
+
Node.js 16+ required:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
node --version # Should be v16.0.0 or higher
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 2. Install the LPC Language Server Extension
|
|
40
|
+
|
|
41
|
+
The extension must be installed in VS Code (the server binary is bundled with it):
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
code --install-extension jlchmura.lpc
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Verify installation:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
ls ~/.vscode/extensions/jlchmura.lpc-*/out/server/src/server.js
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 3. Install Dependencies
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
cd /path/to/lpc-mcp
|
|
57
|
+
npm install
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 4. Create `lpc-config.json` in Your Mudlib
|
|
61
|
+
|
|
62
|
+
The language server needs this config file at your mudlib root to understand includes, simul_efuns, etc.
|
|
63
|
+
|
|
64
|
+
Example `/path/to/your/mudlib/lpc-config.json`:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"driver": {
|
|
69
|
+
"type": "fluffos"
|
|
70
|
+
},
|
|
71
|
+
"libFiles": {
|
|
72
|
+
"master": "adm/obj/master.c",
|
|
73
|
+
"simul_efun": "adm/obj/simul_efun.c",
|
|
74
|
+
"global_include": "include/global.h"
|
|
75
|
+
},
|
|
76
|
+
"libInclude": [
|
|
77
|
+
"include",
|
|
78
|
+
"include/driver",
|
|
79
|
+
"adm/include"
|
|
80
|
+
],
|
|
81
|
+
"exclude": [
|
|
82
|
+
".git/",
|
|
83
|
+
"tmp/"
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Setup for Different AI Tools
|
|
89
|
+
|
|
90
|
+
### Warp (Terminal)
|
|
91
|
+
|
|
92
|
+
Add to your Warp MCP configuration:
|
|
93
|
+
|
|
94
|
+
**Location**: Settings → AI → Model Context Protocol
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"lpc": {
|
|
99
|
+
"command": "node",
|
|
100
|
+
"args": ["/absolute/path/to/lpc-mcp/index.js"],
|
|
101
|
+
"env": {
|
|
102
|
+
"LPC_WORKSPACE_ROOT": "/path/to/your/mudlib"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Important**: Use absolute paths! Replace:
|
|
109
|
+
|
|
110
|
+
- `/absolute/path/to/lpc-mcp/index.js` with the actual path to this repo
|
|
111
|
+
- `/path/to/your/mudlib` with the directory containing your `lpc-config.json`
|
|
112
|
+
|
|
113
|
+
Restart Warp after adding the configuration.
|
|
114
|
+
|
|
115
|
+
### Claude Desktop
|
|
116
|
+
|
|
117
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or equivalent:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"mcpServers": {
|
|
122
|
+
"lpc": {
|
|
123
|
+
"command": "node",
|
|
124
|
+
"args": ["/absolute/path/to/lpc-mcp/index.js"],
|
|
125
|
+
"env": {
|
|
126
|
+
"LPC_WORKSPACE_ROOT": "/path/to/your/mudlib"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Restart Claude Desktop after configuration.
|
|
134
|
+
|
|
135
|
+
### Cline (VS Code Extension)
|
|
136
|
+
|
|
137
|
+
Add to your Cline MCP settings:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"mcpServers": {
|
|
142
|
+
"lpc": {
|
|
143
|
+
"command": "node",
|
|
144
|
+
"args": ["/absolute/path/to/lpc-mcp/index.js"],
|
|
145
|
+
"env": {
|
|
146
|
+
"LPC_WORKSPACE_ROOT": "/path/to/your/mudlib"
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### GitHub Copilot (VS Code)
|
|
154
|
+
|
|
155
|
+
**Prerequisites:**
|
|
156
|
+
|
|
157
|
+
- Install the [Copilot MCP extension](https://marketplace.visualstudio.com/items?itemName=automatalabs.copilot-mcp): `code --install-extension automatalabs.copilot-mcp`
|
|
158
|
+
|
|
159
|
+
**Configuration:**
|
|
160
|
+
Add to `~/Library/Application Support/Code/User/mcp.json` (macOS) or equivalent:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"servers": {
|
|
165
|
+
"lpc": {
|
|
166
|
+
"type": "node",
|
|
167
|
+
"command": "node",
|
|
168
|
+
"args": ["/absolute/path/to/lpc-mcp/index.js"],
|
|
169
|
+
"env": {
|
|
170
|
+
"LPC_WORKSPACE_ROOT": "/path/to/your/mudlib"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"inputs": []
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Other MCP-Compatible Tools
|
|
179
|
+
|
|
180
|
+
The configuration is the same for any MCP-compatible tool:
|
|
181
|
+
|
|
182
|
+
1. Add the server to your MCP configuration
|
|
183
|
+
2. Provide the Node.js command and path to `index.js`
|
|
184
|
+
3. Set `LPC_WORKSPACE_ROOT` environment variable to your mudlib root
|
|
185
|
+
|
|
186
|
+
## Usage Examples
|
|
187
|
+
|
|
188
|
+
Once configured, you can ask your AI assistant natural language questions:
|
|
189
|
+
|
|
190
|
+
**"What does the `query_short()` function do in room.c?"**
|
|
191
|
+
→ AI uses `lpc_hover` to get documentation
|
|
192
|
+
|
|
193
|
+
**"Where is `STD_OBJECT` defined?"**
|
|
194
|
+
→ AI uses `lpc_definition` to find the file
|
|
195
|
+
|
|
196
|
+
**"Find all places that call `set_room_size()`"**
|
|
197
|
+
→ AI uses `lpc_references` to locate all callers
|
|
198
|
+
|
|
199
|
+
**"Explain how the maze generation algorithm works"**
|
|
200
|
+
→ AI reads code and uses hover info to understand functions
|
|
201
|
+
|
|
202
|
+
**"What's the inheritance tree for rooms?"**
|
|
203
|
+
→ AI traces `inherit` statements and jumps to definitions
|
|
204
|
+
|
|
205
|
+
**"Check if this LPC file has any syntax errors"**
|
|
206
|
+
→ AI uses `lpc_diagnostics` to validate the code
|
|
207
|
+
|
|
208
|
+
**"Why won't this LPC code compile?"**
|
|
209
|
+
→ AI checks diagnostics for errors like undeclared variables or type mismatches
|
|
210
|
+
|
|
211
|
+
## Testing
|
|
212
|
+
|
|
213
|
+
To verify the server works:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# Set workspace root for testing
|
|
217
|
+
export LPC_WORKSPACE_ROOT=/path/to/your/mudlib
|
|
218
|
+
|
|
219
|
+
# Start the server (it will wait for MCP protocol messages)
|
|
220
|
+
node index.js
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
The server should output:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
Starting LPC Language Server...
|
|
227
|
+
Initializing LSP...
|
|
228
|
+
LPC Language Server started successfully
|
|
229
|
+
LPC MCP Server running on stdio
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Troubleshooting
|
|
233
|
+
|
|
234
|
+
### Server won't start
|
|
235
|
+
|
|
236
|
+
**Check the LPC extension is installed:**
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
code --list-extensions | grep jlchmura.lpc
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**Check logs** (for Warp):
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
tail -f ~/.local/state/warp-terminal/mcp/*.log
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### Language server not resolving symbols
|
|
249
|
+
|
|
250
|
+
**Verify workspace root:**
|
|
251
|
+
|
|
252
|
+
- Make sure `LPC_WORKSPACE_ROOT` points to the directory with `lpc-config.json`
|
|
253
|
+
- Use absolute paths, not relative
|
|
254
|
+
|
|
255
|
+
**Check your lpc-config.json:**
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
cat $LPC_WORKSPACE_ROOT/lpc-config.json
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Extension version mismatch
|
|
262
|
+
|
|
263
|
+
If the extension path doesn't match, update line 40 in `index.js`:
|
|
264
|
+
|
|
265
|
+
```javascript
|
|
266
|
+
const lspPath = path.join(
|
|
267
|
+
process.env.HOME,
|
|
268
|
+
".vscode/extensions/jlchmura.lpc-VERSION/out/server/src/server.js"
|
|
269
|
+
);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Find your version:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
ls ~/.vscode/extensions/ | grep jlchmura.lpc
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## How It Works
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
AI Assistant
|
|
282
|
+
↓ (natural language)
|
|
283
|
+
MCP Protocol
|
|
284
|
+
↓ (tool calls: lpc_hover, lpc_definition, lpc_references)
|
|
285
|
+
This Server
|
|
286
|
+
↓ (JSON-RPC: textDocument/hover, etc.)
|
|
287
|
+
LPC Language Server
|
|
288
|
+
↓ (parses LPC, reads lpc-config.json)
|
|
289
|
+
Your Mudlib
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
1. AI assistant sends MCP tool requests
|
|
293
|
+
2. Server reads the file and sends `textDocument/didOpen` to LSP
|
|
294
|
+
3. Server translates MCP → LSP JSON-RPC requests
|
|
295
|
+
4. LSP analyzes code using your `lpc-config.json`
|
|
296
|
+
5. Server returns LSP response as MCP result
|
|
297
|
+
6. AI understands your code structure!
|
|
298
|
+
|
|
299
|
+
## Roadmap
|
|
300
|
+
|
|
301
|
+
- [ ] Add completion support
|
|
302
|
+
- [x] Add diagnostics (errors/warnings)
|
|
303
|
+
- [ ] Support signature help
|
|
304
|
+
- [ ] Cache opened documents
|
|
305
|
+
- [ ] Support multiple workspace roots
|
|
306
|
+
- [ ] Add rename symbol support
|
|
307
|
+
- [ ] Add document symbols (outline)
|
|
308
|
+
- [ ] Add workspace symbol search
|
|
309
|
+
|
|
310
|
+
## Contributing
|
|
311
|
+
|
|
312
|
+
PRs welcome! This is a proof-of-concept that can be extended with more LSP features.
|
|
313
|
+
|
|
314
|
+
## Credits
|
|
315
|
+
|
|
316
|
+
- **John (jlchmura)** - The INCOMPARABLY SKILLED MASTER PROGRAMMER whose [LPC language server](https://github.com/jlchmura/lpc-language-server) rescued LPC development from 1995. Without his greatness, kindness, and all-around hunk demeanour, we would still be `grep`-ing through mudlibs like cavemen. This cos theMCP server is merely a humble wrapper around his genius. 🙇
|
|
317
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/) - The protocol making this possible
|
|
318
|
+
- Built in an hour of inspired hacking in 2025
|
|
319
|
+
|
|
320
|
+
## ~~License~~
|
|
321
|
+
|
|
322
|
+
Unlicense - Public Domain. Do whatever you want with this code.
|
|
323
|
+
|
|
324
|
+
### Note
|
|
325
|
+
|
|
326
|
+
The LPC language server itself [(jlchmura/lpc-language-server)](https://github.com/jlchmura/lpc-language-server) is under its own license.
|
package/UNLICENSE.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import js from "@eslint/js"
|
|
2
|
+
import jsdoc from "eslint-plugin-jsdoc"
|
|
3
|
+
import stylistic from "@stylistic/eslint-plugin"
|
|
4
|
+
import globals from "globals"
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
jsdoc.configs['flat/recommended'], {
|
|
9
|
+
name: "gesslar/uglier/ignores",
|
|
10
|
+
ignores: [],
|
|
11
|
+
}, {
|
|
12
|
+
name: "gesslar/uglier/languageOptions",
|
|
13
|
+
languageOptions: {
|
|
14
|
+
ecmaVersion: "latest",
|
|
15
|
+
sourceType: "module",
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
fetch: "readonly",
|
|
19
|
+
Headers: "readonly",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
// Add override for webview files to include browser globals
|
|
24
|
+
{
|
|
25
|
+
name: "gesslar/uglier/webview-env",
|
|
26
|
+
files: ["src/webview/**/*.{js,mjs,cjs}"],
|
|
27
|
+
languageOptions: {
|
|
28
|
+
globals: {
|
|
29
|
+
...globals.browser,
|
|
30
|
+
acquireVsCodeApi: "readonly"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
// Add override for .cjs files to treat as CommonJS
|
|
35
|
+
{
|
|
36
|
+
name: "gesslar/uglier/cjs-override",
|
|
37
|
+
files: ["src/**/*.cjs"],
|
|
38
|
+
languageOptions: {
|
|
39
|
+
sourceType: "script",
|
|
40
|
+
ecmaVersion: 2021
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
// Add override for .mjs files to treat as ES modules
|
|
44
|
+
{
|
|
45
|
+
name: "gesslar/uglier/mjs-override",
|
|
46
|
+
files: ["src/**/*.mjs"],
|
|
47
|
+
languageOptions: {
|
|
48
|
+
sourceType: "module",
|
|
49
|
+
ecmaVersion: 2021
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "gesslar/uglier/lints-js",
|
|
54
|
+
files: ["{work,src}/**/*.{mjs,cjs,js}"],
|
|
55
|
+
plugins: {
|
|
56
|
+
"@stylistic": stylistic,
|
|
57
|
+
},
|
|
58
|
+
rules: {
|
|
59
|
+
"@stylistic/arrow-parens": ["error", "as-needed"],
|
|
60
|
+
"@stylistic/arrow-spacing": ["error", { before: true, after: true }],
|
|
61
|
+
"@stylistic/brace-style": ["error", "1tbs", {allowSingleLine: false}],
|
|
62
|
+
"@stylistic/nonblock-statement-body-position": ["error", "below"],
|
|
63
|
+
"@stylistic/padding-line-between-statements": [
|
|
64
|
+
"error",
|
|
65
|
+
{blankLine: "always", prev: "if", next: "*"},
|
|
66
|
+
{blankLine: "always", prev: "*", next: "return"},
|
|
67
|
+
{blankLine: "always", prev: "while", next: "*"},
|
|
68
|
+
{blankLine: "always", prev: "for", next: "*"},
|
|
69
|
+
{blankLine: "always", prev: "switch", next: "*"},
|
|
70
|
+
{blankLine: "always", prev: "do", next: "*"},
|
|
71
|
+
// {blankLine: "always", prev: ["const", "let", "var"], next: "*"},
|
|
72
|
+
// {blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]},
|
|
73
|
+
{blankLine: "always", prev: "directive", next: "*" },
|
|
74
|
+
{blankLine: "any", prev: "directive", next: "directive" },
|
|
75
|
+
],
|
|
76
|
+
"@stylistic/eol-last": ["error", "always"],
|
|
77
|
+
"@stylistic/indent": ["error", 2, {
|
|
78
|
+
SwitchCase: 1 // Indents `case` statements one level deeper than `switch`
|
|
79
|
+
}],
|
|
80
|
+
"@stylistic/key-spacing": ["error", { beforeColon: false, afterColon: true }],
|
|
81
|
+
"@stylistic/keyword-spacing": ["error", {
|
|
82
|
+
before: false,
|
|
83
|
+
after: true,
|
|
84
|
+
overrides: {
|
|
85
|
+
// Control statements
|
|
86
|
+
return: { before: true, after: true },
|
|
87
|
+
if: { after: false },
|
|
88
|
+
else: { before: true, after: true },
|
|
89
|
+
for: { after: false },
|
|
90
|
+
while: { before: true, after: false },
|
|
91
|
+
do: { after: true },
|
|
92
|
+
switch: { after: false },
|
|
93
|
+
case: { before: true, after: true },
|
|
94
|
+
throw: { before: true, after: false } ,
|
|
95
|
+
|
|
96
|
+
// Keywords
|
|
97
|
+
as: { before: true, after: true },
|
|
98
|
+
of: { before: true, after: true },
|
|
99
|
+
from: { before: true, after: true },
|
|
100
|
+
async: { before: true, after: true },
|
|
101
|
+
await: { before: true, after: false },
|
|
102
|
+
class: { before: true, after: true },
|
|
103
|
+
const: { before: true, after: true },
|
|
104
|
+
let: { before: true, after: true },
|
|
105
|
+
var: { before: true, after: true },
|
|
106
|
+
|
|
107
|
+
// Exception handling
|
|
108
|
+
catch: { before: true, after: true },
|
|
109
|
+
finally: { before: true, after: true },
|
|
110
|
+
}
|
|
111
|
+
}],
|
|
112
|
+
// Blocks
|
|
113
|
+
"@stylistic/space-before-blocks": ["error", "always"],
|
|
114
|
+
"@stylistic/max-len": ["warn", {
|
|
115
|
+
code: 80,
|
|
116
|
+
ignoreComments: true,
|
|
117
|
+
ignoreUrls: true,
|
|
118
|
+
ignoreStrings: true,
|
|
119
|
+
ignoreTemplateLiterals: true,
|
|
120
|
+
ignoreRegExpLiterals: true,
|
|
121
|
+
tabWidth: 2
|
|
122
|
+
}],
|
|
123
|
+
"@stylistic/no-tabs": "error",
|
|
124
|
+
"@stylistic/no-trailing-spaces": ["error"],
|
|
125
|
+
"@stylistic/object-curly-spacing": ["error", "never", {
|
|
126
|
+
objectsInObjects: false,
|
|
127
|
+
arraysInObjects: false
|
|
128
|
+
}],
|
|
129
|
+
"@stylistic/quotes": ["error", "double", {
|
|
130
|
+
avoidEscape: true,
|
|
131
|
+
allowTemplateLiterals: "always"
|
|
132
|
+
}],
|
|
133
|
+
"@stylistic/semi": ["error", "never"],
|
|
134
|
+
"@stylistic/space-before-function-paren": ["error", "never"],
|
|
135
|
+
"@stylistic/yield-star-spacing": ["error", { before: true, after: false }],
|
|
136
|
+
"constructor-super": "error",
|
|
137
|
+
"no-unexpected-multiline": "error",
|
|
138
|
+
"no-unused-vars": ["error", {
|
|
139
|
+
caughtErrors: "all",
|
|
140
|
+
caughtErrorsIgnorePattern: "^_+",
|
|
141
|
+
argsIgnorePattern: "^_+",
|
|
142
|
+
destructuredArrayIgnorePattern: "^_+",
|
|
143
|
+
varsIgnorePattern: "^_+"
|
|
144
|
+
}],
|
|
145
|
+
"no-useless-assignment": "error",
|
|
146
|
+
"prefer-const": "error",
|
|
147
|
+
"@stylistic/no-multiple-empty-lines": ["error", { max: 1 }],
|
|
148
|
+
"@stylistic/array-bracket-spacing": ["error", "never"],
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
name: "gesslar/uglier/lints-jsdoc",
|
|
153
|
+
files: ["{work,src}/**/*.{mjs,cjs,js}"],
|
|
154
|
+
plugins: {
|
|
155
|
+
jsdoc,
|
|
156
|
+
},
|
|
157
|
+
rules: {
|
|
158
|
+
"jsdoc/require-description": "error",
|
|
159
|
+
"jsdoc/tag-lines": ["error", "any", {"startLines":1}],
|
|
160
|
+
"jsdoc/require-jsdoc": ["error", { publicOnly: true }],
|
|
161
|
+
"jsdoc/check-tag-names": "error",
|
|
162
|
+
"jsdoc/check-types": "error",
|
|
163
|
+
"jsdoc/require-param-type": "error",
|
|
164
|
+
"jsdoc/require-returns-type": "error"
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gesslar/lpc-mcp",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "MCP server for LPC language server",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"lpc-mcp": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node src/index.js",
|
|
15
|
+
"types:build": "tsc -p tsconfig.types.json",
|
|
16
|
+
"lint": "eslint src/",
|
|
17
|
+
"lint:fix": "eslint src/ --fix",
|
|
18
|
+
"submit": "npm publish --access public",
|
|
19
|
+
"update": "npx npm-check-updates -u && npm install",
|
|
20
|
+
"pr": "gt submit --publish --restack --ai -m",
|
|
21
|
+
"patch": "npm version patch",
|
|
22
|
+
"minor": "npm version minor",
|
|
23
|
+
"major": "npm version major"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/gesslar/lpc-mcp.git"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [],
|
|
30
|
+
"author": "gesslar",
|
|
31
|
+
"license": "Unlicense",
|
|
32
|
+
"homepage": "https://github.com/gesslar/lpc-mcp#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.20.2",
|
|
35
|
+
"vscode-jsonrpc": "^8.2.1"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@eslint/js": "^9.39.0",
|
|
39
|
+
"@stylistic/eslint-plugin": "^5.5.0",
|
|
40
|
+
"eslint-plugin-jsdoc": "^61.1.11",
|
|
41
|
+
"globals": "^16.5.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {Server} from "@modelcontextprotocol/sdk/server/index.js"
|
|
4
|
+
import {StdioServerTransport} from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
5
|
+
import {
|
|
6
|
+
CallToolRequestSchema,
|
|
7
|
+
ListToolsRequestSchema,
|
|
8
|
+
} from "@modelcontextprotocol/sdk/types.js"
|
|
9
|
+
import {spawn} from "child_process"
|
|
10
|
+
import * as rpc from "vscode-jsonrpc/node.js"
|
|
11
|
+
import path from "path"
|
|
12
|
+
// ...existing code...
|
|
13
|
+
import {Transform} from "stream"
|
|
14
|
+
|
|
15
|
+
class LPCMCPServer {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.server = new Server(
|
|
18
|
+
{
|
|
19
|
+
name: "lpc-mcp-server",
|
|
20
|
+
version: "0.1.0",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
capabilities: {
|
|
24
|
+
tools: {},
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
this.lspProcess = null
|
|
30
|
+
this.lspConnection = null
|
|
31
|
+
this.diagnosticsCache = new Map() // Map of file URI -> diagnostics array
|
|
32
|
+
this.setupHandlers()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async startLSP() {
|
|
36
|
+
// Path to the LPC language server
|
|
37
|
+
const lspPath = path.join(
|
|
38
|
+
process.env.HOME,
|
|
39
|
+
".vscode/extensions/jlchmura.lpc-1.1.42/out/server/src/server.js"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
console.error("Starting LPC Language Server from:", lspPath)
|
|
43
|
+
|
|
44
|
+
// Spawn the LSP server
|
|
45
|
+
// Redirect stderr to /dev/null to prevent log messages from contaminating JSON-RPC
|
|
46
|
+
this.lspProcess = spawn("node", [lspPath, "--stdio"], {
|
|
47
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
this.lspProcess.on("error", err => {
|
|
51
|
+
console.error("LSP Process error:", err)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
this.lspProcess.on("exit", code => {
|
|
55
|
+
console.error("LSP Process exited with code:", code)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
// Create a transform stream to filter out non-JSON-RPC output
|
|
59
|
+
const cleanStream = new Transform({
|
|
60
|
+
transform(chunk, encoding, callback) {
|
|
61
|
+
const data = chunk.toString()
|
|
62
|
+
// Filter out lines that don't look like JSON-RPC
|
|
63
|
+
// JSON-RPC messages start with "Content-Length: "
|
|
64
|
+
const lines = data.split("\n")
|
|
65
|
+
const filtered = lines.filter(line => {
|
|
66
|
+
const trimmed = line.trim()
|
|
67
|
+
|
|
68
|
+
// Keep Content-Length headers and JSON lines
|
|
69
|
+
return trimmed.startsWith("Content-Length:") ||
|
|
70
|
+
trimmed.startsWith("{") ||
|
|
71
|
+
trimmed === ""
|
|
72
|
+
}).join("\n")
|
|
73
|
+
|
|
74
|
+
if(filtered) {
|
|
75
|
+
this.push(filtered)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
callback()
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Pipe stdout through the cleaner
|
|
83
|
+
this.lspProcess.stdout.pipe(cleanStream)
|
|
84
|
+
|
|
85
|
+
// Create JSON-RPC connection
|
|
86
|
+
const reader = new rpc.StreamMessageReader(cleanStream)
|
|
87
|
+
const writer = new rpc.StreamMessageWriter(this.lspProcess.stdin)
|
|
88
|
+
this.lspConnection = rpc.createMessageConnection(reader, writer)
|
|
89
|
+
|
|
90
|
+
this.lspConnection.onError(error => {
|
|
91
|
+
console.error("LSP Connection error:", error)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
this.lspConnection.onClose(() => {
|
|
95
|
+
console.error("LSP Connection closed")
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Listen for diagnostic notifications
|
|
99
|
+
this.lspConnection.onNotification("textDocument/publishDiagnostics", params => {
|
|
100
|
+
console.error(`Received diagnostics for ${params.uri}: ${params.diagnostics.length} issues`)
|
|
101
|
+
this.diagnosticsCache.set(params.uri, params.diagnostics)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
// Start listening
|
|
105
|
+
this.lspConnection.listen()
|
|
106
|
+
|
|
107
|
+
console.error("Initializing LSP...")
|
|
108
|
+
|
|
109
|
+
// Get workspace root from environment variable
|
|
110
|
+
const workspaceRoot = process.env.LPC_WORKSPACE_ROOT || null
|
|
111
|
+
const rootUri = workspaceRoot ? `file://${workspaceRoot}` : null
|
|
112
|
+
|
|
113
|
+
console.error("Using workspace root:", rootUri)
|
|
114
|
+
|
|
115
|
+
// Initialize the LSP with timeout
|
|
116
|
+
try {
|
|
117
|
+
const initResult = await Promise.race([
|
|
118
|
+
this.lspConnection.sendRequest("initialize", {
|
|
119
|
+
processId: process.pid,
|
|
120
|
+
rootUri: rootUri,
|
|
121
|
+
capabilities: {
|
|
122
|
+
textDocument: {
|
|
123
|
+
hover: {dynamicRegistration: true},
|
|
124
|
+
definition: {dynamicRegistration: true},
|
|
125
|
+
references: {dynamicRegistration: true},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
new Promise((_, reject) =>
|
|
130
|
+
setTimeout(() => reject(new Error("LSP init timeout")), 5000)
|
|
131
|
+
),
|
|
132
|
+
])
|
|
133
|
+
|
|
134
|
+
console.error("LSP initialized:", JSON.stringify(initResult, null, 2))
|
|
135
|
+
|
|
136
|
+
await this.lspConnection.sendNotification("initialized", {})
|
|
137
|
+
|
|
138
|
+
console.error("LPC Language Server started successfully")
|
|
139
|
+
} catch(error) {
|
|
140
|
+
console.error("Failed to initialize LSP:", error)
|
|
141
|
+
throw error
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
setupHandlers() {
|
|
146
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async() => ({
|
|
147
|
+
tools: [
|
|
148
|
+
{
|
|
149
|
+
name: "lpc_hover",
|
|
150
|
+
description:
|
|
151
|
+
"Get hover information (documentation) for a symbol at a specific position in an LPC file",
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: "object",
|
|
154
|
+
properties: {
|
|
155
|
+
file: {
|
|
156
|
+
type: "string",
|
|
157
|
+
description: "Absolute path to the LPC file",
|
|
158
|
+
},
|
|
159
|
+
line: {
|
|
160
|
+
type: "number",
|
|
161
|
+
description: "Line number (0-indexed)",
|
|
162
|
+
},
|
|
163
|
+
character: {
|
|
164
|
+
type: "number",
|
|
165
|
+
description: "Character position (0-indexed)",
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
required: ["file", "line", "character"],
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: "lpc_definition",
|
|
173
|
+
description:
|
|
174
|
+
"Go to definition of a symbol at a specific position in an LPC file",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
file: {
|
|
179
|
+
type: "string",
|
|
180
|
+
description: "Absolute path to the LPC file",
|
|
181
|
+
},
|
|
182
|
+
line: {
|
|
183
|
+
type: "number",
|
|
184
|
+
description: "Line number (0-indexed)",
|
|
185
|
+
},
|
|
186
|
+
character: {
|
|
187
|
+
type: "number",
|
|
188
|
+
description: "Character position (0-indexed)",
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
required: ["file", "line", "character"],
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
name: "lpc_references",
|
|
196
|
+
description:
|
|
197
|
+
"Find all references to a symbol at a specific position in an LPC file",
|
|
198
|
+
inputSchema: {
|
|
199
|
+
type: "object",
|
|
200
|
+
properties: {
|
|
201
|
+
file: {
|
|
202
|
+
type: "string",
|
|
203
|
+
description: "Absolute path to the LPC file",
|
|
204
|
+
},
|
|
205
|
+
line: {
|
|
206
|
+
type: "number",
|
|
207
|
+
description: "Line number (0-indexed)",
|
|
208
|
+
},
|
|
209
|
+
character: {
|
|
210
|
+
type: "number",
|
|
211
|
+
description: "Character position (0-indexed)",
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
required: ["file", "line", "character"],
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "lpc_diagnostics",
|
|
219
|
+
description:
|
|
220
|
+
"Get diagnostics (errors, warnings, hints) for an LPC file. This reveals LPC language rules and syntax errors.",
|
|
221
|
+
inputSchema: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
file: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "Absolute path to the LPC file",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
required: ["file"],
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
}))
|
|
234
|
+
|
|
235
|
+
this.server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
236
|
+
const {name, arguments: args} = request.params
|
|
237
|
+
|
|
238
|
+
if(!this.lspConnection) {
|
|
239
|
+
throw new Error("LSP not initialized")
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const uri = `file://${args.file}`
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
// Read the file and send didOpen notification
|
|
246
|
+
const fs = await import("fs/promises")
|
|
247
|
+
const fileContent = await fs.readFile(args.file, "utf-8")
|
|
248
|
+
|
|
249
|
+
await this.lspConnection.sendNotification("textDocument/didOpen", {
|
|
250
|
+
textDocument: {
|
|
251
|
+
uri,
|
|
252
|
+
languageId: "lpc",
|
|
253
|
+
version: 1,
|
|
254
|
+
text: fileContent,
|
|
255
|
+
},
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
switch(name) {
|
|
259
|
+
case "lpc_hover": {
|
|
260
|
+
const result = await this.lspConnection.sendRequest(
|
|
261
|
+
"textDocument/hover",
|
|
262
|
+
{
|
|
263
|
+
textDocument: {uri},
|
|
264
|
+
position: {line: args.line, character: args.character},
|
|
265
|
+
}
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: JSON.stringify(result, null, 2),
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "lpc_definition": {
|
|
279
|
+
const result = await this.lspConnection.sendRequest(
|
|
280
|
+
"textDocument/definition",
|
|
281
|
+
{
|
|
282
|
+
textDocument: {uri},
|
|
283
|
+
position: {line: args.line, character: args.character},
|
|
284
|
+
}
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
content: [
|
|
289
|
+
{
|
|
290
|
+
type: "text",
|
|
291
|
+
text: JSON.stringify(result, null, 2),
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
case "lpc_references": {
|
|
298
|
+
const result = await this.lspConnection.sendRequest(
|
|
299
|
+
"textDocument/references",
|
|
300
|
+
{
|
|
301
|
+
textDocument: {uri},
|
|
302
|
+
position: {line: args.line, character: args.character},
|
|
303
|
+
context: {includeDeclaration: true},
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
content: [
|
|
309
|
+
{
|
|
310
|
+
type: "text",
|
|
311
|
+
text: JSON.stringify(result, null, 2),
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
case "lpc_diagnostics": {
|
|
318
|
+
// Just open the file to trigger diagnostics
|
|
319
|
+
// Wait a bit for diagnostics to come through
|
|
320
|
+
await new Promise(resolve => setTimeout(resolve, 500))
|
|
321
|
+
|
|
322
|
+
const diagnostics = this.diagnosticsCache.get(uri) || []
|
|
323
|
+
|
|
324
|
+
if(diagnostics.length === 0) {
|
|
325
|
+
return {
|
|
326
|
+
content: [
|
|
327
|
+
{
|
|
328
|
+
type: "text",
|
|
329
|
+
text: "No diagnostics found for this file. The code appears to be valid.",
|
|
330
|
+
},
|
|
331
|
+
],
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Format diagnostics in a readable way
|
|
336
|
+
const formatted = diagnostics.map(d => {
|
|
337
|
+
const severity = ["Error", "Warning", "Information", "Hint"][d.severity - 1] || "Unknown"
|
|
338
|
+
const line = d.range.start.line + 1 // Convert to 1-indexed
|
|
339
|
+
const char = d.range.start.character + 1
|
|
340
|
+
|
|
341
|
+
return `[${severity}] Line ${line}:${char} - ${d.message}`
|
|
342
|
+
}).join("\n")
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
content: [
|
|
346
|
+
{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: `Found ${diagnostics.length} diagnostic(s):\n\n${formatted}\n\nRaw diagnostics:\n${JSON.stringify(diagnostics, null, 2)}`,
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
default:
|
|
355
|
+
throw new Error(`Unknown tool: ${name}`)
|
|
356
|
+
}
|
|
357
|
+
} catch(error) {
|
|
358
|
+
return {
|
|
359
|
+
content: [
|
|
360
|
+
{
|
|
361
|
+
type: "text",
|
|
362
|
+
text: `Error: ${error.message}`,
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
isError: true,
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async run() {
|
|
372
|
+
await this.startLSP()
|
|
373
|
+
|
|
374
|
+
const transport = new StdioServerTransport()
|
|
375
|
+
await this.server.connect(transport)
|
|
376
|
+
|
|
377
|
+
console.error("LPC MCP Server running on stdio")
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const server = new LPCMCPServer()
|
|
382
|
+
server.run().catch(console.error)
|