Skip to main content
Glama

MCP Language Server

transport.go7.36 kB
package lsp import ( "bufio" "context" "encoding/json" "fmt" "io" "strings" "github.com/isaacphi/mcp-language-server/internal/logging" ) // Create component-specific loggers var lspLogger = logging.NewLogger(logging.LSP) var wireLogger = logging.NewLogger(logging.LSPWire) var processLogger = logging.NewLogger(logging.LSPProcess) // WriteMessage writes an LSP message to the given writer func WriteMessage(w io.Writer, msg *Message) error { data, err := json.Marshal(msg) if err != nil { return fmt.Errorf("failed to marshal message: %w", err) } // High-level operation log lspLogger.Debug("Sending message: method=%s id=%v", msg.Method, msg.ID) // Wire protocol log (more detailed) wireLogger.Debug("-> Sending: %s", string(data)) _, err = fmt.Fprintf(w, "Content-Length: %d\r\n\r\n", len(data)) if err != nil { return fmt.Errorf("failed to write header: %w", err) } _, err = w.Write(data) if err != nil { return fmt.Errorf("failed to write message: %w", err) } return nil } // ReadMessage reads a single LSP message from the given reader func ReadMessage(r *bufio.Reader) (*Message, error) { // Read headers var contentLength int for { line, err := r.ReadString('\n') if err != nil { return nil, fmt.Errorf("failed to read header: %w", err) } line = strings.TrimSpace(line) if line == "" { break // End of headers } wireLogger.Debug("<- Header: %s", line) if strings.HasPrefix(line, "Content-Length: ") { _, err := fmt.Sscanf(line, "Content-Length: %d", &contentLength) if err != nil { return nil, fmt.Errorf("invalid Content-Length: %w", err) } } } // Read content content := make([]byte, contentLength) _, err := io.ReadFull(r, content) if err != nil { return nil, fmt.Errorf("failed to read content: %w", err) } wireLogger.Debug("<- Received: %s", string(content)) // Parse message var msg Message if err := json.Unmarshal(content, &msg); err != nil { return nil, fmt.Errorf("failed to unmarshal message: %w", err) } // Log higher-level information about the message type if msg.Method != "" && msg.ID != nil && msg.ID.Value != nil { lspLogger.Debug("Received request from server: method=%s id=%v", msg.Method, msg.ID) } else if msg.Method != "" { lspLogger.Debug("Received notification: method=%s", msg.Method) } else if msg.ID != nil && msg.ID.Value != nil { lspLogger.Debug("Received response for ID: %v", msg.ID) } return &msg, nil } // handleMessages reads and dispatches messages in a loop func (c *Client) handleMessages() { for { msg, err := ReadMessage(c.stdout) if err != nil { // Check if this is due to normal shutdown (EOF when closing connection) if strings.Contains(err.Error(), "EOF") { lspLogger.Info("LSP connection closed (EOF)") } else { lspLogger.Error("Error reading message: %v", err) } return } // Handle server->client request (has both Method and ID) if msg.Method != "" && msg.ID != nil && msg.ID.Value != nil { response := &Message{ JSONRPC: "2.0", ID: msg.ID, } // Look up handler for this method c.serverHandlersMu.RLock() handler, ok := c.serverRequestHandlers[msg.Method] c.serverHandlersMu.RUnlock() if ok { lspLogger.Debug("Processing server request: method=%s id=%v", msg.Method, msg.ID) result, err := handler(msg.Params) if err != nil { lspLogger.Error("Error handling server request %s: %v", msg.Method, err) response.Error = &ResponseError{ Code: -32603, Message: err.Error(), } } else { rawJSON, err := json.Marshal(result) if err != nil { lspLogger.Error("Failed to marshal response for %s: %v", msg.Method, err) response.Error = &ResponseError{ Code: -32603, Message: fmt.Sprintf("failed to marshal response: %v", err), } } else { response.Result = rawJSON } } } else { lspLogger.Warn("Method not found: %s", msg.Method) response.Error = &ResponseError{ Code: -32601, Message: fmt.Sprintf("method not found: %s", msg.Method), } } // Send response back to server if err := WriteMessage(c.stdin, response); err != nil { lspLogger.Error("Error sending response to server: %v", err) } continue } // Handle notification (has Method but no ID) if msg.Method != "" && (msg.ID == nil || msg.ID.Value == nil) { c.notificationMu.RLock() handler, ok := c.notificationHandlers[msg.Method] c.notificationMu.RUnlock() if ok { lspLogger.Debug("Handling notification: %s", msg.Method) go handler(msg.Params) } else { lspLogger.Debug("No handler for notification: %s", msg.Method) } continue } // Handle response to our request (has ID but no Method) if msg.ID != nil && msg.ID.Value != nil && msg.Method == "" { // Convert ID to string for map lookup idStr := msg.ID.String() c.handlersMu.RLock() ch, ok := c.handlers[idStr] c.handlersMu.RUnlock() if ok { lspLogger.Debug("Sending response for ID %v to handler", msg.ID) ch <- msg close(ch) } else { lspLogger.Debug("No handler for response ID: %v", msg.ID) } } } } // Call makes a request and waits for the response func (c *Client) Call(ctx context.Context, method string, params any, result any) error { id := c.nextID.Add(1) lspLogger.Debug("Making call: method=%s id=%v", method, id) msg, err := NewRequest(id, method, params) if err != nil { return fmt.Errorf("failed to create request: %w", err) } // Create response channel ch := make(chan *Message, 1) // Convert ID to string for map lookup idStr := msg.ID.String() c.handlersMu.Lock() c.handlers[idStr] = ch c.handlersMu.Unlock() defer func() { c.handlersMu.Lock() delete(c.handlers, idStr) c.handlersMu.Unlock() }() // Send request if err := WriteMessage(c.stdin, msg); err != nil { return fmt.Errorf("failed to send request: %w", err) } lspLogger.Debug("Waiting for response to request ID: %v", msg.ID) // Wait for response resp := <-ch lspLogger.Debug("Received response for request ID: %v", msg.ID) if resp.Error != nil { lspLogger.Error("Request failed: %s (code: %d)", resp.Error.Message, resp.Error.Code) return fmt.Errorf("request failed: %s (code: %d)", resp.Error.Message, resp.Error.Code) } if result != nil { // If result is a json.RawMessage, just copy the raw bytes if rawMsg, ok := result.(*json.RawMessage); ok { *rawMsg = resp.Result return nil } // Otherwise unmarshal into the provided type if err := json.Unmarshal(resp.Result, result); err != nil { lspLogger.Error("Failed to unmarshal result: %v", err) return fmt.Errorf("failed to unmarshal result: %w", err) } } return nil } // Notify sends a notification (a request without an ID that doesn't expect a response) func (c *Client) Notify(ctx context.Context, method string, params any) error { lspLogger.Debug("Sending notification: method=%s", method) msg, err := NewNotification(method, params) if err != nil { return fmt.Errorf("failed to create notification: %w", err) } if err := WriteMessage(c.stdin, msg); err != nil { return fmt.Errorf("failed to send notification: %w", err) } return nil } type NotificationHandler func(params json.RawMessage) type ServerRequestHandler func(params json.RawMessage) (any, error)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/isaacphi/mcp-language-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server