Skip to content

feat(mcp): connect the user's signed-in Chrome through opencli-mcp - #5638

Merged
Astro-Han merged 12 commits into
apache:mainfrom
Astro-Han:feat/mcp-opencli-chrome
Sep 24, 2026
Merged

Astro-Han merged 12 commits into
apache:mainfrom
Astro-Han:feat/mcp-opencli-chrome

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Maka's built-in browser tools drive the conversation's embedded browser, which has none of the user's logins. This PR adds the other half: an MCP connection to the user's own signed-in Chrome through opencli-mcp, bundled with Maka and set up from the MCP page.

Flow. 扩展 → MCP → 推荐 → Chrome +. The click stores a stdio connection and opens the Chrome Web Store page for the extension in Chrome, even when another browser is the default (on Windows, chrome.exe from its per-machine or per-user install location; the default browser only when Chrome is not found). The row shows 等待 Chrome (attention) and the detail keeps a 连接 Chrome action until the extension reaches the host. The page polls the host every 2 s while that is pending, so nothing needs refreshing.

Why one store click remains. Chrome installs consumer extensions only from the Web Store; silent install exists only as enterprise policy. Everything else opencli-mcp setup does now runs inside Maka:

  • No Node install. Maka runs opencli-mcp on its own Electron binary with ELECTRON_RUN_AS_NODE. Two launchers under userData/opencli-mcp/ are rewritten on every start, and they are the only paths stored in mcp.json and in Chrome's host manifest. The executable path itself moves on every AppImage launch.
  • Maka writes the Native Messaging manifest itself instead of calling opencli-mcp's registerHost. That function's launcher runs process.execPath without ELECTRON_RUN_AS_NODE, which under Electron would start a Maka window. The directory list, profile detection and identifiers are loaded from the package, not copied. A manifest that points at another live install (for example a global opencli-mcp setup) is left alone; the stdio server reaches whichever host Chrome starts. An install counts as live only while every path its launcher runs still exists, so a launcher left behind by uninstalling the global package is replaced. Registration writes into Chrome's directories only when the user clicks.
  • Chrome status comes from the host's /health, not from the MCP connection. Without the extension, the server still starts and lists all 18 tools, but every call returns host_unavailable. Showing that as "connected" would hide the missing step until a call fails.

The built-in browser_* tools are unchanged. The two cover different scenarios: the embedded browser is visible inside Maka and has no logins; this connection drives the user's own Chrome.

Packaging. opencli-mcp 0.0.18 is pinned exactly. Its exports map lists only ./adapter-sdk, so the CLI entry and the few host modules are reached by file path from that export's package root, and their types are declared in opencli-mcp.d.ts. The file path holds for the pinned version. jackwener/opencli-mcp#5 asks for a public entry and for stdio to register the host itself, which would remove the self-written manifest too. tree-sitter-javascript's native build output is excluded from the package: opencli-mcp loads only its wasm, and the build intermediates exceeded the Windows path limit in the installer.

Chrome mark. The entry shows Google Chrome's mark, vendored unchanged from Allogo (MIT) at the same pinned commit as the Notion and Linear marks. THIRD_PARTY_LICENSES.txt records its upstream path and SHA-256, which source-legal-inventory.test.mjs checks, and its trademark boundary now names Google Chrome: the mark only identifies the browser this connection drives.

