simple-rail-mcp
# 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
Scored across 1 tool
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.
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.
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.
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.