Skip to content

Commit 82b913c

Browse files
committed
Add custom initialization for ts lang server. All files must be open
1 parent dc965eb commit 82b913c

File tree

5 files changed

+116
-28
lines changed

5 files changed

+116
-28
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
A Model Context Protocol (MCP) server that runs a language server and provides tools for communicating with it.
44

55
## Motivation
6+
67
Claude desktop with the [filesystem](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) server feels like magic when working on small projects. This starts to fall apart after you add a few files and imports. With this project, I want to create that experience when working with large projects.
78

89
Language servers excel at tasks that LLMs often struggle with, such as precisely understanding types, understanding relationships, and providing accurate symbol references. This project aims to makes bring those tools to LLMs. LSP also seems like a clear inspiration for MCP so why not jam them together?
910

1011
## Status
12+
1113
⚠️ Pre-beta Quality ⚠️
1214

1315
I have tested this server with the following language servers
@@ -20,6 +22,7 @@ I have tested this server with the following language servers
2022
But it should be compatible with many more.
2123

2224
## Tools
25+
2326
- `read_definition`: Retrieves the complete source code definition of any symbol (function, type, constant, etc.) from your codebase.
2427
- `find_references`: Locates all usages and references of a symbol throughout the codebase.
2528
- `get_diagnostics`: Provides diagnostic information for a specific file, including warnings and errors.
@@ -32,14 +35,17 @@ Behind the scenes, this MCP server can act on `workspace/applyEdit` requests fro
3235
Each tool supports various options for customizing output, such as including line numbers or additional context. See the tool documentation for detailed usage. Line numbers are necessary for `apply_text_edit` to be able to make accurate edits.
3336

