Skip to content

Commit fbbfd37

Browse files
authored
feat(dora): add LSP find references tool (#29)
* feat(dora): add lsp_references tool for finding symbol usages Add lsp_references MCP tool to the LSP provider that finds all references to a symbol at a given position in a file. - Add tool definition with file, line, character, and optional include_declaration parameters - Implement handleReferences handler using LSPManager.references() - Add test for tool listing - Follow existing LSP tool patterns (path handling, error handling) Issue: #28 * test(dora): add lsp_references test coverage Add missing test cases for lsp_references tool: - TC-2: Error when calling without connection - TC-3: Empty result handling for non-symbol position
1 parent 7938021 commit fbbfd37

3 files changed

Lines changed: 196 additions & 0 deletions

File tree

packages/dora/src/providers/lsp/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ const LSP_TOOLS: ToolDefinition[] = [
5454
file: z.string().describe('Path to the file to analyze'),
5555
}),
5656
},
57+
{
58+
name: 'lsp_references',
59+
description:
60+
'Find all references to a symbol at a specific position in a file. '
61+
+ 'Returns locations where the symbol is used across the workspace.',
62+
inputSchema: z.object({
63+
file: z.string().describe('Path to the file'),
64+
line: z.number().describe('Line number (0-indexed)'),
65+
character: z.number().describe('Character position (0-indexed)'),
66+
include_declaration: z
67+
.boolean()
68+
.optional()
69+
.describe('Include the symbol\'s declaration in results (default: false)'),
70+
}),
71+
},
5772
{
5873
name: 'lsp_status',
5974
description:
@@ -119,6 +134,8 @@ export class LSPProvider implements Provider {
119134
return await this.handleWorkspaceSymbol(args)
120135
case 'lsp_document_symbol':
121136
return await this.handleDocumentSymbol(args)
137+
case 'lsp_references':
138+
return await this.handleReferences(args)
122139
case 'lsp_status':
123140
return await this.handleStatus()
124141
default:
@@ -235,6 +252,41 @@ export class LSPProvider implements Provider {
235252
}
236253
}
237254

255+
private async handleReferences(args: unknown): Promise<ToolResult> {
256+
const parsed = z
257+
.object({
258+
file: z.string(),
259+
line: z.number(),
260+
character: z.number(),
261+
include_declaration: z.boolean().optional(),
262+
})
263+
.parse(args)
264+
265+
const filePath = path.isAbsolute(parsed.file)
266+
? parsed.file
267+
: path.join(this.config.projectPath, parsed.file)
268+
269+
await this.manager!.touchFile(filePath, true)
270+
const references = await this.manager!.references({
271+
file: filePath,
272+
line: parsed.line,
273+
character: parsed.character,
274+
includeDeclaration: parsed.include_declaration ?? false,
275+
})
276+
277+
if (!references.length) {
278+
return {
279+
content: [
280+
{ type: 'text', text: 'No references found at this position' },
281+
],
282+
}
283+
}
284+
285+
return {
286+
content: [{ type: 'text', text: JSON.stringify(references, null, 2) }],
287+
}
288+
}
289+
238290
private async handleStatus(): Promise<ToolResult> {
239291
const status = await this.manager!.status()
240292

packages/dora/test/lsp-provider.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ describe('LSPProvider', () => {
4848
expect(toolNames).toContain('lsp_hover')
4949
expect(toolNames).toContain('lsp_workspace_symbol')
5050
expect(toolNames).toContain('lsp_document_symbol')
51+
expect(toolNames).toContain('lsp_references')
5152
expect(toolNames).toContain('lsp_status')
5253
})
5354

@@ -76,6 +77,33 @@ describe('LSPProvider', () => {
7677
expect(result.isError).toBe(true)
7778
expect(result.content[0]!.text).toContain('Unknown tool')
7879
})
80+
81+
test('lsp_references returns error when not connected', async () => {
82+
provider = new LSPProvider({ projectPath: process.cwd() })
83+
84+
const result = await provider.callTool('lsp_references', {
85+
file: 'test.ts',
86+
line: 0,
87+
character: 0,
88+
})
89+
expect(result.isError).toBe(true)
90+
expect(result.content[0]!.text).toContain('not connected')
91+
})
92+
93+
test('lsp_references returns no references for non-symbol position', async () => {
94+
provider = new LSPProvider({ projectPath: process.cwd() })
95+
await provider.connect()
96+
97+
// Call on a position that doesn't have a symbol (no LSP servers connected)
98+
const result = await provider.callTool('lsp_references', {
99+
file: 'test.ts',
100+
line: 0,
101+
character: 0,
102+
})
103+
// Without connected LSP servers, should return "No references found"
104+
expect(result.isError).toBeFalsy()
105+
expect(result.content[0]!.text).toContain('No references found')
106+
})
79107
})
80108

