Skip to main content
Glama
global-mysterysnailrevolution

simple-rail-mcp

README.md
# Simple Rail MCP Server

MCP (Model Context Protocol) server for controlling linear rails via natural language through Claude AI in Cursor.

## What This Does

Control your linear rail with natural language:

```
You: "Move the rail to position 500mm"
Claude: ✅ Rail moved to 500mm

You: "Move rail to opentrons station"
Claude: ✅ Rail at opentrons station
```

## Supported Controllers

- ✅ **UW PICO 5.09** - HTTP API with G-code forwarding (pre-configured)
- ✅ Any HTTP API that accepts commands
- ✅ Serial port controllers
- ✅ Python script wrappers
- ✅ G-code senders

## Quick Start

### 1. Install

```bash
npm install
```

### 2. Configure

Edit `index.js` lines 32-37:

```javascript
// For UW PICO controller:
this.RAIL_IP = "192.168.1.101";      // Your controller IP
this.RAIL_PORT = "80";
this.RAIL_ENDPOINT = "/command";
this.COMMAND_TEMPLATE = "G0 X{position} F1000";
```

Set your positions (lines 41-47):

```javascript
this.POSITIONS = {
  "pickup_station": 0,
  "opentrons": 500,
  "plate_reader": 750,
  "storage": 1000,
  "home": 0
};
```

### 3. Add to Cursor

Edit `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "rail": {
      "command": "node",
      "args": ["/absolute/path/to/simple-rail-mcp/index.js"],
      "env": {
        "RAIL_IP": "192.168.1.101"
      }
    }
  }
}
```

### 4. Restart Cursor

### 5. Use!

```
"Move the rail to opentrons"
"Move rail to 500mm"
"Move to home position"
```

## Configuration Options

### For UW PICO 5.09 Controller

Your controller has an HTTP API that forwards G-code via UART.

**Test with curl:**
```bash
curl -X POST http://192.168.1.101/command \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "cmd=G0 X500 F1000"
```

**Config:**
```javascript
this.RAIL_IP = "192.168.1.101";
this.RAIL_PORT = "80";
this.RAIL_ENDPOINT = "/command";
this.COMMAND_TEMPLATE = "G0 X{position} F1000";
```

### For Other HTTP APIs

If your controller uses different endpoints/formats:

```javascript
this.RAIL_IP = "192.168.1.100";
this.RAIL_ENDPOINT = "/api/move";
this.COMMAND_TEMPLATE = '{"position": {position}}';  // JSON format
```

### Position Configuration

Edit the `POSITIONS` object to match your physical setup:

```javascript
this.POSITIONS = {
  "station_a": 0,        // First position (mm)
  "station_b": 250,      // Second position (mm)
  "station_c": 500,      // Third position (mm)
  "home": 0              // Home position
};
```

### Timing Configuration

Adjust wait time after movement (line 54):

```javascript
this.MOVE_WAIT_TIME = 5;  // Wait 5 seconds after sending command
```

If your rail is faster or slower, adjust accordingly.

## Environment Variables

You can override config with environment variables:

```json
{
  "mcpServers": {
    "rail": {
      "command": "node",
      "args": ["/path/to/index.js"],
      "env": {
        "RAIL_IP": "192.168.1.101",
        "RAIL_PORT": "80"
      }
    }
  }
}
```

## API Reference

### Tool: `move_rail_and_wait`

Move linear rail to a position and wait for completion.

**Parameters:**

- `position` (string) - Named position from `POSITIONS` config
  - Example: `"opentrons"`, `"pickup_station"`, `"home"`
- `position_number` (number) - Numeric position in mm
  - Example: `500`, `750.5`
- `wait_seconds` (number) - Override default wait time
  - Default: `MOVE_WAIT_TIME` from config

**Examples:**

```
// Via Claude in natural language:
"Move rail to opentrons"
"Move rail to 500mm"
"Move to home position"

// Direct tool call (from code):
move_rail_and_wait({ position: "opentrons" })
move_rail_and_wait({ position_number: 500 })
move_rail_and_wait({ position: "storage", wait_seconds: 10 })
```

## G-code Reference

