yoke-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MOTIVATION.md +73 -0
- package/README.md +207 -0
- package/ROADMAP.md +111 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +93 -0
- package/dist/cli.js.map +1 -0
- package/dist/doctor.d.ts +10 -0
- package/dist/doctor.js +178 -0
- package/dist/doctor.js.map +1 -0
- package/dist/install.d.ts +47 -0
- package/dist/install.js +165 -0
- package/dist/install.js.map +1 -0
- package/dist/mcp-server.d.ts +370 -0
- package/dist/mcp-server.js +606 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/native-host.d.ts +2 -0
- package/dist/native-host.js +170 -0
- package/dist/native-host.js.map +1 -0
- package/dist/protocol.d.ts +339 -0
- package/dist/protocol.js +9 -0
- package/dist/protocol.js.map +1 -0
- package/dist/socket-client.d.ts +16 -0
- package/dist/socket-client.js +89 -0
- package/dist/socket-client.js.map +1 -0
- package/dist/socket-path.d.ts +5 -0
- package/dist/socket-path.js +19 -0
- package/dist/socket-path.js.map +1 -0
- package/extension/browser/background.js +371 -0
- package/extension/browser/cdp.js +259 -0
- package/extension/browser/snapshot.js +154 -0
- package/extension/icons/128.png +0 -0
- package/extension/icons/16.png +0 -0
- package/extension/icons/32.png +0 -0
- package/extension/icons/48.png +0 -0
- package/extension/icons/icon.svg +8 -0
- package/extension/manifest.json +28 -0
- package/extension/protocol.js +8 -0
- package/package.json +53 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hamza Hamidi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/MOTIVATION.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# What the bridge cannot do, and why yoke exists
|
|
2
|
+
|
|
3
|
+
Observations of the Claude in Chrome extension and its MCP bridge (`claude --claude-in-chrome-mcp`), gathered while building [`claude-in-chrome-cli`](https://github.com/hamzahamidi/claude-in-chrome-cli) against it. Each entry says how it was established, because the interesting ones are counter-intuitive and several contradicted assumptions we started with.
|
|
4
|
+
|
|
5
|
+
These are notes on fit, not a complaint. The bridge is built for an agent holding a conversation; most of what follows only bites a programmatic client, and a few are plain bugs worth reporting upstream.
|
|
6
|
+
|
|
7
|
+
These measurements led to yoke, but yoke does not use that CLI or bridge. It is an independent Chrome extension and MCP server that works with any MCP client. Its local requirements are Node 22 or later and Chrome.
|
|
8
|
+
|
|
9
|
+
## The tab group boundary
|
|
10
|
+
|
|
11
|
+
**The bridge sees only tabs inside its own tab group.** A tab the user has open anywhere else is invisible, whatever window it is in. There is no move tool, no all-windows flag, and `select_browser` picks a browser rather than a tab. *Measured on one machine: the bridge could see 4 tabs while a session-file reader saw 29, of which 25 were outside the group.*
|
|
12
|
+
|
|
13
|
+
**Nothing can adopt a tab.** Using a page the user already has open requires the human to move it in through Chrome's own tab context menu, then the client to notice. Our client does this by snapshotting group membership and watching for a stable single addition across two polls. *Measured: it works, and it takes a human gesture every time.*
|
|
14
|
+
|
|
15
|
+
**No group API crosses the bridge.** Groups cannot be listed, named, recoloured or removed. Every group is titled `Claude (MCP)`, so once several exist they are indistinguishable. *Measured; upstream `anthropics/claude-code#86355` tracks the labelling half.*
|
|
16
|
+
|
|
17
|
+
## Group lifetime
|
|
18
|
+
|
|
19
|
+
**A group is lost permanently when its first tab closes.** Closing the group's oldest tab makes the bridge lose the entire group even while other tabs are still open in it. Those tabs then cannot be listed, driven or closed from any session, and the group's pill stays in the tab strip until a human removes it. *Measured in both directions: closing the second tab leaves the group intact and drivable; closing the first yields `Closed tab N. 1 tab(s) remain.` followed by `No MCP tab groups found`.*
|
|
20
|
+
|
|
21
|
+
**A group dies with its last tab, so every create-and-close cycle makes a new one.** Two consecutive cycles produced group ids `528130259` then `339959904`; six cycles left six distinct group tokens in Chrome's session file. A client that tidies up after itself therefore accumulates one stranded pill per run unless it deliberately keeps a tab open forever. *Measured.*
|
|
22
|
+
|
|
23
|
+
**Emptied groups are undetectable.** Chromium writes no group-lifecycle command to session data: group existence is only ever implied by a live tab's membership record. So an emptied group appears in no session snapshot and no API a client can reach. *Measured by diffing `Sessions/Session_*` around a known create-then-empty, which produced no group command at all, and confirmed across all three snapshots on disk.*
|
|
24
|
+
|
|
25
|
+
**`tabs_create_mcp` requires a group to already exist.** With none, it refuses and points at `tabs_context_mcp`. Ordering that is not obvious, and easy to get wrong in a way that only shows up on a clean browser. *Measured: `No MCP tab group exists. Use tabs_context_mcp with createIfEmpty: true first to create one.`*
|
|
26
|
+
|
|
27
|
+
## What comes back in a reply
|
|
28
|
+
|
|
29
|
+
**Every reply embeds the full open-tab list, with titles and raw URLs.** Not just the tab being acted on: every tab in the group, appended to every single reply. For one of us that meant internal staging hostnames and a Slack workspace and channel id landing in ordinary command output, which a script will happily write to a log or a CI artifact. *Measured; it is why our demo recording elides that section.*
|
|
30
|
+
|
|
31
|
+
**Replies carry `<system-reminder>` text addressed to a model.** Advice like preferring `browser_batch` arrives inside the tool result. Reasonable for an agent, noise in a data channel for a program. *Measured.*
|
|
32
|
+
|
|
33
|
+
**Structured values arrive inside prose.** A new tab's id is only ever reported as `Created new tab. Tab ID: 2099042596`, so a client has to parse a sentence to learn it. This is the one place our client reads meaning out of prose rather than a field, and a wording change upstream would break it. *Measured.*
|
|
34
|
+
|
|
35
|
+
**Reply shape is inconsistent for the same tool.** `tabs_context_mcp` answers with JSON when a group exists and with prose (`No MCP tab groups found…`) when none does, so a parser needs both paths. *Measured.*
|
|
36
|
+
|
|
37
|
+
## Tool-level gaps
|
|
38
|
+
|
|
39
|
+
**`navigate` reports success for a URL it did not visit.** Given `file:///path/to/x.html` it answered `Navigated to https://file:///path/to/x.html`: it had prefixed `https://` to the whole thing and gone to a bogus address, while reporting success. `data:` and `about:blank` are rejected outright with `Invalid URL`. *Measured; the silent mangling is the one we would call a bug.*
|
|
40
|
+
|
|
41
|
+
**Screenshots are the page viewport only, and always JPEG.** Browser chrome is never captured, so the tab strip cannot be read; and the `computer` tool exposes no format option, returning JPEG whatever the caller intends to name the file. *Measured: a 1538x784 JPEG against a 1512x949 window, and the tool schema has no format parameter.*
|
|
42
|
+
|
|
43
|
+
**A fresh tab cannot be scripted.** New tabs open on `chrome://newtab`, where `javascript_tool` answers `Cannot access a chrome:// URL`, so a client must navigate somewhere before it can evaluate anything. *Measured.*
|
|
44
|
+
|
|
45
|
+
## Dependencies
|
|
46
|
+
|
|
47
|
+
**The bridge needs the Claude Code CLI installed, and the extension signed in to the same account.** `cic` spawns `claude --claude-in-chrome-mcp`, and a mismatch surfaces as `Browser extension is not connected`. Reasonable for Claude Code users; a hard floor for anyone who only wants browser automation from a shell. *Measured.*
|
|
48
|
+
|
|
49
|
+
This is a dependency of the bridge being measured, not a yoke dependency. yoke does not spawn Claude Code and does not require an Anthropic account or sign in. It needs Node 22 or later and Chrome.
|
|
50
|
+
|
|
51
|
+
## What yoke changed
|
|
52
|
+
|
|
53
|
+
The measurements supported building an independent extension and MCP server. The table records what that decision changed and what remains a browser limit:
|
|
54
|
+
|
|
55
|
+
| Limitation | What yoke does |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Tab group boundary | `chrome.tabs.query({})` sees every tab in every window. |
|
|
58
|
+
| Nothing can adopt a tab | Every existing tab is addressable by explicit id, so adoption is no longer a separate operation. |
|
|
59
|
+
| No group API | `chrome.tabGroups` lists groups, and yoke can group or ungroup named tabs. |
|
|
60
|
+
| Group lost when its first tab closes | Group identity belongs to Chrome, and no tool addresses a tab through its group. |
|
|
61
|
+
| Emptied groups undetectable | yoke queries Chrome's group state directly while a group exists. Emptying a group leaves its tabs open and removes the pill. |
|
|
62
|
+
| Tab list in every reply | A tool returns the data for its own operation. It does not append the whole tab list. |
|
|
63
|
+
| `<system-reminder>` in results | yoke adds no model-directed reminder text to tool results. |
|
|
64
|
+
| Ids inside prose | The extension protocol carries ids as fields. MCP tool results are still concise text. |
|
|
65
|
+
| `navigate` mangling non-http URLs | `navigate` and `open_tab` accept HTTP and HTTPS URLs and refuse other schemes without rewriting them. |
|
|
66
|
+
| Screenshot format and chrome capture | PNG and JPEG are explicit options, and a background tab can be captured. Browser chrome remains out of reach. |
|
|
67
|
+
| Needs Claude Code installed | yoke has no Claude Code dependency. Any MCP client can start `yoke mcp`. |
|
|
68
|
+
|
|
69
|
+
The cost is equally plain. The extension requests `tabs`, `tabGroups`, `nativeMessaging`, `scripting` and `debugger`, plus `host_permissions: ["<all_urls>"]`. That is broad access to a signed in browser. Trusted input and background tab screenshots require `debugger`, which brings Chrome's visible debugging bar and prevents DevTools from sharing an attached tab. Page reading requires `scripting` and access across sites. yoke also owns the browser automation surface and the three process bridge that Chrome's native messaging rules require.
|
|
70
|
+
|
|
71
|
+
That trade was accepted. The extension and MCP server in this repository are the result, and they stand on their own.
|
|
72
|
+
|
|
73
|
+
The measurements and the smallest useful upstream requests are attached at `anthropics/claude-code#75901`.
|
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# yoke
|
|
2
|
+
|
|
3
|
+
yoke lets an MCP client drive the Chrome profile you already use. It can work with every tab in every window, read pages, click and type, take screenshots, and inspect console and network activity. Your signed in sessions, cookies, extensions, and open tabs stay available.
|
|
4
|
+
|
|
5
|
+
That is the reason to use yoke instead of starting a fresh browser through Playwright or Puppeteer. It works where you already work.
|
|
6
|
+
|
|
7
|
+
Chrome requires a three part bridge:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
MCP client <--stdio--> yoke mcp server <--Unix socket--> native host <--connectNative--> Chrome extension
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The MCP client starts `yoke mcp`. Chrome starts the native messaging host when the extension connects to it. The server reaches that host through a local socket. A client cannot connect straight to the extension because Chrome only gives native messaging connections to hosts that Chrome started.
|
|
14
|
+
|
|
15
|
+
The project and repository are named `yoke`. The npm package is `yoke-mcp` because the bare package name belongs to an unrelated project. The command is `yoke`, and the extension is displayed as Yoke.
|
|
16
|
+
|
|
17
|
+
## Why yoke exists
|
|
18
|
+
|
|
19
|
+
yoke grew out of measurements of Anthropic's Claude in Chrome MCP bridge. That bridge could only see and act on tabs in its own managed group, and each reply included its full tab list with raw URLs. Those limits could not be fixed by an outside client. yoke addresses every tab by id and redacts URLs by default. [MOTIVATION.md](MOTIVATION.md) records the observations behind those choices.
|
|
20
|
+
|
|
21
|
+
yoke is not affiliated with or endorsed by Anthropic or Google. It works with any client that speaks Model Context Protocol.
|
|
22
|
+
|
|
23
|
+
## Install from source
|
|
24
|
+
|
|
25
|
+
yoke is not on the Chrome Web Store. You must build it from source and load the extension unpacked.
|
|
26
|
+
|
|
27
|
+
You need Node 22 or later and a Chromium based browser. The manifest requires Chrome 116 or later. Chrome, Chromium, Edge, and Brave registration paths are present. Only macOS has been exercised so far.
|
|
28
|
+
|
|
29
|
+
### 1. Clone and build
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
git clone https://github.com/hamzahamidi/yoke.git
|
|
33
|
+
cd yoke
|
|
34
|
+
npm install
|
|
35
|
+
npm run build
|
|
36
|
+
node dist/cli.js install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Do not skip the build. `extension/manifest.json` loads `extension/browser/background.js`, and `extension/browser/` is generated output that is not stored in Git.
|
|
40
|
+
|
|
41
|
+
`node dist/cli.js install` registers the native messaging host with each Chrome family browser it finds. If the `yoke` binary is already on your `PATH`, `yoke install` runs the same command.
|
|
42
|
+
|
|
43
|
+
On Windows, `yoke install` does not complete registration. It prints a registry instruction instead of changing the registry. The Windows and Linux paths have not been tested yet.
|
|
44
|
+
|
|
45
|
+
### 2. Load the extension
|
|
46
|
+
|
|
47
|
+
In Chrome, Chromium, or Brave:
|
|
48
|
+
|
|
49
|
+
1. Open `chrome://extensions`.
|
|
50
|
+
2. Turn on Developer mode.
|
|
51
|
+
3. Choose **Load unpacked**.
|
|
52
|
+
4. Select the `extension/` directory inside the clone, not the repository root.
|
|
53
|
+
|
|
54
|
+
In Edge, use `edge://extensions` and follow the same steps.
|
|
55
|
+
|
|
56
|
+
The extension id must be `oceljemfocgfidhhdlbojkbkmlbfclna`. A public key in `extension/manifest.json` pins that id. The native host manifest allowlists it, so native messaging will refuse a build with a different id.
|
|
57
|
+
|
|
58
|
+
The native messaging host id is `io.github.hamzahamidi.yoke`.
|
|
59
|
+
|
|
60
|
+
### 3. Check every connection
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
node dist/cli.js doctor
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When setup is complete, the last line is:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
Working. Every link in the chain answered.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If something is wrong, `doctor` stops after the first broken link, names it, and prints a suggested fix. It checks the compiled host, browser registration, local socket, extension reply, and whether tabs are visible.
|
|
73
|
+
|
|
74
|
+
For a shorter connection check, run:
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
node dist/cli.js status
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Connect an MCP client
|
|
81
|
+
|
|
82
|
+
An MCP client needs to start the command `yoke` with the argument `mcp`.
|
|
83
|
+
|
|
84
|
+
If you want the binary from this checkout on your `PATH`, run this once from the repository:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
npm link
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
A typical MCP server entry then looks like this:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"yoke": {
|
|
96
|
+
"command": "yoke",
|
|
97
|
+
"args": ["mcp"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Clients use different names and locations for their MCP configuration. If you do not want to run `npm link`, set the command to `node` and pass the absolute path to `dist/cli.js` before `mcp`.
|
|
104
|
+
|
|
105
|
+
## What it can do
|
|
106
|
+
|
|
107
|
+
yoke exposes 19 MCP tools. They are grouped here by the job they help with.
|
|
108
|
+
|
|
109
|
+
| Job | Tools | What they do |
|
|
110
|
+
| --- | --- | --- |
|
|
111
|
+
| Work with tabs | `list_tabs`, `list_tab_groups`, `open_tab`, `navigate`, `close_tab`, `group_tabs`, `ungroup_tabs` | See every window, open background tabs, visit HTTP or HTTPS URLs, close tabs, and manage Chrome tab groups. |
|
|
112
|
+
| Read a page | `get_page_text`, `read_page`, `find`, `screenshot` | Read visible text, describe interactive elements, find a matching element, or capture a foreground or background tab. |
|
|
113
|
+
| Act on a page | `click`, `type_text`, `press_key`, `scroll`, `run_javascript` | Use trusted input by element reference, scroll the page, or evaluate JavaScript in the page's own world. |
|
|
114
|
+
| Debug a page | `read_console`, `read_network`, `release_tab` | Read recorded console and network activity, then detach yoke and clear that tab's buffers. |
|
|
115
|
+
|
|
116
|
+
Any tool that acts on an existing tab requires an explicit `tab_id` obtained from `list_tabs`. There is no active tab default. yoke never acts on whichever tab you happen to be viewing because no tab was named.
|
|
117
|
+
|
|
118
|
+
`read_page` returns references such as `e1` for interactive elements. `click`, `type_text`, `press_key`, and reference based scrolling use those references. A reference expires when the page navigates or renders the element again, so call `read_page` again when a reference is stale.
|
|
119
|
+
|
|
120
|
+
`navigate` and `open_tab` accept HTTP and HTTPS URLs. New tabs open in the background unless the caller asks to focus one.
|
|
121
|
+
|
|
122
|
+
### Tab groups
|
|
123
|
+
|
|
124
|
+
Every tab opened by yoke goes into a cyan group titled `yoke`, so the tab strip names what is driving those tabs. Pass `group_title` to `open_tab` for a different label, which is worth doing when more than one agent works in the same browser. The extension creates that group when needed and reuses a group with the same title in the same window. Reuse comes from Chrome's current group state, so it still works after Chrome has stopped and restarted the extension service worker. You do not collect a row of identical group pills.
|
|
125
|
+
|
|
126
|
+
Tabs that were already open are never moved into this group. Grouping is visual only, so no tool depends on it to find a tab. `ungroup_tabs` removes tabs from their group without closing them. When the group becomes empty, Chrome removes its pill.
|
|
127
|
+
|
|
128
|
+
### URL redaction
|
|
129
|
+
|
|
130
|
+
yoke reduces URLs in tool output to origin and path by default. Query strings and fragments are omitted because they can contain session tokens or credentials. Non HTTP wrapper URLs are reduced to their scheme because their path can contain another full URL.
|
|
131
|
+
|
|
132
|
+
`list_tabs` accepts `full_urls: true` when a caller needs raw URLs. The other tools that print URLs currently keep them redacted.
|
|
133
|
+
|
|
134
|
+
## Permissions and access
|
|
135
|
+
|
|
136
|
+
Installing Yoke gives a local MCP client broad control over the browser profile where you are signed in. It can read site content, run JavaScript, send trusted input, and observe network activity. That can include private accounts and private data. Install it only for MCP clients and agents you trust with that access.
|
|
137
|
+
|
|
138
|
+
The extension requests these permissions:
|
|
139
|
+
|
|
140
|
+
| Permission | What yoke uses it for | What the grant means |
|
|
141
|
+
| --- | --- | --- |
|
|
142
|
+
| `tabs` | List tabs in every window, including ids, titles, URLs, and group membership. Open, navigate, and close named tabs. | The extension can see which pages are open and can change the tab strip. |
|
|
143
|
+
| `tabGroups` | List groups, create or reuse them, set their title and colour, and ungroup tabs. | The extension can inspect and change tab grouping in every window. |
|
|
144
|
+
| `nativeMessaging` | Connect to the local host named `io.github.hamzahamidi.yoke`. | The extension can exchange data with a program installed on your computer. |
|
|
145
|
+
| `scripting` | Inject the functions that read visible text, collect interactive elements, and resolve element references. | The extension can read and run code inside allowed pages. |
|
|
146
|
+
| `debugger` | Use the DevTools Protocol for trusted clicks and typing, JavaScript evaluation, background tab screenshots, console messages, and network requests. | The extension gets deep control and inspection access on each tab it attaches to. |
|
|
147
|
+
| `host_permissions: ["<all_urls>"]` | Let `scripting` work on ordinary sites regardless of host. | The site grant is broad. It is not limited to a list of sites chosen during installation. Per site permission is future work. |
|
|
148
|
+
|
|
149
|
+
Chrome's own pages and the Chrome Web Store still block page script injection. yoke reports that restriction instead of returning an empty page.
|
|
150
|
+
|
|
151
|
+
### Why the debugger permission is required
|
|
152
|
+
|
|
153
|
+
An extension cannot create trusted input through page JavaScript. A click made by a content script reaches the page with `isTrusted: false`, and many sites ignore it. `chrome.debugger` is the extension route to `Input.dispatchMouseEvent`, which creates input the page cannot distinguish from a person's input.
|
|
154
|
+
|
|
155
|
+
Chrome shows its "started debugging this browser" bar when yoke drives a tab. This is expected. Chrome also permits one debugger client per tab. A tab with DevTools open cannot be driven by yoke, and DevTools cannot open on a tab while yoke is attached. Call `release_tab` to detach yoke, remove the bar, and make the tab available to DevTools.
|
|
156
|
+
|
|
157
|
+
The same permission lets `screenshot` capture a background tab. `chrome.tabs.captureVisibleTab` can only capture the active tab in a window.
|
|
158
|
+
|
|
159
|
+
## Local connection and process lifetime
|
|
160
|
+
|
|
161
|
+
The bridge does not open a TCP port. On macOS and Linux, the native host listens on a Unix socket inside a directory readable only by your user account. The socket itself is also restricted to that account. Windows uses a named pipe.
|
|
162
|
+
|
|
163
|
+
Chrome owns the native host process. It starts the host when the extension calls `connectNative` and stops it when that connection closes. This is why the Unix socket between the MCP server and host is required.
|
|
164
|
+
|
|
165
|
+
yoke has zero runtime dependencies. TypeScript and the Chrome type declarations are used only during the build.
|
|
166
|
+
|
|
167
|
+
## Commands
|
|
168
|
+
|
|
169
|
+
After `npm link`, the command surface is:
|
|
170
|
+
|
|
171
|
+
| Command | Purpose |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `yoke install` | Register the native messaging host with each detected Chrome family browser. |
|
|
174
|
+
| `yoke doctor` | Check each link from the build through tab visibility and suggest the first fix. |
|
|
175
|
+
| `yoke status` | Report whether the extension answers and print the local socket path. |
|
|
176
|
+
| `yoke uninstall` | Remove the native host registration. |
|
|
177
|
+
| `yoke mcp` | Run the MCP server over standard input and standard output. This is what an MCP client starts. |
|
|
178
|
+
|
|
179
|
+
From a checkout that has not been linked, replace `yoke` with `node dist/cli.js` in these commands.
|
|
180
|
+
|
|
181
|
+
## Current limits
|
|
182
|
+
|
|
183
|
+
1. yoke is young and pre 1.0. Tool names, arguments, and results can change.
|
|
184
|
+
2. Only macOS has been exercised. Linux and Windows install paths are written but untested. On Windows, `yoke install` prints a manual registry step but does not make the registry change.
|
|
185
|
+
3. The project has one offline test file, `test/mcp-server.test.ts`. It checks the MCP surface and URL redaction, but this is not yet a well tested browser project.
|
|
186
|
+
4. A driven tab shows Chrome's debugging bar and cannot share its debugger slot with DevTools.
|
|
187
|
+
5. Element references expire after navigation or a page render. Console and network history starts when yoke first attaches to that tab, not before.
|
|
188
|
+
6. Screenshots capture the page viewport, not browser chrome such as the address bar or tab strip.
|
|
189
|
+
7. Chrome's internal pages and the Chrome Web Store cannot be read through the page tools.
|
|
190
|
+
|
|
191
|
+
## Uninstall
|
|
192
|
+
|
|
193
|
+
Remove the browser's native host registration with:
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
yoke uninstall
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Then remove Yoke from the browser's extensions page. If you linked the checkout with npm, remove that link with:
|
|
200
|
+
|
|
201
|
+
```sh
|
|
202
|
+
npm unlink --global yoke-mcp
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
[MIT](LICENSE)
|
package/ROADMAP.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
yoke is a Chrome extension and local MCP server for driving the browser you are already signed in to. It gives any MCP client one explicit interface to tabs, page content, trusted input, screenshots, console output and network activity, without a tab-group boundary or a second browser.
|
|
4
|
+
|
|
5
|
+
This roadmap records what has been built and what comes next. Each release is small, shippable on its own, and the non-goals are part of the plan.
|
|
6
|
+
|
|
7
|
+
It was originally written as four releases walking from reading the browser to driving it, one permission at a time. That is not what happened: all four landed together in one unreleased body of work. The sequence below has been rewritten to say what exists rather than what was planned, because a roadmap that disagrees with the manifest is worse than no roadmap. What the staged plan was protecting, a permission set a reviewer can follow, is now the job of v0.2.0.
|
|
8
|
+
|
|
9
|
+
## Why this exists
|
|
10
|
+
|
|
11
|
+
yoke exists because a useful browser tool should be able to work with the Chrome profile a person already uses, including every tab in every window. The measured bridge that preceded it could see only tabs inside its own group, offered no group API, and lost a group permanently when its first tab closed. [MOTIVATION.md](MOTIVATION.md) records the evidence, which is also attached upstream at `anthropics/claude-code#75901`.
|
|
12
|
+
|
|
13
|
+
Tab groups are extension-only surface, proven by closing every alternative: page JavaScript sees a `chrome` object holding only `loadTimes, csi, app`; the DevTools Protocol has no tab-group surface across its 51 domains; Chrome's AppleScript dictionary has no group vocabulary. So the limitation is not something a client can work around. It has to be answered by an extension, which is this one.
|
|
14
|
+
|
|
15
|
+
## Invariants
|
|
16
|
+
|
|
17
|
+
Six rules hold across every release below.
|
|
18
|
+
|
|
19
|
+
1. **No tab-group boundary, ever.** Every tab in every window is addressable. This is the reason the project exists, and a release that reintroduces a scope boundary has failed.
|
|
20
|
+
2. **Permissions are earned, never requested speculatively.** Every permission in the manifest must be traceable to a tool that needs it, and the README must say what each one buys. This is the invariant the current tree comes closest to breaking: because reading and driving pages arrived in the same body of work, `scripting`, `debugger` and `host_permissions: ["<all_urls>"]` were all requested at once rather than one release at a time. Each is used by tools that exist, so the letter holds, but `<all_urls>` is broader than any single tool needs. Narrowing it is v0.2.0 and the first thing on the list.
|
|
21
|
+
3. **MCP is the interface.** One implementation serves an agent and a shell script alike, so behaviour cannot diverge between them. A convenience CLI may wrap it; it never becomes a second implementation.
|
|
22
|
+
4. **URLs are redacted by default.** Origin and path for http and https, bare scheme for anything else, and raw only on an explicit per-call opt-in. A tab listing is exactly where a session token gets copied into a log.
|
|
23
|
+
5. **The extension never acts on a tab it was not told to.** No implicit current tab, no acting on the active tab because none was named.
|
|
24
|
+
6. **Zero runtime dependencies.** TypeScript at build time, nothing shipped but the compiled output and the extension.
|
|
25
|
+
|
|
26
|
+
One boundary is deliberately outside the sequence: this does not become a general-purpose scraping or automation farm. It drives the browser a human is signed in to, on that human's machine, which is the only thing it is good at and the only thing that justifies the permissions.
|
|
27
|
+
|
|
28
|
+
## v0.1.0: read and drive the browser
|
|
29
|
+
|
|
30
|
+
Theme: prove the thesis. Built, working end to end, not yet tagged or published.
|
|
31
|
+
|
|
32
|
+
- TypeScript throughout, `strict` on, compiled to plain Node output with no runtime dependencies. Source in `src/`, extension in `extension/`, build to `dist/`.
|
|
33
|
+
- The three-process shape: the extension connects to a native messaging host that Chrome spawns, the host owns a unix socket in a 0700 directory, and the MCP server connects to that socket. Chrome will only ever spawn the host itself, so this hop is not optional.
|
|
34
|
+
- 19 MCP tools. Tabs and groups (`list_tabs`, `list_tab_groups`, `open_tab`, `navigate`, `close_tab`, `group_tabs`, `ungroup_tabs`), reading (`get_page_text`, `read_page`, `find`, `screenshot`), driving (`click`, `type_text`, `press_key`, `scroll`, `run_javascript`), and inspection (`read_console`, `read_network`, `release_tab`).
|
|
35
|
+
- Trusted input through `chrome.debugger`, because an extension cannot dispatch it any other way: a synthesised event arrives with `isTrusted` false and many sites ignore it. Both consequences are documented rather than hidden: Chrome shows its debugging bar on a driven tab, and one debugger client per tab means DevTools cannot share it.
|
|
36
|
+
- Elements are addressed by reference from `read_page`, never by coordinate, and a reference is re-resolved at the moment of use.
|
|
37
|
+
- Tabs yoke opens join one reused group titled `yoke`, per window, created if absent. Tabs the user already had are never moved into it.
|
|
38
|
+
- `yoke install` writes the host manifest for every Chromium-family browser present, `doctor` names the first broken link in the chain, `status` says whether the extension is connected, `mcp` runs the server on stdio.
|
|
39
|
+
- The extension id is pinned by a key in its manifest, because native messaging allowlists by id and an unpacked load would otherwise get a fresh one each time. The private key never enters the repository, and CI asserts the key still derives the allowlisted id.
|
|
40
|
+
- An icon, at 16, 32, 48 and 128, drawn on an 8 unit grid so every line lands on a pixel boundary at 16.
|
|
41
|
+
- Offline tests for the MCP surface and the redaction, driven without a browser. CI green on Node 22 and 24 across Linux, macOS and Windows.
|
|
42
|
+
|
|
43
|
+
The acceptance test is one number, and it holds: `list_tabs` returns every tab in the browser rather than the handful inside a managed group. On the machine this was built against that is 41 tabs, all of them addressable.
|
|
44
|
+
|
|
45
|
+
Permissions as shipped: `tabs`, `tabGroups`, `nativeMessaging`, `scripting`, `debugger`, and `host_permissions: ["<all_urls>"]`.
|
|
46
|
+
|
|
47
|
+
Non-goals that held: no publication, no recording, no macros, no scripting language. Composition belongs to the caller.
|
|
48
|
+
|
|
49
|
+
### Promised in the original plan and not built
|
|
50
|
+
|
|
51
|
+
Recorded here rather than quietly dropped, because each one is still wanted.
|
|
52
|
+
|
|
53
|
+
- `focus_tab`, and moving a tab between windows.
|
|
54
|
+
- `get_page_html`. `get_page_text` and `read_page` cover what callers actually asked for so far.
|
|
55
|
+
- Form filling as its own operation: a native `select` cannot be driven by clicking, and a checkbox's state cannot be read back.
|
|
56
|
+
- An explicit, revocable opt-in separating reading a page from driving one. Reading and driving are different grants and one grant currently covers both.
|
|
57
|
+
|
|
58
|
+
## v0.2.0: ask for less
|
|
59
|
+
|
|
60
|
+
Theme: the permission set becomes something a reviewer can follow, and yoke asks for less than the extension it replaces.
|
|
61
|
+
|
|
62
|
+
- Move `<all_urls>` to `optional_host_permissions`, granted per site at the moment a tool first needs it. This is the release's whole point. An extension that drives a signed in browser earns trust by asking narrowly, and a community extension replacing a first-party one has to be strictly better on this axis or the argument for it is weak.
|
|
63
|
+
- Separate the read grant from the drive grant, which the original v0.4.0 promised and did not deliver. Attaching the debugger is the moment to ask, not installation.
|
|
64
|
+
- Every permission traced to the tools that need it, in a table in the README, with the failure mode stated for each one a user declines.
|
|
65
|
+
- Anything unused comes out of the manifest.
|
|
66
|
+
|
|
67
|
+
Non-goals: no new tools. A release that narrows permissions and adds surface at the same time cannot be reviewed.
|
|
68
|
+
|
|
69
|
+
## v0.3.0: the gaps
|
|
70
|
+
|
|
71
|
+
Theme: the operations a caller currently has to work around, and the ones with no workaround at all.
|
|
72
|
+
|
|
73
|
+
- `form_input`: set a `select`, read a checkbox back, drive a native control that a click cannot reach.
|
|
74
|
+
- `file_upload` through `DOM.setFileInputFiles`. Clicking a file input opens an OS picker nothing can dismiss, so there is no substitute for this one.
|
|
75
|
+
- The four items from the list above: `focus_tab`, moving a tab between windows, `get_page_html`, and the separated grants if 0.2.0 leaves them.
|
|
76
|
+
- `press_key` past its eight named keys, with modifiers. Back and forward. Hover.
|
|
77
|
+
- Elements inside a shadow root, which the snapshot cannot currently see.
|
|
78
|
+
- `read_console` and `read_network` print the `url` and `line` they already collect.
|
|
79
|
+
|
|
80
|
+
Non-goals: still no recording or macros.
|
|
81
|
+
|
|
82
|
+
## v0.4.0: publication
|
|
83
|
+
|
|
84
|
+
Theme: installable by someone who is not us.
|
|
85
|
+
|
|
86
|
+
- Chrome Web Store listing, with the privacy policy and permission justifications that `debugger` and host access will be asked to defend. Review is typically days and can be weeks, so publication keeps its own release clock.
|
|
87
|
+
- Reproducible build from a tagged commit, so the published bundle can be checked against source. An extension asking for these permissions has to be auditable, and "trust the listing" is not auditable.
|
|
88
|
+
- Verify the store honours the pinned `key`, so the published id matches the one the native messaging manifest allowlists. If it does not, the host registration points at the wrong id and nothing connects, which is worth finding before shipping.
|
|
89
|
+
- npm publish of `yoke-mcp`, which has not happened yet.
|
|
90
|
+
|
|
91
|
+
## v0.5.x: stabilisation
|
|
92
|
+
|
|
93
|
+
Bug fixes only. Soak the debugger path, decide whether Windows is supported and either finish it or say so, and leave the tool names alone.
|
|
94
|
+
|
|
95
|
+
## v1.0.0: the contract release
|
|
96
|
+
|
|
97
|
+
1. Frozen tool names, arguments and result shapes. Changing one after 1.0 requires a major bump.
|
|
98
|
+
2. Three-platform CI green, including the native messaging install path rather than only the build.
|
|
99
|
+
3. A week of the maintainer's real use without an orphaned host process, a stuck socket or a lost group.
|
|
100
|
+
4. Every permission in the manifest traceable to a tool that needs it, and none broader than that tool requires.
|
|
101
|
+
5. Zero known cases where a raw URL, a password, or any other value a caller did not ask for escapes into output.
|
|
102
|
+
|
|
103
|
+
## Decisions
|
|
104
|
+
|
|
105
|
+
**Project identity.** The name is `yoke`. Nothing in the project is specific to one vendor's assistant, so the old vendor-specific framing described the history rather than the software. Google's brand guidance reserves "Chrome" for Google's own products, which also made the previous `chrome-live` code name a poor choice for a possible store listing. The repository and project are `yoke`, the npm package is `yoke-mcp` because the bare package name was taken, the binary is `yoke`, the extension display name is `Yoke`, and the native messaging host id is `io.github.hamzahamidi.yoke`.
|
|
106
|
+
|
|
107
|
+
## Open decisions
|
|
108
|
+
|
|
109
|
+
Recorded rather than guessed at, because they are not implementation details.
|
|
110
|
+
|
|
111
|
+
**Whether browser operations get direct CLI commands.** The `yoke` binary already owns installation, diagnosis, status, removal and the MCP server entry point. MCP remains the browser interface. The open question is whether thin shell commands should expose those same operations for cron jobs and shell pipelines. Invariant 3 permits a wrapper, but not a second implementation.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The one command: register the native host, check the connection, or run the
|
|
3
|
+
// MCP server. Everything else this project does is a tool call.
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { install, uninstall } from './install.js';
|
|
7
|
+
import { endpointPath } from './socket-path.js';
|
|
8
|
+
const USAGE = `yoke - browser automation in the Chrome you are already signed in to.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
yoke install register the native messaging host with your browsers
|
|
12
|
+
yoke doctor check every link in the chain and say which one is broken
|
|
13
|
+
yoke status is the extension connected?
|
|
14
|
+
yoke uninstall remove the host registration
|
|
15
|
+
yoke mcp run the MCP server on stdio (what an MCP client spawns)
|
|
16
|
+
|
|
17
|
+
Getting set up, once:
|
|
18
|
+
1. npm run build
|
|
19
|
+
2. yoke install
|
|
20
|
+
3. load extension/ at chrome://extensions with Developer mode on
|
|
21
|
+
4. yoke doctor -> says which link is broken, if any
|
|
22
|
+
5. register the server with your MCP client:
|
|
23
|
+
command: yoke
|
|
24
|
+
args: ["mcp"]
|
|
25
|
+
`;
|
|
26
|
+
const EXIT = { OK: 0, UNAVAILABLE: 3, USAGE: 64 };
|
|
27
|
+
async function main() {
|
|
28
|
+
const action = process.argv[2];
|
|
29
|
+
if (action === 'mcp') {
|
|
30
|
+
// Hands over to the server, which owns stdio from here on.
|
|
31
|
+
const { main: serve } = await import('./mcp-server.js');
|
|
32
|
+
serve();
|
|
33
|
+
return EXIT.OK;
|
|
34
|
+
}
|
|
35
|
+
if (action === 'install') {
|
|
36
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const result = install({ hostPath: join(here, 'native-host.js') });
|
|
38
|
+
for (const [browser, file] of result.written) {
|
|
39
|
+
process.stdout.write(`registered for ${browser}: ${file}\n`);
|
|
40
|
+
}
|
|
41
|
+
for (const [browser, reason] of result.skipped) {
|
|
42
|
+
process.stderr.write(`skipped ${browser}: ${reason}\n`);
|
|
43
|
+
}
|
|
44
|
+
if (result.platform === 'win32') {
|
|
45
|
+
process.stdout.write('On Windows the host is declared in the registry rather than on disk. '
|
|
46
|
+
+ `Add a REG_SZ default value under HKCU\\Software\\Google\\Chrome\\NativeMessagingHosts\\${result.hostName} `
|
|
47
|
+
+ 'pointing at the manifest file.\n');
|
|
48
|
+
return EXIT.OK;
|
|
49
|
+
}
|
|
50
|
+
if (result.written.length === 0) {
|
|
51
|
+
return EXIT.UNAVAILABLE;
|
|
52
|
+
}
|
|
53
|
+
process.stdout.write('\nNow load extension/ at chrome://extensions with Developer mode on.\n');
|
|
54
|
+
process.stdout.write(`It has to keep the id ${result.extensionId}, which the pinned key in its manifest guarantees.\n`);
|
|
55
|
+
return EXIT.OK;
|
|
56
|
+
}
|
|
57
|
+
if (action === 'uninstall') {
|
|
58
|
+
const { removed } = uninstall();
|
|
59
|
+
for (const [browser, file] of removed) {
|
|
60
|
+
process.stdout.write(`removed for ${browser}: ${file}\n`);
|
|
61
|
+
}
|
|
62
|
+
if (removed.length === 0) {
|
|
63
|
+
process.stdout.write('nothing was registered\n');
|
|
64
|
+
}
|
|
65
|
+
return EXIT.OK;
|
|
66
|
+
}
|
|
67
|
+
if (action === 'doctor') {
|
|
68
|
+
const { doctor, render } = await import('./doctor.js');
|
|
69
|
+
const checks = await doctor();
|
|
70
|
+
process.stdout.write(render(checks));
|
|
71
|
+
return checks.every((check) => check.ok) ? EXIT.OK : EXIT.UNAVAILABLE;
|
|
72
|
+
}
|
|
73
|
+
if (action === 'status') {
|
|
74
|
+
const { available } = await import('./socket-client.js');
|
|
75
|
+
const reachable = await available();
|
|
76
|
+
process.stdout.write(`extension: ${reachable ? 'connected' : 'not reachable'}\n`);
|
|
77
|
+
process.stdout.write(`socket: ${endpointPath()}\n`);
|
|
78
|
+
if (!reachable) {
|
|
79
|
+
process.stdout.write('\nIf install has run and the extension is loaded, open chrome://extensions and '
|
|
80
|
+
+ 'check its service worker is running. Chrome starts the host on demand.\n');
|
|
81
|
+
}
|
|
82
|
+
return reachable ? EXIT.OK : EXIT.UNAVAILABLE;
|
|
83
|
+
}
|
|
84
|
+
process.stdout.write(USAGE);
|
|
85
|
+
return action === undefined || action === '-h' || action === '--help' ? EXIT.OK : EXIT.USAGE;
|
|
86
|
+
}
|
|
87
|
+
main()
|
|
88
|
+
.then((code) => { process.exitCode = code; })
|
|
89
|
+
.catch((failure) => {
|
|
90
|
+
process.stderr.write(`yoke: ${failure instanceof Error ? failure.message : String(failure)}\n`);
|
|
91
|
+
process.exitCode = EXIT.UNAVAILABLE;
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface Check {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
label: string;
|
|
4
|
+
detail: string;
|
|
5
|
+
/** What to do about it, when there is something to do. */
|
|
6
|
+
fix?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Runs the checks in order, stopping the remote ones once a local one fails. */
|
|
9
|
+
export declare function doctor(): Promise<Check[]>;
|
|
10
|
+
export declare function render(checks: Check[]): string;
|