This project combines:
- opencode with a Playwright browser MCP server and recording API
- Odoo MCP (module + Python MCP server) for full CRUD access with granular permissions
- opencode — AI coding agent, served on port
4096 - Browser MCP server — Playwright-based browser automation with anti-bot spoofing, session recording, and sandboxed network access
- HTTP API — list, download, or delete session recordings on port
80 - SFTP access — browse the container filesystem via SSH on port
22 - Xvfb — virtual display so the browser runs headlessly inside the container
- Odoo MCP module — JSON endpoints with CRUD and per-model permissions
- Odoo MCP server — Python MCP server that calls the Odoo module endpoints
- Docker
opencode.json— your opencode config filemcp_server.py— the browser MCP server (included)- Odoo instance with addons path access
docker build -t opencode-browser-mcp .docker run -d \
-p 4096:4096 \
-p 80:80 \
-p 22:22 \
--name opencode \
opencode-browser-mcpOpen http://localhost:4096 in your browser.
- Copy
odoo_mcp/odoo_module/odoo_mcp_moduleinto your Odoo addons path. - Update your Apps list and install MCP Read API.
- (Recommended) Set a token in Odoo:
- Settings -> Technical -> Parameters -> System Parameters
- Key:
mcp.token - Value:
<your-secret>
- Create per-model MCP permissions in Odoo:
- Settings -> MCP -> Access
- Enable the models and operations you want to allow
- Default is deny for any model not listed
- Create an Odoo API key for the user that should be used by MCP
System parameters (optional):
mcp.require_auth(default 1) requires login+api_key on every requestmcp.default_deny(default 1) denies any model not listed in MCP Access
cd odoo_python_mcp_server
python -m pip install -r requirements.txt
set ODOO_BASE_URL=http://localhost:8069
set ODOO_DB=your_db_name
set ODOO_MCP_TOKEN=your-secret
set ODOO_LOGIN=your_odoo_login
set ODOO_API_KEY=your_odoo_api_key
python server.pyThe Docker image already includes odoo_python_mcp_server and reads its settings from env vars.
Start the container and pass Odoo credentials as secrets at runtime:
docker run -d \
-p 4096:4096 \
-p 80:80 \
-p 22:22 \
-e ODOO_BASE_URL=http://odoo:8069 \
-e ODOO_DB=your_db_name \
-e ODOO_MCP_TOKEN=your-secret \
-e ODOO_LOGIN=your_odoo_login \
-e ODOO_API_KEY=your_odoo_api_key \
--name opencode \
opencode-browser-mcp
## SFTP access
Connect with any SFTP client (FileZilla, WinSCP, etc.):
| Field | Value |
|----------|-------------|
| Protocol | SFTP |
| Host | `localhost` |
| Port | `22` |
| User | `root` |
| Password | `rootpass` |
> **Change the default password** before exposing this container publicly. Edit the `echo "root:rootpass" | chpasswd` line in the Dockerfile.
## Recording API
Browser sessions are automatically recorded as `.webm` files and stored in `/app/recordings` inside the container.
### List all recordings
GET http://localhost/recordings
Returns a list of all recording files, sorted newest first:
```json
{
"recordings": [
{ "filename": "session-abc123.webm", "size_bytes": 2345678 },
{ "filename": "session-def456.webm", "size_bytes": 1234567 }
],
"count": 2
}
GET http://localhost/recording/download/{filename}
Returns the named file encoded as base64:
{
"filename": "session-abc123.webm",
"size_bytes": 2345678,
"base64": "AAAA..."
}Save it locally:
curl http://localhost/recording/download/session-abc123.webm | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
open(data['filename'], 'wb').write(base64.b64decode(data['base64']))
print('Saved:', data['filename'])
"Or to download the newest recording in one shot, list first then download:
FILENAME=$(curl -s http://localhost/recordings | python3 -c "import sys,json; print(json.load(sys.stdin)['recordings'][0]['filename'])")
curl http://localhost/recording/download/$FILENAME | python3 -c "
import sys, json, base64
data = json.load(sys.stdin)
open(data['filename'], 'wb').write(base64.b64decode(data['base64']))
print('Saved:', data['filename'])
"DELETE http://localhost/recordings
{
"deleted": ["session-abc123.webm", "session-def456.webm"],
"count": 2
}| Variable | Default | Description |
|---|---|---|
MCP_HEADLESS |
false |
Run browser in headless mode |
MCP_VIDEO_DIR |
/app/recordings |
Directory where recordings are saved |
MCP_HTTP_PORT |
80 |
Port for the recording HTTP API |
ODOO_BASE_URL |
http://odoo:8069 |
Base URL of the Odoo server |
ODOO_DB |
your_db_name |
Odoo database name |
ODOO_MCP_TOKEN |
your-secret |
Shared MCP token (optional) |
ODOO_LOGIN |
your_odoo_login |
Odoo login for API access |
ODOO_API_KEY |
your_odoo_api_key |
Odoo API key |
| File | Description |
|---|---|
Dockerfile |
Container definition |
mcp_server.py |
Browser MCP server with HTTP API |
opencode.json |
opencode configuration (you provide) |
odoo_python_mcp_server |
Odoo MCP Python server |
- Playwright records sessions as
.webmfiles (a CDP limitation). Recordings are saved as-is — no transcoding required. - The browser blocks requests to
localhost,127.x,192.168.x,10.x, andfile://URLs as a sandbox policy. - Real Chrome is used if found at common paths to reduce bot detection. Falls back to Playwright's bundled Chromium otherwise.
- The Odoo module respects normal record rules and access rights.
- If
mcp.tokenis set, requests must include the token. - The Python MCP server forwards token/login/api_key automatically.