pointclick 0.1.0__tar.gz

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.
@@ -0,0 +1,25 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ with:
18
+ python-version: ${{ matrix.python }}
19
+ - run: uv sync --group dev
20
+ - run: uv run ruff check .
21
+ - run: uv run ruff format --check .
22
+ - run: uv run playwright install --with-deps chromium
23
+ - run: uv run pytest -q
24
+ env:
25
+ BROWSER_CHANNEL: chromium
@@ -0,0 +1,19 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ pypi:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: astral-sh/setup-uv@v6
16
+ - name: Tag matches pyproject version
17
+ run: test "${GITHUB_REF_NAME#v}" = "$(uv version --short)"
18
+ - run: uv build
19
+ - run: uv publish --trusted-publishing always
@@ -0,0 +1,4 @@
1
+ .venv/
2
+ __pycache__/
3
+ .playwright-mcp/
4
+ dist/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dashgin
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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Browser Use
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.
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.5
2
+ Name: pointclick
3
+ Version: 0.1.0
4
+ Summary: A browser MCP server with a small footprint: a numbered list of what you can click, and eight tools.
5
+ Project-URL: Homepage, https://github.com/dashgin/pointclick
6
+ Project-URL: Issues, https://github.com/dashgin/pointclick/issues
7
+ Author-email: Dashgin <dashgin@praxis.az>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ License-File: LICENSE-jev
11
+ Keywords: agent,automation,browser,mcp,playwright
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: Testing
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: mcp>=2
19
+ Requires-Dist: playwright>=1.50
20
+ Description-Content-Type: text/markdown
21
+
22
+ # pointclick
23
+
24
+ **A browser MCP server in under 1,000 tokens.** The page comes back as a numbered list of what
25
+ you can click, type into or select. Your agent points at a number; pointclick clicks it.
26
+
27
+ [![PyPI](https://img.shields.io/pypi/v/pointclick.svg)](https://pypi.org/project/pointclick/)
28
+ [![CI](https://github.com/dashgin/pointclick/actions/workflows/ci.yml/badge.svg)](https://github.com/dashgin/pointclick/actions/workflows/ci.yml)
29
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
30
+
31
+ ```
32
+ url: https://en.wikipedia.org/wiki/Main_Page
33
+ title: Wikipedia, the free encyclopedia
34
+ [1] link 'Wikipedia The Free Encyclopedia' <CLICK>
35
+ [2] searchbox 'Search Wikipedia' <TYPE,CLICK>
36
+ [3] button 'Search' <CLICK>
37
+ ...
38
+ (more below: 3156px; SCROLL_DOWN or observe(full=true))
39
+
40
+ text:
41
+ Welcome to
42
+ Wikipedia
43
+ ...
44
+ ```
45
+
46
+ ```
47
+ act("TYPE", "2", "Ada Lovelace") → the new list, once the page has settled
48
+ act("PRESS", "2", "Enter")
49
+ ```
50
+
51
+ ## Why
52
+
53
+ Same task, same model (Claude Sonnet 5 in headless Claude Code), one browser MCP each —
54
+ sign in, search, sort, open a record, read a number off it. 18 of 18 runs got it right.
55
+
56
+ | | turns | tokens read | cost per run | time |
57
+ |---|---|---|---|---|
58
+ | **pointclick** | **7** | **104k** | **$0.056** | **15.8 s** |
59
+ | Playwright MCP | 13 | 266k | $0.118 | 27.5 s |
60
+ | agent-browser | 12 | 525k | $0.223 | 17.8 s |
61
+
62
+ Medians. Cost is the API price Claude Code reports; time is noisy at this sample size.
63
+
64
+ - **Eight tools, under 1k tokens of definitions.** Playwright MCP's are ~5k, Chrome DevTools MCP's
65
+ ~7k, agent-browser's ~18k. Clients that load every tool up front pay that on every turn.
66
+ - **Only what you can act on.** No wrapper `div`s, no layout tree: controls, their state, and the
67
+ visible text.
68
+ - **Every action returns the settled page.** No follow-up snapshot call, and no waiting for the
69
+ network to go quiet: long-polls and streams don't stall it.
70
+
71
+ ## Quick start
72
+
73
+ Needs Python 3.12+ and [uv](https://docs.astral.sh/uv/). Uses your installed Chrome, or
74
+ Playwright's Chromium if there is none (`uvx --from pointclick playwright install chromium`).
75
+
76
+ **Claude Code**
77
+
78
+ ```bash
79
+ claude mcp add pointclick -- uvx pointclick
80
+ ```
81
+
82
+ **Cursor, Claude Desktop, Windsurf, and other clients** — add to the MCP config:
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "pointclick": { "command": "uvx", "args": ["pointclick"] }
88
+ }
89
+ }
90
+ ```
91
+
92
+ **VS Code**
93
+
94
+ ```bash
95
+ code --add-mcp '{"name":"pointclick","command":"uvx","args":["pointclick"]}'
96
+ ```
97
+
98
+ **Codex**
99
+
100
+ ```bash
101
+ codex mcp add pointclick -- uvx pointclick
102
+ ```
103
+
104
+ ### Your first prompt
105
+
106
+ ```
107
+ Open news.ycombinator.com, go to the second page, and tell me the top story there.
108
+ ```
109
+
110
+ ## Tools
111
+
112
+ | Tool | |
113
+ |---|---|
114
+ | `navigate(url)` | Open a page, return the list |
115
+ | `observe(full=false)` | The list again. Viewport only unless `full` |
116
+ | `act(operation, target, text)` | `CLICK` `TYPE` `SELECT` `PRESS` `HOVER` `SCROLL_DOWN` `SCROLL_UP` `WAIT` |
117
+ | `upload(target, paths)` | Set files on a file input |
118
+ | `screenshot(full_page=false)` | JPEG |
119
+ | `evaluate(js, max_chars=6000)` | Run JS in the page, JSON back. A longer result is cut and says so; `0` for all of it |
120
+ | `console()` | Console messages and page errors since the last call |
121
+ | `close()` | End the session; the next call starts fresh |
122
+
123
+ `target` is a number from the list (`"3"`, or `"3:2"` for the second option of a select), or any
124
+ Playwright selector (`"input[type=password]"`, `"text=Send"`, `"role=button[name='OK']"`).
125
+ Password and file inputs are never in the list; reach them by selector.
126
+
127
+ | Env | |
128
+ |---|---|
129
+ | `HEADED=1` | Show the window |
130
+ | `BROWSER_CHANNEL` | `chrome` (default), `msedge`, or `chromium` for Playwright's bundled build |
131
+
132
+ ## How it works
133
+
134
+ - **The list** comes from one script that reads every visible control and the visible text in a
135
+ single pass — adapted from [jev-ultrafast](https://github.com/browser-use/jev-ultrafast)
136
+ (Browser Use × TypeSafe). It runs in every frame, cross-origin ones included, and a control that
137
+ only repeats its wrapper's label is listed once.
138
+ - **Settling.** After an action, pointclick returns once the page has been still for 150 ms and
139
+ no request younger than 0.5 s is pending — at most 2 s. Older requests (long-polls, streams)
140
+ don't hold it up. If the page is still busy after that, `WAIT` returns the moment it changes.
141
+ - **Numbers point at real elements**, not at a position. If a framework re-renders the element,
142
+ pointclick follows it when its label is unique on the page and says so; a duplicate label (two
143
+ "Start" buttons) is refused rather than guessed.
144
+ - **A timeout on a page that changed anyway** is reported that way, so the agent checks before
145
+ retrying instead of clicking twice.
146
+ - **Playwright underneath** handles selectors, uploads, popups (they become the current page),
147
+ screenshots and the console. Every session is a fresh, isolated browser context.
148
+
149
+ `server.py` is ~350 lines and `snapshot.js` ~110. Small enough to read before you trust it.
150
+
151
+ ## Benchmarks
152
+
153
+ `bench/` has a small SPA shaped like a real dashboard — demo login, a list with search and sort, a
154
+ detail page, API calls over the network, and an optional long-poll like a realtime fallback.
155
+
156
+ **With a real agent** — headless Claude Code, built-in tools off, the task given in plain words:
157
+
158
+ ```bash
159
+ uv run python bench/agent_race.py --runs 3 --model sonnet
160
+ ```
161
+
162
+ | | correct | turns | tokens read | tokens written | cost | time, no long-poll | time, long-poll open |
163
+ |---|---|---|---|---|---|---|---|
164
+ | **pointclick** | 6/6 | **7** | **104k** | **620–660** | **$0.056** | **13.7 s** | 16.7 s |
165
+ | Playwright MCP 0.0.81 | 6/6 | 13 | 266–289k | 1.1k | $0.113–0.118 | 23.3 s | 28.5 s |
166
+ | agent-browser 0.38.1 | 6/6 | 12 | 525k | 760–780 | $0.223 | 19.1 s | **14.8 s** |
167
+
168
+ Medians of 3 runs per column. Turns and tokens barely move between runs; time moves by up to a
169
+ third with model latency, so read it as a range. Playwright MCP's action replies link to a
170
+ snapshot file instead of including the page, so the model asks for a snapshot after each step.
171
+ agent-browser's actions return nothing, so every step is an action plus a snapshot too.
172
+
173
+ **Tools alone** — the same flow driven by a script, so no model time is included:
174
+
175
+ ```bash
176
+ uv run python bench/race.py --runs 8 --poll 0
177
+ ```
178
+
179
+ | | calls | text to the model | tool time | with a long-poll open |
180
+ |---|---|---|---|---|
181
+ | **pointclick** | **6** | **6.6k chars (~1.9k tokens)** | 2.25 s | **2.31 s** |
182
+ | Playwright MCP 0.0.81 | 6 | 17.0k chars (~4.9k tokens) | 3.32 s | 8.27 s |
183
+ | agent-browser 0.38.1 | 18 | 12.3k chars (~3.5k tokens) | **1.13 s** | 1.12 s |
184
+
185
+ agent-browser's tool time is lowest because its actions return nothing: it re-snapshots, and the
186
+ script polls for free. In a real agent every one of those calls is a model turn.
187
+
188
+ **Tool definitions**, sent with every request by clients that don't load tools lazily (all four
189
+ estimated the same way, JSON characters ÷ 3.5):
190
+
191
+ | | tools | definitions |
192
+ |---|---|---|
193
+ | **pointclick** | **8** | **~0.8k tokens** |
194
+ | Playwright MCP | 26 | ~5.2k tokens |
195
+ | Chrome DevTools MCP | 29 | ~6.9k tokens |
196
+ | agent-browser (`core`) | 29 | ~18.4k tokens |
197
+
198
+ Measured on an M-series Mac with Chrome 153. Raw results are in `bench/*.json`.
199
+
200
+ ## When to use something else
201
+
202
+ - **Playwright MCP** — you need network interception, tracing, PDF export, or the full Playwright
203
+ surface.
204
+ - **Chrome DevTools MCP** — performance traces, Lighthouse-style audits, deep DevTools debugging.
205
+ - **agent-browser** — you'd rather drive the browser from a shell (CLI + skills), want saved auth
206
+ profiles, or need its long tail of commands.
207
+ - **Stagehand** — you want natural-language `act`/`extract` backed by its own model, or hosted
208
+ browsers on Browserbase.
209
+
210
+ pointclick does one job: get an agent through a web app with as little text and as few turns as
211
+ it can — locally, with no account and no second model.
212
+
213
+ ## Security
214
+
215
+ - Everything on a page — text, labels, titles — is **untrusted input** to your model. A page can
216
+ try to instruct your agent. Keep the agent's permissions narrow and confirm consequential steps.
217
+ - Each session is an isolated browser context: no access to your Chrome profile, cookies or
218
+ passwords. Nothing persists after `close()`.
219
+ - `evaluate` runs arbitrary JavaScript in the page; `upload` reads files you name from disk.
220
+
221
+ ## Limits
222
+
223
+ - The list is viewport-only by default; long pages take `SCROLL_DOWN` or `observe(full=true)`.
224
+ - Every action returns the whole list, not a diff.
225
+ - Chromium only, one page at a time per server. Calls sent in parallel run one after another.
226
+ - No network inspection, PDF export or dialog control; `evaluate` covers some of it.
227
+
228
+ ## Development
229
+
230
+ ```bash
231
+ uv sync --group dev
232
+ uv run pytest # 45 tests; fixture pages served from two local origins
233
+ uv run ruff check . && uv run ruff format --check .
234
+ ```
235
+
236
+ To release, bump `version` in `pyproject.toml`, then push a matching tag (`v0.1.0`). `release.yml` publishes
237
+ to PyPI through trusted publishing; there is no token to keep.
238
+
239
+ ## Credits
240
+
241
+ The page snapshot is adapted from [jev-ultrafast](https://github.com/browser-use/jev-ultrafast),
242
+ MIT © 2026 Browser Use — see `LICENSE-jev`.
243
+
244
+ ## License
245
+
246
+ MIT
@@ -0,0 +1,225 @@
1
+ # pointclick
2
+
3
+ **A browser MCP server in under 1,000 tokens.** The page comes back as a numbered list of what
4
+ you can click, type into or select. Your agent points at a number; pointclick clicks it.
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/pointclick.svg)](https://pypi.org/project/pointclick/)
7
+ [![CI](https://github.com/dashgin/pointclick/actions/workflows/ci.yml/badge.svg)](https://github.com/dashgin/pointclick/actions/workflows/ci.yml)
8
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
+
10
+ ```
11
+ url: https://en.wikipedia.org/wiki/Main_Page
12
+ title: Wikipedia, the free encyclopedia
13
+ [1] link 'Wikipedia The Free Encyclopedia' <CLICK>
14
+ [2] searchbox 'Search Wikipedia' <TYPE,CLICK>
15
+ [3] button 'Search' <CLICK>
16
+ ...
17
+ (more below: 3156px; SCROLL_DOWN or observe(full=true))
18
+
19
+ text:
20
+ Welcome to
21
+ Wikipedia
22
+ ...
23
+ ```
24
+
25
+ ```
26
+ act("TYPE", "2", "Ada Lovelace") → the new list, once the page has settled
27
+ act("PRESS", "2", "Enter")
28
+ ```
29
+
30
+ ## Why
31
+
32
+ Same task, same model (Claude Sonnet 5 in headless Claude Code), one browser MCP each —
33
+ sign in, search, sort, open a record, read a number off it. 18 of 18 runs got it right.
34
+
35
+ | | turns | tokens read | cost per run | time |
36
+ |---|---|---|---|---|
37
+ | **pointclick** | **7** | **104k** | **$0.056** | **15.8 s** |
38
+ | Playwright MCP | 13 | 266k | $0.118 | 27.5 s |
39
+ | agent-browser | 12 | 525k | $0.223 | 17.8 s |
40
+
41
+ Medians. Cost is the API price Claude Code reports; time is noisy at this sample size.
42
+
43
+ - **Eight tools, under 1k tokens of definitions.** Playwright MCP's are ~5k, Chrome DevTools MCP's
44
+ ~7k, agent-browser's ~18k. Clients that load every tool up front pay that on every turn.
45
+ - **Only what you can act on.** No wrapper `div`s, no layout tree: controls, their state, and the
46
+ visible text.
47
+ - **Every action returns the settled page.** No follow-up snapshot call, and no waiting for the
48
+ network to go quiet: long-polls and streams don't stall it.
49
+
50
+ ## Quick start
51
+
52
+ Needs Python 3.12+ and [uv](https://docs.astral.sh/uv/). Uses your installed Chrome, or
53
+ Playwright's Chromium if there is none (`uvx --from pointclick playwright install chromium`).
54
+
55
+ **Claude Code**
56
+
57
+ ```bash
58
+ claude mcp add pointclick -- uvx pointclick
59
+ ```
60
+
61
+ **Cursor, Claude Desktop, Windsurf, and other clients** — add to the MCP config:
62
+
63
+ ```json
64
+ {
65
+ "mcpServers": {
66
+ "pointclick": { "command": "uvx", "args": ["pointclick"] }
67
+ }
68
+ }
69
+ ```
70
+
71
+ **VS Code**
72
+
73
+ ```bash
74
+ code --add-mcp '{"name":"pointclick","command":"uvx","args":["pointclick"]}'
75
+ ```
76
+
77
+ **Codex**
78
+
79
+ ```bash
80
+ codex mcp add pointclick -- uvx pointclick
81
+ ```
82
+
83
+ ### Your first prompt
84
+
85
+ ```
86
+ Open news.ycombinator.com, go to the second page, and tell me the top story there.
87
+ ```
88
+
89
+ ## Tools
90
+
91
+ | Tool | |
92
+ |---|---|
93
+ | `navigate(url)` | Open a page, return the list |
94
+ | `observe(full=false)` | The list again. Viewport only unless `full` |
95
+ | `act(operation, target, text)` | `CLICK` `TYPE` `SELECT` `PRESS` `HOVER` `SCROLL_DOWN` `SCROLL_UP` `WAIT` |
96
+ | `upload(target, paths)` | Set files on a file input |
97
+ | `screenshot(full_page=false)` | JPEG |
98
+ | `evaluate(js, max_chars=6000)` | Run JS in the page, JSON back. A longer result is cut and says so; `0` for all of it |
99
+ | `console()` | Console messages and page errors since the last call |
100
+ | `close()` | End the session; the next call starts fresh |
101
+
102
+ `target` is a number from the list (`"3"`, or `"3:2"` for the second option of a select), or any
103
+ Playwright selector (`"input[type=password]"`, `"text=Send"`, `"role=button[name='OK']"`).
104
+ Password and file inputs are never in the list; reach them by selector.
105
+
106
+ | Env | |
107
+ |---|---|
108
+ | `HEADED=1` | Show the window |
109
+ | `BROWSER_CHANNEL` | `chrome` (default), `msedge`, or `chromium` for Playwright's bundled build |
110
+
111
+ ## How it works
112
+
113
+ - **The list** comes from one script that reads every visible control and the visible text in a
114
+ single pass — adapted from [jev-ultrafast](https://github.com/browser-use/jev-ultrafast)
115
+ (Browser Use × TypeSafe). It runs in every frame, cross-origin ones included, and a control that
116
+ only repeats its wrapper's label is listed once.
117
+ - **Settling.** After an action, pointclick returns once the page has been still for 150 ms and
118
+ no request younger than 0.5 s is pending — at most 2 s. Older requests (long-polls, streams)
119
+ don't hold it up. If the page is still busy after that, `WAIT` returns the moment it changes.
120
+ - **Numbers point at real elements**, not at a position. If a framework re-renders the element,
121
+ pointclick follows it when its label is unique on the page and says so; a duplicate label (two
122
+ "Start" buttons) is refused rather than guessed.
123
+ - **A timeout on a page that changed anyway** is reported that way, so the agent checks before
124
+ retrying instead of clicking twice.
125
+ - **Playwright underneath** handles selectors, uploads, popups (they become the current page),
126
+ screenshots and the console. Every session is a fresh, isolated browser context.
127
+
128
+ `server.py` is ~350 lines and `snapshot.js` ~110. Small enough to read before you trust it.
129
+
130
+ ## Benchmarks
131
+
132
+ `bench/` has a small SPA shaped like a real dashboard — demo login, a list with search and sort, a
133
+ detail page, API calls over the network, and an optional long-poll like a realtime fallback.
134
+
135
+ **With a real agent** — headless Claude Code, built-in tools off, the task given in plain words:
136
+
137
+ ```bash
138
+ uv run python bench/agent_race.py --runs 3 --model sonnet
139
+ ```
140
+
141
+ | | correct | turns | tokens read | tokens written | cost | time, no long-poll | time, long-poll open |
142
+ |---|---|---|---|---|---|---|---|
143
+ | **pointclick** | 6/6 | **7** | **104k** | **620–660** | **$0.056** | **13.7 s** | 16.7 s |
144
+ | Playwright MCP 0.0.81 | 6/6 | 13 | 266–289k | 1.1k | $0.113–0.118 | 23.3 s | 28.5 s |
145
+ | agent-browser 0.38.1 | 6/6 | 12 | 525k | 760–780 | $0.223 | 19.1 s | **14.8 s** |
146
+
147
+ Medians of 3 runs per column. Turns and tokens barely move between runs; time moves by up to a
148
+ third with model latency, so read it as a range. Playwright MCP's action replies link to a
149
+ snapshot file instead of including the page, so the model asks for a snapshot after each step.
150
+ agent-browser's actions return nothing, so every step is an action plus a snapshot too.
151
+
152
+ **Tools alone** — the same flow driven by a script, so no model time is included:
153
+
154
+ ```bash
155
+ uv run python bench/race.py --runs 8 --poll 0
156
+ ```
157
+
158
+ | | calls | text to the model | tool time | with a long-poll open |
159
+ |---|---|---|---|---|
160
+ | **pointclick** | **6** | **6.6k chars (~1.9k tokens)** | 2.25 s | **2.31 s** |
161
+ | Playwright MCP 0.0.81 | 6 | 17.0k chars (~4.9k tokens) | 3.32 s | 8.27 s |
162
+ | agent-browser 0.38.1 | 18 | 12.3k chars (~3.5k tokens) | **1.13 s** | 1.12 s |
163
+
164
+ agent-browser's tool time is lowest because its actions return nothing: it re-snapshots, and the
165
+ script polls for free. In a real agent every one of those calls is a model turn.
166
+
167
+ **Tool definitions**, sent with every request by clients that don't load tools lazily (all four
168
+ estimated the same way, JSON characters ÷ 3.5):
169
+
170
+ | | tools | definitions |
171
+ |---|---|---|
172
+ | **pointclick** | **8** | **~0.8k tokens** |
173
+ | Playwright MCP | 26 | ~5.2k tokens |
174
+ | Chrome DevTools MCP | 29 | ~6.9k tokens |
175
+ | agent-browser (`core`) | 29 | ~18.4k tokens |
176
+
177
+ Measured on an M-series Mac with Chrome 153. Raw results are in `bench/*.json`.
178
+
179
+ ## When to use something else
180
+
181
+ - **Playwright MCP** — you need network interception, tracing, PDF export, or the full Playwright
182
+ surface.
183
+ - **Chrome DevTools MCP** — performance traces, Lighthouse-style audits, deep DevTools debugging.
184
+ - **agent-browser** — you'd rather drive the browser from a shell (CLI + skills), want saved auth
185
+ profiles, or need its long tail of commands.
186
+ - **Stagehand** — you want natural-language `act`/`extract` backed by its own model, or hosted
187
+ browsers on Browserbase.
188
+
189
+ pointclick does one job: get an agent through a web app with as little text and as few turns as
190
+ it can — locally, with no account and no second model.
191
+
192
+ ## Security
193
+
194
+ - Everything on a page — text, labels, titles — is **untrusted input** to your model. A page can
195
+ try to instruct your agent. Keep the agent's permissions narrow and confirm consequential steps.
196
+ - Each session is an isolated browser context: no access to your Chrome profile, cookies or
197
+ passwords. Nothing persists after `close()`.
198
+ - `evaluate` runs arbitrary JavaScript in the page; `upload` reads files you name from disk.
199
+
200
+ ## Limits
201
+
202
+ - The list is viewport-only by default; long pages take `SCROLL_DOWN` or `observe(full=true)`.
203
+ - Every action returns the whole list, not a diff.
204
+ - Chromium only, one page at a time per server. Calls sent in parallel run one after another.
205
+ - No network inspection, PDF export or dialog control; `evaluate` covers some of it.
206
+
207
+ ## Development
208
+
209
+ ```bash
210
+ uv sync --group dev
211
+ uv run pytest # 45 tests; fixture pages served from two local origins
212
+ uv run ruff check . && uv run ruff format --check .
213
+ ```
214
+
215
+ To release, bump `version` in `pyproject.toml`, then push a matching tag (`v0.1.0`). `release.yml` publishes
216
+ to PyPI through trusted publishing; there is no token to keep.
217
+
218
+ ## Credits
219
+
220
+ The page snapshot is adapted from [jev-ultrafast](https://github.com/browser-use/jev-ultrafast),
221
+ MIT © 2026 Browser Use — see `LICENSE-jev`.
222
+
223
+ ## License
224
+
225
+ MIT
@@ -0,0 +1,110 @@
1
+ [
2
+ {
3
+ "run": 0,
4
+ "tool": "pointclick",
5
+ "ok": true,
6
+ "answer": "14",
7
+ "s": 17.4,
8
+ "wall_s": 22.3,
9
+ "turns": 7,
10
+ "context_tokens": 88417,
11
+ "output_tokens": 646,
12
+ "cost_usd": 0.0725808
13
+ },
14
+ {
15
+ "run": 0,
16
+ "tool": "playwright",
17
+ "ok": true,
18
+ "answer": "14",
19
+ "s": 31.3,
20
+ "wall_s": 36.3,
21
+ "turns": 13,
22
+ "context_tokens": 263170,
23
+ "output_tokens": 1143,
24
+ "cost_usd": 0.1277116
25
+ },
26
+ {
27
+ "run": 0,
28
+ "tool": "agent-browser",
29
+ "ok": true,
30
+ "answer": "14",
31
+ "s": 30.2,
32
+ "wall_s": 34.5,
33
+ "turns": 12,
34
+ "context_tokens": 525561,
35
+ "output_tokens": 879,
36
+ "cost_usd": 0.24370040000000004
37
+ },
38
+ {
39
+ "run": 1,
40
+ "tool": "playwright",
41
+ "ok": true,
42
+ "answer": "14",
43
+ "s": 23.3,
44
+ "wall_s": 28.0,
45
+ "turns": 14,
46
+ "context_tokens": 292592,
47
+ "output_tokens": 1140,
48
+ "cost_usd": 0.11849500000000002
49
+ },
50
+ {
51
+ "run": 1,
52
+ "tool": "agent-browser",
53
+ "ok": true,
54
+ "answer": "14",
55
+ "s": 19.1,
56
+ "wall_s": 24.0,
57
+ "turns": 12,
58
+ "context_tokens": 524845,
59
+ "output_tokens": 756,
60
+ "cost_usd": 0.22254819999999997
61
+ },
62
+ {
63
+ "run": 1,
64
+ "tool": "pointclick",
65
+ "ok": true,
66
+ "answer": "14",
67
+ "s": 13.7,
68
+ "wall_s": 18.1,
69
+ "turns": 7,
70
+ "context_tokens": 104512,
71
+ "output_tokens": 620,
72
+ "cost_usd": 0.055791
73
+ },
74
+ {
75
+ "run": 2,
76
+ "tool": "agent-browser",
77
+ "ok": true,
78
+ "answer": "14",
79
+ "s": 16.4,
80
+ "wall_s": 20.9,
81
+ "turns": 12,
82
+ "context_tokens": 525054,
83
+ "output_tokens": 780,
84
+ "cost_usd": 0.22290980000000002
85
+ },
86
+ {
87
+ "run": 2,
88
+ "tool": "pointclick",
89
+ "ok": true,
90
+ "answer": "14",
91
+ "s": 13.4,
92
+ "wall_s": 17.8,
93
+ "turns": 7,
94
+ "context_tokens": 104249,
95
+ "output_tokens": 581,
96
+ "cost_usd": 0.0552002
97
+ },
98
+ {
99
+ "run": 2,
100
+ "tool": "playwright",
101
+ "ok": true,
102
+ "answer": "14",
103
+ "s": 21.7,
104
+ "wall_s": 26.4,
105
+ "turns": 13,
106
+ "context_tokens": 265844,
107
+ "output_tokens": 1038,
108
+ "cost_usd": 0.11100080000000001
109
+ }
110
+ ]