@treedy/pyright-mcp 1.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/README.md +223 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +49 -0
- package/dist/lsp-client.d.ts +15 -0
- package/dist/lsp-client.js +80 -0
- package/dist/pyright-worker.mjs +148 -0
- package/dist/tools/completions.d.ts +18 -0
- package/dist/tools/completions.js +71 -0
- package/dist/tools/definition.d.ts +16 -0
- package/dist/tools/definition.js +65 -0
- package/dist/tools/diagnostics.d.ts +12 -0
- package/dist/tools/diagnostics.js +35 -0
- package/dist/tools/hover.d.ts +16 -0
- package/dist/tools/hover.js +44 -0
- package/dist/tools/references.d.ts +18 -0
- package/dist/tools/references.js +28 -0
- package/dist/tools/rename.d.ts +18 -0
- package/dist/tools/rename.js +44 -0
- package/dist/tools/search.d.ts +20 -0
- package/dist/tools/search.js +91 -0
- package/dist/tools/signature-help.d.ts +16 -0
- package/dist/tools/signature-help.js +54 -0
- package/dist/tools/status.d.ts +14 -0
- package/dist/tools/status.js +147 -0
- package/dist/utils/position.d.ts +33 -0
- package/dist/utils/position.js +74 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# @treedy/pyright-mcp
|
|
2
|
+
|
|
3
|
+
MCP (Model Context Protocol) server that exposes [Pyright](https://github.com/microsoft/pyright) LSP features for Python code intelligence. Works with Claude Code, Codex, and other MCP-compatible AI tools.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **hover** - Get type information and documentation at a position
|
|
8
|
+
- **definition** - Jump to symbol definition
|
|
9
|
+
- **references** - Find all references to a symbol
|
|
10
|
+
- **completions** - Get code completion suggestions
|
|
11
|
+
- **diagnostics** - Get type errors and warnings
|
|
12
|
+
- **signature_help** - Get function signature information
|
|
13
|
+
- **rename** - Preview symbol renaming
|
|
14
|
+
- **search** - Search for patterns in files (ripgrep-style)
|
|
15
|
+
- **status** - Check Python/Pyright environment status
|
|
16
|
+
|
|
17
|
+
## Prerequisites
|
|
18
|
+
|
|
19
|
+
Install Pyright globally:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g pyright
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start with npx
|
|
26
|
+
|
|
27
|
+
You can run directly without installation:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx @treedy/pyright-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g @treedy/pyright-mcp
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### With Claude Code
|
|
42
|
+
|
|
43
|
+
Add to your Claude Code MCP settings (`~/.claude/claude_desktop_config.json`):
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"pyright": {
|
|
49
|
+
"command": "npx",
|
|
50
|
+
"args": ["@treedy/pyright-mcp"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or if installed globally:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"pyright": {
|
|
62
|
+
"command": "pyright-mcp"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### With Other MCP Clients
|
|
69
|
+
|
|
70
|
+
Run the server via stdio:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx @treedy/pyright-mcp
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Project Configuration
|
|
77
|
+
|
|
78
|
+
pyright-mcp automatically detects your project root by looking for:
|
|
79
|
+
- `pyrightconfig.json`
|
|
80
|
+
- `pyproject.toml`
|
|
81
|
+
- `.git` directory
|
|
82
|
+
|
|
83
|
+
### Basic pyrightconfig.json
|
|
84
|
+
|
|
85
|
+
Create a `pyrightconfig.json` in your project root:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"include": ["src"],
|
|
90
|
+
"pythonVersion": "3.11",
|
|
91
|
+
"typeCheckingMode": "basic"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### With Virtual Environment
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"include": ["src"],
|
|
100
|
+
"pythonVersion": "3.11",
|
|
101
|
+
"venvPath": ".",
|
|
102
|
+
"venv": ".venv",
|
|
103
|
+
"typeCheckingMode": "strict"
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Using pyproject.toml
|
|
108
|
+
|
|
109
|
+
Add a `[tool.pyright]` section to your `pyproject.toml`:
|
|
110
|
+
|
|
111
|
+
```toml
|
|
112
|
+
[tool.pyright]
|
|
113
|
+
include = ["src"]
|
|
114
|
+
pythonVersion = "3.11"
|
|
115
|
+
typeCheckingMode = "basic"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Tool Examples
|
|
119
|
+
|
|
120
|
+
### Check Environment Status
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
Tool: status
|
|
124
|
+
Arguments: { "file": "/path/to/your/project/main.py" }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Returns project root, Pyright version, config details, and Python environment info.
|
|
128
|
+
|
|
129
|
+
### Get Hover Information
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
Tool: hover
|
|
133
|
+
Arguments: { "file": "/path/to/file.py", "line": 10, "column": 5 }
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Go to Definition
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Tool: definition
|
|
140
|
+
Arguments: { "file": "/path/to/file.py", "line": 10, "column": 5 }
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Find References
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
Tool: references
|
|
147
|
+
Arguments: { "file": "/path/to/file.py", "line": 10, "column": 5 }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Get Completions
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
Tool: completions
|
|
154
|
+
Arguments: { "file": "/path/to/file.py", "line": 10, "column": 5 }
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Get Diagnostics
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
Tool: diagnostics
|
|
161
|
+
Arguments: { "file": "/path/to/file.py" }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Search in Files
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
Tool: search
|
|
168
|
+
Arguments: { "pattern": "def main", "path": "/path/to/project", "glob": "*.py" }
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Clone the repository
|
|
175
|
+
git clone <repo-url>
|
|
176
|
+
cd pyright-mcp
|
|
177
|
+
|
|
178
|
+
# Install dependencies
|
|
179
|
+
npm install
|
|
180
|
+
|
|
181
|
+
# Build
|
|
182
|
+
npm run build
|
|
183
|
+
|
|
184
|
+
# Test with MCP Inspector
|
|
185
|
+
npm run inspector
|
|
186
|
+
|
|
187
|
+
# Run tests
|
|
188
|
+
npm run test:mcp
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Publishing to npm
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Login to npm
|
|
195
|
+
npm login
|
|
196
|
+
|
|
197
|
+
# Publish (scoped packages need --access public for first publish)
|
|
198
|
+
npm publish --access public
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
After publishing, users can run directly with:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
npx @treedy/pyright-mcp
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## How It Works
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
┌─────────────────┐ stdio ┌─────────────────────┐ stdio ┌──────────────────┐
|
|
211
|
+
│ Claude / AI │ ◄────────────► │ pyright-mcp │ ◄────────────► │ pyright-langserver│
|
|
212
|
+
│ │ MCP │ │ LSP │ │
|
|
213
|
+
└─────────────────┘ └─────────────────────┘ └──────────────────┘
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
1. AI client sends MCP tool calls (e.g., hover, definition)
|
|
217
|
+
2. pyright-mcp converts to LSP requests
|
|
218
|
+
3. pyright-langserver analyzes Python code
|
|
219
|
+
4. Results are formatted and returned to the AI
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { hover, hoverSchema } from './tools/hover.js';
|
|
5
|
+
import { definition, definitionSchema } from './tools/definition.js';
|
|
6
|
+
import { references, referencesSchema } from './tools/references.js';
|
|
7
|
+
import { completions, completionsSchema } from './tools/completions.js';
|
|
8
|
+
import { diagnostics, diagnosticsSchema } from './tools/diagnostics.js';
|
|
9
|
+
import { signatureHelp, signatureHelpSchema } from './tools/signature-help.js';
|
|
10
|
+
import { rename, renameSchema } from './tools/rename.js';
|
|
11
|
+
import { search, searchSchema } from './tools/search.js';
|
|
12
|
+
import { status, statusSchema } from './tools/status.js';
|
|
13
|
+
import { getLspClient } from './lsp-client.js';
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: 'pyright-mcp',
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
});
|
|
18
|
+
// Register hover tool
|
|
19
|
+
server.tool('hover', 'Get type information and documentation at a specific position in a Python file', hoverSchema, async (args) => hover(args));
|
|
20
|
+
// Register definition tool
|
|
21
|
+
server.tool('definition', 'Go to definition of a symbol at a specific position in a Python file', definitionSchema, async (args) => definition(args));
|
|
22
|
+
// Register references tool
|
|
23
|
+
server.tool('references', 'Find all references to a symbol at a specific position in a Python file', referencesSchema, async (args) => references(args));
|
|
24
|
+
// Register completions tool
|
|
25
|
+
server.tool('completions', 'Get code completion suggestions at a specific position in a Python file', completionsSchema, async (args) => completions(args));
|
|
26
|
+
// Register diagnostics tool
|
|
27
|
+
server.tool('diagnostics', 'Get diagnostics (errors, warnings) for a Python file', diagnosticsSchema, async (args) => diagnostics(args));
|
|
28
|
+
// Register signature help tool
|
|
29
|
+
server.tool('signature_help', 'Get function signature help at a specific position in a Python file', signatureHelpSchema, async (args) => signatureHelp(args));
|
|
30
|
+
// Register rename tool
|
|
31
|
+
server.tool('rename', 'Preview renaming a symbol at a specific position in a Python file', renameSchema, async (args) => rename(args));
|
|
32
|
+
// Register search tool
|
|
33
|
+
server.tool('search', 'Search for a pattern in files and return file:line:column locations', searchSchema, async (args) => search(args));
|
|
34
|
+
// Register status tool
|
|
35
|
+
server.tool('status', 'Check Python/Pyright environment status for a project', statusSchema, async (args) => status(args));
|
|
36
|
+
// Start the server
|
|
37
|
+
async function main() {
|
|
38
|
+
console.error(`Pyright MCP server`);
|
|
39
|
+
console.error(` Workspace: auto-detected from file path`);
|
|
40
|
+
// Initialize LSP client (lazy start on first request)
|
|
41
|
+
getLspClient();
|
|
42
|
+
const transport = new StdioServerTransport();
|
|
43
|
+
await server.connect(transport);
|
|
44
|
+
console.error(` Ready`);
|
|
45
|
+
}
|
|
46
|
+
main().catch((error) => {
|
|
47
|
+
console.error('Failed to start server:', error);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Position, Hover, Location, LocationLink, CompletionItem, CompletionList, SignatureHelp, WorkspaceEdit, Diagnostic } from 'vscode-languageserver-protocol';
|
|
2
|
+
export declare class LspClient {
|
|
3
|
+
constructor();
|
|
4
|
+
start(): Promise<void>;
|
|
5
|
+
stop(): Promise<void>;
|
|
6
|
+
private runWorker;
|
|
7
|
+
hover(filePath: string, position: Position): Promise<Hover | null>;
|
|
8
|
+
definition(filePath: string, position: Position): Promise<Location | Location[] | LocationLink[] | null>;
|
|
9
|
+
references(filePath: string, position: Position, includeDeclaration?: boolean): Promise<Location[] | null>;
|
|
10
|
+
completions(filePath: string, position: Position): Promise<CompletionItem[] | CompletionList | null>;
|
|
11
|
+
signatureHelp(filePath: string, position: Position): Promise<SignatureHelp | null>;
|
|
12
|
+
rename(filePath: string, position: Position, newName: string): Promise<WorkspaceEdit | null>;
|
|
13
|
+
getDiagnostics(filePath: string): Promise<Diagnostic[]>;
|
|
14
|
+
}
|
|
15
|
+
export declare function getLspClient(): LspClient;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { dirname, resolve } from 'path';
|
|
4
|
+
import { findProjectRoot } from './utils/position.js';
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
const workerScript = resolve(__dirname, 'pyright-worker.mjs');
|
|
8
|
+
function log(message) {
|
|
9
|
+
console.error(`[LSP] ${message}`);
|
|
10
|
+
}
|
|
11
|
+
export class LspClient {
|
|
12
|
+
constructor() {
|
|
13
|
+
// No longer stores workspace root - it's determined per-file
|
|
14
|
+
}
|
|
15
|
+
async start() {
|
|
16
|
+
// No-op, worker starts fresh for each request
|
|
17
|
+
}
|
|
18
|
+
async stop() {
|
|
19
|
+
// No-op
|
|
20
|
+
}
|
|
21
|
+
runWorker(method, filePath, args) {
|
|
22
|
+
// Automatically find project root from file path
|
|
23
|
+
const workspaceRoot = findProjectRoot(filePath);
|
|
24
|
+
log(`Workspace root: ${workspaceRoot}`);
|
|
25
|
+
const params = JSON.stringify({
|
|
26
|
+
workspaceRoot,
|
|
27
|
+
method,
|
|
28
|
+
filePath,
|
|
29
|
+
...args,
|
|
30
|
+
});
|
|
31
|
+
log(`Running worker: ${method}`);
|
|
32
|
+
try {
|
|
33
|
+
const result = execSync(`node "${workerScript}" '${params.replace(/'/g, "'\\''")}'`, {
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
timeout: 30000,
|
|
36
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
37
|
+
});
|
|
38
|
+
const parsed = JSON.parse(result.trim());
|
|
39
|
+
if (parsed.success) {
|
|
40
|
+
log(`Worker success: ${method}`);
|
|
41
|
+
return parsed.result;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
throw new Error(parsed.error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
log(`Worker error: ${error}`);
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async hover(filePath, position) {
|
|
53
|
+
return this.runWorker('hover', filePath, { position });
|
|
54
|
+
}
|
|
55
|
+
async definition(filePath, position) {
|
|
56
|
+
return this.runWorker('definition', filePath, { position });
|
|
57
|
+
}
|
|
58
|
+
async references(filePath, position, includeDeclaration = true) {
|
|
59
|
+
return this.runWorker('references', filePath, { position, includeDeclaration });
|
|
60
|
+
}
|
|
61
|
+
async completions(filePath, position) {
|
|
62
|
+
return this.runWorker('completions', filePath, { position });
|
|
63
|
+
}
|
|
64
|
+
async signatureHelp(filePath, position) {
|
|
65
|
+
return this.runWorker('signatureHelp', filePath, { position });
|
|
66
|
+
}
|
|
67
|
+
async rename(filePath, position, newName) {
|
|
68
|
+
return this.runWorker('rename', filePath, { position, newName });
|
|
69
|
+
}
|
|
70
|
+
async getDiagnostics(filePath) {
|
|
71
|
+
return this.runWorker('diagnostics', filePath, {});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let client = null;
|
|
75
|
+
export function getLspClient() {
|
|
76
|
+
if (!client) {
|
|
77
|
+
client = new LspClient();
|
|
78
|
+
}
|
|
79
|
+
return client;
|
|
80
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Standalone worker script that communicates with pyright-langserver
|
|
3
|
+
// Uses vscode-jsonrpc for reliable LSP communication
|
|
4
|
+
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import * as rpc from 'vscode-jsonrpc/node.js';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
|
|
9
|
+
const args = JSON.parse(process.argv[2]);
|
|
10
|
+
const { workspaceRoot, method, filePath, position, newName, includeDeclaration } = args;
|
|
11
|
+
|
|
12
|
+
function log(msg) {
|
|
13
|
+
console.error(`[worker] ${msg}`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pathToUri(p) {
|
|
17
|
+
return p.startsWith('file://') ? p : `file://${p}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create process and connection at module level like the working test script
|
|
21
|
+
const proc = spawn('pyright-langserver', ['--stdio'], {
|
|
22
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
proc.stderr.on('data', (data) => {
|
|
26
|
+
log(`stderr: ${data.toString().trim()}`);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const connection = rpc.createMessageConnection(
|
|
30
|
+
new rpc.StreamMessageReader(proc.stdout),
|
|
31
|
+
new rpc.StreamMessageWriter(proc.stdin)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
connection.onError((err) => log(`Connection error: ${err}`));
|
|
35
|
+
connection.onClose(() => log('Connection closed'));
|
|
36
|
+
connection.listen();
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
try {
|
|
40
|
+
// Initialize with simpler capabilities like the working test
|
|
41
|
+
log('Sending initialize...');
|
|
42
|
+
await connection.sendRequest('initialize', {
|
|
43
|
+
processId: process.pid,
|
|
44
|
+
rootUri: pathToUri(workspaceRoot),
|
|
45
|
+
capabilities: {
|
|
46
|
+
textDocument: {
|
|
47
|
+
hover: { contentFormat: ['markdown', 'plaintext'] },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
log('Initialize done');
|
|
52
|
+
|
|
53
|
+
await connection.sendNotification('initialized', {});
|
|
54
|
+
log('Sent initialized notification');
|
|
55
|
+
|
|
56
|
+
// Open document
|
|
57
|
+
let uri = null;
|
|
58
|
+
if (filePath) {
|
|
59
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
60
|
+
uri = pathToUri(filePath);
|
|
61
|
+
|
|
62
|
+
log(`Opening document: ${uri}`);
|
|
63
|
+
await connection.sendNotification('textDocument/didOpen', {
|
|
64
|
+
textDocument: {
|
|
65
|
+
uri,
|
|
66
|
+
languageId: 'python',
|
|
67
|
+
version: 1,
|
|
68
|
+
text: content,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Wait for analysis
|
|
73
|
+
log('Waiting for analysis...');
|
|
74
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let result;
|
|
78
|
+
|
|
79
|
+
switch (method) {
|
|
80
|
+
case 'hover':
|
|
81
|
+
log(`Sending hover request at ${JSON.stringify(position)}`);
|
|
82
|
+
result = await connection.sendRequest('textDocument/hover', {
|
|
83
|
+
textDocument: { uri },
|
|
84
|
+
position,
|
|
85
|
+
});
|
|
86
|
+
break;
|
|
87
|
+
|
|
88
|
+
case 'definition':
|
|
89
|
+
log('Sending definition request');
|
|
90
|
+
result = await connection.sendRequest('textDocument/definition', {
|
|
91
|
+
textDocument: { uri },
|
|
92
|
+
position,
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case 'references':
|
|
97
|
+
log('Sending references request');
|
|
98
|
+
result = await connection.sendRequest('textDocument/references', {
|
|
99
|
+
textDocument: { uri },
|
|
100
|
+
position,
|
|
101
|
+
context: { includeDeclaration: includeDeclaration !== false },
|
|
102
|
+
});
|
|
103
|
+
break;
|
|
104
|
+
|
|
105
|
+
case 'completions':
|
|
106
|
+
log('Sending completions request');
|
|
107
|
+
result = await connection.sendRequest('textDocument/completion', {
|
|
108
|
+
textDocument: { uri },
|
|
109
|
+
position,
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case 'signatureHelp':
|
|
114
|
+
log('Sending signatureHelp request');
|
|
115
|
+
result = await connection.sendRequest('textDocument/signatureHelp', {
|
|
116
|
+
textDocument: { uri },
|
|
117
|
+
position,
|
|
118
|
+
});
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case 'rename':
|
|
122
|
+
log('Sending rename request');
|
|
123
|
+
result = await connection.sendRequest('textDocument/rename', {
|
|
124
|
+
textDocument: { uri },
|
|
125
|
+
position,
|
|
126
|
+
newName,
|
|
127
|
+
});
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'diagnostics':
|
|
131
|
+
result = [];
|
|
132
|
+
break;
|
|
133
|
+
|
|
134
|
+
default:
|
|
135
|
+
throw new Error(`Unknown method: ${method}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(JSON.stringify({ success: true, result }));
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.log(JSON.stringify({ success: false, error: error.message }));
|
|
141
|
+
} finally {
|
|
142
|
+
connection.dispose();
|
|
143
|
+
proc.kill();
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
main();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const completionsSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
line: z.ZodNumber;
|
|
5
|
+
column: z.ZodNumber;
|
|
6
|
+
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
7
|
+
};
|
|
8
|
+
export declare function completions(args: {
|
|
9
|
+
file: string;
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
limit?: number;
|
|
13
|
+
}): Promise<{
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getLspClient } from '../lsp-client.js';
|
|
3
|
+
import { toPosition } from '../utils/position.js';
|
|
4
|
+
import { CompletionItemKind } from 'vscode-languageserver-protocol';
|
|
5
|
+
export const completionsSchema = {
|
|
6
|
+
file: z.string().describe('Absolute path to the Python file'),
|
|
7
|
+
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
8
|
+
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
9
|
+
limit: z.number().int().positive().optional().default(20).describe('Maximum number of completions to return'),
|
|
10
|
+
};
|
|
11
|
+
const kindNames = {
|
|
12
|
+
[CompletionItemKind.Text]: 'Text',
|
|
13
|
+
[CompletionItemKind.Method]: 'Method',
|
|
14
|
+
[CompletionItemKind.Function]: 'Function',
|
|
15
|
+
[CompletionItemKind.Constructor]: 'Constructor',
|
|
16
|
+
[CompletionItemKind.Field]: 'Field',
|
|
17
|
+
[CompletionItemKind.Variable]: 'Variable',
|
|
18
|
+
[CompletionItemKind.Class]: 'Class',
|
|
19
|
+
[CompletionItemKind.Interface]: 'Interface',
|
|
20
|
+
[CompletionItemKind.Module]: 'Module',
|
|
21
|
+
[CompletionItemKind.Property]: 'Property',
|
|
22
|
+
[CompletionItemKind.Unit]: 'Unit',
|
|
23
|
+
[CompletionItemKind.Value]: 'Value',
|
|
24
|
+
[CompletionItemKind.Enum]: 'Enum',
|
|
25
|
+
[CompletionItemKind.Keyword]: 'Keyword',
|
|
26
|
+
[CompletionItemKind.Snippet]: 'Snippet',
|
|
27
|
+
[CompletionItemKind.Color]: 'Color',
|
|
28
|
+
[CompletionItemKind.File]: 'File',
|
|
29
|
+
[CompletionItemKind.Reference]: 'Reference',
|
|
30
|
+
[CompletionItemKind.Folder]: 'Folder',
|
|
31
|
+
[CompletionItemKind.EnumMember]: 'EnumMember',
|
|
32
|
+
[CompletionItemKind.Constant]: 'Constant',
|
|
33
|
+
[CompletionItemKind.Struct]: 'Struct',
|
|
34
|
+
[CompletionItemKind.Event]: 'Event',
|
|
35
|
+
[CompletionItemKind.Operator]: 'Operator',
|
|
36
|
+
[CompletionItemKind.TypeParameter]: 'TypeParameter',
|
|
37
|
+
};
|
|
38
|
+
export async function completions(args) {
|
|
39
|
+
const client = getLspClient();
|
|
40
|
+
const position = toPosition(args.line, args.column);
|
|
41
|
+
const limit = args.limit ?? 20;
|
|
42
|
+
const result = await client.completions(args.file, position);
|
|
43
|
+
if (!result) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: 'text', text: 'No completions available at this position.' }],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
let items;
|
|
49
|
+
if (Array.isArray(result)) {
|
|
50
|
+
items = result;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
items = result.items;
|
|
54
|
+
}
|
|
55
|
+
if (items.length === 0) {
|
|
56
|
+
return {
|
|
57
|
+
content: [{ type: 'text', text: 'No completions available at this position.' }],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const limitedItems = items.slice(0, limit);
|
|
61
|
+
let output = `**Completions** at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
62
|
+
output += `Showing ${limitedItems.length} of ${items.length} completion(s):\n\n`;
|
|
63
|
+
for (const item of limitedItems) {
|
|
64
|
+
const kind = item.kind ? kindNames[item.kind] || 'Unknown' : 'Unknown';
|
|
65
|
+
const detail = item.detail ? ` - ${item.detail}` : '';
|
|
66
|
+
output += `- **${item.label}** (${kind})${detail}\n`;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: output }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const definitionSchema: {
|
|
3
|
+
file: z.ZodString;
|
|
4
|
+
line: z.ZodNumber;
|
|
5
|
+
column: z.ZodNumber;
|
|
6
|
+
};
|
|
7
|
+
export declare function definition(args: {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
content: {
|
|
13
|
+
type: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
}[];
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getLspClient } from '../lsp-client.js';
|
|
3
|
+
import { toPosition, fromPosition, uriToPath } from '../utils/position.js';
|
|
4
|
+
export const definitionSchema = {
|
|
5
|
+
file: z.string().describe('Absolute path to the Python file'),
|
|
6
|
+
line: z.number().int().positive().describe('Line number (1-based)'),
|
|
7
|
+
column: z.number().int().positive().describe('Column number (1-based)'),
|
|
8
|
+
};
|
|
9
|
+
export async function definition(args) {
|
|
10
|
+
const client = getLspClient();
|
|
11
|
+
const position = toPosition(args.line, args.column);
|
|
12
|
+
const result = await client.definition(args.file, position);
|
|
13
|
+
if (!result) {
|
|
14
|
+
return {
|
|
15
|
+
content: [{ type: 'text', text: 'No definition found at this position.' }],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const locations = [];
|
|
19
|
+
if (Array.isArray(result)) {
|
|
20
|
+
for (const item of result) {
|
|
21
|
+
if ('targetUri' in item) {
|
|
22
|
+
// LocationLink
|
|
23
|
+
const link = item;
|
|
24
|
+
const pos = fromPosition(link.targetSelectionRange.start);
|
|
25
|
+
locations.push({
|
|
26
|
+
file: uriToPath(link.targetUri),
|
|
27
|
+
line: pos.line,
|
|
28
|
+
column: pos.column,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
// Location
|
|
33
|
+
const loc = item;
|
|
34
|
+
const pos = fromPosition(loc.range.start);
|
|
35
|
+
locations.push({
|
|
36
|
+
file: uriToPath(loc.uri),
|
|
37
|
+
line: pos.line,
|
|
38
|
+
column: pos.column,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
// Single Location
|
|
45
|
+
const loc = result;
|
|
46
|
+
const pos = fromPosition(loc.range.start);
|
|
47
|
+
locations.push({
|
|
48
|
+
file: uriToPath(loc.uri),
|
|
49
|
+
line: pos.line,
|
|
50
|
+
column: pos.column,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
if (locations.length === 0) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: 'text', text: 'No definition found at this position.' }],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
let output = `**Definition(s)** for symbol at ${args.file}:${args.line}:${args.column}\n\n`;
|
|
59
|
+
for (const loc of locations) {
|
|
60
|
+
output += `- ${loc.file}:${loc.line}:${loc.column}\n`;
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
content: [{ type: 'text', text: output }],
|
|
64
|
+
};
|
|
65
|
+
}
|