3437
## About
38+
3539
This codebase makes use of edited code from [gopls](https://go.googlesource.com/tools/+/refs/heads/master/gopls/internal/protocol) to handle LSP communication. See ATTRIBUTION for details.
3640

3741
[mcp-golang](https://github.com/metoro-io/mcp-golang) is used for MCP communication.
3842

3943
## Prerequisites
40-
Install Go: Follow instructions at https://golang.org/doc/install
44+
45+
Install Go: Follow instructions at <https://golang.org/doc/install>
4146

4247
Fetch or update this server:
48+
4349
```bash
4450
go install github.com/isaacphi/mcp-language-server@latest
4551
```
@@ -53,6 +59,7 @@ Install a language server for your codebase:
5359
- Or use any language server
5460

5561
## Setup
62+
5663
Add something like the following configuration to your Claude Desktop settings (or similar MCP-enabled client):
5764

5865
```json
@@ -87,6 +94,7 @@ Replace:
8794
- `DEBUG=1` is optional. See below.
8895

8996
## Development
97+
9098
Clone the repository:
9199

92100
```bash
@@ -126,19 +134,23 @@ Configure your Claude Desktop (or similar) to use the local binary:
126134
}
127135
}
128136
```
137+
129138
Rebuild after making changes.
130139

131140
## Feedback
132141

133142
Include
143+
134144
```
135145
env: {
136146
"DEBUG": 1
137147
}
138148
```
149+
139150
To get detailed LSP and application logs. Please include as much information as possible when opening issues.
140151

141152
The following features are on my radar:
153+
142154
- [x] Read definition
143155
- [x] Get references
144156
- [x] Apply edit

cmd/test-lsp/main.go

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"time"
1212

1313
"github.com/isaacphi/mcp-language-server/internal/lsp"
14-
"github.com/isaacphi/mcp-language-server/internal/protocol"
1514
"github.com/isaacphi/mcp-language-server/internal/tools"
1615
)
1716

@@ -80,23 +79,18 @@ func main() {
8079
}
8180
fmt.Printf("Server capabilities: %+v\n\n", initResult.Capabilities)
8281

83-
err = client.Initialized(ctx, protocol.InitializedParams{})
84-
if err != nil {
85-
log.Fatalf("Initialized notification failed: %v", err)
86-
}
87-
8882
if err := client.WaitForServerReady(ctx); err != nil {
8983
log.Fatalf("Server failed to become ready: %v", err)
9084
}
9185

9286
///////////////////////////////////////////////////////////////////////////
9387
// Test Tools
94-
// response, err := tools.ReadDefinition(ctx, client, cfg.keyword, true)
95-
// if err != nil {
96-
// log.Fatalf("ReadDefinition failed: %v", err)
97-
// }
98-
// fmt.Println(response)
99-
//
88+
response, err := tools.ReadDefinition(ctx, client, cfg.keyword, true)
89+
if err != nil {
90+
log.Fatalf("ReadDefinition failed: %v", err)
91+
}
92+
fmt.Println(response)
93+
10094
// edits := []tools.TextEdit{
10195
// tools.TextEdit{
10296
// Type: tools.Insert,
@@ -117,11 +111,11 @@ func main() {
117111
// }
118112
// fmt.Println(response)
119113

120-
response, err := tools.GetDiagnosticsForFile(ctx, client, cfg.keyword, true, true)
121-
if err != nil {
122-
log.Fatalf("GetDiagnostics failed: %v", err)
123-
}
124-
fmt.Println(response)
114+
// response, err = tools.GetDiagnosticsForFile(ctx, client, cfg.keyword, true, true)
115+
// if err != nil {
116+
// log.Fatalf("GetDiagnostics failed: %v", err)
117+
// }
118+
// fmt.Println(response)
125119

126120
time.Sleep(time.Second * 1)
127121

internal/lsp/client.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"log"
910
"os"
1011
"os/exec"
12+
"strings"
1113
"sync"
1214
"sync/atomic"
1315
"time"
@@ -128,7 +130,8 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
128130
Name: "mcp-language-server",
129131
Version: "0.1.0",
130132
},
131-
RootURI: protocol.DocumentUri("file://" + workspaceDir),
133+
RootPath: workspaceDir,
134+
RootURI: protocol.DocumentUri("file://" + workspaceDir),
132135
Capabilities: protocol.ClientCapabilities{
133136
Workspace: protocol.WorkspaceClientCapabilities{
134137
Configuration: true,
@@ -204,6 +207,22 @@ func (c *Client) InitializeLSPClient(ctx context.Context, workspaceDir string) (
204207
c.RegisterNotificationHandler("textDocument/publishDiagnostics",
205208
func(params json.RawMessage) { HandleDiagnostics(c, params) })
206209

210+
// Notify the LSP server
211+
err := c.Initialized(ctx, protocol.InitializedParams{})
212+
if err != nil {
213+
return nil, fmt.Errorf("initialization failed: %w", err)
214+
}
215+
216+
// LSP sepecific Initialization
217+
path := strings.ToLower(c.Cmd.Path)
218+
switch {
219+
case strings.Contains(path, "typescript-language-server"):
220+
err := initializeTypescriptLanguageServer(ctx, c, workspaceDir)
221+
if err != nil {
222+
return nil, err
223+
}
224+
}
225+
207226
return &result, nil
208227
}
209228

@@ -343,10 +362,13 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error {
343362
URI: protocol.DocumentUri(uri),
344363
},
345364
}
365+
log.Println("Closing", params.TextDocument.URI.Dir())
366+
// TODO: properly close files. typescript-language-server currently
367+
// doesn't work properly whith my file watcher implementation
346368

347-
if err := c.Notify(ctx, "textDocument/didClose", params); err != nil {
348-
return err
349-
}
369+
// if err := c.Notify(ctx, "textDocument/didClose", params); err != nil {
370+
// return err
371+
// }
350372

351373
c.openFilesMu.Lock()
352374
delete(c.openFiles, uri)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package lsp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
)
9+
10+
// typescript-language-server requires files to be opened in order to load the project
11+
func initializeTypescriptLanguageServer(ctx context.Context, client *Client, rootDir string) error {
12+
tsFiles, err := findAllTypeScriptFiles(rootDir)
13+
if err != nil {
14+
return fmt.Errorf("error finding TypeScript files: %w", err)
15+
}
16+
17+
if len(tsFiles) == 0 {
18+
return fmt.Errorf("no TypeScript files found in %s", rootDir)
19+
}
20+
21+
// Open all the TypeScript files
22+
for _, file := range tsFiles {
23+
err = client.OpenFile(ctx, file)
24+
if err != nil {
25+
return fmt.Errorf("error opening TypeScript file %s: %w", file, err)
26+
}
27+
}
28+
29+
return nil
30+
}
31+
32+
func findAllTypeScriptFiles(rootDir string) ([]string, error) {
33+
var tsFiles []string
34+
35+
err := filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
36+
// Check for walk errors
37+
if err != nil {
38+
return err
39+
}
40+
41+
// Skip directories
42+
if info.IsDir() {
43+
// Skip node_modules directories
44+
if info.Name() == "node_modules" {
45+
return filepath.SkipDir
46+
}
47+
return nil
48+
}
49+
50+
// Check if file has .ts or .tsx extension
51+
ext := filepath.Ext(path)
52+
if ext == ".ts" || ext == ".tsx" {
53+
tsFiles = append(tsFiles, path)
54+
}
55+
56+
return nil
57+
})
58+
59+
if err != nil {
60+
return nil, fmt.Errorf("error walking directory: %w", err)
61+
}
62+
63+
return tsFiles, nil
64+
}
65+

main.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"time"
1414

1515
"github.com/isaacphi/mcp-language-server/internal/lsp"
16-
"github.com/isaacphi/mcp-language-server/internal/protocol"
1716
"github.com/isaacphi/mcp-language-server/internal/watcher"
1817
"github.com/metoro-io/mcp-golang"
1918
"github.com/metoro-io/mcp-golang/transport/stdio"
@@ -102,11 +101,6 @@ func (s *server) initializeLSP() error {
102101
log.Printf("Server capabilities: %+v\n\n", initResult.Capabilities)
103102
}
104103

105-
err = client.Initialized(s.ctx, protocol.InitializedParams{})
106-
if err != nil {
107-
return fmt.Errorf("initialized notification failed: %v", err)
108-
}
109-
110104
go s.workspaceWatcher.WatchWorkspace(s.ctx, s.config.workspaceDir)
111105
return client.WaitForServerReady(s.ctx)
112106
}
@@ -144,6 +138,7 @@ func main() {
144138
parentDeath := make(chan struct{})
145139

146140
// Monitor parent process termination
141+
// Claude desktop does not properly kill child processes for MCP servers
147142
go func() {
148143
ppid := os.Getppid()
149144
if debug {

0 commit comments

Comments
 (0)