Skip to content

Semantic Pattern Adoption

This case study shows how gopls-mcp helps code AI agent to understand existing project patterns, and adopt the same semantic logic.

In gopls-mcp implementation, we follow the same idea of go_diagnostics to implement go_build_check mcp tool. We can ask claude to build check the project to ensure everything works.

Given a project where main_test.go exists (causing duplicate analysis) and main.go has an unused variable:

main.go
package main
func main() {
x := 42 // unused
}
// project structures:
6 collapsed lines
// tree .
// .
// ├── CLAUDE.md
// ├── go.mod
// ├── main.go
// └── main_test.go

gopls-mcp@b6de828f1 reports duplicated diagnostics for project above.

Terminal window
❯ check whether current project could be built?
● I'll check if the current project builds successfully using the
semantic build check tool.
⎿ {
"diagnostics": [
{
"code_snippet": "x := 42 // unused",
"column": 2,
"file": "/home/xieyuschen/codespace/go-playground/main.go",
"line": 4,
"message": "declared and not used: x",
"severity": "Error"
},
7 collapsed lines
{
"code_snippet": "x := 42 // unused",
"column": 2,
"file": "/home/xieyuschen/codespace/go-playground/main.go",
"line": 4,
"message": "declared and not used: x",
"severity": "Error"
}
],
...
}

This indicates that we missed some logic handled inside gopls.

Enter claude, ensures gopls-mcp is available as claude mcp setting is different per project.

Terminal window
❯ You should use precise semantic analysis gopls-mcp server to understand
current go project, see @CLAUDE.md for more info.
When i use gopls-mcp mcp tool go_build_check defined in
@gopls/mcpbridge/core/server.go, I got the dulicapted diagnostics.
Native gopls diagnostics should handle this well, tell me how gopls does
it and use the same way to fix it.

By using gopls-mcp tools, AI agent can focus on reasoning further code changes instead of excessively searching symbols and usages from huge amount of code lines.

We can highlight some notable changes for AI agent when it integrates with gopls-mcp.

The AI agent starts by searching for handleGoDiagnostics to understand how diagnostics are currently processed.

