Content
Content tools are how an agent reads the page. They are all typed accessors. There is no generic script execution surface. This is a deliberate constraint (OQ-2 in the design notes) so that every byte leaving the browser can be routed through the credential firewall before it reaches the client.
There are two layers of content tools:
- Page-level tools return whole-page material as a single response. Good for “read this article” and “search this doc”.
- Node-level tools work through a resolved node reference (
@ref). Good for “click the third button in the modal” once you have located it.
Page-level tools
Section titled “Page-level tools”browser_page_content
Section titled “browser_page_content”Return the full readable content of the page in a structured form.
Parameters
Section titled “Parameters”| Name | Type | Required | Description |
|------|------|----------|-------------|
| tab_id | string | yes | Target tab. |
| format | string | no | One of markdown (default), text, html. |
Response
Section titled “Response”{ "url": "https://example.com/article", "title": "How browsers handle paste", "byline": "Anna Reed", "reading_time_min": 7, "word_count": 1844, "content": "# How browsers handle paste\n\n..."}The content field is the reader-mode extraction. The format argument controls its representation only, not what is extracted.
browser_page_text
Section titled “browser_page_text”Return only the visible text of the page. No structure, no HTML.
Parameters
Section titled “Parameters”| Name | Type | Required |
|------|------|----------|
| tab_id | string | yes |
Response
Section titled “Response”{ "url": "https://example.com/article", "text": "How browsers handle paste. When you paste into a web page..."}Useful for wc, tokenizers, embedding, or anything that expects a flat string.
browser_search_in_page
Section titled “browser_search_in_page”Search page text with a regex and return match ranges.
Parameters
Section titled “Parameters”| Name | Type | Required | Description |
|------|------|----------|-------------|
| tab_id | string | yes | Target tab. |
| pattern | string | yes | RE2-compatible pattern. |
| case_sensitive | boolean | no | Default: false. |
| max_matches | integer | no | Cap. Default: 100. Hard maximum: 1000. |
Response
Section titled “Response”{ "matches": [ { "text": "paste event", "offset": 132, "line": 4 }, { "text": "paste event", "offset": 1043, "line": 21 } ], "total_matches": 2, "truncated": false}truncated is true when the response was capped at max_matches and more matches exist.
browser_page_context
Section titled “browser_page_context”High-level summary of the page. Useful as a cheap first call.
Parameters
Section titled “Parameters”| Name | Type | Required |
|------|------|----------|
| tab_id | string | yes |
Response
Section titled “Response”{ "url": "https://example.com/article", "title": "How browsers handle paste", "byline": "Anna Reed", "excerpt": "A short summary pulled from the page metadata.", "reading_time_min": 7, "word_count": 1844, "language": "en"}The excerpt is derived from <meta name="description">, Open Graph, or the first paragraph, in that order. It is not generated by a model.
Node-level tools
Section titled “Node-level tools”Node-level tools operate on a reference (@ref) resolved by page_query_selector. References are opaque strings, valid for the lifetime of the current page load in the target tab, and invalidated on navigation.
page_query_selector
Section titled “page_query_selector”Resolve a CSS selector to a node reference.
Parameters
Section titled “Parameters”| Name | Type | Required | Description |
|------|------|----------|-------------|
| tab_id | string | yes | Target tab. |
| selector | string | yes | Standard CSS selector. |
| all | boolean | no | Return every match instead of the first. Default: false. |
Response
Section titled “Response”{ "refs": [ { "ref": "@ref_a1", "tag": "button", "text_preview": "Sign up" } ]}text_preview is a short excerpt for orientation. Do not treat it as content.
page_get_text
Section titled “page_get_text”Read the visible text of a resolved node.
Parameters
Section titled “Parameters”| Name | Type | Required |
|------|------|----------|
| tab_id | string | yes |
| ref | string | yes |
Response
Section titled “Response”{ "text": "Sign up to continue" }page_get_attribute
Section titled “page_get_attribute”Read an attribute of a resolved node.
Parameters
Section titled “Parameters”| Name | Type | Required |
|------|------|----------|
| tab_id | string | yes |
| ref | string | yes |
| name | string | yes |
Response
Section titled “Response”{ "name": "href", "value": "/signup" }page_wait_for_selector
Section titled “page_wait_for_selector”Wait until a selector resolves. Returns as soon as it does or the timeout elapses.
Parameters
Section titled “Parameters”| Name | Type | Required | Description |
|------|------|----------|-------------|
| tab_id | string | yes | Target tab. |
| selector | string | yes | CSS selector to wait for. |
| timeout_ms | integer | no | Max wait. Default: 5000. Hard maximum: 30000. |
| state | string | no | attached (default), visible, hidden. |
Response
Section titled “Response”{ "ref": "@ref_b2", "waited_ms": 342 }Timeout returns -32603 with data.reason = "timeout".
Egress rules
Section titled “Egress rules”Every response from every content tool passes through the credential firewall’s four vectors before it lands in your client:
- Password fields:
<input type="password">value redacted. - HTTP headers:
Authorization,Cookie,Set-Cookie,Proxy-Authorization, and configured custom auth headers redacted (relevant to HAR / capture surfaces, not content tools, but the shape is consistent). - Accessibility-tree autofill hints: any node with
data-autofillor ARIAautocompletefields marked sensitive is redacted. - URL fragments and query parameters:
access_token,id_token,refresh_token,session, and configured additional keys redacted to[REDACTED]in any URL that leaves the process.
The full rule set is on the security page. From the agent’s perspective the behavior is: values that would leak a secret come back as the literal string [REDACTED].
Why no arbitrary JS surface
Section titled “Why no arbitrary JS surface”The design decision is that any tool that lets an agent read the page directly from JS space bypasses the firewall. There is no version of “let the agent run JavaScript” that keeps the redaction guarantee. Instead, we expose the specific accessors an agent actually needs, and every one of them is a firewall pass-through.
If a workflow needs a computation that Maho does not currently expose, the fix is to add a typed tool, not to open the JavaScript surface.