Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,38 @@ All notable changes to this project will be documented in this file.

### New Features

- **Automatic Connection**: Added automatic connection feature with CLI support
for seamless integration with current project Neovim instances
- **Project-Scoped Auto-Discovery**: Automatically find and connect to Neovim
instances associated with the current project directory
- **Flexible Connection Modes**: Support for manual, automatic, and specific
target connection modes via CLI
- **HTTP Server Transport**: Added HTTP server mode for web-based integrations
with streamable HTTP transport support
- **Multi-Transport Support**: Server now supports both stdio (default) and
HTTP server transport modes for different integration scenarios

### New CLI Options

- `--connect <MODE>` - Connection mode: 'manual' (default), 'auto', or specific
target (TCP address/socket path)
- `manual`: Traditional workflow using get_targets and connect tools
- `auto`: Automatically connect to all project-associated Neovim instances
- Specific target: Direct connection to TCP address or socket path
- `--http-port <PORT>` - Enable HTTP server mode on the specified port
- `--http-host <HOST>` - HTTP server bind address (defaults to 127.0.0.1)

### Auto-Connection Behavior

- **Project Detection**: Automatically detects current project root using git
repository or working directory
- **Socket Pattern Matching**: Finds Neovim instances using project-specific
socket naming patterns
- **Graceful Fallback**: Continues serving with manual connection capability
if auto-connection fails
- **Connection Validation**: Validates target formats and provides clear error
messages for invalid targets

### Dependencies

- Added `hyper` for high-performance HTTP server transport
Expand Down
15 changes: 13 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ with proper error handling throughout.
cargo build
cargo run

# Auto-connect to current project Neovim instances
cargo run -- --connect auto

# Connect to specific target
cargo run -- --connect 127.0.0.1:6666
cargo run -- --connect /tmp/nvim.sock

# With custom logging options
cargo run -- --log-file ./nvim-mcp.log --log-level debug

# HTTP server mode
cargo run -- --http-port 8080
# HTTP server mode with auto-connection
cargo run -- --http-port 8080 --connect auto

# HTTP server mode with custom bind address
cargo run -- --http-port 8080 --http-host 0.0.0.0
Expand All @@ -42,6 +49,10 @@ nix develop .

**CLI Options:**

- `--connect <MODE>`: Connection mode (defaults to manual)
- `manual`: Traditional workflow using get_targets and connect tools
- `auto`: Automatically connect to all project-associated Neovim instances
- Specific target: Direct connection to TCP address or socket path
- `--log-file <PATH>`: Log file path (defaults to stderr)
- `--log-level <LEVEL>`: Log level (trace, debug, info, warn, error;
defaults to info)
Expand Down
70 changes: 57 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,32 @@ cargo install --path .
### 1. Start the Server

```bash
# Start as stdio MCP server (default)
# Start as stdio MCP server (default, manual connection mode)
nvim-mcp

# Auto-connect to current project Neovim instances
nvim-mcp --connect auto

# Connect to specific target (TCP address or socket path)
nvim-mcp --connect 127.0.0.1:6666
nvim-mcp --connect /tmp/nvim.sock

# With custom logging
nvim-mcp --log-file ./nvim-mcp.log --log-level debug

# HTTP server mode
nvim-mcp --http-port 8080
# HTTP server mode with auto-connection
nvim-mcp --http-port 8080 --connect auto

# HTTP server mode with custom bind address
nvim-mcp --http-port 8080 --http-host 0.0.0.0
```

#### Command Line Options

- `--connect <MODE>`: Connection mode (default: manual)
- `manual`: Traditional workflow using get_targets and connect tools
- `auto`: Automatically connect to all project-associated Neovim instances
- Specific target: TCP address (e.g., `127.0.0.1:6666`) or absolute socket path
- `--log-file <PATH>`: Path to log file (defaults to stderr)
- `--log-level <LEVEL>`: Log level (trace, debug, info, warn, error;
defaults to info)
Expand Down Expand Up @@ -106,11 +117,51 @@ Or add to your Neovim config:
vim.fn.serverstart("127.0.0.1:6666")
```

### 3. Basic Usage Workflow
### 3. Usage Workflows

Once both the MCP server and Neovim are running, here are the available workflows:

#### Automatic Connection Mode (Recommended)

When using `--connect auto`, the server automatically discovers and connects to
Neovim instances associated with your current project:

1. **Start server with auto-connect**:

```bash
nvim-mcp --connect auto
```

2. **Server automatically**:
- Detects current project root (git repository or working directory)
- Finds all Neovim instances for the current project
- Establishes connections with deterministic `connection_id`s
- Reports connection status and IDs
3. **Use connection-aware tools directly**:
- Server logs will show the `connection_id`s for connected instances
- Use tools like `list_buffers`, `buffer_diagnostics`, etc. with these IDs
- Access resources immediately without manual connection setup

Once both the MCP server and Neovim are running, here's a typical workflow:
#### Specific Target Mode

#### Using Unix Socket (Recommended)
For direct connection to a known target:

1. **Connect to specific target**:

```bash
# TCP connection
nvim-mcp --connect 127.0.0.1:6666

# Unix socket connection
nvim-mcp --connect /tmp/nvim.sock
```

2. **Server automatically connects and reports the `connection_id`**
3. **Use connection-aware tools with the reported ID**

#### Manual Connection Mode (Traditional)

For traditional discovery-based workflow:

1. **Discover available Neovim instances**:
- Use `get_targets` tool to list available socket paths
Expand All @@ -125,13 +176,6 @@ Once both the MCP server and Neovim are running, here's a typical workflow:
4. **Optional cleanup**:
- Use `disconnect` tool when completely done

#### Using TCP Connection

1. **Connect to TCP endpoint**:
- Use `connect_tcp` tool with address like "127.0.0.1:6666"
- Save the returned `connection_id`
2. **Follow steps 3-4 above** with your connection ID

## HTTP Server Transport

The server supports HTTP transport mode for web-based integrations and
Expand Down
23 changes: 22 additions & 1 deletion docs/instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,20 @@ Connection-scoped diagnostic resources using `nvim-diagnostics://` scheme:

