chrome.tabs, chrome.scripting, and chrome.bookmarks.
This is both a setup guide and a process document. It covers the extension-specific mechanics (manifest.json, abp-app.html,
--load-extension) AND the implementation process (inventory, mapping, validation). Before starting, complete the Required Reading — three sections of the ABP Implementation Guide that cover the core ABP principles (Critical Rule, self-containment, forbidden patterns, response formats) that apply equally to extensions and web apps.Required Reading
Before reading this guide, read these three sections of the ABP Implementation Guide. They cover core ABP principles that apply to all implementations — web apps and extensions alike. This guide builds on them and won’t repeat their content.- Section 1 — The Critical Rule — The headless test, delivery vs. content production, self-containment. Every capability must produce a complete result for a program with no human present.
- Section 7 — Forbidden Patterns — Browser APIs you must not use inside capability handlers:
alert(),confirm(), clipboard, downloads, share, file pickers, notifications. - Section 8 — Response Patterns — BinaryData format, expected output by capability type, error response format, the consistency rule.
manifest.json setup, wrapping chrome.* APIs, extension-specific forbidden patterns, and testing.
The Critical Rule (Extensions)
Every capability call MUST produce a complete, usable result for a program controlling the browser, with no human present.This is the same Critical Rule from the ABP Implementation Guide (Section 1, covered in Required Reading). Before shipping any capability, ask:
“Would this produce a complete, usable result for a program controlling the browser, with no human present?”Extension-specific implications:
chrome.tabs.create({ url })is fine — it creates a tab programmatically and returns the tab objectchrome.downloads.download({ url })needs care — the agent needs the file data, not a download ID it can’t access. Consider returning content directly viachrome.scripting.executeScriptinsteadchrome.notifications.create()is a delivery mechanism — return the notification-worthy data in the response instead, and let the agent decide how to surface itchrome.identity.getAuthToken()may trigger an interactive auth flow — handle denial gracefully withPERMISSION_DENIED
tabs.navigate before scrape.page — make scrape.page accept a URL parameter and handle navigation internally.
Delivery vs. content production, self-containment, and real-world failure examples are covered in detail in the ABP Implementation Guide Section 1 (see Required Reading).
Why Chrome Extensions?
Web apps can only access standard Web APIs. Chrome extensions unlock privileged browser APIs that web pages cannot use:| API | What It Enables |
|---|---|
chrome.tabs | Query, create, update, and close browser tabs |
chrome.scripting | Inject scripts into any web page |
chrome.bookmarks | Read and manage bookmarks |
chrome.history | Access browsing history |
chrome.storage | Persistent cross-session storage |
chrome.downloads | Manage file downloads |
chrome.cookies | Read and modify cookies for any domain |
Architecture
chrome-extension://ID/abp-app.html) has full access to chrome.* APIs. When Puppeteer calls page.evaluate() on that page, the code runs in the extension’s context with all its permissions.
Inventory Your chrome.* APIs
Before writing ABP code, create an API inventory — the extension equivalent of the web app Feature Inventory. For eachchrome.* API you plan to expose, document:
- What it does — the API’s purpose
- What parameters it takes — and which are required vs. optional
- What it returns — the shape of the data
- Side effects — does it create tabs, modify bookmarks, trigger downloads, etc.?
- Permissions required — what must be declared in
manifest.json
API Inventory Template
| chrome.* API | Purpose | Parameters | Returns | Side Effects | Permission |
|---|---|---|---|---|---|
chrome.tabs.query({}) | List open tabs | queryInfo (optional filter) | Tab[] — id, url, title, active, windowId | None (read-only) | tabs |
chrome.tabs.create({url}) | Open a new tab | url (required) | Tab — id, url | Creates a tab | tabs |
chrome.scripting.executeScript({target, func}) | Inject and run JS in a page | tabId, func or files | InjectionResult[] | Executes code in target tab | scripting, host permission |
chrome.bookmarks.search(query) | Search bookmarks | query string or object | BookmarkTreeNode[] | None (read-only) | bookmarks |
chrome.storage.local.get(keys) | Read from extension storage | keys (string or array) | Record<string, any> | None (read-only) | storage |
Why This Step Matters
The inventory prevents two common mistakes:-
Exposing too much: Not every
chrome.*API should become an ABP capability.chrome.management.uninstall()orchrome.browsingData.remove()are destructive operations that agents probably shouldn’t have unsupervised access to. -
Missing the data shape:
chrome.tabs.query()returns a richTabobject with 20+ fields. Your capability should return a curated subset — what the agent actually needs — not dump the raw Chrome API response.
Map APIs to ABP Capabilities
After inventorying your APIs, map them to ABP capability names.Extension-Specific Namespaces
Extensions can use standard ABP namespaces where they fit, plus extension-specific ones:| Namespace | Purpose | Examples |
|---|---|---|
extension.* | Extension lifecycle / health | extension.ping, extension.info |
tabs.* | Tab management | tabs.list, tabs.create, tabs.close |
scrape.* or crawl.* | Content extraction from web pages | scrape.page, crawl.singlePage, crawl.multiPage |
bookmarks.* | Bookmark operations | bookmarks.search, bookmarks.list, bookmarks.create |
history.* | Browsing history | history.search, history.recent |
storage.* | Extension storage | storage.get, storage.set |
cookies.* | Cookie management | cookies.get, cookies.getAll |
camelCase for multi-word names: crawl.singlePage, not crawl.single-page.
Mapping Process
For each API in your inventory: Step A — Name it. Choose a capability name.chrome.tabs.query() -> tabs.list. chrome.scripting.executeScript() for text extraction -> scrape.page.
Step B — Define inputs. What parameters does the agent provide? Simplify from the raw Chrome API. Instead of exposing the full chrome.tabs.query(queryInfo) with all its fields, accept just the parameters the agent is likely to use:
tabs.create, bookmarks.delete) so agents know the operation is not read-only.
Step E — Check for dangerous operations. Some chrome.* APIs are destructive or sensitive:
| API | Risk | Recommendation |
|---|---|---|
chrome.tabs.remove() | Closes tabs (data loss if unsaved) | Expose with clear naming (tabs.close), consider requiring confirmation |
chrome.browsingData.remove() | Clears browsing data permanently | Generally don’t expose as ABP capability |
chrome.management.uninstall() | Removes extensions | Don’t expose |
chrome.cookies.remove() | Deletes cookies (may log user out) | Expose cautiously with clear naming |
chrome.bookmarks.remove() | Deletes bookmarks | Expose with clear naming |
Step-by-Step Implementation
Extension manifest.json
Your Chrome extension needs a Key points:
manifest.json (Manifest V3). Declare the permissions your ABP capabilities will use:- Only request permissions your capabilities actually need
host_permissionswith<all_urls>is needed forchrome.scripting.executeScripton arbitrary pages- A
background.service_workeris required for the extension to load (even if minimal)
Create abp-app.html
This is the ABP entry page. The MCP Bridge navigates to this page after loading the extension.Convention: Name this file
abp-app.html. The MCP Bridge defaults to this filename when connecting to extensions.File Structure
Discovery: How It Differs from Web Apps
For web apps, the ABP client performs HTTP-based pre-flight discovery:- Fetch HTML
<head>via HTTP - Parse
<link rel="abp-manifest" href="..."> - Fetch manifest JSON via HTTP
chrome-extension:// URLs cannot be fetched from Node.js. Instead, the bridge uses runtime-only discovery:
- Launch browser with
--load-extension=/path/to/extension - Poll
browser.targets()to discover the extension ID from its service worker URL - Navigate to
chrome-extension://ID/abp-app.html - Wait for
window.abpand callinitialize()+listCapabilities() - Build a synthetic manifest from the runtime data
- No
<link rel="abp-manifest">needed inabp-app.html - No
abp.jsonmanifest file needed (though you can include one for documentation purposes) - Capability discovery happens entirely at runtime via
initialize()andlistCapabilities()
Example: Wrapping chrome.* APIs as Capabilities
chrome.tabs — Tab Management
chrome.scripting — Script Injection
chrome.bookmarks — Bookmark Access
Forbidden Patterns for Extensions
The Forbidden Patterns from the ABP Implementation Guide (Section 7, covered in Required Reading) apply fully to extensions. Additionally, extensions have their own patterns to avoid:| Forbidden Pattern | Why It Fails | ABP Alternative |
|---|---|---|
chrome.notifications.create() | Delivery mechanism — agent can’t see or dismiss OS notifications | Return notification-worthy data in the response |
chrome.downloads.download({ url }) and returning just the download ID | Agent can’t access files in the browser download bar | Fetch the content via chrome.scripting.executeScript, return it in the response |
chrome.identity.launchWebAuthFlow() without error handling | May open interactive login — agent can’t interact with auth UI | Handle errors with PERMISSION_DENIED; document the requirement |
chrome.windows.create({ type: 'popup' }) as output | Agent can’t see or interact with popup windows | Return data in the response; use tabs internally for processing only |
chrome.action.openPopup() | Opens extension popup UI — agent can’t interact with it | Expose the popup’s functionality as capabilities directly |
Relying on chrome.runtime.sendMessage() round-trips for output | Adds complexity; ABP page can call chrome.* APIs directly | Call chrome.* APIs directly from abp-app.html — no message passing needed |
Extension-Specific Self-Containment
The self-containment principle is especially important for extensions becausechrome.* APIs are inherently stateful (tabs exist, pages are loaded, bookmarks are created). Each capability must handle its own setup:
Response Patterns
Extension capabilities use the same ABP response format as web apps (covered in ABP Implementation Guide Section 8 — see Required Reading). Key points:- Return actual data, not status messages.
tabs.listreturns{ tabs: [...] }, not{ message: "Listed 5 tabs" }. - Use standard error codes:
NOT_INITIALIZED,UNKNOWN_CAPABILITY,INVALID_PARAMS,OPERATION_FAILED,PERMISSION_DENIED. - Binary data (screenshots, exported files) uses the
BinaryDataformat:{ content, mimeType, encoding, size, filename }. - Consistency: All capabilities in the same namespace should return data in the same shape.
Extension-Specific Error Handling
chrome.* API errors have consistent patterns. Wrap them into ABP error responses:
Async Patterns for Long-Running Operations
Some extension capabilities (like crawling multiple pages) take time. Use progress reporting to keep the agent informed:Testing
Manual Testing (DevTools Console)
Automated Testing with Puppeteer
Connecting with the MCP Bridge
Once your extension implements ABP, connect to it using the MCP Bridge. There are two methods:By directory path (unpacked/development extensions)
By extension ID (Chrome Web Store extensions)
If the extension is published on the Chrome Web Store, you can connect using its 32-character ID. Find the ID in the Web Store URL (e.g.,https://chromewebstore.google.com/detail/extension-name/<id>).
What happens during connect
Regardless of the method, the bridge will:- Launch Chrome with your extension loaded
- Discover the extension ID automatically
- Navigate to
abp-app.html - Initialize the ABP session
- List available capabilities
abp_call as usual:
Custom ABP Entry Page
If your extension’s ABP page has a different filename, specify it:Disconnecting
Headless vs Headful Mode
Puppeteer’s defaultheadless: true mode supports Chrome extensions. Most extension capabilities (tab management, script injection, bookmarks, storage) work fine in headless mode.
Set ABP_HEADLESS=false if your extension needs:
- Visual page rendering for screenshots
- GPU access for canvas/WebGL operations
- User interaction for permission prompts
Permissions Best Practices
- Request only what you need — Don’t declare
<all_urls>unless your capabilities actually need cross-origin access - Use
activeTabwhere possible — Limits access to the current tab until the user interacts - Document permissions — Explain in
listCapabilities()descriptions why each permission is needed - Handle permission errors — If a
chrome.*API call fails due to missing permissions, return a clear ABP error withcode: 'PERMISSION_DENIED'
Validation Checklist
Run through this checklist before shipping your ABP extension. For additional checks, see also the web app Validation Checklist.Extension Setup
-
manifest.jsonis valid Manifest V3 with correctmanifest_version: 3 - All required
permissionsare declared (tabs, scripting, bookmarks, etc.) -
host_permissionsare declared if usingchrome.scripting.executeScripton arbitrary pages -
background.service_workeris declared and the file exists -
abp-app.htmlexists in the extension root (or the custom page specified byabpPage) -
abp-runtime.jsis loaded via<script>inabp-app.html
Runtime
-
window.abpis defined whenabp-app.htmlloads -
window.abp.initialize()returnssessionId,protocolVersion,app,capabilities,features -
window.abp.call()routes to the correct handler for each capability -
window.abp.call()returns{ success: false, error: { code: 'NOT_INITIALIZED' } }if called beforeinitialize() -
window.abp.call()returns{ success: false, error: { code: 'UNKNOWN_CAPABILITY' } }for unknown capabilities -
window.abp.listCapabilities()returns an array (NOT a{ success, data }envelope) with at leastnameandavailablefields per capability -
window.abp.listCapabilities()includesinputSchemafor each capability (this is the only way the bridge discovers parameter schemas for extensions) -
window.abp.shutdown()resets session state
Headless Test (per capability)
For each capability, verify:- The capability produces a complete result with no human present
- No
alert(),confirm(),prompt()calls - No
chrome.notifications.create()as output — return data instead - No
chrome.identity.launchWebAuthFlow()without error handling - No reliance on popup UI (
chrome.action.openPopup()) - Side effects (tab creation, bookmark modification) are cleaned up where appropriate
Self-Containment
- Each capability operates on its input parameters, not on state from previous calls
- Capabilities that scrape pages accept a URL parameter and handle tab lifecycle internally
- No capability requires the agent to call another capability first to “set up” state
- Tabs created for internal processing are closed after use
Data Quality
- Capabilities return actual data, not status messages (e.g.,
{ tabs: [...] }not{ message: "Listed 5 tabs" }) -
chrome.*API results are curated — return the fields the agent needs, not raw Chrome objects - Binary content (screenshots, files) uses
BinaryDataformat with correctmimeTypeandencoding - Error responses use standard ABP error codes (
PERMISSION_DENIED,INVALID_PARAMS,OPERATION_FAILED, etc.)
Consistency
- All capabilities in the same namespace return data in the same shape
- Error handling is consistent across all capabilities (same error wrapping pattern)