Unrelated test fix. model-factory-thinking.test.ts still pinned Vercel's openai/gpt-5.1-thinking to the four efforts of the snapshot before #5678, which now declares minimal and xhigh as well. main's test job stopped at the packages/ui type error first (fixed by #5697), so this failure only showed up here.

Verification

  • opencli-chrome.test.ts: the POSIX launchers actually run an entry in Node mode, including a path that contains a quote (stdio and host, with Chrome's appended origin ignored); Windows .cmd content; manifest targeting (Chrome always, other browsers only when their profile exists, a stale manifest overwritten, another live install kept, a global launcher whose main.js was removed replaced); on Windows the store page starts an installed chrome.exe instead of the default browser, which is used only when no Chrome is found (a stand-in chrome.exe, so not run on a real Windows browser).
  • module-hub-mcp-controller.test.ts: a configured Chrome server is polled until the extension connects, and polling then stops. mcp-preload-scope.test.ts covers the two new channels.
  • Stories: ExtensionsMcpChrome (the click adds the server, starts connecting once, leaves 推荐) and ExtensionsMcpChromeWaiting (the row shows 等待 Chrome and counts as needing attention, not "16 个工具"). Storybook smoke passes: 409 stories, 443 theme renders.
  • Packaged app (electron-builder --mac dir): dist/main/opencli-chrome.js loads from app.asar under ELECTRON_RUN_AS_NODE, writes launchers that point into the asar, and the written stdio launcher answers initialize.
  • Dev app with an isolated profile and the real Web Store extension, driven over CDP:
    • Before registration, with Chrome running and no host: 推荐 → Chrome + adds the server, which lists 18 tools and shows 等待 Chrome. The click writes the manifests for Chrome, Chromium and Edge.
    • The extension reaches the host about 9 s later, and the row turns into 18 个工具 without a refresh. The host reports one session, which is Maka's stdio server that started before the host existed.
    • 测试连接 passes.
  • Host restart through Maka's launcher: kill the host, start the launcher while the host is down, then call tools on that same session. tab_list returns host_unavailable. About 3 s later the extension has respawned the host, and on the same session tab_list and tab_open succeed. Disconnecting closes the tab that was opened.
  • typecheck, typecheck:stories, format, lint, check:third-party-notices, audit-shipped-dependencies, windows:inventory.

Not run:

  • Clicking Add to Chrome in the Web Store: the extension was already installed, so the store page opened anyway because the extension had not yet reached the host.
  • Quitting Chrome itself: killing the host covers the same stdio path.
  • A tool call from a model turn.
  • Windows and Linux, locally.
  • E2E, which is left to CI.

MCP recommendations, light
MCP recommendations, dark
Chrome detail after adding, light
Chrome detail after adding, dark

AI use

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

Tool(s) and scope: Claude Code: investigation, implementation, tests, and this description.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

Add Chrome as an MCP recommendation backed by the bundled opencli-mcp.
One click stores a stdio connection and sends the user to the Chrome Web
Store for the extension; the detail keeps a Connect Chrome action and
follows the extension until it reaches the host.

Chrome only installs extensions from the store, so that click cannot be
removed. Everything else that `opencli-mcp setup` does runs inside Maka:

- Maka runs opencli-mcp on its own Electron binary in Node mode, so users
  need no Node install. Two launchers under userData are rewritten on every
  start and are the only paths mcp.json and Chrome's host manifest hold;
  the executable itself moves on every AppImage launch.
- Maka writes the Native Messaging manifest itself instead of calling
  opencli-mcp's registerHost, whose launcher runs process.execPath without
  ELECTRON_RUN_AS_NODE and would open a Maka window. Directory lists and
  identifiers are reused from the package. A manifest owned by another live
  install is left alone.
- Chrome status comes from the host's health endpoint, not the MCP status:
  without the extension the server still starts in its embedded runtime and
  lists every tool.

The built-in browser tools stay as they are: they drive the conversation's
embedded browser without the user's logins; this connection drives the
user's own Chrome.

The notice generator now reads packages that a registry dependency ships
inside itself and links by `file:`, which npm records without a version.

Generated-by: Claude Code
@github-actions github-actions Bot added the effort/L Under 1000 readable lines label Sep 23, 2026
A new desktop shared file grew the legacy AppShell renderer closure, which the
architecture ratchet forbids. The status is part of the MCP bridge surface, so
it lives with the other MCP bridge types.

Generated-by: Claude Code
The Chrome native host registration calls reg.exe on Windows.

Generated-by: Claude Code
… package

opencli-mcp parses JavaScript through web-tree-sitter and loads only the
grammar's wasm. The rebuilt native binding carried MSBuild intermediates
under app.asar.unpacked whose paths exceed MAX_PATH once installed, which
broke the Windows sandbox ACL walk and the uninstaller cleanup in the
installer lifecycle check.

Generated-by: Claude Code
0.0.18 keeps the stdio server usable when Chrome starts after Maka: tool
calls report host_unavailable until the host appears instead of staying
in an embedded runtime that never reaches it (jackwener/opencli-mcp#3).
It also ships the adapter SDK inside the package under Apache-2.0
(jackwener/opencli-mcp#2), so the inferred MIT copyright override and the
lockfile link handling for the separate SDK package go away.

0.0.17 added an exports map that lists only ./adapter-sdk. The host
modules and CLI entry are now reached by file path from that export's
package root; this holds for the pinned version and goes away once
jackwener/opencli-mcp#5 gives embedders a public entry. The host command
drops --native, which 0.0.18 no longer takes.

Generated-by: Claude Code
…n tests

apache#5510 added assertions on `TurnViewModel.assistant`, which apache#5559 had
already removed, so `tsc -p packages/ui` fails on main and every job that
builds @maka/ui fails with it. The answers now come from the turn's text
timeline items. Each assertion keeps what it proved: the earlier snapshot
still holds only the original answer, and the appended step shows up as
its own text item.

Generated-by: Claude Code
The models.dev refresh in apache#5678 gave Vercel's openai/gpt-5.1-thinking the
minimal and xhigh efforts, but the Vercel Gateway test still pinned the
previous snapshot's four levels. main's test job stopped at the
packages/ui type error first, so this failure stayed hidden there.

Generated-by: Claude Code
# Conflicts:
#	packages/ui/src/__tests__/transcript-projection.test.ts
@Astro-Han
Astro-Han marked this pull request as ready for review September 24, 2026 10:59
Replace the generic pointer icon with Google Chrome's mark, vendored
unchanged from Allogo (MIT) at the same pinned commit as the Notion and
Linear marks. The renderer inventory records its upstream path and
SHA-256 under the Allogo entry, and the trademark boundary now names
Google Chrome: the mark only identifies the browser the connection
drives.

Generated-by: Claude Code
@liuxiaocs7

Copy link
Copy Markdown
Member

AI-assisted review by Codex of commit bdc84f33bd0e9648c3de40ec4dfcbc9e14d482df.

The Chrome icon update follows the existing image-rendering path, and the source inventory checks pass (5/5). Two P2 issues remain in the connection setup:

  1. [P2] Launch Chrome explicitly on Windows — opencli-chrome.ts:146–157

    openInChrome() only defines launch candidates for macOS and Linux. On Windows it always falls through to shell.openExternal(). If Chrome is installed but Edge or Firefox is the default browser, clicking Chrome + opens the store in that browser instead of Chrome, breaking the intended Chrome extension onboarding flow. Please add Windows Chrome discovery and launch handling, with an appropriate fallback when Chrome is unavailable.

  2. [P2] Detect stale launchers whose target was removed — opencli-chrome.ts:135–138

    ownedByAnotherInstall() treats an existing launcher file as proof of a live installation. However, upstream setup writes its launcher under ~/.opencli-mcp/bin/; uninstalling the global npm package can leave that launcher and the Chrome manifest behind while removing the referenced main.js. Maka then keeps skipping registration, so repeatedly clicking Connect Chrome cannot repair the connection.

    I reproduced this with a launcher in the upstream format: it ran successfully before its target was removed, then failed with MODULE_NOT_FOUND; registerOpencliNativeHost() returned [] and left the manifest pointing at the broken launcher even though Maka's replacement launcher worked. Please distinguish a usable external installation from a leftover launcher and replace the registration when its referenced target is no longer available.

The Windows routing check used a simulated platform branch, not a real Windows browser session. The existing Windows test only compares the generated .cmd text; actual startup coverage would provide stronger confidence.

The adapter boundary looks reasonable, and these fixes should not require a deeper refactor. I recommend addressing both issues before merging. This is an automated review, not an independent human approval.

@liuxiaocs7 liuxiaocs7 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks @Astro-Han!

…rations

On Windows the store page always went to the default browser, so with
Edge or Firefox as the default the extension could not be installed.
Start chrome.exe from Chrome's install locations (per-machine and
per-user) first, and fall back to the default browser only when none
starts.

Uninstalling a global opencli-mcp leaves its launcher and host manifest
behind while removing the main.js the launcher runs. Maka treated the
surviving launcher as a live install and never replaced the manifest,
so Connect Chrome could not repair the connection. Another install now
counts as live only while every absolute path its launcher quotes still
exists.

Generated-by: Claude Code
@Astro-Han

Copy link
Copy Markdown
Contributor Author

Thanks, both fixed in f44ff87.

  1. Windows launch. openInChrome now starts chrome.exe from Chrome's install locations (ProgramFiles, ProgramFiles(x86), LOCALAPPDATA under Google\Chrome\Application) and falls back to the default browser only when none starts. The new test runs a stand-in chrome.exe for the Windows branch: it receives the store URL and the default browser is not used; with no Chrome found, the default browser gets the URL. As you noted, this is still not a real Windows browser session.
  2. Leftover launcher. Another install now counts as live only while every absolute path its launcher quotes still exists. The registration test adds a launcher in the upstream ~/.opencli-mcp/bin/ format whose main.js is gone: its manifest is replaced with Maka's launcher, while one whose main.js exists is still kept.

The test's stand-in chrome.exe is a shell script, so it is skipped on
Windows and has to be recorded in the Windows skip inventory.

Generated-by: Claude Code
@Astro-Han
Astro-Han merged commit acaa29e into apache:main Sep 24, 2026
18 checks passed
@Astro-Han
Astro-Han deleted the feat/mcp-opencli-chrome branch September 24, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/L Under 1000 readable lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants