This repo runs a ChatGPT-compatible remote MCP server with two runtime modes:
- Docker mode: the original Windows + Docker Desktop workflow.
- Host mode: direct host execution for Termux, Linux, and macOS without Docker.
Host mode is the default on Termux/Linux/macOS. Docker mode remains the default on Windows.
src/server.js: MCP server exposed over Streamable HTTPsrc/runtime.js: runtime selector for Docker vs host modesrc/docker-runtime.js: original Docker-backed devbox runtimesrc/host-runtime.js: host-backed runtime for Termux/Linux/macOSsrc/host-tools.js: host shell + allowed-program execution helperssrc/launcher.js: local service launcher helpersbin/devbox.js: installabledevboxcommandruntime.Dockerfile: reproducible Linux devbox image for Docker modescripts/Start-ChatGptDevboxMcp.ps1: Windows/Docker startup flowscripts/Stop-ChatGptDevboxMcp.ps1: Windows/Docker shutdown flow
Use host mode when Docker is unavailable or unnecessary.
Supported today:
- Termux on Android
- Linux
- macOS
Behavior:
- shell/file operations run directly on the host
devbox_exec_readonlyis best-effort in host mode; it is not container-sandboxed- host tools are exposed through
host_*MCP tools - legacy
windows_host_*tool names remain as compatibility aliases
Use Docker mode when you want the original reproducible container workflow.
Behavior:
- devbox shell/file tools run inside the Docker runtime container
- read-only shell commands still use disposable read-only containers
- host tools remain explicit and separate from the devbox runtime
- Node.js
- npm
- Git
- optional but useful:
gh,python3,ripgrep,curl
- Windows
- Docker Desktop
- Node.js
- PowerShell
- Git
- optional: GitHub CLI, Cloudflare Tunnel, Cloudflare Access
git clone https://github.com/adybag14-cyber/devbox.git
cd devbox
cp .env.example .env
npm installImportant .env values:
DEVBOX_RUNTIME_MODE=auto|host|dockerHOST_WORKSPACE_PATHHOST_DEFAULT_WORKDIRHOST_SHELLHOST_PROGRAM_ALLOWLISTPUBLIC_BASE_URLwhen using OAuth modesENABLE_GATEWAY_BRIDGE=true|falseto allow ChatGPT Web / browser clients to call the local open server fromhttps://chatgpt.comGATEWAY_BRIDGE_ORIGINS=https://chatgpt.com,https://chat.openai.com
Full notes: docs/TERMUX.md
pkg install nodejs git ripgrep python curl
cd ~/devbox
cp .env.example .env
npm install
npm link
devboxThat starts the MCP service in the background and prints the local URL plus log path.
Useful commands:
devbox status
devbox restart
devbox stop
devbox rundevbox run keeps the server in the foreground. Plain devbox behaves like devbox start.
Full notes: docs/HOST_COMPATIBILITY.md
cd /path/to/devbox
cp .env.example .env
npm install
npm link
DEVBOX_RUNTIME_MODE=host devboxIf you want the old container workflow on Linux/macOS, set:
DEVBOX_RUNTIME_MODE=dockergit clone https://github.com/adybag14-cyber/devbox.git
cd .\devbox
Copy-Item .env.example .env
npm install
.\scripts\Start-ChatGptDevboxMcp.ps1After the service is up, inspect:
curl http://127.0.0.1:8100/Typical local values:
Name:Devbox MCPMCP Server URL:http://127.0.0.1:8100/mcplocally, or your public base URL when exposedAuthentication:none,demo-oauth, orcloudflare-accessgateway_bridge.enabled:truefor local open-server requests when browser bridging is enabled
When MCP_AUTH_MODE=none, local loopback requests automatically expose a browser bridge for ChatGPT Web origins.
That bridge:
- returns
mcp_urlfor local requests even whenPUBLIC_BASE_URLis blank - answers CORS preflights for configured
GATEWAY_BRIDGE_ORIGINS - allows secure-page browser clients to call the local open MCP server on loopback
Default allowed origins:
https://chatgpt.comhttps://chat.openai.com
You can disable or customize it with:
ENABLE_GATEWAY_BRIDGE=false
GATEWAY_BRIDGE_ORIGINS=https://chatgpt.com,https://chat.openai.comIf you expose the service publicly, set:
PUBLIC_BASE_URLMCP_AUTH_MODE=demo-oauthorMCP_AUTH_MODE=cloudflare-access
Cloudflare Access details are still documented by the existing helper scripts and Windows docs.
host_execis high risk because it provides direct host shell access.host_run_programis limited byHOST_PROGRAM_ALLOWLIST.windows_host_*tools remain for compatibility, but on non-Windows hosts they route to the generic host implementation.- Do not commit
.env,run/,workspace/, or other live runtime state.
npm test
node bin/devbox.js start
curl http://127.0.0.1:8100/healthz
node bin/devbox.js stopDO NOT DELETED.."!
let lastClicked = null;
setInterval(() => {
const btn = [...document.querySelectorAll('button')].find(b => {
const rect = b.getBoundingClientRect();
const bg = getComputedStyle(b).backgroundColor;
const sizeMatch =
rect.width >= 195 && rect.width <= 199 &&
rect.height >= 35 && rect.height <= 37;
const colorMatch =
bg === 'rgb(13, 13, 13)' ||
bg === 'rgb(0, 0, 0)';
return sizeMatch && colorMatch ;
});
if (btn && btn !== lastClicked) {
lastClicked = btn;
console.log('Clicking:', btn.innerText.trim(), btn.getBoundingClientRect());
btn.click();
}
}, 1000)
If you are lazy to type continue and press enter here's another console script for you.
(function() {
function getMainBoxAndButton() {
// Find the first visible contenteditable box
const box = Array.from(document.querySelectorAll('[contenteditable="true"]'))
.find(el => el.offsetParent !== null); // only visible elements
// Try to find a send button within the same container
let sendBtn = null;
if (box) {
const container = box.closest('div');
if (container) {
sendBtn = container.querySelector('button, input[type="submit"]');
}
}
return { box, sendBtn };
}
function typeAndSend() {
const { box, sendBtn } = getMainBoxAndButton();
if (!box) {
console.warn('No visible typing box found!');
return;
}
// Focus the box
box.focus();
// Move cursor to the end
const sel = window.getSelection();
const range = document.createRange();
range.selectNodeContents(box);
range.collapse(false);
sel.removeAllRanges();
sel.addRange(range);
// Insert the exact phrase "continue "
document.execCommand('insertText', false, 'continue ');
// Click send if a button exists
if (sendBtn && !sendBtn.disabled) {
sendBtn.click();
} else {
// If no button, try simulating Enter key
const enterEvent = new KeyboardEvent('keydown', {
key: 'Enter',
code: 'Enter',
keyCode: 13,
which: 13,
bubbles: true
});
box.dispatchEvent(enterEvent);
}
}
// Run immediately
typeAndSend();
// Repeat every 2 minutes
setInterval(typeAndSend, 2 * 60 * 1000);
})();
and the auto continue script above too.