81109
describe('createLSPProvider', () => {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Specification: LSP Find References Tool
2+
3+
**Spec Number**: 003
4+
**Feature Name**: lsp-find-references
5+
**Created**: 2025-12-19
6+
**Status**: Draft
7+
8+
## Summary
9+
10+
Add an `lsp_references` MCP tool to the `@pleaseai/dora` package that finds all usages/references of a symbol at a given position in a source file using the Language Server Protocol.
11+
12+
## Problem Statement
13+
14+
Developers using AI coding assistants need to understand how symbols (functions, classes, variables) are used across a codebase. Currently, dora provides:
15+
- `lsp_workspace_symbol` - Search by name
16+
- `lsp_document_symbol` - Get file structure
17+
18+
However, there's no way to find all references to a specific symbol at a cursor position, which is essential for:
19+
- Understanding impact of changes
20+
- Refactoring safely
21+
- Navigating codebases
22+
- Learning how APIs are used
23+
24+
## Solution
25+
26+
Add `lsp_references` tool that:
27+
1. Takes a file path and cursor position (line, character)
28+
2. Optionally includes the symbol's declaration in results
29+
3. Returns all locations where the symbol is referenced
30+
31+
## Functional Requirements
32+
33+
### FR-1: Tool Definition
34+
- **Name**: `lsp_references`
35+
- **Input Parameters**:
36+
- `file` (string, required): Path to the file (relative or absolute)
37+
- `line` (number, required): Line number (0-indexed)
38+
- `character` (number, required): Character position (0-indexed)
39+
- `include_declaration` (boolean, optional, default: false): Include the symbol's declaration
40+
41+
### FR-2: Output Format
42+
Returns JSON array of Location objects:
43+
```json
44+
[
45+
{
46+
"uri": "file:///path/to/file.ts",
47+
"range": {
48+
"start": { "line": 10, "character": 5 },
49+
"end": { "line": 10, "character": 15 }
50+
}
51+
}
52+
]
53+
```
54+
55+
### FR-3: Empty Result Handling
56+
When no references are found, return user-friendly message:
57+
```
58+
No references found at this position
59+
```
60+
61+
### FR-4: Path Resolution
62+
- Support both relative and absolute paths
63+
- Relative paths resolved from project root (config.projectPath)
64+
65+
## Non-Functional Requirements
66+
67+
### NFR-1: Consistency
68+
Follow existing LSP tool patterns in `packages/dora/src/providers/lsp/index.ts`:
69+
- Zod schema validation
70+
- Path normalization
71+
- Error handling with isError flag
72+
- JSON stringified output
73+
74+
### NFR-2: Performance
75+
- Leverage existing LSPManager connection pooling
76+
- No additional overhead beyond LSP protocol
77+
78+
## Implementation Notes
79+
80+
### Existing Infrastructure
81+
- `LSPManager.references()` already implemented in `@pleaseai/code-lsp`
82+
- Returns `Location[]` from all connected language servers
83+
- Handles multi-server aggregation internally
84+
85+
### Integration Points
86+
1. Add tool definition to `LSP_TOOLS` array
87+
2. Add case to `callTool` switch
88+
3. Implement `handleReferences` method
89+
4. Add unit test
90+
91+
## Test Cases
92+
93+
### TC-1: Tool Listing
94+
Verify `lsp_references` appears in listTools() output
95+
96+
### TC-2: Not Connected Error
97+
Calling tool before connect() returns error with "not connected"
98+
99+
### TC-3: Unknown Position
100+
Calling tool on non-symbol position returns "No references found"
101+
102+
## Out of Scope
103+
104+
- Go to Definition (separate feature)
105+
- Find Implementations (separate feature)
106+
- Code completion (separate feature)
107+
- Symbol renaming (separate feature)
108+
109+
## Acceptance Criteria
110+
111+
1. `lsp_references` tool available via MCP
112+
2. Returns correct reference locations from LSP servers
113+
3. Handles empty results gracefully
114+
4. Handles relative and absolute paths
115+
5. Unit tests pass
116+
6. Documentation updated (CLAUDE.md)

0 commit comments

Comments
 (0)