Noodle

Docs / Agents guide

Agents

Noodle has a built-in MCP server. An agent connected to it drives real tabs in your profile, with your logins, through a small set of tools built to need few turns and few tokens: snapshots with short element refs, batched actions that return only what changed, and typed answers from the DOM that take milliseconds.

The server is off until you turn it on, and it only listens on 127.0.0.1.

Connecting an agent

Turn it on in Settings > Agent. The section shows the status, the port (47821 unless you change it), a token, and two snippets ready to copy. The server's address is http://127.0.0.1:47821/mcp (Streamable HTTP), and every request needs the token as a bearer header.

A client that speaks HTTP needs the address and the header:

{
  "type": "http",
  "url": "http://127.0.0.1:47821/mcp",
  "headers": { "Authorization": "Bearer <token>" }
}

Clients that only start local commands use the stdio bridge that ships with Noodle (resources/noodle-mcp.mjs, needs Node 18 or later and nothing else). Settings > Agent fills in the bridge's real path:

{
  "mcpServers": {
    "noodle": {
      "command": "node",
      "args": ["<path from Settings>/noodle-mcp.mjs"],
      "env": {
        "NOODLE_MCP_URL": "http://127.0.0.1:47821/mcp",
        "NOODLE_MCP_TOKEN": "<token>"
      }
    }
  }
}

Any other MCP client works the same way: point it at the address with the header, or start the bridge. The bridge reconnects on its own when Noodle restarts or the token changes.

New token in Settings closes every open session. The server refuses requests whose Host or Origin isn't the loopback address with its port, bodies over 4 MB, and more than 16 sessions.

How results look

Every tool result starts with a status line, and carries the same status in structuredContent, so a harness can branch without reading the text:

status: navigated
done: 2/2
tab 3 | Search results | https://duckduckgo.com/?q=noodle
...
Status Meaning
ok Done
navigated act finished and the page changed to another document
element_not_found A ref, selector or search found nothing
waiting_for_human A CAPTCHA is in the way; you have been asked to solve it
blocked_by_policy Refused: you denied it, it is outside the allowed sites, or Stop is on
timeout A wait or script ran out of time
error Anything else, with a message

structuredContent also holds tab_id for tools that work on a tab, plus a few tool-specific fields listed below.

Tools

Most tools take an optional tab_id. Without it they use the last tab the session used.

list_tabs

No input. One line per tab: id, space, title, URL and flags (current, shown to user, loading, unloaded, an error). Tabs on sites outside the allowed list show no title or URL. Result field: tab_count.

open_tab

url (an address or search words), snapshot (default true), max_tokens (default 1500). Opens a tab in the Agent space, waits for it to load (up to 10 seconds) and returns a snapshot.

close_tab

tab_id. Closing a tab the agent didn't open asks you first.

snapshot

max_tokens (default 3000), selector (CSS, only that part of the page), viewport_only. The visible, interactive elements with refs, plus headings, landmarks and key text:

tab 3 | Example Domain | https://example.com/
scroll 0/600 (viewport 600)
# Example Domain
This domain is for use in illustrative examples in documents.
[e1] link "More information..."

Refs last until the page navigates. Over the budget, plain text is dropped first, then the list is cut with a note saying how to get the rest.

act

actions (1 to 30, run in order), max_tokens for the diff (default 1500), settle_ms (default 5000). Actions:

[
  { "click": "e4" },
  { "type": "e7", "text": "noodle browser", "submit": true },
  { "press": "Escape" },
  { "select": "e9", "value": "Large" },
  { "scroll": "down", "amount": 2 },
  { "hover": "e12" },
  { "navigate": "https://example.com" },
  { "back": true },
  { "wait": 500, "for": "Results", "timeout": 8000 }
]

Clicks and typing are real input events at the element, after scrolling it into view; force: true clicks from script when something covers the element. The batch stops at the first failure. After the last action Noodle waits until the network is idle and the DOM has been quiet, then returns done: n/m and either a diff (+ added, - removed, ~ changed) or, after a navigation, a fresh snapshot. Result field: navigated.

read_page

selector, max_tokens (default 6000). The page as Markdown. On pages that read as articles it returns the same text reader view shows.

find

query, regex, max_results (default 20). Matches with context and the ref of the nearest element. Result field: total.

extract

Either selector with an optional attr for a list of values, or item_selector with fields for a list of objects. limit up to 500.

{
  "item_selector": ".result",
  "fields": {
    "title": "h2",
    "link": { "selector": "a", "attr": "href" },
    "tags": { "selector": ".tag", "all": true }
  }
}

Returns {"count": n, "data": [...]}. Result field: count.

check