Terminal window
● I'll help you fix the duplicate diagnostics issue in gopls-mcp. Let me first
search for the handleGoDiagnostics function to understand the current implementation.
● gopls-mcp - go_search (MCP)(Cwd: "/home/xieyuschen/codespace/gopls-mcp-case-st
udy-mimic/gopls", query: "handleGoDiagnostics")
4 collapsed lines
⎿ {
"summary": "Found 2 symbol(s):\n - handleGoDiagnostics (Function in
/home/xieyuschen/codespace/gopls-mcp-case-study-mimic/gopls/mcpbridge/c
… +24 lines (ctrl+o to expand)
Read(mcpbridge/core/gopls_wrappers.go)
⎿  Read 1550 lines

To adapt gopls’s behavior, the agent investigates PackageDiagnostics to see how the native implementation returns diagnostics.

Terminal window
● Now let me look at how gopls handles diagnostics deduplication. I'll search for
the PackageDiagnostics function to understand how it returns diagnostics.
● gopls-mcp - go_search (MCP)(Cwd: "/home/xieyuschen/codespace/gopls-mcp-case-st
udy-mimic/gopls", query: "PackageDiagnostics")
5 collapsed lines
⎿ {
"summary": "Found 8 symbol(s):\n - Snapshot.PackageDiagnostics (Meth
od in /home/xieyuschen/codespace/gopls-mcp-case-study-mimic/gopls/inter
… +92 lines (ctrl+o to expand)
● gopls-mcp - go_definition (MCP)(include_body: true, locator: {"symbol_name":"P
ackageDiagnostics","context_file":"/home/xieyus
chen/codespace/gopls-mcp-case-study-mimic/gopls
/internal/cache/snapshot.go","kind":"method","l
ine_hint":534,"parent_scope":"Snapshot"})
4 collapsed lines
⎿ {
"summary": "Definition found at /home/xieyuschen/codespace/gopls-mcp-
case-study-mimic/gopls/internal/cache/snapshot.go:534\n\n**Name**: `Pac
… +58 lines (ctrl+o to expand)

The agent continues by tracing workspaceDiagnosticsHandler and summarizeDiagnostics to see if deduplication happens at the workspace level.

Terminal window
● Now let me search for how gopls's native workspace diagnostics handler
deduplicates diagnostics:
● gopls-mcp - go_search (MCP)(Cwd: "/home/xieyuschen/codespace/gopls-mcp-case-st
udy-mimic/gopls", query:
5 collapsed lines
"workspaceDiagnosticsHandler")
⎿ {
"summary": "Found 1 symbol(s):\n - handler.workspaceDiagnosticsHandl
er (Method in /home/xieyuschen/codespace/gopls-mcp-case-study-mimic/gop
… +13 lines (ctrl+o to expand)
Read(internal/mcp/workspace_diagnostics.go)
⎿  Read 93 lines
● Let me check the summarizeDiagnostics function to see if there's deduplication
happening there:
● gopls-mcp - go_search (MCP)(Cwd: "/home/xieyuschen/codespace/gopls-mcp-case-st
5 collapsed lines
udy-mimic/gopls", query: "summarizeDiagnostics")
⎿ {
"summary": "Found 1 symbol(s):\n - summarizeDiagnostics (Function in
/home/xieyuschen/codespace/gopls-mcp-case-study-mimic/gopls/internal/m
… +13 lines (ctrl+o to expand)
Read(internal/mcp/file_diagnostics.go)
⎿  Read 204 lines

The agent correctly identifies CombineDiagnostics as the place where gopls handles deduplication.

Terminal window
● Let me look at the CombineDiagnostics function which might handle deduplication:
● gopls-mcp - go_search (MCP)(Cwd: "/home/xieyuschen/codespace/gopls-mcp-case-st
udy-mimic/gopls", query: "CombineDiagnostics")
5 collapsed lines
⎿ {
"summary": "Found 1 symbol(s):\n - CombineDiagnostics (Function in /
home/xieyuschen/codespace/gopls-mcp-case-study-mimic/gopls/internal/gol
… +13 lines (ctrl+o to expand)
● gopls-mcp - go_definition (MCP)(include_body: true, locator:
{"context_file":"/home/xieyuschen/codespace/gop
ls-mcp-case-study-mimic/gopls/internal/golang/d
iagnostics.go","kind":"function","line_hint":83
,"symbol_name":"CombineDiagnostics"})
⎿ {
3 collapsed lines
"summary": "Definition found at /home/xieyuschen/codespace/gopls-mcp-
case-study-mimic/gopls/internal/golang/diagnostics.go:83\n\n**Name**: `
… +82 lines (ctrl+o to expand)

The agent then explains the root cause and the solution derived from the codebase analysis:

Terminal window
❯ how does gopls solve this issue?
⎿ ⧉ Selected 1 lines from mcpbridge/core/server.go in Visual Studio Code
● Based on my investigation, here\s how gopls solves the duplicate diagnostics issue:
The Root Cause
When a file belongs to multiple packages (e.g., a package with test files),
PackageDiagnostics returns the same diagnostic multiple times - once for each
package that includes the file.
gopls\s Solution: CombineDiagnostics
In gopls/internal/golang/diagnostics.go:83, gopls uses a map-based
deduplication approach:
func CombineDiagnostics(tdiags []*cache.Diagnostic, adiags []*cache.Diagnostic)
[]*cache.Diagnostic {
// Build index using a composite key
type key struct {
Range protocol.Range
message string
}
...
}

Finally, the agent applies the findings by attempting to update the code to use a map-based deduplication strategy, mirroring the CombineDiagnostics implementation found in gopls/internal/golang/diagnostics.go:83.

Terminal window
● Now I understand the issue and the fix! Let me create a todo list for this fix:
● Now I\ll fix the handleGoDiagnostics function to deduplicate diagnostics using
the same approach as gopls:
Update(mcpbridge/core/gopls_wrappers.go)
⎿ User rejected update to mcpbridge/core/gopls_wrappers.go
389 return nil, nil, fmt.Errorf("diagnostics failed: %v",
err)
390 }
391
392 - var diagnostics []api.Diagnostic
393 - var summary strings.Builder
394 - total := 0
392 + // Deduplicate diagnostics using the same approach as gopls
393 + // Use a map with key based on range and message to
+identify duplicates
394 + type key struct {
395 + Range protocol.Range
396 + message string
397 + }
398 + uniqueDiags := make(map[key]*cache.Diagnostic)
399 +
400 for _, diags := range reports {
401 for _, diag := range diags {
402 - total++
402 + k := key{diag.Range, diag.Message}
403 + if _, exists := uniqueDiags[k]; !exists {
404 + uniqueDiags[k] = diag
405 + }
406 + }
407 + }
408 +
409 + var diagnostics []api.Diagnostic
410 + var summary strings.Builder
411 + total := len(uniqueDiags)
412 + for _, diag := range uniqueDiags {
413 // Convert DiagnosticSeverity to string
414 severityStr := "Unknown"
415 switch diag.Severity {

Raw Claude Thread