Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3c058a0
first testing framework
isaacphi Apr 14, 2025
01e4532
add logging implementation
isaacphi Apr 14, 2025
066a242
add claude instructions
isaacphi Apr 14, 2025
823f146
format and core logs
isaacphi Apr 14, 2025
e308cdd
solid testing framework
isaacphi Apr 14, 2025
491edfc
add to gitignore
isaacphi Apr 14, 2025
b2d5479
some cleanup
isaacphi Apr 15, 2025
5f52f10
slight cleanup
isaacphi Apr 15, 2025
adca07f
refactor test suite
isaacphi Apr 16, 2025
d0a3f86
watcher test
isaacphi Apr 16, 2025
33e3da7
fix snap test, remove extra go workspace
isaacphi Apr 16, 2025
07e6016
tests for edit and minor logic fixes
isaacphi Apr 16, 2025
8604315
fix exit condition
isaacphi Apr 16, 2025
1a3efe4
github actions
isaacphi Apr 16, 2025
5d50313
increase timeout for ci test
isaacphi Apr 17, 2025
c26663f
skip in gh actions
isaacphi Apr 17, 2025
9fcc0d7
fix errors
isaacphi Apr 17, 2025
df1ec99
implement CI checks
isaacphi Apr 17, 2025
28a4a76
simplify and document logging
isaacphi Apr 17, 2025
833f1c6
fix boundary condition and add unit tests
isaacphi Apr 17, 2025
581308b
improve test logs
isaacphi Apr 17, 2025
0e2e025
improve test logs
isaacphi Apr 17, 2025
f7dc0cf
improve tests and fix read definition logic
isaacphi Apr 17, 2025
b79ee30
diagnostics test
isaacphi Apr 17, 2025
e5eaa05
improve formatting and references tests
isaacphi Apr 17, 2025
3268142
missed last commit
isaacphi Apr 17, 2025
9fa0117
remove test-lsp
isaacphi Apr 18, 2025
ff0dfed
text_edit test
isaacphi Apr 18, 2025
3d5c8c7
tests
isaacphi Apr 18, 2025
434a0f7
update workflow
isaacphi Apr 18, 2025
d172cb8
fix workflow
isaacphi Apr 18, 2025
b36b530
fix CI
isaacphi Apr 18, 2025
237f25b
fix tests and bahavior for edit tool
isaacphi Apr 18, 2025
9956233
update gitignore
isaacphi Apr 18, 2025
a13b5a5
fix line numbers for references
isaacphi Apr 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "go"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "github-actions"
53 changes: 53 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: CI Workflow

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

jobs:
test:
name: Run Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
check-latest: true
cache: true

- name: Install gopls
run: go install golang.org/x/tools/gopls@latest

- name: Run tests
run: go test ./...

check:
name: Build and Code Quality Checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: "1.24"
check-latest: true
cache: true

- name: Build
run: go build -o mcp-language-server

- name: Install just
run: curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to /usr/local/bin

- name: Install gopls
run: go install golang.org/x/tools/gopls@latest

- name: Run code quality checks
run: just check
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Binary
mcp-language-server

# Test output
test-output/
*.diff

# Temporary files
*~

CLAUDE.md
2 changes: 1 addition & 1 deletion ATTRIBUTION
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This project includes code derived from the following third-party sources:

## Go Tools LSP Protocol Generator

The code in `cmd/generate_protocol/` includes modified code from the Go Tools project's LSP protocol implementation.
The code in `cmd/generate/` includes modified code from the Go Tools project's LSP protocol implementation.

- **Source**: https://go.googlesource.com/tools
- **License**: BSD 3-Clause
Expand Down
29 changes: 24 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# MCP Language Server

