MCP Language Server
by isaacphi
- cmd
- test-lsp
package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"time"
"github.com/isaacphi/mcp-language-server/internal/lsp"
"github.com/isaacphi/mcp-language-server/internal/protocol"
"github.com/isaacphi/mcp-language-server/internal/tools"
)
type config struct {
workspaceDir string
lspCommand string
lspArgs []string
keyword string
}
func parseConfig() (*config, error) {
cfg := &config{}
flag.StringVar(&cfg.keyword, "keyword", "main", "keyword to look up definition for")
flag.StringVar(&cfg.workspaceDir, "workspace", ".", "Path to workspace directory (optional)")
flag.StringVar(&cfg.lspCommand, "lsp", "gopls", "LSP command to run")
flag.Parse()
// Get remaining args after -- as LSP arguments
cfg.lspArgs = flag.Args()
// Validate and resolve workspace directory
workspaceDir, err := filepath.Abs(cfg.workspaceDir)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path for workspace: %v", err)
}
cfg.workspaceDir = workspaceDir
if _, err := os.Stat(cfg.workspaceDir); os.IsNotExist(err) {
return nil, fmt.Errorf("workspace directory does not exist: %s", cfg.workspaceDir)
}
// Validate LSP command
if _, err := exec.LookPath(cfg.lspCommand); err != nil {
return nil, fmt.Errorf("LSP command not found: %s", cfg.lspCommand)
}
return cfg, nil
}
func main() {
cfg, err := parseConfig()
if err != nil {
log.Fatal(err)
}
// Change to the workspace directory
if err := os.Chdir(cfg.workspaceDir); err != nil {
log.Fatalf("Failed to change to workspace directory: %v", err)
}
fmt.Printf("Using workspace: %s\n", cfg.workspaceDir)
fmt.Printf("Starting %s %v...\n", cfg.lspCommand, cfg.lspArgs)
// Create a new LSP client
client, err := lsp.NewClient(cfg.lspCommand, cfg.lspArgs...)
if err != nil {
log.Fatalf("Failed to create LSP client: %v", err)
}
defer client.Close()
ctx := context.Background()
initResult, err := client.InitializeLSPClient(ctx, cfg.workspaceDir)
if err != nil {
log.Fatalf("Initialize failed: %v", err)
}
fmt.Printf("Server capabilities: %+v\n\n", initResult.Capabilities)
err = client.Initialized(ctx, protocol.InitializedParams{})
if err != nil {
log.Fatalf("Initialized notification failed: %v", err)
}
if err := client.WaitForServerReady(ctx); err != nil {
log.Fatalf("Server failed to become ready: %v", err)
}
///////////////////////////////////////////////////////////////////////////
// Test Tools
// response, err := tools.ReadDefinition(ctx, client, cfg.keyword, true)
// if err != nil {
// log.Fatalf("ReadDefinition failed: %v", err)
// }
// fmt.Println(response)
//
// edits := []tools.TextEdit{
// tools.TextEdit{
// Type: tools.Insert,
// StartLine: 2,
// EndLine: 2,
// NewText: "two\n",
// },
// tools.TextEdit{
// Type: tools.Replace,
// StartLine: 4,
// EndLine: 4,
// NewText: "",
// },
// }
// response, err = tools.ApplyTextEdits(cfg.keyword, edits)
// if err != nil {
// log.Fatalf("ApplyTextEdits failed: %v", err)
// }
// fmt.Println(response)
response, err := tools.GetDiagnosticsForFile(ctx, client, cfg.keyword, true, true)
if err != nil {
log.Fatalf("GetDiagnostics failed: %v", err)
}
fmt.Println(response)
time.Sleep(time.Second * 1)
///////////////////////////////////////////////////////////////////////////
// Cleanup
fmt.Println("\nShutting down...")
err = client.Shutdown(ctx)
if err != nil {
log.Fatalf("Shutdown failed: %v", err)
}
err = client.Exit(ctx)
if err != nil {
log.Fatalf("Exit failed: %v", err)
}
fmt.Println("Server shut down successfully")
}