arc-control-mcp 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,255 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.3.0] - 2026-08-31
9
+
10
+ This release is mostly about one class of bug: the server used to report success
11
+ for things that had not happened. If you have been running 0.1.x, assume any
12
+ `ok: true` you saw from a page-touching tool was unverified. The findings came
13
+ from an agent review of the running server, kept at
14
+ [docs/agent-review-2026-08-31.md](docs/agent-review-2026-08-31.md).
15
+
16
+ The second thread is protocol conformance. The tools themselves worked. How
17
+ results, failures, timeouts and cancellations were reported over MCP did not
18
+ match what a client, or a model reading the response, has any right to expect.
19
+
20
+ ### Changed: a tool that changes a tab will not touch the tab you are looking at
21
+
22
+ A call with no `tab_id` used to resolve to this agent's own tab and then, failing
23
+ that, to whatever tab was active in Arc. That second fallback applied to every
24
+ tool, so an agent that called `go_back` or `reload_tab` without a `tab_id`
25
+ navigated and reloaded a tab a human was reading. That happened.
26
+
27
+ Resolution is now split by whether the tool changes anything:
28
+
29
+ - An explicit `tab_id` always wins.
30
+ - Otherwise, a tab this agent opened.
31
+ - Otherwise, for a **read-only** tool, the tab you are looking at. Reading the
32
+ page you already have open is useful and harmless.
33
+ - Otherwise the call is **refused**, with an error naming what to pass instead.
34
+
35
+ `tab_id` was deliberately not made mandatory everywhere. That would force a
36
+ `list_tabs` round trip before every call, and each `osascript` spawn costs a few
37
+ hundred milliseconds. It would also break the implicit targeting that lets
38
+ `open_url`, then `click`, then `fill` run without ids.
39
+
40
+ `arc_status` now reports both branches, as `resolvesTo.readOnly` and
41
+ `resolvesTo.mutating`, since one string can no longer describe both.
42
+
43
+ Also fixed in the same area: a state file written by the pre-0.3.0 flat format is
44
+ no longer treated as a finished run whose tabs can be reaped. It carries no
45
+ session identity, so it may equally belong to a server still running the old
46
+ code, and treating it as dead once marked two live user tabs for closing.
47
+
48
+ ### Added: arguments are validated before a handler runs
49
+
50
+ Nothing validated tool arguments at all, so a wrong type surfaced as a confusing
51
+ error from inside the page. Every tool now declares its arguments as a Zod
52
+ schema, the JSON Schema advertised over MCP is generated from it, and the same
53
+ schema validates the call. A bad argument comes back as an `isError` result
54
+ naming it:
55
+
56
+ ```
57
+ Invalid arguments for click. selector: Invalid input: expected string, received number
58
+ ```
59
+
60
+ Because `batch` calls its peers through the registry, batch steps are validated
61
+ too. Declared defaults are now applied by the parser rather than by a fallback in
62
+ each handler. `zod` becomes a declared dependency: it was already present via the
63
+ MCP SDK, so it deduplicates to a single copy and adds nothing to an install.
64
+
65
+ The server stays on the SDK's low-level `Server` rather than moving to
66
+ `McpServer.registerTool`, which rejects generated JSON Schema and would mean
67
+ rewriting every schema again. It also does not declare `outputSchema` or return
68
+ `structuredContent`: the low-level `Server` does not validate them, so declaring
69
+ them would commit the project to a specification MUST with nothing enforcing it,
70
+ and `execute_javascript` and `batch` have genuinely unschematisable outputs.
71
+
72
+ ### MCP protocol conformance
73
+
74
+ - **A tool that ran and failed now says so where the model can see it.** A
75
+ failed `click` used to arrive as an ordinary successful MCP result whose text
76
+ happened to contain `"ok": false` somewhere inside it. A model reads a
77
+ successful result as "the call worked" and carries on building on something
78
+ that never happened. Failures now set `isError` on the result, so a failed
79
+ click is visible as a failure and the model can retry or pick a different
80
+ selector.
81
+ - **An unknown tool name is now a JSON-RPC error.** It used to come back as a
82
+ tool result carrying `isError`, which is the shape reserved for a tool that
83
+ ran and failed. The spec lists an unknown tool under protocol errors, so it
84
+ now raises `InvalidParams`, with the list of real tool names in the message.
85
+ - **A call can no longer outlive the client's deadline.** An MCP client gives up
86
+ on a request after 60 seconds by default, and once it does, the server's
87
+ answer is thrown away: you see `RequestTimeout` instead of the `timedOut`
88
+ payload that says what the page was actually doing. `open_url`'s worst case
89
+ was a 45s open budget plus a 15s load wait, exactly 60000 ms, so a slow page
90
+ reliably produced a client timeout rather than the server's own honest "still
91
+ loading" answer. A caller-supplied `timeout_ms` had no cap at all. The open
92
+ budget is now 25s, every `timeout_ms` is clamped to 30s, and the ceiling is
93
+ stated both as a schema `maximum` and in the description, together with what
94
+ to do instead: call `wait_for_load` or `wait_for_selector` again. Repeated
95
+ short waits each hand back a real `readyState`, `url` or element count, which
96
+ one long wait does not.
97
+ - **Cancelling a call now stops it.** The polling loops behind
98
+ `wait_for_load`, `wait_for_selector`, `open_url`, `go_back`, `go_forward`,
99
+ `reload_tab` and `batch` check the client's cancellation signal between polls.
100
+ Before, a cancelled call kept spawning `osascript` processes against your
101
+ browser for a result nobody was going to read. A cancelled `batch` reports how
102
+ many of its steps had already run, because whatever those steps did to the
103
+ page stands.
104
+ - **`execute_javascript` and `batch` no longer claim `destructiveHint: false`.**
105
+ That is not defensible for two tools that can do anything a page can do,
106
+ including submitting a form or clicking a delete button. `openWorldHint` is
107
+ now true for every tool that touches page content, and false only for the
108
+ tools that read or move Arc's own tab and space bookkeeping.
109
+ - **`close_tab` now says that a bare call can close the tab you are looking
110
+ at.** Its description was "Close a tab." A call with no `tab_id` resolves to
111
+ this agent's current tab or, when it has none yet, whatever tab is active in
112
+ Arc, which may well be yours. The description says so now, and points at
113
+ `close_own_tabs` for cleanup.
114
+ - **`batch` has an aggregate response cap.** The individual read tools capped
115
+ their own output, but nothing capped the total, so a batch of several reads
116
+ could return a response large enough for a client to clip mid-JSON. There is
117
+ now a 60000 character budget across all steps. Past it the batch stops early
118
+ and reports `truncated`, with a note telling you to pass `max_chars` to the
119
+ reading steps or split the sequence across two calls.
120
+ - **The server introduces itself at initialize.** It now sends MCP
121
+ `instructions`, plus a display `title` and a `websiteUrl`. The instructions
122
+ carry the facts a model otherwise wastes a call discovering: call `arc_status`
123
+ first, background tabs are fully scriptable so prefer passing a `tab_id` over
124
+ switching what the user is looking at, `text=` is substring matching, batch a
125
+ known sequence, and page content is untrusted data rather than instructions.
126
+
127
+ ### Fixed
128
+
129
+ - **A page script that threw was reported as success.** The JXA layer collapsed
130
+ Arc's empty response into `null`, and every handler spread that `null` into a
131
+ cheerful `{ok: true}`. In the worst observed case, a four-step `batch` in
132
+ which nothing at all happened returned `ok: true` on every step and at the top
133
+ level. Every injected script now returns an explicit envelope, so a failure
134
+ comes back as a real error carrying the page's own message, for example
135
+ `The page script failed: SyntaxError: ...`. An invalid selector and a missing
136
+ element are now distinguishable.
137
+ - **`execute_javascript` silently returned `null` for any statement body.** Code
138
+ without the literal word `return` was wrapped as `return (code)`, which is a
139
+ syntax error for anything that is not a single expression. A thrown error, a
140
+ syntax error and a legitimate null result were all indistinguishable. Code is
141
+ now validated in Node before it is injected: the expression form is tried
142
+ first, then the statement form, and if neither parses you get the actual
143
+ `SyntaxError` message without Arc being contacted. The response reports which
144
+ form was used, so a real null is no longer ambiguous.
145
+
146
+ Note the limits of this. A statement body still yields a value only through
147
+ `return`, because giving `let n = 2; n * 3` the completion value `6` would
148
+ need `eval` inside the page, and that breaks on any site with a strict
149
+ Content-Security-Policy. Instead of a bare `null` you now get an explanatory
150
+ note telling you to add a `return`.
151
+ - **`go_back` was a silent no-op that reported success.** `Arc.goBack` does
152
+ nothing on a background tab (reproduced on a tab with a real back entry,
153
+ polled out to 4 seconds), and none of the history tools checked that the tab
154
+ had moved. `go_back` and `go_forward` now go through the page's own history
155
+ API and verify the URL changed before reporting success. The tab snapshot in
156
+ the response is taken after the navigation, not 400ms into it.
157
+ - **Two agents sharing a label corrupted each other's tab ownership.** The
158
+ in-process set of owned tabs was seeded from a shared state file, and whichever
159
+ process wrote second dropped the other's tabs. That meant `close_own_tabs`
160
+ from one agent could close another agent's tabs. Ownership is now tracked per
161
+ session, and stale entries from previous runs are reaped deliberately at
162
+ cleanup time rather than by accident on the next write.
163
+ - **`get_page_content` with a selector returned only the first match.** Asking
164
+ for `#mw-content-text p` on a Wikipedia article with 19 paragraphs returned the
165
+ lead paragraph and `truncated: false`, with nothing in the response hinting at
166
+ the other 18. It now returns all matches and reports how many there were.
167
+ - **`fill` reported success on elements that cannot be filled.** Filling an
168
+ `<h1>` returned `ok: true`. It now returns a descriptive error naming the tag.
169
+ - **`text=` did not prefer exact matches.** `text=Save` could click "Save and
170
+ close" because matching is by substring and the first DOM-order hit won. Exact
171
+ matches are now ranked ahead of substring matches.
172
+ - **1x1 screen-reader elements were reported as visible.** The visibility check
173
+ only rejected elements whose width and height were both zero, so the standard
174
+ clipped 1x1 accessibility span passed as a plausible click target. It is now
175
+ rejected.
176
+ - **Non-serializable script results came back as a bare `{}`.** `return window`
177
+ looked like an empty object with no explanation. The response now carries a
178
+ note saying the value could not be serialized.
179
+
180
+ ### Changed
181
+
182
+ - **Responses are smaller.** `batch` used to repeat the full tab object,
183
+ including a 300-character URL, on every step; the tab is now emitted once per
184
+ batch and repeated only when it changes mid-batch. Element descriptions no
185
+ longer include `rect` and long attribute values by default, which are behind a
186
+ new `verbose` option on the tools that describe elements.
187
+ - **`press_key` no longer returns page text.** It used to include several hundred
188
+ characters of the body's `innerText` as its `target`.
189
+ - **Error messages distinguish an Arc or page problem from a bug in this
190
+ server.** Anything that is not a mapped Arc error is now labelled as internal.
191
+ - **Selector documentation states that `text=` is substring matching**, in the
192
+ tool schemas as well as the README, so an agent reading the schema knows.
193
+
194
+ ### Added
195
+
196
+ - `execute_javascript` now reports the wrapper it chose as `form`
197
+ (`"expression"` or `"statement"`), which is what makes a legitimate `null`
198
+ result distinguishable from a script that failed.
199
+
200
+ - `list_spaces` reports `topAppCount`, so its per-space tab counts reconcile
201
+ with `list_tabs`. Arc excludes topApp favourites from `space.tabs`, which made
202
+ 9 reported tabs against 21 real ones look like a bug.
203
+ - An `exact: true` option on the tools that take a selector, so a `text=`
204
+ selector can require the whole text instead of a substring. They also report
205
+ how many elements matched, so a label that hits 37 elements says so.
206
+ - `close_own_tabs` takes `include_stale`, and `arc_status` reports
207
+ `staleTabCount`, so tabs leaked by a dead earlier run of the same label can be
208
+ cleaned up on purpose instead of being swept up by a restart.
209
+ - `get_html` takes `nth` and reports how many elements matched, so a
210
+ multi-match selector is visible there too.
211
+ - `ARC_MCP_STATE_DIR` to override where tab ownership is recorded.
212
+ - `--version` and `--help` on the server binary, so an install can be checked
213
+ without an MCP client. `--help` prints the tool count and the environment
214
+ variables.
215
+ - Documentation of a genuine limitation that is not a bug: synthetic events
216
+ cannot drive widgets gated on trusted events. Wikipedia's search suggestions
217
+ never open from `fill`, and hand-dispatched per-character
218
+ `keydown`/`input`/`keyup` does not help, because Arc's
219
+ `execute javascript` gives no CDP access. The workaround does work: `fill`
220
+ with `submit: true`, or navigate straight to the search URL.
221
+ - Public project files: MIT license, contributing guide, security policy, code
222
+ of conduct, issue and pull request templates, and CI that runs the unit tests
223
+ on Node 20, 22 and 24 plus a syntax check over every file in `src/`.
224
+ - Distribution. This is the first version published to npm, as
225
+ `arc-control-mcp`, and listed in the MCP Registry as
226
+ `io.github.DB-25/arc-control-mcp`. Installing is now a config block running
227
+ `npx -y arc-control-mcp@latest` in any MCP client, instead of a clone plus a
228
+ hand-written absolute path, and upgrading is a restart rather than a `git
229
+ pull`. The npm package declares `"os": ["darwin"]`, so a Linux or Windows
230
+ install fails at once with `EBADPLATFORM` rather than succeeding and then
231
+ failing at the first Apple Event. There is no Docker image and there cannot
232
+ be one: Apple Events do not cross a container boundary.
233
+
234
+ ## [0.1.0] - 2026-08-27
235
+
236
+ Initial version.
237
+
238
+ - MCP server over stdio that drives Arc on macOS through JXA
239
+ (`osascript -l JavaScript`), with tool arguments injected as a JSON literal
240
+ rather than concatenated into script source.
241
+ - 26 tools in six modules: tabs, navigation, content, interaction, spaces and
242
+ scripting, composed by a registry that validates tool and handler parity at
243
+ load.
244
+ - Implicit tab targeting, per-agent tab ownership with a `mine` flag on every
245
+ tab, and `close_own_tabs` for cleanup.
246
+ - No focus stealing: `open_url` restores the user's previous tab selection when
247
+ Arc auto-selects a new tab, and only when Arc actually took it.
248
+ - Selectors accept CSS or `text=Some label`.
249
+ - `batch` for running several tools in one round trip, which matters because
250
+ each `osascript` spawn costs a few hundred milliseconds.
251
+ - Arc's raw AppleScript error codes mapped to messages that name the remedy,
252
+ including both required macOS permissions.
253
+
254
+ [0.3.0]: https://github.com/DB-25/arc-control-mcp/releases/tag/v0.3.0
255
+ [0.1.0]: https://github.com/DB-25/arc-control-mcp/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dhruv Baradiya
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.