Typed answers from the DOM, usually in 10 to 50 ms. Ask several at once:

  • exists: { ref }, { text } or { selector }. Answers { exists, visible, count }.
  • element: a ref. Answers tag, role, name, visible, in_viewport, enabled, checked, value (password fields only say whether they are filled), text and href.
  • page_state: true. Answers { state, evidence, also }, where state is one of loaded, loading, captcha, error_page, login_wall, paywall, cookie_banner or unknown.
  • is_sensitive: one act action, classified and not run. Answers { sensitive, blocked, category, reason, verdict }.
{ "page_state": true, "exists": { "text": "Sign out" }, "is_sensitive": { "click": "e5" } }

The answers are also in structuredContent, next to status.

devtools

network and console (both default true), cookies (names and flags, never values), filter, errors_only, limit (default 50 per list). Requests and console messages from the time the agent used the tab.

screenshot

max_width (default 1280). A PNG of the tab. The text tools are cheaper; use this when they aren't enough.

Tools from userscripts

Userscripts can add their own tools, named scriptname.toolname. They show up in the tool list once the script has run on a page, and the list changes (with a tools/list_changed notification) when scripts are turned on, off or edited. See userscripts.md.

Approvals

A local rule engine classifies every action before it runs. Nothing about the page leaves your machine for this. Actions that look like a purchase, a payment field, sending a message, deleting something, a download, the first form submit to a site, or closing one of your own tabs are sensitive, and so is every call to a userscript tool.

A sensitive action shows a card in the agent panel in the sidebar: what it will do, the element, the site and the reason, with a two minute countdown. Passwords and secret-looking fields show as dots and card numbers as their last four digits. The answers are Deny, Allow once, and Allow on the site for this session. With no answer in two minutes the call returns blocked_by_policy. When Noodle isn't focused, the taskbar flashes and a notification appears.

Allow on a site is kept until you restart or lock Noodle, and Settings > Agent lists those grants with remove buttons.

Bypass

Settings > Agent > Sensitive actions has two choices: Ask me first (the default) and Bypass, never ask. The command palette can switch it too. With bypass on, sensitive actions run without asking, and a "Bypass" label stays in the top bar the whole time. Allowed sites and blocked addresses still apply, and Stop still works.

Allowed sites

Settings > Agent > Allowed sites takes one site per line; example.com covers its subdomains too. Empty means any site. With a list set, agents can't open or navigate to other sites, and the reading tools refuse tabs that show one. Addresses other than http and https (file:, chrome: and the rest) are always refused.

Stop

The stop button in the agent panel, the "Stop agents (kill switch)" palette command and Ctrl+Shift+. (Cmd+Shift+. on macOS) stop every agent at once. Calls in flight are aborted, waiting approvals are refused, pages being acted on stop loading, and new calls get blocked_by_policy until you press Resume. The shortcut only belongs to Noodle while an agent is connected or working; otherwise the page gets the key.

CAPTCHAs

Noodle never tries to solve a CAPTCHA. When open_tab, act or check finds one, the call returns waiting_for_human, the tab comes to the front with a banner, and you get a notification. Noodle checks every 1.5 seconds and clears the wait once the challenge is gone or solved, so the agent's next call works again. Invisible widgets, such as the reCAPTCHA badge, don't count.

What you see while an agent works

The agent panel in the sidebar shows the status, the client's name and a log of calls, newest first. Click an entry to open its tab. Tabs the agent uses get an outline and a bot icon in the tab list and an "Agent" label in the top bar. The element being clicked or typed into gets a one second outline.

Writing fast agent loops

Turns cost more than anything else, so:

  • Ask check before reaching for a snapshot. { "page_state": true } tells you about a login wall or a cookie banner in one call, and exists answers "did my search load" without reading the page.
  • Put a whole form in one act: type each field, then click submit. The diff that comes back is usually enough to plan the next step.
  • Keep max_tokens low and use selector or viewport_only on long pages. Snapshots drop plain text first when over budget, so refs survive.
  • Use extract for lists instead of reading the page and parsing the Markdown.
  • Use wait with for inside act rather than polling with snapshots.
  • Branch on structuredContent.status. navigated means your refs are gone and the result already holds the new snapshot.

Limits

  • Cross-origin iframes and closed shadow roots are listed but not walked, so there are no refs inside them (embedded payment forms, for one).
  • Permission prompts from agent tabs wait for you; act only mentions them.
  • A link that downloads a file without looking like a download isn't caught by the rules.
  • Typing into a payment field always asks, even if you said yes in the chat.
  • Native <select> menus are set from script.
  • While the window is minimized pages don't render: clicks and typing fall back to script (act says so) and screenshots fail.
  • The stop shortcut only works inside Noodle, and Stop does not survive a restart.