Skip to main content
How to add ABP support to a Chrome extension, giving AI agents access to privileged browser APIs like 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.
  1. 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.
  2. Section 7 — Forbidden Patterns — Browser APIs you must not use inside capability handlers: alert(), confirm(), clipboard, downloads, share, file pickers, notifications.
  3. Section 8 — Response Patterns — BinaryData format, expected output by capability type, error response format, the consistency rule.
Once you’ve read those, this guide covers everything specific to Chrome extensions: architecture, 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 object
  • chrome.downloads.download({ url }) needs care — the agent needs the file data, not a download ID it can’t access. Consider returning content directly via chrome.scripting.executeScript instead
  • chrome.notifications.create() is a delivery mechanism — return the notification-worthy data in the response instead, and let the agent decide how to surface it
  • chrome.identity.getAuthToken() may trigger an interactive auth flow — handle denial gracefully with PERMISSION_DENIED
Self-containment applies too: Each capability must be a self-contained, stateless operation. It receives input parameters, does its work, and returns output — all in a single call. Don’t require the agent to call 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: By wrapping these APIs as ABP capabilities, you give AI agents structured access to browser-level functionality that would otherwise require fragile UI automation.

Architecture

Key insight: an extension page (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 each chrome.* API you plan to expose, document:
  1. What it does — the API’s purpose
  2. What parameters it takes — and which are required vs. optional
  3. What it returns — the shape of the data
  4. Side effects — does it create tabs, modify bookmarks, trigger downloads, etc.?
  5. Permissions required — what must be declared in manifest.json

API Inventory Template

Why This Step Matters

The inventory prevents two common mistakes:
  1. Exposing too much: Not every chrome.* API should become an ABP capability. chrome.management.uninstall() or chrome.browsingData.remove() are destructive operations that agents probably shouldn’t have unsupervised access to.
  2. Missing the data shape: chrome.tabs.query() returns a rich Tab object 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: Use 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:
Step C — Define outputs. Curate the response. Don’t return raw Chrome API objects — return the fields the agent needs:
Step D — Identify side effects. If the API creates, modifies, or deletes something (tabs, bookmarks, storage), the agent should understand this from the capability description. Write-operations should be clearly named (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:

Step-by-Step Implementation

1

Extension manifest.json

Your Chrome extension needs a manifest.json (Manifest V3). Declare the permissions your ABP capabilities will use:
Key points:
  • Only request permissions your capabilities actually need
  • host_permissions with <all_urls> is needed for chrome.scripting.executeScript on arbitrary pages
  • A background.service_worker is required for the extension to load (even if minimal)
2

Create background.js

A minimal service worker is required for the extension to load:
3

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.
4

Create abp-runtime.js

Implement window.abp on the extension page. This is identical to a web app’s ABP runtime, except your capability implementations can call chrome.* APIs:

File Structure

Discovery: How It Differs from Web Apps

For web apps, the ABP client performs HTTP-based pre-flight discovery:
  1. Fetch HTML <head> via HTTP
  2. Parse <link rel="abp-manifest" href="...">
  3. Fetch manifest JSON via HTTP
For Chrome extensions, this HTTP-based discovery is not possible because chrome-extension:// URLs cannot be fetched from Node.js. Instead, the bridge uses runtime-only discovery:
  1. Launch browser with --load-extension=/path/to/extension
  2. Poll browser.targets() to discover the extension ID from its service worker URL
  3. Navigate to chrome-extension://ID/abp-app.html
  4. Wait for window.abp and call initialize() + listCapabilities()
  5. Build a synthetic manifest from the runtime data
This means:
  • No <link rel="abp-manifest"> needed in abp-app.html
  • No abp.json manifest file needed (though you can include one for documentation purposes)
  • Capability discovery happens entirely at runtime via initialize() and listCapabilities()

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:

Extension-Specific Self-Containment

The self-containment principle is especially important for extensions because chrome.* 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.list returns { 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 BinaryData format: { 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)

1

Load your extension

Go to chrome://extensions/ > “Load unpacked” > select your extension directory.
2

Find the extension ID

The ID is shown on the extensions page.
3

Navigate to ABP page

Go to chrome-extension://YOUR_ID/abp-app.html.
4

Test in 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>).
The bridge automatically downloads the extension from the Chrome Web Store, extracts it to a local cache, and loads it into Puppeteer. No Chrome installation or manual setup is required — the bridge handles everything. Downloaded extensions are cached, so subsequent connections with the same ID skip the download.

What happens during connect

Regardless of the method, the bridge will:
  1. Launch Chrome with your extension loaded
  2. Discover the extension ID automatically
  3. Navigate to abp-app.html
  4. Initialize the ABP session
  5. List available capabilities
Then use abp_call as usual:

Custom ABP Entry Page

If your extension’s ABP page has a different filename, specify it:

Disconnecting

This shuts down the ABP session and closes the browser (including the loaded extension).

Headless vs Headful Mode

Puppeteer’s default headless: 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

  1. Request only what you need — Don’t declare <all_urls> unless your capabilities actually need cross-origin access
  2. Use activeTab where possible — Limits access to the current tab until the user interacts
  3. Document permissions — Explain in listCapabilities() descriptions why each permission is needed
  4. Handle permission errors — If a chrome.* API call fails due to missing permissions, return a clear ABP error with code: '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.json is valid Manifest V3 with correct manifest_version: 3
  • All required permissions are declared (tabs, scripting, bookmarks, etc.)
  • host_permissions are declared if using chrome.scripting.executeScript on arbitrary pages
  • background.service_worker is declared and the file exists
  • abp-app.html exists in the extension root (or the custom page specified by abpPage)
  • abp-runtime.js is loaded via <script> in abp-app.html

Runtime

  • window.abp is defined when abp-app.html loads
  • window.abp.initialize() returns sessionId, 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 before initialize()
  • 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 least name and available fields per capability
  • window.abp.listCapabilities() includes inputSchema for 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 BinaryData format with correct mimeType and encoding
  • 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)

Next Steps

MCP Bridge Architecture

How the bridge handles extension connections

API Reference

Complete ABP API documentation

Common Pitfalls

Mistakes to avoid in ABP implementations

Building ABP Web Apps

General ABP implementation guide for web apps