dockerMCP-ChatGPT
Docker MCP ChatGPT Sandbox is a self-hosted remote MCP server for ChatGPT that runs shell commands and edits code inside a hardened Ubuntu container, with OAuth via Keycloak and optional Cloudflare Tunnel.
README
Docker MCP ChatGPT Coding Runner
A self-hosted remote MCP server for ChatGPT that can run shell commands, edit code, install Ubuntu packages, use Git, and authenticate with GitHub CLI. OAuth is bundled with Keycloak. Cloudflare can expose the stack through either a zero-account Quick Tunnel for testing or a stable named tunnel for regular use.
What this reuses
This project avoids rebuilding mature infrastructure:
- Model Context Protocol Python SDK provides FastMCP, Streamable HTTP, OAuth protected-resource metadata, bearer-token middleware, and tool annotations.
- Keycloak provides OAuth/OIDC, login UI, authorization-code flow, PKCE, refresh tokens, and client management.
- Caddy provides reverse proxying so MCP and OAuth use one public hostname.
- Cloudflare Tunnel provides optional outbound-only public routes, including Quick Tunnels and named tunnels.
- Ubuntu 24.04 supplies Bash, Python, Node.js, Git, GitHub CLI (
gh), compilers, ripgrep, fd, jq, and curl.
There was no existing application code in this repository to preserve; the original repository contained only an empty README file.
Architecture
ChatGPT
|
| HTTPS / OAuth
v
Cloudflare Quick Tunnel or named tunnel
|
v
Caddy gateway :8080
|-- /mcp and RFC 9728 metadata --> non-root FastMCP service
| |
| | Unix domain socket only
| v
| Ubuntu command runner
| - root user
| - writable container filesystem
| - outbound internet access
| - Git and GitHub CLI
| - persistent /workspace and /root
|
`-- /realms/mcp/* --> Keycloak --> PostgreSQL
The public MCP service and privileged command runner are separate containers. The MCP service remains non-root, read-only, capability-dropped, and connected to the runner only through a Unix socket. Commands requested through MCP execute as root in the runner container.
Exposed MCP tools
sandbox_info: report root, network, filesystem, GitHub CLI, and execution-limit state.run_command: execute a root Bash command starting under/workspace.read_file: read a bounded text file from/workspace.write_file: create or explicitly overwrite a text file under/workspace.list_files: list a bounded directory tree without following symlinks.
The dedicated file tools remain confined to /workspace. The run_command tool is intentionally not confined to workspace file access because root package installation requires access to the container filesystem.
Included development tools
The image includes:
bash, build-essential, curl, fd, git, gh, jq,
nodejs, npm, python3, pip, venv, ripgrep
Check them from the runner:
docker compose exec runner git --version
docker compose exec runner gh --version
Requirements
- Ubuntu host with Docker Engine and Docker Compose v2.
- A ChatGPT plan or workspace that supports custom remote MCP apps.
- For a stable deployment: a public HTTPS hostname managed by Cloudflare or another reverse proxy.
- For temporary testing: no Cloudflare account or domain is required when using the Quick Tunnel helper.
Initial setup
Copy the environment template:
cp .env.example .env
Generate independent secrets and put them in .env:
openssl rand -hex 32
At minimum, replace POSTGRES_PASSWORD, KEYCLOAK_ADMIN_PASSWORD, OAUTH_CLIENT_SECRET, and MCP_USER_PASSWORD.
For a stable deployment, set PUBLIC_BASE_URL to the final HTTPS origin:
PUBLIC_BASE_URL=https://mcp.example.com
Do not include /mcp in PUBLIC_BASE_URL.
For Quick Tunnel testing, the initial value can remain a placeholder. scripts/quick-tunnel.sh replaces it with the generated trycloudflare.com origin and recreates the URL-sensitive services.
Obtain the ChatGPT callback URL
While creating the custom app in ChatGPT, select OAuth and copy the exact callback URL shown by ChatGPT. It looks similar to:
https://chatgpt.com/connector/oauth/<callback_id>
Set the full value as CHATGPT_CALLBACK_URL. Do not replace the callback ID, add a trailing slash, or use a wildcard.
The OAuth values entered in ChatGPT must match .env:
MCP endpoint: https://PUBLIC_HOST/mcp
Client ID: value of OAUTH_CLIENT_ID
Client secret: value of OAUTH_CLIENT_SECRET
Scopes: openid profile email offline_access mcp:tools
The authorization-server issuer is:
https://PUBLIC_HOST/realms/mcp
The initial realm user is configured by MCP_USER and MCP_USER_PASSWORD. Keycloak forces a password change on first login.
Run locally without Cloudflare Tunnel
docker compose up -d --build
Endpoints:
MCP: http://localhost:8080/mcp
Health: http://localhost:8080/health
Keycloak admin: http://localhost:8081/admin/
OAuth issuer URLs must match the token issuer exactly. For a ChatGPT connection, use the public HTTPS origin rather than the local URL.
Quick Tunnel: no Cloudflare account or token
Cloudflare Quick Tunnels create a random https://*.trycloudflare.com URL without a Cloudflare account, domain, DNS record, or tunnel token. They are intended only for development and testing.
After setting the required secrets and ChatGPT callback URL in .env, run:
bash scripts/quick-tunnel.sh
The helper performs the complete bootstrap sequence:
- builds and starts the local application;
- recreates the
cloudflared-quickcontainer to obtain a fresh hostname; - reads the generated URL from the Cloudflare logs;
- writes that origin to
PUBLIC_BASE_URLin.env; - recreates Keycloak, MCP, and Caddy so OAuth metadata and hostname validation use the new URL;
- prints the MCP endpoint and OAuth issuer.
Example output:
Public origin: https://random-words.trycloudflare.com
MCP endpoint: https://random-words.trycloudflare.com/mcp
OAuth issuer: https://random-words.trycloudflare.com/realms/mcp
Enter the printed MCP endpoint in ChatGPT.
The Quick Tunnel can also be started directly, but direct startup does not automatically update PUBLIC_BASE_URL:
docker compose --profile quick-tunnel up -d --build
docker compose logs -f cloudflared-quick
Important limitations:
- A new Quick Tunnel process receives a new random hostname.
- Rerunning the helper requires updating the MCP endpoint in ChatGPT.
- Quick Tunnels are testing infrastructure, not a production service.
- Cloudflare documents a 200-concurrent-request limit and no Server-Sent Events support for Quick Tunnels.
- This MCP server uses stateless JSON responses rather than SSE, but the temporary hostname and testing-only service limits still apply.
- Do not enable the
tunnelandquick-tunnelprofiles at the same time.
Use a named tunnel when the ChatGPT connector must survive container restarts without reconfiguration.
Stable named Cloudflare Tunnel
Create a named tunnel in Cloudflare Zero Trust and configure one public hostname:
Hostname: mcp.example.com
Service: http://gateway:8080
Copy the tunnel token into CLOUDFLARE_TUNNEL_TOKEN, then start the profile:
docker compose --profile tunnel up -d --build
Without --profile tunnel, the named cloudflared container does not start.
The tunnel does not replace OAuth. It only publishes the gateway through an outbound connection. Do not put another interactive login in front of this hostname unless ChatGPT can satisfy it; Keycloak already protects MCP.
Install software at runtime
The runner executes as root, has outbound network access, and has a writable root filesystem. Install packages from the host:
docker compose exec runner bash -lc \
'apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y shellcheck'
The same command can be invoked through the MCP run_command tool:
apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y shellcheck
Runtime-installed packages remain while the current runner container exists, but disappear when Docker recreates that container. For permanent dependencies, add them to the Dockerfile and rebuild:
docker compose up -d --build --force-recreate runner mcp
GitHub login
The official GitHub CLI is installed as gh. Authenticate interactively from the host terminal rather than sending credentials through ChatGPT:
docker compose exec runner gh auth login \
--hostname github.com \
--git-protocol https \
--web
Then configure Git to use GitHub CLI as its credential helper:
docker compose exec runner gh auth setup-git
docker compose exec runner gh auth status
For a headless token login, keep the token out of shell history:
printf '%s' "$GH_TOKEN" | docker compose exec -T runner gh auth login --with-token
docker compose exec runner gh auth setup-git
GitHub CLI configuration, Git configuration, and SSH material under /root persist in the runner_home Docker volume. The workspace persists separately in the workspace volume.
After authentication, commands can use both tools:
docker compose exec runner git clone https://github.com/OWNER/REPOSITORY.git /workspace/REPOSITORY
docker compose exec runner gh repo view OWNER/REPOSITORY
Workspace management
Import a project:
docker compose cp ./my-project/. runner:/workspace/
Export it:
docker compose cp runner:/workspace/. ./workspace-export
Open a root shell:
docker compose exec runner bash
OAuth and realm changes
Keycloak imports the realm only when the mcp realm does not already exist. Changing CHATGPT_CALLBACK_URL, the OAuth client secret, or the bootstrap user in .env does not rewrite an existing realm.
Changing PUBLIC_BASE_URL does take effect after recreating Keycloak and MCP. The Quick Tunnel helper performs that recreation automatically.
For other existing-realm changes, update the client in the local Keycloak admin console. During disposable development, stop the stack and remove only the PostgreSQL data volume to re-import the realm. Do not remove workspace or runner_home unless you intend to delete source files or GitHub credentials.
Remaining isolation
- The public MCP service is non-root and read-only.
- MCP and runner are separate processes in separate containers.
- The runner has no Docker socket.
- The runner is not attached to the internal Keycloak/PostgreSQL network.
- The runner reaches MCP only through a shared Unix socket.
- CPU, memory, PID, file-descriptor, output-size, file-size, and timeout limits remain enabled.
- Workspace path traversal and symlink escape checks remain enabled for dedicated file tools.
- OAuth access-token signature, issuer, audience, expiry, and scope validation remain enabled.
- Keycloak admin endpoints are blocked from the public gateway and bound separately to loopback.
- Both Cloudflare tunnel containers remain disabled unless their profile or helper is selected.
Critical security warning
This configuration gives an OAuth-authenticated LLM a remote root shell with outbound internet access. That is not a strong sandbox. A malicious instruction, prompt injection, compromised dependency, or stolen OAuth session can:
- modify the runner operating system;
- read persisted GitHub credentials;
- push code or change repositories using your GitHub identity;
- download and execute arbitrary software;
- exfiltrate workspace data;
- attack services reachable through the host or internet.
Use a dedicated low-privilege GitHub account or narrowly scoped token. Do not mount the Docker socket, host filesystem, personal SSH agent, cloud credentials, or production secrets. Keep ChatGPT action confirmations enabled.
Docker containers share the host kernel. For hostile or multi-user workloads, use gVisor, Kata Containers, or a Firecracker-style microVM and isolate the runner on a separate machine or disposable VM.
Useful commands
# Start a fresh Quick Tunnel and update OAuth URLs
bash scripts/quick-tunnel.sh
# Show status
docker compose ps
# Follow application logs
docker compose logs -f mcp runner keycloak gateway
# Follow Quick Tunnel logs
docker compose --profile quick-tunnel logs -f cloudflared-quick
# Check privilege and tools
docker compose exec runner bash -lc 'id && git --version && gh --version'
# Stop services without deleting data
docker compose down
# Stop all services including profile containers
docker compose --profile tunnel --profile quick-tunnel down
# Rebuild after source or Dockerfile changes
docker compose up -d --build
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。