### Connection Workflow for LLMs

#### Automatic Connection (Recommended)

When the nvim-mcp server is started with `--connect auto`, connections are
established automatically:

1. **Pre-established Connections**: Server automatically discovers and connects
to project-associated Neovim instances
2. **Connection ID Retrieval**: Use the `nvim-connections://` resource to get
available `connection_id`s
3. **Direct Tool Usage**: Use connection-aware tools immediately with the
retrieved `connection_id`s

#### Manual Connection Workflow

1. **Discovery Phase**: Use `get_targets` to find available Neovim instances
2. **Connection Phase**: Use `connect` with a target from the discovery results
3. **Caching Phase**: Store the `connection_id` for reuse across multiple operations
Expand Down Expand Up @@ -378,9 +392,16 @@ Connection-scoped diagnostic resources using `nvim-diagnostics://` scheme:

### Integration Workflows

#### Automatic Connection Workflow

1. Start server with `nvim-mcp --connect auto`
2. Read nvim-connections:// resource to get available connection IDs
3. Use connection IDs directly with connection-aware tools
4. Server maintains all connections automatically

#### Diagnostic Analysis

1. Connect to Neovim instance (cache connection_id)
1. Connect to Neovim instance (cache connection_id) or use auto-connected IDs
2. Read workspace diagnostics resource
3. Group diagnostics by severity and file
4. Use buffer_diagnostics for detailed file analysis (reuse connection_id)
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ mod server;
#[cfg(test)]
pub mod test_utils;

pub use server::NeovimMcpServer;
pub use server::{
NeovimMcpServer,
core::{auto_connect_current_project_targets, auto_connect_single_target},
};

pub type Result<T> = std::result::Result<T, ServerError>;

Expand Down
79 changes: 76 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{path::PathBuf, sync::OnceLock};
use std::{path::PathBuf, str::FromStr, sync::OnceLock};

use clap::Parser;
use hyper_util::{
Expand All @@ -13,10 +13,10 @@ use rmcp::{
streamable_http_server::session::local::LocalSessionManager,
},
};
use tracing::{error, info};
use tracing::{error, info, warn};
use tracing_subscriber::EnvFilter;

use nvim_mcp::NeovimMcpServer;
use nvim_mcp::{NeovimMcpServer, auto_connect_current_project_targets, auto_connect_single_target};

static LONG_VERSION: OnceLock<String> = OnceLock::new();

Expand All @@ -40,6 +40,43 @@ fn long_version() -> &'static str {
.as_str()
}

#[derive(Clone, Debug)]
enum ConnectBehavior {
Manual,
Auto,
SpecificTarget(String),
}

impl FromStr for ConnectBehavior {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"manual" => Ok(ConnectBehavior::Manual),
"auto" => Ok(ConnectBehavior::Auto),
target => {
// Validate TCP address format
if target.parse::<std::net::SocketAddr>().is_ok() {
return Ok(ConnectBehavior::SpecificTarget(target.to_string()));
}

// Validate file path (socket/pipe)
let path = std::path::Path::new(target);
if path.is_absolute()
&& (path.exists() || path.parent().is_some_and(|p| p.exists()))
{
return Ok(ConnectBehavior::SpecificTarget(target.to_string()));
}

Err(format!(
"Invalid target: '{}'. Must be 'manual', 'auto', TCP address (e.g., '127.0.0.1:6666'), or absolute socket path",
target
))
}
}
}
}

#[derive(Parser)]
#[command(version, long_version=long_version(), about, long_about = None)]
struct Cli {
Expand All @@ -58,6 +95,10 @@ struct Cli {
/// HTTP server bind address (default: 127.0.0.1)
#[arg(long, default_value = "127.0.0.1")]
http_host: String,

/// Connection mode: 'manual', 'auto', or specific target (TCP address/socket path)
#[arg(long, default_value = "manual")]
connect: ConnectBehavior,
}

#[tokio::main]
Expand Down Expand Up @@ -102,6 +143,37 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("Starting nvim-mcp Neovim server");
let server = NeovimMcpServer::new();

// Handle connection mode
match cli.connect {
ConnectBehavior::Auto => {
match auto_connect_current_project_targets(&server).await {
Ok(connections) => {
if connections.is_empty() {
info!("No Neovim instances found for current project");
} else {
info!("Auto-connected to {} project instances", connections.len());
}
}
Err(failures) => {
warn!("Auto-connection failed for all {} targets", failures.len());
for (target, error) in &failures {
warn!(" {target}: {error}");
}
// Continue serving - manual connections still possible
}
}
}
ConnectBehavior::SpecificTarget(target) => {
match auto_connect_single_target(&server, &target).await {
Ok(id) => info!("Connected to specific target {} with ID {}", target, id),
Err(e) => return Err(format!("Failed to connect to {}: {}", target, e).into()),
}
}
ConnectBehavior::Manual => {
info!("Manual connection mode - use get_targets and connect tools");
}
}

if let Some(port) = cli.http_port {
// HTTP server mode
let addr = format!("{}:{}", cli.http_host, port);
Expand Down Expand Up @@ -134,6 +206,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
} else {
// Default stdio mode
info!("Starting Neovim server on stdio");
let service = server.serve(stdio()).await.inspect_err(|e| {
error!("Error starting Neovim server: {}", e);
})?;
Expand Down
Loading