@predotdev/mcp 2.1.1 → 2.1.2

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/README.md CHANGED
@@ -132,7 +132,7 @@ Once you are signed in to pre.dev (`setup` or `login` does it, or set `PREDEV_AP
132
132
  - `chrome_act`: click or type into an element described in plain words, like *"the Create button in the dialog"*, in under a second with no snapshot needed. If it isn't sure, it lists the likely matches instead of guessing.
133
133
  - `chrome_wait` with `condition`: wait until a plain-words statement about the page is true, like *"the export has finished"*.
134
134
 
135
- **Pricing.** The MCP server is free and open source, and the Chrome tools run free on your Mac. Plain-words actions and the cloud tools use pre.dev credits, the same credits as the rest of pre.dev ([pricing](https://pre.dev/pricing)). Each action costs a small fraction of one credit, and the free trial's credits cover more than a thousand actions. They show up in your usage as `browser-agents-local`, and `check` shows your plan. If the free plan's allowance or your credits run out, your agent tells you and offers to open the [billing page](https://pre.dev/billing) in your Chrome; everything else keeps working. Every other tool runs entirely on your machine and never calls pre.dev.
135
+ **Pricing.** The MCP server is free and open source, and the Chrome tools run free on your Mac. Plain-words actions and the cloud tools use pre.dev credits, the same credits as the rest of pre.dev ([pricing](https://pre.dev/pricing)). Each action costs a small fraction of one credit, and the free trial's credits cover hundreds of them. They show up in your usage as `browser-agents-local`, and `check` shows your plan. If the free plan's allowance or your credits run out, your agent tells you and offers to open the [billing page](https://pre.dev/billing) in your Chrome; everything else keeps working. Every other tool runs entirely on your machine and never calls pre.dev.
136
136
 
137
137
  ## Setup for every agent
138
138
 
@@ -516,6 +516,7 @@ npx -y @predotdev/mcp uninstall # remove it from every agent and delete its fil
516
516
  | You had the hosted pre.dev MCP or an older `chrome` install | `setup` replaced it; this server has all of its tools. |
517
517
  | The agent can't start the server, or `npx`/`node` not found | Run `setup`: it registers full paths that work in desktop apps. |
518
518
  | Codex says a tool call timed out | Set `tool_timeout_sec = 900` (see the Codex setup). |
519
+ | `codex exec` says a tool call `requires approval, but approval policy is never` | Non-interactive Codex can't ask you, so allow the tools for that run: `codex exec -c 'mcp_servers.predev.default_tools_approval_mode="approve"' "..."`. |
519
520
  | Anything else | Run `npx -y @predotdev/mcp check`, and look at `~/.predev/mcp/predev-mcp.log`. |
520
521
 
521
522
  ## Platform support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@predotdev/mcp",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "pre.dev in any coding agent: Browser Agents in the Chrome you already use and in the cloud, plus specs and plans. One command to set up. Free and open source.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/tools.mjs CHANGED
@@ -180,10 +180,15 @@ async function pageLine(info, session) {
180
180
  return `Tab ${tabId(info)} · ${label(profileOf(info))} · ${(title || '(untitled)').slice(0, 100)}\n${shortUrl}`;
181
181
  }
182
182
 
183
+ // Dev servers on this computer or the local network, which rarely speak https.
184
+ const LOCAL_HOST = /^(localhost|127(\.\d{1,3}){3}|0\.0\.0\.0|\[::1\]|10(\.\d{1,3}){3}|192\.168(\.\d{1,3}){2}|172\.(1[6-9]|2\d|3[01])(\.\d{1,3}){2}|[^\s/:?#@]+\.(localhost|test))(:\d+)?([/?#]|$)/i;
185
+
183
186
  function normalizeUrl(url) {
184
187
  const value = String(url || '').trim();
185
188
  if (!value) throw new Error('Pass a url.');
186
- return /^[a-z][a-z0-9+.-]*:/i.test(value) ? value : `https://${value}`;
189
+ // "https:", "about:", "chrome:"... is a scheme, but "localhost:3000/app" is a host and port.
190
+ if (/^[a-z][a-z0-9+.-]*:/i.test(value) && !/^[^\s/:?#@]+:\d+([/?#]|$)/.test(value)) return value;
191
+ return `${LOCAL_HOST.test(value) ? 'http' : 'https'}://${value}`;
187
192
  }
188
193
 
189
194
  const isLocalDev = url => {
@@ -274,9 +279,10 @@ function shortHref(href) {
274
279
  return s.length > 70 ? s.slice(0, 69) + '…' : s;
275
280
  } catch (e) { return ''; }
276
281
  }
277
- function snapshot(max) {
282
+ // peek: list the elements without replacing the refs the agent got from its last snapshot.
283
+ function snapshot(max, peek) {
278
284
  const refs = new Map();
279
- window.__agentRefs = refs;
285
+ if (!peek) window.__agentRefs = refs;
280
286
  const found = new Set();
281
287
  collect(document, found);
282
288
  const all = [...found];
@@ -382,7 +388,7 @@ function point(q) {
382
388
  while (hit?.shadowRoot) { const inner = hit.shadowRoot.elementFromPoint(x, y); if (!inner || inner === hit) break; hit = inner; }
383
389
  if (hit && !contains(el, hit) && !contains(hit, el)) covered = describe(hit);
384
390
  }
385
- return { x, y, desc: describe(el), covered };
391
+ return { x, y, desc: describe(el), covered, select: el.tagName === 'SELECT' };
386
392
  }
387
393
  function focus(q, clear) {
388
394
  const f = find(q);
@@ -548,11 +554,17 @@ async function matchElement(session, description, ctx) {
548
554
  return { ref: answer.ref || null, desc: answer.text, p: Number(answer.confidence) || 0, list };
549
555
  }
550
556
 
557
+ // What a plain-words wait checks: the page's text and its interactive elements, since an empty field
558
+ // or an icon button has no text of its own. Elements go first: pre.dev reads the first 12,000 characters.
551
559
  async function pageState(session) {
552
- return evaluate(session, `(() => {
560
+ const [head, text] = await evaluate(session, `(() => {
553
561
  const dialog = [...document.querySelectorAll('[role=dialog],[aria-modal=true],dialog[open],[role=alert]')].map(d => d.innerText).join('\\n').slice(0, 1500);
554
- return 'Title: ' + document.title + '\\nURL: ' + location.href + (dialog ? '\\nDialog/alert: ' + dialog : '') + '\\n\\n' + (document.body ? document.body.innerText : '').slice(0, 8000);
562
+ return ['Title: ' + document.title + '\\nURL: ' + location.href + (dialog ? '\\nDialog/alert: ' + dialog : ''), document.body ? document.body.innerText : ''];
555
563
  })()`, 10000);
564
+ const snap = await inPage(session, 'snapshot(150, true)', 10000).catch(() => '');
565
+ const elements = String(snap).split('\n').filter(line => /^\[e\d+\] /.test(line)).map(line => line.replace(/^\[e\d+\] /, '- ')).join('\n').slice(0, 3000);
566
+ const room = Math.min(8000, Math.max(2000, 11500 - head.length - elements.length));
567
+ return `${head}${elements ? `\nInteractive elements:\n${elements}` : ''}\n\nText:\n${text.slice(0, room)}`;
556
568
  }
557
569
 
558
570
  // ---------------------------------------------------------------- tools
@@ -681,6 +693,8 @@ const TOOLS = [
681
693
  ({ x, y } = hit);
682
694
  what = hit.desc;
683
695
  if (hit.covered) note = `\nNote: ${hit.covered} was on top of it at that point; the click went there.`;
696
+ // A click only opens a dropdown's list; arrow keys and option clicks don't reach it in every Chrome.
697
+ if (hit.select) note += `\nNote: this is a dropdown. To pick an option, call chrome_type with this element and the option's text.`;
684
698
  }
685
699
  await click(session, x, y, args.double ? 2 : 1);
686
700
  await settle(session, before);