From dcfc03bfe4f2ec2ce02b756f02ab408d19d75141 Mon Sep 17 00:00:00 2001 From: bux agent Date: Wed, 13 May 2026 23:45:57 +0000 Subject: [PATCH] Register Composio MCP for Codex agents --- agent/bootstrap.sh | 35 +++++++++++++++++++++++++++++------ agent/telegram_bot.py | 4 ++++ agent/test_telegram_bot.py | 33 +++++++++++++++++++++++++++++++++ install.sh | 25 +++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/agent/bootstrap.sh b/agent/bootstrap.sh index 4b67333..e9b0c98 100755 --- a/agent/bootstrap.sh +++ b/agent/bootstrap.sh @@ -131,19 +131,22 @@ install -d -o bux -g bux -m 0755 /var/lib/bux # Why MCP at all: cloud holds the platform's Composio API key plus every # integration the user OAuth'd via cloud.browser-use.com. Rather than # duplicating that ceremony on each box (Composio key, per-toolkit auth -# configs, OAuth callbacks, refresh-token storage), we point Claude Code +# configs, OAuth callbacks, refresh-token storage), we point the local CLIs # at a cloud-hosted MCP endpoint that proxies tool calls through with the # box's project_id as the Composio entity_id. Net effect: any toolkit the # user has connected on cloud (Gmail, Calendar, Slack, …) is automatically # available to the box agent as native tools — zero per-box setup. # -# Token rotation: BUX_BOX_TOKEN gets baked into ~/.claude.json by -# `claude mcp add` at registration time. If the cloud rotates the token, +# Token rotation: Claude stores the bearer header in ~/.claude.json at +# registration time, while Codex stores the env-var name in ~/.codex/config.toml +# and reads BUX_BOX_TOKEN when a turn starts. If the cloud rotates the token, # the next /update re-runs this section, which removes + re-adds the MCP -# server with the fresh token. Manual rotation: re-run bootstrap.sh. +# server with the fresh token/header shape. Manual rotation: re-run +# bootstrap.sh. # -# To disable: as the bux user, `claude mcp remove composio`. The next -# /update will re-add it unless this section is removed too. +# To disable: as the bux user, `claude mcp remove composio` and/or +# `codex mcp remove composio`. The next /update will re-add it unless this +# section is removed too. if [ -f /etc/bux/env ]; then # shellcheck disable=SC1091 . /etc/bux/env || true @@ -193,6 +196,26 @@ else fi fi +if [ -z "${BUX_BOX_TOKEN:-}" ]; then + echo "bootstrap: BUX_BOX_TOKEN not set; skipping Codex Composio MCP registration" >&2 +elif ! sudo -iu bux command -v codex >/dev/null 2>&1; then + echo "bootstrap: codex CLI not on PATH; skipping Codex Composio MCP registration" >&2 +else + # Codex keeps MCP configuration separately from Claude. Register the same + # cloud proxy here so `/codex` Agency workers can inspect connected context + # instead of seeing an empty MCP tool list while Claude has Composio. + sudo -iu bux codex mcp remove composio >/dev/null 2>&1 || true + sudo -iu bux codex mcp add composio \ + --url https://api.browser-use.com/cloud/composio/mcp \ + --bearer-token-env-var BUX_BOX_TOKEN >/dev/null || \ + echo "bootstrap: WARN failed to register Codex cloud Composio MCP server; continuing bootstrap" >&2 + if ! sudo -iu bux codex mcp list 2>/dev/null | grep -q '^composio[[:space:]]'; then + echo "bootstrap: WARN Codex composio MCP registration didn't take" >&2 + else + echo "bootstrap: registered Codex cloud Composio MCP server" + fi +fi + # --- login banner: live browser URL on each ssh login --------------------- if ! grep -q 'BU_BROWSER_LIVE_URL' /home/bux/.profile 2>/dev/null; then cat >> /home/bux/.profile <<'PROFILE' diff --git a/agent/telegram_bot.py b/agent/telegram_bot.py index 7210b77..327f020 100644 --- a/agent/telegram_bot.py +++ b/agent/telegram_bot.py @@ -3829,6 +3829,8 @@ def _build_env( the per-user `~/.npm-global/bin/codex` lookup, ~/.local/bin tools). - BU_* + BROWSER_USE_API_KEY + BUX_PROFILE_ID so claude can drive the browser without re-fetching credentials. + - BUX_BOX_TOKEN for Codex only, so Codex's Composio MCP server can + read its configured bearer-token env var at turn runtime. - OPENAI_* from /home/bux/.secrets/openai.env (codex needs this). - TG_CHAT_ID + TG_THREAD_ID so `tg-send` routes back to this forum topic when the agent shells out for background work. @@ -3852,6 +3854,8 @@ def _build_env( } if box_env.get("BROWSER_USE_API_KEY"): env["BROWSER_USE_API_KEY"] = box_env["BROWSER_USE_API_KEY"] + if agent == AGENT_CODEX and box_env.get("BUX_BOX_TOKEN"): + env["BUX_BOX_TOKEN"] = box_env["BUX_BOX_TOKEN"] if box_env.get("BUX_PROFILE_ID"): env["BUX_PROFILE_ID"] = box_env["BUX_PROFILE_ID"] env["BU_PROFILE_ID"] = box_env["BUX_PROFILE_ID"] diff --git a/agent/test_telegram_bot.py b/agent/test_telegram_bot.py index df5009c..ef17108 100644 --- a/agent/test_telegram_bot.py +++ b/agent/test_telegram_bot.py @@ -2,6 +2,7 @@ import json import sys +import tempfile import unittest from pathlib import Path from unittest import mock @@ -90,6 +91,38 @@ def test_auth_and_quota_errors_trigger_login_picker_detection(self) -> None: self.assertTrue(telegram_bot._is_codex_auth_error("usage limit reached")) +class AgentEnvTest(unittest.TestCase): + def test_codex_gets_box_token_for_mcp_but_claude_does_not(self) -> None: + with tempfile.TemporaryDirectory() as td: + root = Path(td) + box_env = root / "box.env" + browser_env = root / "browser.env" + openai_env = root / "openai.env" + box_env.write_text( + "BROWSER_USE_API_KEY=bu_test\n" + "BUX_PROFILE_ID=profile_123\n" + "BUX_BOX_TOKEN=box_secret\n" + ) + browser_env.write_text("") + openai_env.write_text("OPENAI_API_KEY=sk_test\n") + + bot = telegram_bot.Bot.__new__(telegram_bot.Bot) + bot.state = {"offset": 0, "agents": {}, "codex_settings": {}, "owners": {}} + + with ( + mock.patch.object(telegram_bot, "BOX_ENV", box_env), + mock.patch.object(telegram_bot, "BROWSER_ENV", browser_env), + mock.patch.object(telegram_bot, "OPENAI_ENV", openai_env), + ): + codex_env = bot._build_env((100, 55), telegram_bot.AGENT_CODEX) + claude_env = bot._build_env((100, 55), telegram_bot.AGENT_CLAUDE) + + self.assertEqual(codex_env["BUX_BOX_TOKEN"], "box_secret") + self.assertEqual(codex_env["BROWSER_USE_API_KEY"], "bu_test") + self.assertEqual(codex_env["OPENAI_API_KEY"], "sk_test") + self.assertNotIn("BUX_BOX_TOKEN", claude_env) + + class AgencyButtonPromptTest(unittest.TestCase): def test_custom_button_prompt_includes_card_context(self) -> None: prompt = telegram_bot._agency_build_custom_dispatch_prompt( diff --git a/install.sh b/install.sh index f4f5532..360307f 100755 --- a/install.sh +++ b/install.sh @@ -73,14 +73,17 @@ fi # --- collect config -------------------------------------------------------- BROWSER_USE_API_KEY="${BROWSER_USE_API_KEY:-}" BUX_PROFILE_ID="${BUX_PROFILE_ID:-}" +BUX_BOX_TOKEN="${BUX_BOX_TOKEN:-}" # If /etc/bux/env already exists (rerun), seed missing values from it so the # script is truly idempotent without making the user re-type secrets. -if [ -z "$BROWSER_USE_API_KEY" ] && [ -r /etc/bux/env ]; then +if [ -r /etc/bux/env ]; then # shellcheck disable=SC1091 - BROWSER_USE_API_KEY="$(. /etc/bux/env && printf %s "${BROWSER_USE_API_KEY:-}")" + BROWSER_USE_API_KEY="${BROWSER_USE_API_KEY:-$(. /etc/bux/env && printf %s "${BROWSER_USE_API_KEY:-}")}" # shellcheck disable=SC1091 BUX_PROFILE_ID="${BUX_PROFILE_ID:-$(. /etc/bux/env && printf %s "${BUX_PROFILE_ID:-}")}" + # shellcheck disable=SC1091 + BUX_BOX_TOKEN="${BUX_BOX_TOKEN:-$(. /etc/bux/env && printf %s "${BUX_BOX_TOKEN:-}")}" fi if [ -z "$BROWSER_USE_API_KEY" ] && [ -t 0 ]; then @@ -483,6 +486,9 @@ if [ ! -f /etc/bux/env ]; then BROWSER_USE_API_KEY=$BROWSER_USE_API_KEY BUX_PROFILE_ID=$BUX_PROFILE_ID EOF + if [ -n "$BUX_BOX_TOKEN" ]; then + printf 'BUX_BOX_TOKEN=%s\n' "$BUX_BOX_TOKEN" >> /etc/bux/env + fi chmod 640 /etc/bux/env chown root:bux /etc/bux/env else @@ -545,6 +551,21 @@ if ! sudo -iu bux command -v codex >/dev/null 2>&1; then || warn 'codex install failed (non-fatal — /codex login will hint how to install later)' fi +# Codex keeps MCP configuration separately from Claude. Register the same +# cloud Composio proxy that bootstrap.sh registers for Claude so `/codex` +# Agency workers can inspect connected Gmail / Slack / GitHub context. +if [ -z "${BUX_BOX_TOKEN:-}" ]; then + warn 'BUX_BOX_TOKEN not set; skipping Codex Composio MCP registration' +elif sudo -iu bux command -v codex >/dev/null 2>&1; then + sudo -iu bux codex mcp remove composio >/dev/null 2>&1 || true + sudo -iu bux codex mcp add composio \ + --url https://api.browser-use.com/cloud/composio/mcp \ + --bearer-token-env-var BUX_BOX_TOKEN >/dev/null \ + || warn 'failed to register Codex cloud Composio MCP server' +else + warn 'codex CLI not on PATH; skipping Codex Composio MCP registration' +fi + # --- login banner: print live browser URL on each ssh login --------------- if ! grep -q 'BU_BROWSER_LIVE_URL' /home/bux/.profile 2>/dev/null; then cat >> /home/bux/.profile <<'PROFILE'