feat: add workspace_symbol, implementation, and call_hierarchy tools - #133
Open
scgm11 wants to merge 3 commits into
Open
feat: add workspace_symbol, implementation, and call_hierarchy tools#133scgm11 wants to merge 3 commits into
scgm11 wants to merge 3 commits into
Conversation
The bridge already wraps the corresponding LSP methods at the client layer (client.Symbol, client.Implementation, client.PrepareCallHierarchy, client.IncomingCalls, client.OutgoingCalls) — they were just never surfaced as MCP tools. This PR adds three thin wrappers in internal/tools/ and registers them in tools.go. The new tools fill three navigation gaps the existing 6 don't cover: 1. workspace_symbol(query) — fuzzy symbol search across the workspace. The existing `definition` and `references` tools require an EXACT symbol name (e.g. `mypackage.MyFunction`); workspace_symbol forwards a query string to LSP `workspace/symbol` for fuzzy match (gopls / tsserver / clangd all support fuzzy by default). One call replaces a `grep -r "func MyFunc\|type MyFunc\|var MyFunc"` scan. 2. implementation(filePath, line, column) — find every type or method that implements the interface at the given position. Wraps `textDocument/implementation`. Critical for Go and TypeScript: structural typing has no `implements` keyword for grep to match, so this is the only AST-aware path to enumerate an interface's satisfaction set. 3. call_hierarchy(filePath, line, column) — list direct callers AND callees of a function. Wraps the 3-call LSP dance: prepareCallHierarchy → incomingCalls + outgoingCalls. Output mirrors `gopls call_hierarchy`: `caller[N]: …` lines per incoming call, an `identifier: …` line for the target, `callee[N]: …` lines per outgoing call. Interface change: WorkspaceSymbolResult gains GetKind() and GetContainerName() methods (both concrete types — WorkspaceSymbol and SymbolInformation — already have the underlying fields, so the implementation is trivial). This lets workspace_symbol render results as `<file>:<line>:<column> <name> <Kind>[ (in <ContainerName>)]`, matching what every editor's symbol picker shows. URI handling, position 0/1-index conversion, file open semantics (client.OpenFile before position-based calls), and error formatting all follow the patterns established by the existing hover / definition / references tools. Verified: - `go build ./...` clean. - `go vet ./...` clean. - `go test ./internal/...` — every existing test still green. - Built binary + MCP handshake against gopls (workspace = large Go monorepo, 7923 files): `tools/list` returns 9 tools (the original 6 + the 3 new ones), each with the documented input schema.
…erarchy
Adds Go integration tests for the three new tools, mirroring the
shape of integrationtests/tests/go/hover/hover_test.go (table-
driven cases + TestSuite + SnapshotTest + per-case assertions on
expected substrings).
Coverage per tool:
workspace_symbol/
ExactType — query "SharedStruct"
ExactInterface — query "SharedInterface"
ExactConstant — query "SharedConstant"
WorkspaceUniqueType — query "TestStruct" (workspace-unique
so the snapshot stays stable across Go
stdlib versions; a bare "Method" query
would match every Method in reflect /
abi / runtime and the snapshot would
flap on every Go release)
NoMatch — query that doesn't exist
EmptyQuery — validation gate fires before the LSP
call (test in a separate function so
the snapshot tests don't have to deal
with an error case)
implementation/
InterfaceToImpl — cursor on `SharedInterface`
InterfaceMethodToImpl — cursor on a method declared in the
interface body
NoImpls — cursor on a const; documents that
gopls returns an LSP error
("is a const, not a type") while
spec-compliant servers return an
empty list — accepts both shapes
call_hierarchy/
OnMethodName — cursor on SharedStruct.Method
OnInterfaceImpl — cursor on SharedStruct.Process (a method
satisfying SharedInterface)
NonFunction — cursor on a const; same dual-shape
assertion as implementation/NoImpls
(gopls error vs spec-compliant empty)
All 14 test cases pass on the verification run (no UPDATE_SNAPSHOTS
flag) — snapshots are deterministic.
Two LSP-behavior notes baked into the dual-shape assertions
(NoImpls / NonFunction):
- LSP spec says prepareCallHierarchy / textDocument/implementation
SHOULD return null / empty list for invalid positions.
- gopls instead returns a JSON-RPC error with a clear message
("X is a const, not a type" / "X is not a function").
Both are acceptable from the agent's perspective — the test
accepts either. A future tsserver-targeted version of these tests
would likely exercise the spec-compliant path.
…nce bug Three issues found while reviewing the new integration tests for machine-specific leaks. Snapshots generated on one developer's machine should be byte-equal to the same snapshot generated on a CI runner — otherwise every PR that touches a snapshotted tool churns a diff for every contributor. 1. `normalizePaths` (in integrationtests/tests/common/helpers.go) handled only the FIRST `/workspace/` occurrence per line via `strings.Split(..., "/workspace/")` + reading only `parts[1]`. call_hierarchy output embeds the workspace path TWICE per `caller[N]:` row (once for the range location, once for the symbol declaration), so the trailing path was silently dropped AND the absolute prefix between them leaked through verbatim (e.g. `/private/var/folders/.../TestCallHierarchy_*`). Rewrote as a forward-scanning loop in `normalizeWorkspaceMarker` that walks each marker occurrence and replaces the preceding path-segment prefix. Idempotent (advances past the freshly- written placeholder so the trailing `/workspace/` doesn't re- match). 2. workspace_symbol's `TestStruct` query was fuzzy-matched by gopls against Go runtime symbols (`stringStruct`, `stringStructOf`, `stringStructDWARF`), pulling Go-stdlib paths (`<homebrew>/Cellar/go/<version>/libexec/src/runtime/...` on macOS, `/usr/local/go/src/runtime/...` on Linux) into the snapshot. Switched to `AnotherConsumer` — present only in the test workspace fixture, no substring overlap with any stdlib symbol name. 3. call_hierarchy's `OnInterfaceImpl` case targeted `Process` (a method that calls `fmt.Printf`), so gopls' callee[0] resolved into the Go stdlib's `fmt/print.go` with the same path-leak problem. Switched to `GetName` — also satisfies SharedInterface but has no body calls. Verified: all 14 cases pass on a cold run without UPDATE_SNAPSHOTS. Final snapshot scan finds zero machine-specific paths.
judggernaut
added a commit
to judggernaut/mcp-language-server
that referenced
this pull request
Jul 3, 2026
…rrences Discovered while porting upstream PR isaacphi#133's call_hierarchy tool, whose output embeds the same workspace path twice per line (once per LSP location field). The old normalizePaths used strings.Split(line, "/workspace/") and kept only parts[1], which had two bugs: 1. It replaced the ENTIRE line with just the placeholder + remainder, silently discarding any text before the first '/workspace/' match. Every 'No diagnostics found for <path>' and every 'File: <path>' line in every snapshot across all five languages had that leading text silently dropped, undetected because the test's actual strings.Contains() assertions check the raw (non-normalized) result, not the snapshot, so no test was ever wrong, only the auxiliary snapshot file was incomplete. 2. Only the first '/workspace/' occurrence on a line was normalized; a second occurrence kept the real machine-local absolute path, which would make call_hierarchy snapshots (two paths per caller line) non-portable across machines/CI runners. Replace it with normalizeWorkspaceMarker, which scans a line for every occurrence of the marker, walks backward to the nearest path-segment boundary to find where the absolute path starts, and rewrites just that segment, preserving everything else on the line. Regenerated every existing snapshot with UPDATE_SNAPSHOTS=true and confirmed each diff is exactly the previously-dropped prefix text reappearing (e.g. 'File: ' before a path, 'No diagnostics found for ' before a path) with no other change; all five language integration suites still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
judggernaut
added a commit
to judggernaut/mcp-language-server
that referenced
this pull request
Jul 3, 2026
Upstream PR isaacphi#133 (isaacphi/mcp-language-server). The existing definition/references tools require an exact symbol name and answer "where is X defined" / "where is X used". Three structural questions had no tool at all, despite the LSP client already having the underlying methods (Symbol, Implementation, PrepareCallHierarchy, IncomingCalls, OutgoingCalls) wired up and unused: - workspace_symbol: search the whole workspace by (possibly fuzzy) query when you don't know which file a symbol lives in, replacing an N-call grep-the-codebase scan with one workspace/symbol call. - implementation: find every concrete type/method that satisfies an interface. Go and TypeScript both use structural typing with no "implements" keyword for grep to match, so this is the only reliable way to enumerate an interface's satisfaction set. - call_hierarchy: list direct callers AND callees of a function or method in one call (textDocument/prepareCallHierarchy + callHierarchy incoming/outgoing calls), for refactor impact analysis and data-flow tracing. internal/protocol/interfaces.go: extend WorkspaceSymbolResult with GetKind()/GetContainerName() (both WorkspaceSymbol and SymbolInformation already carry this data via BaseSymbolInformation- shaped fields) so workspace_symbol output can show a symbol's kind and containing package/class. Adapted from the upstream PR to this fork's conventions: uses protocol.URIFromPath / DocumentUri.Path() instead of raw file:// string concatenation (consistent with the Windows URI fix), and adds the same ReadOnlyHint tool annotations the other read-only tools in this fork already carry (see the earlier MCP tool annotations commit). Ported the three Go integration test suites unchanged (line/column positions and symbol names in the shared test fixtures matched this fork's copy exactly) and generated fresh snapshots, which came out byte-identical to the upstream PR's own committed snapshots. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Surfaces three LSP methods this bridge already wraps at the client layer but doesn't expose as MCP tools.
workspace_symbol(query)— fuzzy symbol search across the workspace viaworkspace/symbol. Use BEFOREdefinition/referenceswhen you don't know which file a symbol lives in: one call replaces agrep -r "func MyFunc\|type MyFunc\|var MyFunc"scan. gopls / tsserver / clangd all support fuzzy matching by default.implementation(filePath, line, column)— find every type or method that satisfies the interface at the given position viatextDocument/implementation. Critical for Go and TypeScript: structural typing has noimplementskeyword for grep to match, so this is the only AST-aware path to enumerate an interface's satisfaction set.call_hierarchy(filePath, line, column)— list direct callers AND callees of a function via the 3-call LSP dance (prepareCallHierarchy→incomingCalls+outgoingCalls). Output mirrorsgopls call_hierarchy:caller[N]: …,identifier: …,callee[N]: …markers.Why
These three are the most-asked questions an editor's "Go to symbol", "Go to implementation", and "Show call hierarchy" buttons answer — and the LSP methods to power them are already in
internal/lsp/methods.go(Symbol,Implementation,PrepareCallHierarchy,IncomingCalls,OutgoingCalls). Wrapping them as MCP tools means an agent driving this bridge gets the same surface a human gets in vscode/zed.I'd been maintaining an out-of-tree fork to add these for our project's Claude Code wiring; opening this PR so it can live upstream instead.
How
internal/tools/*.gofiles — thin wrappers around the existing client methods, following the exact shape ofhover.go(URI scheme, 1→0 indexed conversion,client.OpenFilebefore position-based calls, error formatting).mcp.NewTool(...)registrations intools.go, mirroring thehover/rename_symbolregistration pattern (string + numeric args withfloat64/inttype switching).internal/protocol/interfaces.go:WorkspaceSymbolResultgainsGetKind()andGetContainerName()methods. Both concrete types (WorkspaceSymbolandSymbolInformation) already have the underlying fields, so the implementation is two lines each. The new methods letworkspace_symbolrender results as<file>:<line>:<column> <name> <Kind>[ (in <ContainerName>)]— the canonical editor-symbol-picker format.Verified
go build ./...clean.go vet ./...clean.go test ./internal/...— every existing test still green.tools/listreturns 9 tools (the original 6 + the 3 new ones), each with the documented input schema. Sampleworkspace_symbol "ExpectedSchemaVersion"returns 4 matches (the constant + 3 tests).workspace_symbol/implementation/call_hierarchysubcommand output, so callers that already parse gopls CLI output don't need to rewrite their parser.Compatibility
WorkspaceSymbolResultis internal — onlyWorkspaceSymbolandSymbolInformationimplement it, both controlled by this repo. No breaking changes to public consumers.mcp.WithDescription/mcp.WithString/mcp.WithNumber/mcp.Required).LocationLinkfallback path inimplementation.gohandles servers that advertiselinkSupport=true(rust-analyzer, clangd).🤖 Generated with Claude Code