AGENTS.md

AGENTS.md

This file provides guidance to AI coding agents when working with code in this repository.

Common Commands

# Build the project
cargo build

# Build in release mode
cargo build --release

# Run the project (requires a config file)
cargo run -- --config sample_config.json

# Run tests
cargo test

# Run a specific test
cargo test <test_name>

# Check for compilation errors without building
cargo check

# Format code
cargo fmt

# Run clippy linter
cargo clippy

Architecture Overview

lsp2mcp is a bridge that translates arbitrary LSP (Language Server Protocol) servers into MCP (Model Context Protocol) servers, enabling AI coding agents to receive real-time diagnostics and ground truth about the code they write.

Three-Layer Architecture

The application operates with three concurrent layers:

  1. MCP Server Layer (MCPServer struct)

    • Exposes tools to AI agents via MCP protocol
    • Runs on stdio transport (reads from stdin, writes to stdout)
    • Currently implements a read tool that returns file content + LSP diagnostics
  2. LSP Client Layer (async_lsp::MainLoop)

    • Acts as an LSP client to communicate with external LSP servers
    • Spawns the LSP server process as a subprocess (stdin/stdout pipes)
    • Handles LSP notifications (Progress, PublishDiagnostics, ShowMessage)
    • Router-based event handling with LSPClientState
  3. LSP Server Process (spawned subprocess)

    • External process (e.g., rust-analyzer, typescript-language-server)
    • Configured via JSON config file (sample_config.json)
    • Killed automatically when parent process exits (kill_on_drop: true)

Execution Flow

AI Agent (MCP Client)
    ↓ stdio
MCPServer::read() tool
    ↓ LSPServerSocket::document_diagnostic()
async_lsp MainLoop (LSP Client)
    ↓ subprocess stdin/stdout pipes
LSP Server Process (e.g., rust-analyzer)

Critical Concurrency Detail

The application uses futures::join!() to run two async tasks concurrently:

  • mainloop_fut: The LSP client mainloop reading/writing from LSP server subprocess
  • server.waiting(): The MCP server awaiting requests from AI agents

Both must run simultaneously because MCP tool calls need to send LSP requests and await responses.

Configuration

The config file (sample_config.json) specifies:

  • lsp_server_command: Command and args to spawn the LSP server subprocess
  • project_root: Workspace root directory (passed to LSP server initialization and used for resolving relative file paths)

File paths in tool arguments are relative to project_root and are canonicalized before use.

Key Framework Usage Patterns

rmcp (MCP server framework):

  • Use #[tool_router] macro on impl blocks to register tools
  • Use #[tool(description = "...")] macro on methods to expose them as MCP tools
  • Tool arguments must be deserializable (serde::Deserialize) and have JSON schema (schemars::JsonSchema)
  • Extract parameters with Parameters<T> wrapper
  • Return Result<CallToolResult, MCPError>

async-lsp (LSP client framework):

  • Use Router::new() with a state type (LSPClientState)
  • Chain .notification::<NotificationType>(handler) to handle LSP notifications
  • Handlers return ControlFlow::Continue(()) or ControlFlow::Break(result)
  • The MainLoop::new_client() creates both the mainloop runner and the ServerSocket client handle
  • ServerSocket is cloneable and can be shared across async contexts (used in MCPServer)

Current Limitations and TODOs

  • Only the read tool is implemented; other tools like edit, complete, definition, etc. are not yet implemented
  • Diagnostics are fetched on-demand via document_diagnostic (pull-based), not via PublishDiagnostics notifications (push-based)
  • Commented-out did_open code suggests file opening/tracking may be needed for some LSP servers
  • Unused variables and dead code indicate work-in-progress state (see compiler warnings)

Adding New MCP Tools

To add a new tool that leverages LSP capabilities:

  1. Add a method to the #[tool_router] impl MCPServer block
  2. Define argument and output structs with serde::Deserialize + schemars::JsonSchema
  3. Use self.lsp_server.clone().<lsp_method>() to call LSP methods
  4. Construct and return CallToolResult with diagnostics/results in structured_content

Example LSP methods available via LSPServerSocket:

  • document_diagnostic() - Get diagnostics for a file
  • completion() - Get code completions
  • goto_definition() - Find symbol definitions
  • hover() - Get hover information
  • did_open(), did_change(), did_close() - Notify file changes