Common G-code commands for linear rails:

| Command | Description |
|---------|-------------|
| `G0 X500 F1000` | Move to 500mm at 1000mm/min (fast) |
| `G1 X500 F500` | Move to 500mm at 500mm/min (controlled) |
| `G28 X` | Home X axis |
| `G90` | Set absolute positioning mode |
| `G91` | Set relative positioning mode |
| `G92 X0` | Set current position as zero |
| `M114` | Query current position |
| `M400` | Wait for all moves to complete |

## Troubleshooting

### Cannot connect to controller

1. **Check IP:**
   ```bash
   ping 192.168.1.101
   ```

2. **Test endpoint:**
   ```bash
   curl http://192.168.1.101/command
   ```

3. **Check firewall** - Allow Node.js/Cursor through firewall

### Rail doesn't move

1. **Test G-code manually:**
   ```bash
   curl -X POST http://192.168.1.101/command -d "cmd=G28 X"  # Home first
   curl -X POST http://192.168.1.101/command -d "cmd=G0 X500 F1000"
   ```

2. **Check positioning mode:**
   ```bash
   curl -X POST http://192.168.1.101/command -d "cmd=G90"  # Absolute mode
   ```

3. **Verify position is in range** - Check your rail's physical limits

### Wrong position

1. **Check units** - Make sure G21 (mm) is set:
   ```bash
   curl -X POST http://192.168.1.101/command -d "cmd=G21"
   ```

2. **Calibrate positions** - Measure actual distances and update `POSITIONS`

3. **Home the rail first:**
   ```bash
   curl -X POST http://192.168.1.101/command -d "cmd=G28 X"
   ```

### MCP server not loading in Cursor

1. **Test manually:**
   ```bash
   node index.js
   ```

2. **Check Node version:**
   ```bash
   node --version  # Should be ≥18.0.0
   ```

3. **Check path in `.cursor/mcp.json`** - Use absolute path

4. **Restart Cursor completely**

## Advanced: Custom Controller Support

To add support for other controller types, modify the `sendHttpCommand` method:

```javascript
async sendHttpCommand(position) {
  const gcode = this.COMMAND_TEMPLATE.replace('{position}', position);
  const url = `http://${this.RAIL_IP}:${this.RAIL_PORT}${this.RAIL_ENDPOINT}`;
  
  // Customize request format here
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'  // Change as needed
    },
    body: JSON.stringify({ command: gcode })  // Change format
  });
  
  // Customize response parsing here
  const data = await response.json();
  return data;
}
```

## Integration Example

Use with other MCP servers for full automation:

```javascript
// Example: Pick up plate and move to Opentrons
"Move rail to pickup station"  // This MCP server
"Run VLA pickup script"        // VLA MCP server
"Move rail to opentrons"       // This MCP server
"Upload purple_mixing.py"      // Opentrons MCP server
"Start the protocol"           // Opentrons MCP server
```

Claude AI automatically orchestrates all systems!

## Requirements

- **Node.js** ≥18.0.0
- **Cursor IDE** with MCP support
- **Linear rail** with HTTP/serial/script control

## License

MIT

## Contributing

Issues and pull requests welcome!

## Credits

- **Model Context Protocol** - Anthropic
- **Claude AI** - Natural language orchestration
- **Cursor IDE** - Development environment

---

**Questions?** Open an issue or check the [troubleshooting guide](#troubleshooting).

TDQS

B3.3/5.0

Scored across 1 tool

Disambiguation5/5

There is only one tool, so there is no possibility of confusing it with another tool in the set. Its purpose is clearly stated as moving a linear rail and waiting for completion.

Naming Consistency5/5

With a single tool, there is no inconsistency across names. The name move_rail_and_wait follows a readable snake_case verb_noun pattern and is descriptive.

Tool Count3/5

A single tool is borderline thin for a server, even one described as simple. If the intended scope is only command-based movement, it may suffice, but typical rail control often needs more operations.

Completeness2/5

The tool surface covers only moving and waiting, leaving significant gaps for a rail-control domain. Missing operations such as stop, home, get position, or set speed could cause agent failures in common workflows.