@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 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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ }