Find every reference to a Swift symbol
swiftFindSymbolReferencesQueries SourceKit-LSP to find all references to a Swift symbol across a project, returning callsites and captures with code snippets. An index store is needed for cross-file results.
Instructions
[mg.code] Locates the symbol's declaration in filePath, then asks SourceKit-LSP for textDocument/references. Returns every callsite + capture across the project, with a snippet of each line. Requires an IndexStoreDB at <projectRoot>/.build/index/store for cross-file references — build it with swift build -Xswiftc -index-store-path -Xswiftc <projectRoot>/.build/index/store. The result includes a needsIndex: true hint when the index is missing.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| symbolName | Yes | Name of the Swift symbol to find references for. | |
| filePath | Yes | Path to a Swift file where the symbol is declared. The LSP query needs a position; we locate it in this file via a regex pre-scan. | |
| projectRoot | No | Override the project root. Default discovers the nearest Package.swift / .xcodeproj / .xcworkspace. | |
| includeDeclaration | No | Include the declaration site itself in the result set. |
Implementation Reference
- Main handler function: resolves file path, locates symbol declaration via regex pre-scan, acquires SourceKit-LSP client, calls lspReferences to find all references, attaches code snippets, and returns results with a needsIndex hint when no references found.
export async function swiftFindSymbolReferences( input: SwiftFindSymbolReferencesInput, ): Promise<SwiftFindSymbolReferencesResult> { const file = resolvePath(input.filePath); if (!existsSync(file)) { throw new Error(`File not found: ${file}`); } const root = input.projectRoot ? resolvePath(input.projectRoot) : projectRootFor(file); const pos = findSymbolDeclaration(file, input.symbolName); if (!pos) { return { ok: true, symbolName: input.symbolName, totalReferences: 0, references: [], }; } const client = await acquireClient(root); const refs = await lspReferences( client, file, pos.line, pos.character, input.includeDeclaration ?? true, ); const refsWithSnippets = refs.map((r) => ({ ...r, snippet: snippetAt(r.filePath, r.line), })); return { ok: true, symbolName: input.symbolName, totalReferences: refs.length, references: refsWithSnippets, needsIndex: refs.length === 0 ? true : undefined, }; } - Zod schema defining the tool's input: symbolName (required), filePath (required), projectRoot (optional), includeDeclaration (optional, default true).
export const swiftFindSymbolReferencesSchema = z.object({ symbolName: z .string() .min(1) .describe("Name of the Swift symbol to find references for."), filePath: z .string() .min(1) .describe( "Path to a Swift file where the symbol is declared. The LSP query needs a position; we locate it in this file via a regex pre-scan.", ), projectRoot: z .string() .optional() .describe( "Override the project root. Default discovers the nearest Package.swift / .xcodeproj / .xcworkspace.", ), includeDeclaration: z .boolean() .default(true) .describe("Include the declaration site itself in the result set."), }); - src/index.ts:441-453 (registration)MCP server registration of the tool with name 'swiftFindSymbolReferences', title, description, input schema, and async handler that delegates to the main handler function.
server.registerTool( "swiftFindSymbolReferences", { title: "Find every reference to a Swift symbol", description: "[mg.code] Locates the symbol's declaration in `filePath`, then asks SourceKit-LSP for `textDocument/references`. Returns every callsite + capture across the project, with a snippet of each line. **Requires an IndexStoreDB** at `<projectRoot>/.build/index/store` for cross-file references — build it with `swift build -Xswiftc -index-store-path -Xswiftc <projectRoot>/.build/index/store`. The result includes a `needsIndex: true` hint when the index is missing.", inputSchema: swiftFindSymbolReferencesSchema.shape, }, async (input) => { const result = await swiftFindSymbolReferences(input); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; }, ); - src/tools/swift/index.ts:18-23 (registration)Re-export aggregator for the swift tools module, making swiftFindSymbolReferences and its schema available from a single import point.
export { swiftFindSymbolReferences, swiftFindSymbolReferencesSchema, type SwiftFindSymbolReferencesInput, type SwiftFindSymbolReferencesResult, } from "./findSymbolReferences.js"; - src/tools/swift/_helpers.ts:30-80 (helper)Helper function that finds a symbol's declaration position in a file using regex pre-scan. First tries declaration keywords (class, struct, etc.), then falls back to any word match. Returns 0-based LSP positions.
export function findSymbolDeclaration( filePath: string, symbolName: string, ): SymbolPosition | null { const text = readFileSync(filePath, "utf8"); const lines = text.split(/\r?\n/); const declRe = new RegExp( `\\b(?:class|struct|enum|protocol|func|var|let|actor|extension)\\s+${escapeRegex(symbolName)}\\b`, ); for (let i = 0; i < lines.length; i++) { const m = lines[i].match(declRe); if (m) { const ch = lines[i].indexOf(symbolName, m.index ?? 0); if (ch >= 0) { return { line: i, character: ch, matchedText: lines[i] }; } } } const wordRe = new RegExp(`\\b${escapeRegex(symbolName)}\\b`); for (let i = 0; i < lines.length; i++) { const m = lines[i].match(wordRe); if (m) { return { line: i, character: m.index ?? 0, matchedText: lines[i], }; } } return null; } export function escapeRegex(s: string): string { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } /** Extract a few lines of context around `line` for snippet display. */ export function snippetAt(filePath: string, line: number, padding = 1): string { try { const text = readFileSync(filePath, "utf8"); const lines = text.split(/\r?\n/); const start = Math.max(0, line - padding); const end = Math.min(lines.length - 1, line + padding); return lines.slice(start, end + 1).join("\n"); } catch { return ""; } }