Skip to content

feat: provision the guest-browser MCP server for all agents - #223

Merged
yuga-hashimoto merged 1 commit into
mainfrom
feat/browser-mcp-provision
Aug 5, 2026
Merged

feat: provision the guest-browser MCP server for all agents#223
yuga-hashimoto merged 1 commit into
mainfrom
feat/browser-mcp-provision

Conversation

@yuga-hashimoto

Copy link
Copy Markdown
Owner

Seeds /usr/local/bin/andcode-browser-mcp.py (stdlib-only MCP server) into the Alpine and Antigravity rootfses at install time and registers it with every agent so OpenCode, Claude Code and Antigravity all expose the same browser_* tools:

  • OpenCode: ~/.config/opencode/opencode.json
  • Claude Code: ~/.claude.json
  • Antigravity: ~/.gemini/config/mcp_config.json

Provisioning is idempotent, preserves user-added MCP servers, and re-runs on runtime start so existing installs pick it up without a reinstall. Supersedes the guest-tools script from #222.

The runtime installer now seeds /usr/local/bin/andcode-browser-mcp.py
(stdlib-only MCP server, no third-party deps) into both the Alpine and
Antigravity rootfses and registers it in each agent's config:

- OpenCode:  ~/.config/opencode/opencode.json (mcp.and-code-browser)
- Claude:    ~/.claude.json (mcpServers.and-code-browser)
- Antigravity: ~/.gemini/config/mcp_config.json

Provisioning is idempotent, preserves user-added servers, and also runs
on startup for runtimes installed before this change, so OpenCode,
Claude Code and Antigravity all expose the same browser_* tools.

Supersedes the guest-tools script from #222.
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 6 issue(s) in this PR.

  • ✅ Successfully posted inline: 6 comment(s)

sock.sendall(b"GET /json HTTP/1.1\r\nHost: localhost\r\n\r\n")
data = b""
while b"\r\n0\r\n\r\n" not in data and not data.endswith(b"]"):
while not data.endswith(b"]"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · high]
読み取りループの終了条件変更は回帰リスクがあります。既存の b"\r\n0\r\n\r\n" によるchunked終端の解析コード(下の if b"\r\n0\r\n\r\n" in body:)が残っていることから、WebView devtools の /json レスポンスはchunked転送エンコーディングで返される想定です。その場合レスポンスは ] の後に 0\r\n\r\n が続くため data.endswith(b"]") が成立せず、再び sock.recv() に入ります。HTTP keep-aliveで接続が維持されると _connect() で設定された10秒のタイムアウトまでブロックし、socket.timeout が発生して browser_navigate/browser_info など全CDPツール呼び出しが遅延または失敗します。旧条件 b"\r\n0\r\n\r\n" not in data を復元することを推奨します。

Suggestion:

Suggested change
while not data.endswith(b"]"):
while b"\r\n0\r\n\r\n" not in data and not data.endswith(b"]"):

continue
try:
msg = json.loads(line)
except json.JSONDecodeError:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[other · low]
JSONとしてパースできない入力行を無言で捨てています。JSON-RPC仕様ではparse errorは id: null-32700 Parse error を応答すべきで、現状はクライアントが応答待ちのままタイムアウトする可能性があります。応答を返してから continue するのが望ましいです(少なくともエラーを無視した事実が検知できるようになります)。

Suggestion:

Suggested change
except json.JSONDecodeError:
except json.JSONDecodeError:
respond({"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}})
continue

},
}
)
elif mid is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · medium]
MCPプロトコル(2024-11-05)ではサーバーは ping リクエストに応答することが必須とされています。標準的なMCP SDKクライアントは接続維持・疎通確認のために定期的に ping を送信しますが、現状はこの分岐に該当せず -32601 method not found を返します。クライアントによってはping失敗をプロトコル異常とみなしてセッションを切断するため、ping に対して {"jsonrpc":"2.0","id":mid,"result":{}} を返すハンドラを追加してください。

Suggestion:

Suggested change
elif mid is not None:
elif method == "ping":
respond({"jsonrpc": "2.0", "id": mid, "result": {}})
elif mid is not None:

Comment on lines +472 to +476
mergeJsonConfig(File(rootfs, "root/.gemini/config/mcp_config.json")) { root ->
val servers = root.optJSONObject("mcpServers") ?: JSONObject()
servers.put(BROWSER_MCP_NAME, browserMcpEntry("antigravity"))
root.put("mcpServers", servers)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · medium]
プロジェクト全体は kotlinx.serialization を標準で使用していますが、新規コードのみ org.json (JSONObject/JSONArray) を導入しています。さらに Antigravity の mcp_config.json は既に AntigravityMcp オブジェクトが同じパスを管理しており(command/serverUrl 形式・一時ファイル→rename の原子的書き込み)、このコードは同じ設定を別ライブラリで重複更新しています。書き込み方式や形式の差異により将来ハンドリングが分離し、保守コストが増えます。既存の kotlinx.serialization を利用し、antigravity 向けは AntigravityMcp の既存ヘルパーを再利用することを推奨します。

file.parentFile?.mkdirs()
val root =
if (file.isFile) {
runCatching { JSONObject(file.readText()) }.getOrNull() ?: JSONObject()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[bug · medium]
mergeJsonConfig は JSON パース失敗を runCatching で捕捉して空の JSONObject に置き換え、その後 mutate で内容が変われば既存ファイルを直接上書き writeText します。このため、ユーザーが手動編集した不正 JSON の設定が失われる可能性があります。また writeText は原子的でないため、書き込み途中でプロセスが中断するとルートfs内の設定ファイルが壊れます。既存 AntigravityMcp.writeServers のように「テンポラリへ書き出して rename」する原子的書き込みを行い、パース失敗時はログを残すか、失敗時に書き込みをスキップすることを推奨します。

Comment on lines +506 to +508
val before = root.toString()
mutate(root)
if (root.toString() != before) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[maintainability · low]
変更検知を JSONObject.toString() の文字列比較で行っています。Android の org.json は挿入順を保持するため通常は機能しますが、将来の実装やライブラリ差し替えでキー順序が変わる可能性があり、同じ内容でも毎回書き込みが発生する壊れやすい作りです。root.has(BROWSER_MCP_NAME) でキー存在を確認してから書き込むか、similar() 等の意味比較を用いる方が堅牢です。

@yuga-hashimoto
yuga-hashimoto merged commit 0be3094 into main Aug 5, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant