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:
-
MCP Server Layer (
MCPServerstruct)- Exposes tools to AI agents via MCP protocol
- Runs on stdio transport (reads from stdin, writes to stdout)
- Currently implements a
readtool that returns file content + LSP diagnostics
-
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
-
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 subprocessserver.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 subprocessproject_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(())orControlFlow::Break(result) - The
MainLoop::new_client()creates both the mainloop runner and theServerSocketclient handle ServerSocketis cloneable and can be shared across async contexts (used inMCPServer)
Current Limitations and TODOs
- Only the
readtool is implemented; other tools likeedit,complete,definition, etc. are not yet implemented - Diagnostics are fetched on-demand via
document_diagnostic(pull-based), not viaPublishDiagnosticsnotifications (push-based) - Commented-out
did_opencode 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:
- Add a method to the
#[tool_router] impl MCPServerblock - Define argument and output structs with
serde::Deserialize+schemars::JsonSchema - Use
self.lsp_server.clone().<lsp_method>()to call LSP methods - Construct and return
CallToolResultwith diagnostics/results instructured_content
Example LSP methods available via LSPServerSocket:
document_diagnostic()- Get diagnostics for a filecompletion()- Get code completionsgoto_definition()- Find symbol definitionshover()- Get hover informationdid_open(),did_change(),did_close()- Notify file changes