Skip to content

Commit f446ea9

Browse files
committed
Add MCP server for Claude remote control of ViperIDE
MCP (Model Context Protocol) server that lets Claude Code or Claude Desktop programmatically control ViperIDE while the user interacts with it in the browser simultaneously. Core: the MCP server serves built ViperIDE on localhost, opens the browser on first tool call, and bridges MCP tool calls over WebSocket to a control client injected into the IDE. 23 MCP tools expose device connection, file operations, editor control, code execution, terminal I/O, and package management. Serial bridge: Node.js opens USB serial ports directly and relays data over WebSocket, faking a WebREPL handshake so ViperIDE's existing transport works unchanged. Bypasses browser WebSerial permission picker entirely. Distribution: build-dist.sh produces per-platform .tar.gz and .mcpb (Claude Desktop Extension) bundles with pre-built ViperIDE assets. build-npm.sh stages a cross-platform npm package. CI workflow builds all three platforms and publishes to npm + GitHub Releases on tag. Also increases the WebREPL password prompt timeout from 1s to 5s for slow embedded WiFi stacks.
1 parent 55830f2 commit f446ea9

19 files changed

Lines changed: 2187 additions & 2 deletions

.github/workflows/mcp-dist.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
name: Build and publish MCP distributions
2+
3+
on:
4+
push:
5+
tags: ['mcp-v*']
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
id-token: write
11+
12+
env:
13+
# Extract version from tag (mcp-v0.5.2-mcp.1 -> 0.5.2-mcp.1), or use package.json for manual runs
14+
MCP_VERSION: ${{ startsWith(github.ref, 'refs/tags/mcp-v') && github.ref_name || '' }}
15+
16+
jobs:
17+
build-viperide:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- uses: actions/checkout@v4
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: '22'
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.x'
27+
- run: pip install requests
28+
- run: npm install && python3 build.py
29+
- uses: actions/upload-artifact@v4
30+
with:
31+
name: viperide-build
32+
path: build/
33+
34+
build-dist:
35+
needs: build-viperide
36+
strategy:
37+
matrix:
38+
include:
39+
- os: ubuntu-latest
40+
platform: linux
41+
- os: macos-latest
42+
platform: darwin
43+
- os: windows-latest
44+
platform: win32
45+
runs-on: ${{ matrix.os }}
46+
steps:
47+
- uses: actions/checkout@v4
48+
- uses: actions/setup-node@v4
49+
with:
50+
node-version: '22'
51+
- uses: actions/download-artifact@v4
52+
with:
53+
name: viperide-build
54+
path: build/
55+
- name: Set version from tag
56+
if: env.MCP_VERSION != ''
57+
working-directory: mcp
58+
shell: bash
59+
run: |
60+
VERSION="${MCP_VERSION#mcp-v}"
61+
node -e "const p=require('./package.json'); p.version='$VERSION'; require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\n')"
62+
- name: Build distribution
63+
working-directory: mcp
64+
shell: bash
65+
run: bash build-dist.sh ${{ matrix.platform }}
66+
- uses: actions/upload-artifact@v4
67+
with:
68+
name: dist-${{ matrix.platform }}
69+
path: |
70+
mcp/dist/*.tar.gz
71+
mcp/dist/*.mcpb
72+
73+
build-npm:
74+
needs: build-viperide
75+
runs-on: ubuntu-latest
76+
steps:
77+
- uses: actions/checkout@v4
78+
- uses: actions/setup-node@v4
79+
with:
80+
node-version: '22'
81+
- uses: actions/download-artifact@v4
82+
with:
83+
name: viperide-build
84+
path: build/
85+
- name: Set version from tag
86+
if: env.MCP_VERSION != ''
87+
working-directory: mcp
88+
run: |
89+
VERSION="${MCP_VERSION#mcp-v}"
90+
node -e "const p=require('./package.json'); p.version='$VERSION'; require('fs').writeFileSync('package.json',JSON.stringify(p,null,2)+'\n')"
91+
- name: Build npm package
92+
working-directory: mcp
93+
run: bash build-npm.sh
94+
- uses: actions/upload-artifact@v4
95+
with:
96+
name: npm-package
97+
path: mcp/dist/npm-staging/
98+
99+
publish-npm:
100+
needs: build-npm
101+
if: startsWith(github.ref, 'refs/tags/mcp-v')
102+
runs-on: ubuntu-latest
103+
permissions:
104+
id-token: write
105+
contents: read
106+
steps:
107+
- uses: actions/download-artifact@v4
108+
with:
109+
name: npm-package
110+
path: package/
111+
- uses: actions/setup-node@v4
112+
with:
113+
node-version: '24'
114+
registry-url: 'https://registry.npmjs.org'
115+
# Uses NPM_TOKEN for initial publish. To switch to trusted publishing:
116+
# 1. Configure OIDC at npmjs.com/package/@andrewleech/viperide-mcp/access
117+
# 2. Delete the NPM_TOKEN secret from GitHub repo settings
118+
# 3. Remove the NODE_AUTH_TOKEN env line below
119+
- name: Publish to npm
120+
working-directory: package
121+
run: |
122+
VERSION=$(node -p "require('./package.json').version")
123+
TAG="latest"
124+
if echo "$VERSION" | grep -q '-'; then TAG="next"; fi
125+
npm publish --access public --provenance --tag "$TAG"
126+
env:
127+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
128+
129+
publish-release:
130+
needs: build-dist
131+
if: startsWith(github.ref, 'refs/tags/mcp-v')
132+
runs-on: ubuntu-latest
133+
steps:
134+
- uses: actions/download-artifact@v4
135+
with:
136+
path: artifacts/
137+
merge-multiple: false
138+
- name: Collect release files
139+
run: |
140+
mkdir release
141+
find artifacts/dist-* -type f \( -name '*.tar.gz' -o -name '*.mcpb' \) -exec cp {} release/ \;
142+
ls -lh release/
143+
- name: Create GitHub Release
144+
uses: softprops/action-gh-release@v2
145+
with:
146+
files: release/*
147+
generate_release_notes: true

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules/
44
.parcel-cache/
55
extra/
66
src/tools_vfs/lib/python_minifier/
7+
bun.lock

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import globals from "globals"
22
import pluginJs from "@eslint/js"
33

44
export default [
5-
{ ignores: ["build/", "src/websocket_relay.js"] },
5+
{ ignores: ["build/", "src/websocket_relay.js", "mcp/"] },
66
{ languageOptions: { globals: globals.browser }},
77
pluginJs.configs.recommended,
88
{

mcp/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
bun.lock
3+
package-lock.json
4+
dist/

mcp/.mcpbignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
test/
2+
bun.lock
3+
package-lock.json
4+
install.sh
5+
build-mcpb.sh
6+
.mcpbignore
7+
.gitignore
8+
README.md

mcp/README.md

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# ViperIDE MCP Server
2+
3+
An MCP (Model Context Protocol) server that gives Claude full remote control of [ViperIDE](https://viper-ide.org), a MicroPython/CircuitPython IDE. The IDE opens in your browser for direct interaction while Claude simultaneously controls it via MCP tools.
4+
5+
## Architecture
6+
7+
The MCP server acts as a bridge between Claude and ViperIDE running in your browser. It also provides a serial-to-WebSocket bridge so USB devices can be connected without browser permission dialogs.
8+
9+
| Component | Role | Connection |
10+
|---|---|---|
11+
| **Claude** (Code/Desktop) | Sends MCP tool calls | stdio JSON-RPC to MCP server |
12+
| **MCP Server** (Node.js) | Routes commands, serves IDE, bridges serial | localhost HTTP + WebSocket |
13+
| **Browser** (ViperIDE) | IDE UI, executes commands on device | WS control channel (`/ws`) |
14+
| **USB Device** (MicroPython) | Target hardware | Serial port via WS bridge (`/serial/...`) |
15+
16+
The MCP server:
17+
1. Serves the built ViperIDE on a random localhost port
18+
2. Opens your default browser to the IDE
19+
3. Maintains a WebSocket control channel for sending commands
20+
4. Exposes IDE operations as MCP tools that Claude can call
21+
22+
Both you and Claude can interact with the IDE simultaneously.
23+
24+
## Install
25+
26+
### Claude Desktop
27+
28+
Download the `.mcpb` bundle for your platform from [Releases](https://github.com/andrewleech/ViperIDE/releases). In Claude Desktop, go to Settings > Extensions > Advanced Settings > Install Extension, then select the `.mcpb` file.
29+
30+
### Claude Code
31+
32+
```bash
33+
claude mcp add viperIDE -- npx -y @andrewleech/viperide-mcp
34+
```
35+
36+
### From source
37+
38+
```bash
39+
git clone https://github.com/andrewleech/ViperIDE.git
40+
cd ViperIDE && npm install && python3 build.py
41+
cd mcp && npm install
42+
claude mcp add viperIDE -- node $(pwd)/src/index.js
43+
```
44+
45+
## Available Tools
46+
47+
### Connection
48+
| Tool | Description |
49+
|---|---|
50+
| `viperIDE_get_status` | Connection status, device info, open file, editor state |
51+
| `viperIDE_connect_device` | Connect via WebSocket (`ws`), virtual device (`vm`), or prompt user for USB/BLE |
52+
| `viperIDE_connect_serial` | Connect to a USB serial MicroPython device via the local WebSocket bridge |
53+
| `viperIDE_list_serial_ports` | List available USB serial ports with device details |
54+
| `viperIDE_disconnect_device` | Disconnect current device |
55+
56+
### File Operations (on device)
57+
| Tool | Description |
58+
|---|---|
59+
| `viperIDE_list_files` | List all files and directories with sizes |
60+
| `viperIDE_read_file` | Read file content |
61+
| `viperIDE_write_file` | Write content to file (creates parent dirs) |
62+
| `viperIDE_delete_file` | Delete a file |
63+
| `viperIDE_delete_dir` | Delete a directory |
64+
| `viperIDE_mkdir` | Create a directory |
65+
| `viperIDE_create_file` | Create a new file and open it in the editor |
66+
67+
### Editor
68+
| Tool | Description |
69+
|---|---|
70+
| `viperIDE_open_file` | Open a device file in the editor |
71+
| `viperIDE_get_editor` | Get current editor content and filename |
72+
| `viperIDE_set_editor` | Replace editor content |
73+
| `viperIDE_save_file` | Save editor content to device |
74+
75+
### Execution
76+
| Tool | Description |
77+
|---|---|
78+
| `viperIDE_run_file` | Run current file on device (non-blocking) |
79+
| `viperIDE_stop` | Interrupt running script (Ctrl-C) |
80+
| `viperIDE_reboot` | Reboot device (soft/hard/bootloader) |
81+
82+
### Terminal
83+
| Tool | Description |
84+
|---|---|
85+
| `viperIDE_read_terminal` | Read REPL output |
86+
| `viperIDE_write_terminal` | Send input to REPL |
87+
| `viperIDE_clear_terminal` | Clear terminal |
88+
89+
### Packages
90+
| Tool | Description |
91+
|---|---|
92+
| `viperIDE_install_package` | Install a MicroPython package by name or URL |
93+
94+
## USB Serial Bridge
95+
96+
The MCP server includes a serial-to-WebSocket bridge that lets Claude connect directly to USB serial MicroPython devices without requiring the browser's WebSerial permission picker.
97+
98+
1. Use `viperIDE_list_serial_ports` to discover connected devices
99+
2. Use `viperIDE_connect_serial` with the device path to connect
100+
101+
The bridge opens the serial port at 115200 baud and fakes a WebREPL handshake so ViperIDE's existing WebSocket transport works unchanged. Multiple devices can be bridged simultaneously.
102+
103+
Requires the `serialport` npm package (installed automatically with `npm install`).
104+
105+
## Limitations
106+
107+
- **Bluetooth connections** require the user to click the connect button in the browser (browser security policy requires a user gesture for device picker dialogs).
108+
- **USB connections** can be made via the serial bridge (`viperIDE_connect_serial`) without user interaction, or via the browser's WebSerial API which requires a user gesture.
109+
- **WebSocket and VM connections** can be initiated fully by Claude.
110+
- Running scripts is fire-and-forget. Use `read_terminal` to monitor output and `stop` to interrupt.
111+
- Only one browser tab should be connected to the MCP server at a time.
112+
113+
## Development
114+
115+
During development, you can run the MCP server directly:
116+
117+
```bash
118+
# Build ViperIDE first
119+
npm install && python3 build.py
120+
121+
# Install MCP deps
122+
cd mcp && npm install
123+
124+
# Run the server (logs to stderr, MCP protocol on stdio)
125+
node mcp/src/index.js
126+
```
127+
128+
The server picks a random port and opens the browser automatically.

0 commit comments

Comments
 (0)