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.
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