[![Go Tests](https://github.com/isaacphi/mcp-language-server/actions/workflows/go.yml/badge.svg)](https://github.com/isaacphi/mcp-language-server/actions/workflows/go.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/isaacphi/mcp-language-server)](https://goreportcard.com/report/github.com/isaacphi/mcp-language-server)
[![GoDoc](https://pkg.go.dev/badge/github.com/isaacphi/mcp-language-server)](https://pkg.go.dev/github.com/isaacphi/mcp-language-server)
[![Go Version](https://img.shields.io/github/go-mod/go-version/isaacphi/mcp-language-server)](https://github.com/isaacphi/mcp-language-server/blob/main/go.mod)

A Model Context Protocol (MCP) server that runs a language server and provides tools for communicating with it.

## Motivation
Expand Down Expand Up @@ -78,7 +83,7 @@ Add something like the following configuration to your Claude Desktop settings (
"--stdio"
],
"env": {
"DEBUG": "1"
"LOG_LEVEL": "INFO"
}
}
}
Expand All @@ -91,7 +96,7 @@ Replace:
- `/opt/homebrew/bin/pyright-langserver` with the path to your language server (found using `which` command e.g. `which pyright-langserver`)
- Any aruments after `--` are sent as arguments to your language server.
- Any env variables are passed on to the language server. Some may be necessary for you language server. For example, `gopls` required `GOPATH` and `GOCACHE` in order for me to get it working properly.
- `DEBUG=1` is optional. See below.
- `LOG_LEVEL` is optional. See below.

## Development

Expand All @@ -114,6 +119,18 @@ Build:
go build
```

Run tests:

```bash
go test ./...
```

Update test snapshots:

```bash
UPDATE_SNAPSHOTS=true go test ./integrationtests/...
```

Configure your Claude Desktop (or similar) to use the local binary:

```json
Expand All @@ -128,7 +145,7 @@ Configure your Claude Desktop (or similar) to use the local binary:
"/path/to/language/server"
],
"env": {
"DEBUG": "1"
"LOG_LEVEL": "DEBUG"
}
}
}
Expand All @@ -143,11 +160,12 @@ Include

```
env: {
"DEBUG": 1
"LOG_LEVEL": "DEBUG",
"LOG_COMPONENT_LEVELS": "wire:DEBUG"
}
```

To get detailed LSP and application logs. Please include as much information as possible when opening issues.
To get detailed LSP and application logs. Setting `LOG_LEVEL` to DEBUG enables verbose logging for all components. Adding `LOG_COMPONENT_LEVELS` with `wire:DEBUG` shows raw LSP JSON messages. Please include as much information as possible when opening issues.

The following features are on my radar:

Expand All @@ -162,3 +180,4 @@ The following features are on my radar:
- [ ] Add LSP server configuration options and presets for common languages
- [ ] Make a more consistent and scalable API for tools (pagination, etc.)
- [ ] Create tools at a higher level of abstraction, combining diagnostics, code lens, hover, and code actions when reading definitions or references.

2 changes: 1 addition & 1 deletion cmd/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func generateDoc(out *bytes.Buffer, doc string) {
return
}
var list bool
for _, line := range strings.Split(doc, "\n") {
for _, line := range strings.SplitN(doc, "\n", -1) {
// Lists in metaModel.json start with a dash.
// To make a go doc list they have to be preceded
// by a blank line, and indented.
Expand Down
10 changes: 7 additions & 3 deletions cmd/generate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ func processinline() {
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(tmpdir) // ignore error
defer func() {
if err := os.RemoveAll(tmpdir); err != nil {
log.Printf("Failed to remove temporary directory: %v", err)
}
}()

// Clone the repository.
cmd := exec.Command("git", "clone", "--quiet", "--depth=1", "-c", "advice.detachedHead=false", vscodeRepo, "--branch="+lspGitRef, "--single-branch", tmpdir)
Expand Down Expand Up @@ -268,7 +272,7 @@ func (t *Type) UnmarshalJSON(data []byte) error {
Value *Type `json:"value"`
}
if err := json.Unmarshal(data, &x); err != nil {
return fmt.Errorf("Type.kind=map: %v", err)
return fmt.Errorf("Type.kind=map: %v", err) //lint:ignore ST1005 ignore
}
t.Key = x.Key
t.Value = x.Value
Expand All @@ -279,7 +283,7 @@ func (t *Type) UnmarshalJSON(data []byte) error {
}

if err := json.Unmarshal(data, &z); err != nil {
return fmt.Errorf("Type.kind=literal: %v", err)
return fmt.Errorf("Type.kind=literal: %v", err) //lint:ignore ST1005 ignore
}
t.Value = z.Value

Expand Down
4 changes: 2 additions & 2 deletions cmd/generate/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestParseContents(t *testing.T) {
if err != nil {
t.Fatal(err)
}
var our interface{}
var our any
if err := json.Unmarshal(out, &our); err != nil {
t.Fatal(err)
}
Expand All @@ -50,7 +50,7 @@ func TestParseContents(t *testing.T) {
if err != nil {
t.Fatalf("could not read metaModel.json: %v", err)
}
var raw interface{}
var raw any
if err := json.Unmarshal(buf, &raw); err != nil {
t.Fatal(err)
}
Expand Down
6 changes: 2 additions & 4 deletions cmd/generate/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ func generateMethodForRequest(out *bytes.Buffer, r *Request) {
// Generate doc comment
fmt.Fprintf(out, "\n// %s\n", methodName+" sends a "+r.Method+" request to the LSP server.")
if r.Documentation != "" {
docLines := strings.Split(cleanDocComment(r.Documentation), "\n")
for _, line := range docLines {
for _, line := range strings.SplitN(cleanDocComment(r.Documentation), "\n", -1) {
fmt.Fprintf(out, "// %s\n", line)
}
}
Expand Down Expand Up @@ -110,8 +109,7 @@ func generateMethodForNotification(out *bytes.Buffer, n *Notification) {
// Generate doc comment
fmt.Fprintf(out, "\n// %s\n", methodName+" sends a "+n.Method+" notification to the LSP server.")
if n.Documentation != "" {
docLines := strings.Split(cleanDocComment(n.Documentation), "\n")
for _, line := range docLines {
for _, line := range strings.SplitN(cleanDocComment(n.Documentation), "\n", -1) {
fmt.Fprintf(out, "// %s\n", line)
}
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/generate/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"bytes"
"fmt"
"log"
"slices"
"sort"
"strings"
)
Expand Down Expand Up @@ -220,7 +221,7 @@ func genStructs(model *Model) {
out.WriteString(lspLink(model, camelCase(s.Name)))
fmt.Fprintf(out, "type %s struct {%s\n", nm, linex(s.Line))
// for gpls compatibilitye, embed most extensions, but expand the rest some day
props := append([]NameType{}, s.Properties...)
props := slices.Clone(s.Properties)
if s.Name == "SymbolInformation" { // but expand this one
for _, ex := range s.Extends {
fmt.Fprintf(out, "\t// extends %s\n", ex.Name)
Expand Down
Loading