Skip to content

Commit 99387c8

Browse files
committed
gopls/internal/mcp: add references mcp tool
For golang/go#73580 Change-Id: I6aa02c9a23f255349a66681f07e9ff696c4eedd5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/681555 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent bf1b00e commit 99387c8

File tree

3 files changed

+143
-47
lines changed

3 files changed

+143
-47
lines changed

gopls/internal/mcp/mcp.go

Lines changed: 33 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -122,67 +122,53 @@ func HTTPHandler(eventChan <-chan lsprpc.SessionEvent, isDaemon bool) http.Handl
122122

123123
func newServer(session *cache.Session, server protocol.Server) *mcp.Server {
124124
s := mcp.NewServer("golang", "v0.1", nil)
125-
125+
locationInput := mcp.Input(
126+
mcp.Property(
127+
"location",
128+
mcp.Description("location inside of a text file"),
129+
mcp.Property("uri", mcp.Description("URI of the text document")),
130+
mcp.Property("range",
131+
mcp.Description("range within text document"),
132+
mcp.Required(false),
133+
mcp.Property(
134+
"start",
135+
mcp.Description("start position of range"),
136+
mcp.Property("line", mcp.Description("line number (zero-based)")),
137+
mcp.Property("character", mcp.Description("column number (zero-based, UTF-16 encoding)")),
138+
),
139+
mcp.Property(
140+
"end",
141+
mcp.Description("end position of range"),
142+
mcp.Property("line", mcp.Description("line number (zero-based)")),
143+
mcp.Property("character", mcp.Description("column number (zero-based, UTF-16 encoding)")),
144+
),
145+
),
146+
),
147+
)
126148
s.AddTools(
127149
mcp.NewServerTool(
128150
"context",
129151
"Provide context for a region within a Go file",
130152
func(ctx context.Context, _ *mcp.ServerSession, request *mcp.CallToolParamsFor[ContextParams]) (*mcp.CallToolResultFor[struct{}], error) {
131153
return contextHandler(ctx, session, request)
132154
},
133-
mcp.Input(
134-
mcp.Property(
135-
"location",
136-
mcp.Description("location inside of a text file"),
137-
mcp.Property("uri", mcp.Description("URI of the text document")),
138-
mcp.Property("range",
139-
mcp.Description("range within text document"),
140-
mcp.Required(false),
141-
mcp.Property(
142-
"start",
143-
mcp.Description("start position of range"),
144-
mcp.Property("line", mcp.Description("line number (zero-based)")),
145-
mcp.Property("character", mcp.Description("column number (zero-based, UTF-16 encoding)")),
146-
),
147-
mcp.Property(
148-
"end",
149-
mcp.Description("end position of range"),
150-
mcp.Property("line", mcp.Description("line number (zero-based)")),
151-
mcp.Property("character", mcp.Description("column number (zero-based, UTF-16 encoding)")),
152-
),
153-
),
154-
),
155-
),
155+
locationInput,
156156
),
157157
mcp.NewServerTool(
158158
"diagnostics",
159159
"Provide diagnostics for a region within a Go file",
160160
func(ctx context.Context, _ *mcp.ServerSession, request *mcp.CallToolParamsFor[DiagnosticsParams]) (*mcp.CallToolResultFor[struct{}], error) {
161161
return diagnosticsHandler(ctx, session, server, request)
162162
},
163-
mcp.Input(
164-
mcp.Property(
165-
"location",
166-
mcp.Description("location inside of a text file"),
167-
mcp.Property("uri", mcp.Description("URI of the text document")),
168-
mcp.Property("range",
169-
mcp.Description("range within text document"),
170-
mcp.Required(false),
171-
mcp.Property(
172-
"start",
173-
mcp.Description("start position of range"),
174-
mcp.Property("line", mcp.Description("line number (zero-based)")),
175-
mcp.Property("character", mcp.Description("column number (zero-based, UTF-16 encoding)")),
176-
),
177-
mcp.Property(
178-
"end",
179-
mcp.Description("end position of range"),
180-
mcp.Property("line", mcp.Description("line number (zero-based)")),
181-
mcp.Property("character", mcp.Description("column number (zero-based, UTF-16 encoding)")),
182-
),
183-
),
184-
),
185-
),
163+
locationInput,
164+
),
165+
mcp.NewServerTool(
166+
"references",
167+
"Provide the locations of references to a given object",
168+
func(ctx context.Context, _ *mcp.ServerSession, request *mcp.CallToolParamsFor[FindReferencesParams]) (*mcp.CallToolResultFor[struct{}], error) {
169+
return referenceHandler(ctx, session, request)
170+
},
171+
locationInput,
186172
),
187173
)
188174
return s

gopls/internal/mcp/references.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
package mcp
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"path/filepath"
10+
"strings"
11+
"unicode"
12+
13+
"golang.org/x/tools/gopls/internal/cache"
14+
"golang.org/x/tools/gopls/internal/golang"
15+
"golang.org/x/tools/gopls/internal/protocol"
16+
"golang.org/x/tools/internal/mcp"
17+
)
18+
19+
type FindReferencesParams struct {
20+
Location protocol.Location `json:"location"`
21+
}
22+
23+
func referenceHandler(ctx context.Context, session *cache.Session, params *mcp.CallToolParamsFor[FindReferencesParams]) (*mcp.CallToolResultFor[struct{}], error) {
24+
fh, snapshot, release, err := session.FileOf(ctx, params.Arguments.Location.URI)
25+
if err != nil {
26+
return nil, err
27+
}
28+
defer release()
29+
pos := params.Arguments.Location.Range.Start
30+
refs, err := golang.References(ctx, snapshot, fh, pos, true)
31+
if err != nil {
32+
return nil, err
33+
}
34+
if len(refs) == 0 {
35+
return nil, fmt.Errorf("no references found")
36+
}
37+
var builder strings.Builder
38+
fmt.Fprintf(&builder, "The object has %v references. Their locations are listed below\n", len(refs))
39+
for i, r := range refs {
40+
fmt.Fprintf(&builder, "Reference %d\n", i+1)
41+
fmt.Fprintf(&builder, "Located in the file: %s\n", filepath.ToSlash(r.URI.Path()))
42+
refFh, err := snapshot.ReadFile(ctx, r.URI)
43+
// If for some reason there is an error reading the file content, we should still
44+
// return the references URIs.
45+
if err != nil {
46+
continue
47+
}
48+
content, err := refFh.Content()
49+
if err != nil {
50+
continue
51+
}
52+
lines := strings.Split(string(content), "\n")
53+
var lineContent string
54+
if int(r.Range.Start.Line) < len(lines) {
55+
lineContent = strings.TrimLeftFunc(lines[r.Range.Start.Line], unicode.IsSpace)
56+
} else {
57+
continue
58+
}
59+
fmt.Fprintf(&builder, "The reference is located on line %v, which has content %q\n", r.Range.Start.Line, lineContent)
60+
builder.WriteString("\n")
61+
}
62+
return &mcp.CallToolResultFor[struct{}]{
63+
Content: []*mcp.Content{
64+
mcp.NewTextContent(builder.String()),
65+
},
66+
}, nil
67+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
This test exercises the "references" MCP tool.
2+
3+
-- flags --
4+
-mcp
5+
-ignore_extra_diags
6+
7+
-- go.mod --
8+
module example.com
9+
10+
-- a/a.go --
11+
package a
12+
13+
func Foo() {} //@loc(Foo, "Foo")
14+
15+
func callFoo() {
16+
Foo()
17+
}
18+
19+
-- b/b.go --
20+
package b
21+
22+
import "example.com/a"
23+
24+
func callFoo() {
25+
a.Foo()
26+
}
27+
28+
//@mcptool("references", `{}`, Foo, output=threeref)
29+
30+
-- @threeref --
31+
The object has 3 references. Their locations are listed below
32+
Reference 1
33+
Located in the file: $SANDBOX_WORKDIR/a/a.go
34+
The reference is located on line 2, which has content "func Foo() {} //@loc(Foo, \"Foo\")"
35+
36+
Reference 2
37+
Located in the file: $SANDBOX_WORKDIR/a/a.go
38+
The reference is located on line 5, which has content "Foo()"
39+
40+
Reference 3
41+
Located in the file: $SANDBOX_WORKDIR/b/b.go
42+
The reference is located on line 5, which has content "a.Foo()"
43+

0 commit comments

Comments
 (0)