Skip to main content
Deep dive into the ABP MCP Bridge reference implementation.

Overview

The ABP MCP Bridge is a generic MCP server that connects AI agents to ABP-compliant web applications. It’s the reference implementation demonstrating best practices for ABP client development. Key Characteristics:
  • Generic: Works with ANY ABP-compliant app
  • Zero configuration: Discovers capabilities automatically
  • Smart data flow: Routes large outputs to files
  • Complete: Implements all ABP features

Architecture

Core Components

1. ABP Bridge Manager

File: src/bridge/ABPBridgeManager.ts The orchestrator. It owns instances of the other bridge classes and coordinates the connect/disconnect/call flows. The constructor requires an MCP Server instance (for handler wiring to forward notifications/progress via server.sendLoggingMessage()). Key Methods:
  • connect(url) — Runs the full web app connection sequence (steps 1-5, plus guard attach at 3.5 and handler wiring at 4.5)
  • connectExtension(extensionPath, abpPage?) — Runs the Chrome extension connection sequence (see Extension Connect Flow below)
  • disconnect() — Shuts down session, detaches guard, and closes browser
  • callCapabilityByName(name, params) — Called by abp_call (looks up by dot-notation name)
  • callCapability(toolName, params) — Called by dynamic tools (looks up by abp_ prefixed name)
  • state — Current connection state (status, url, app, session, capabilities, error)
  • registeredCapabilities — Full list with manifest + runtime details
  • onToolsChanged — Callback invoked when capabilities change (triggers notifications/tools/list_changed)
During executeCapability(), the orchestrator:
  1. Checks page health via guard.isPageAlive
  2. Arms the guard to track side effects (guard.armForCall())
  3. Executes the ABP call
  4. Checks if the response already has binary data (dataFlow.hasBinaryResponse())
  5. If no binary data, checks the guard for intercepted outputs (print PDF, downloads)
  6. Falls through to normal DataFlowManager processing

2. Browser Manager

File: src/bridge/BrowserManager.ts Wraps Puppeteer. Handles browser lifecycle and provides the page.evaluate() bridge to the browser context. Key Methods:
  • launch(options?) — Starts Chromium in headless mode (by default) with --no-first-run, --disable-default-apps, --disable-popup-blocking. When options.extensionPath is provided, adds --load-extension and --disable-extensions-except flags to load a Chrome extension.
  • getExtensionId() — Polls browser.targets() for a chrome-extension:// URL and extracts the 32-character extension ID. Uses 200ms polling interval with browserTimeout as deadline. Used during the extension connect flow.
  • navigate(url) — Goes to the URL (HTTP or chrome-extension://), waits for networkidle2
  • evaluate(fn, ...args) — Runs code in the browser context, with detection for detached frames and closed targets
  • exposeFunction(name, fn) — Exposes a Node.js function to the browser (used for notifications/progress/elicitation)
  • currentBrowser — Accessor for the Browser instance (used by BrowserEventGuard for permissions and CDP)
  • currentPage — Accessor for the current Page instance
  • close() — Closes the browser, handles errors gracefully
  • Listens for the browser disconnected event and resets state

3. ABP Session Manager

File: src/bridge/ABPSessionManager.ts The window.abp protocol layer. All interactions with the web app go through this class. Each method uses page.evaluate() to execute code in the browser context. Parameters are serialized and passed as arguments to page.evaluate() (they cross the Node.js/browser boundary). Key Methods:
  • waitForABP(timeout) — Waits for window.abp to be defined on the page
  • initialize() — Calls window.abp.initialize() with bridge identity and feature flags
  • listCapabilities() — Calls window.abp.listCapabilities() for full capability details
  • call(name, params) — Calls window.abp.call() with the capability name, params, and timeout
  • shutdown() — Calls window.abp.shutdown() to end the session

4. Capability Registry

File: src/bridge/CapabilityRegistry.ts Tracks capabilities from two sources: the manifest (pre-flight) and the runtime (listCapabilities()). Key Behaviors:
  • Maintains a map of ABP capability name -> RegisteredCapability (which includes both manifest and runtime data)
  • Maintains a reverse map of MCP tool name -> ABP capability name
  • Naming convention: convert.markdownToHtml becomes abp_convert_markdownToHtml (dots replaced with underscores, prefixed with abp_)
  • When the runtime provides capabilities not in the manifest, they are added
  • When the runtime provides schemas that the manifest lacked, they are merged
Key Methods:
  • register(capabilities) — Register capabilities from manifest
  • get(name) — Get capability details by ABP name
  • getByToolName(toolName) — Get capability details by MCP tool name
  • list() — List all capabilities

5. Data Flow Manager

File: src/bridge/DataFlowManager.ts Inspects ABP responses and decides how to return them. See Data Flow for the full routing logic. Key Behaviors:
  • Saves all successful results to files (the agent receives a file path and reads on demand)
  • Detects BinaryData objects by checking both binary MIME types and encoding: 'base64'
  • Scans one level deep into response data to find nested binary objects (with sibling properties collected as metadata)
  • Provides hasBinaryResponse() for quick binary detection without full processing (used by ABPBridgeManager to decide whether to check the guard)
  • Error responses are returned inline (small diagnostic text, not content)
Key Methods:
  • processResult(response, capabilityName) — Process capability output (always saves to file)
  • hasBinaryResponse(response) — Quick check for binary data presence

6. Browser Event Guard

File: src/bridge/BrowserEventGuard.ts Intercepts browser-level side effects that would block agentic workflows — compensating for ABP apps that trigger native browser UI instead of returning data programmatically. It has a two-phase design: Persistent listeners (set up once at connect time via attach()):
  • Dialog auto-handling: alert -> dismiss, confirm -> accept, prompt -> dismiss, beforeunload -> accept
  • Page crash/close detection: sets error flags, notifies the bridge via onPageLost callback
  • Permission auto-granting: auto-grants notifications via browserContext.overridePermissions()
  • Download interception: uses a CDP session (Browser.setDownloadBehavior) to intercept and track downloads to the output directory
  • Print interception: overrides window.print() with a no-op that sets a flag (window.__abpPrintCalled)
Per-call methods (used around each capability call):
  • armForCall() — Resets per-call tracking (print flag, dialog list, download tracker)
  • collectInterceptedOutputs(capabilityName) — After the call, checks what happened:
    • If window.__abpPrintCalled is true, generates a PDF via page.pdf() (with CDP Page.printToPDF fallback for headful mode)
    • Collects completed downloads
    • Reports handled dialogs as informational entries
Binary response priority: if the ABP response already contains proper BinaryData, the guard’s intercepted outputs are skipped entirely. Well-implemented apps are unaffected. See Troubleshooting: Browser Issues for common browser event problems.

7. ABP Handler Wiring

File: src/bridge/ABPHandlerWiring.ts Wires the NotificationHandler, ProgressHandler, and ElicitationHandler to ABP runtime events. The 3-step process:
  1. Exposes Node-side handler callbacks into the browser via page.exposeFunction() (e.g., __abpOnNotification, __abpOnProgress, __abpOnElicitation)
  2. Registers them with the ABP runtime via page.evaluate() (e.g., window.abp.onNotification(n => window.__abpOnNotification(n)))
  3. Checks for existence of each ABP method before registering (defensive)
Must be called after session.initialize() because the ABP runtime must be ready. The guard attaches before initialize to catch dialogs during init.

Tool System

Static Tools

File: src/tools/static-tools.ts Always available:
  • abp_connect(url | extensionPath | extensionId, abpPage?) — Connect to an ABP web app by URL, a Chrome extension by directory path, or a Chrome Web Store extension by ID. Provide exactly one of url, extensionPath, or extensionId. When extensionId is used, the bridge downloads the extension from the Chrome Web Store automatically.
  • abp_disconnect() — Disconnect and cleanup
  • abp_status() — Show connection status and capabilities
  • abp_call(capability, params?) — Invoke any capability by name. All results are saved to files. This is the primary, reliable method for calling capabilities.
  • abp_render_to_pdf(html, options?) — Render HTML to a PDF file using the browser. Requires an active connection.

Dynamic Tools

File: src/tools/dynamic-tool-factory.ts Generated from capabilities after connection:
  • Capability convert.markdownToHtml -> Tool abp_convert_markdownToHtml
  • Capability export.pdf -> Tool abp_export_pdf
Factory process:
  1. Fetch manifest
  2. Parse capabilities
  3. Generate MCP tool definitions
  4. Register tools with MCP server

Event Handling

Notifications

File: src/handlers/notification-handler.ts Handles app -> agent notifications:
  • State changes
  • Capability changes
  • Errors

Progress

File: src/handlers/progress-handler.ts Handles progress updates:
  • Displays progress to agent
  • Updates operation status

Elicitation

File: src/handlers/elicitation-handler.ts Handles elicitation requests:
  • Prompts user for input
  • Returns user response to app

Browser Event Handling

Some ABP apps trigger native browser UI (print dialogs, alert/confirm popups, permission prompts, file downloads) instead of returning data programmatically. This blocks agentic workflows because the agent has no way to interact with native browser chrome. The BrowserEventGuard intercepts these side effects and handles them automatically: When an ABP app calls window.print() (common for “export to PDF” capabilities), the guard:
  1. The window.print() override (set up at connect time) sets window.__abpPrintCalled = true instead of opening the print dialog
  2. After the capability call returns, collectInterceptedOutputs() detects the flag
  3. Generates a real PDF using page.pdf({ printBackground: true, format: 'A4' }) (headless mode) or CDP Page.printToPDF (headful fallback)
  4. Saves the PDF to the output directory and returns the file path
The agent receives a file path to a real PDF instead of a useless status message like "print_dialog_opened". Priority rule: if the ABP response already contains proper BinaryData (the app returned the PDF itself), the guard steps aside. Well-implemented apps are completely unaffected — the guard only activates when the app fails to return binary data. See Troubleshooting: Browser Issues for resolving common browser event problems.

Connection Lifecycle

What Happens During abp_connect

The connect process has five sequential steps. If any step fails, all previous steps are rolled back (browser closed, registry cleared).
The bridge supports one connection at a time. Calling abp_connect while already connected automatically disconnects first.

Extension Connect Flow

When abp_connect receives extensionPath or extensionId instead of url, it runs a different connect flow via connectExtension(). If extensionId is provided, the bridge first downloads the extension’s CRX package from the Chrome Web Store, extracts it to a local cache directory, then proceeds as if extensionPath were given:
Key differences from web app flow:
  • No HTTP-based manifest discovery (Step 1 of web app flow is replaced by Steps 1-2)
  • Extension ID must be discovered from browser targets (it’s dynamically assigned)
  • Manifest is synthesized from runtime data instead of fetched via HTTP
  • Steps 4-6 are identical to the web app flow — page.evaluate() works the same on extension pages

What Happens During abp_disconnect

Data Flow Example

Configuration

Environment variables:

Logging

All logs go to stderr (never stdout — stdout is reserved for JSON-RPC). Each message is timestamped in ISO 8601 format:
The logger supports four levels (debug, info, warn, error) controlled by ABP_LOG_LEVEL:
  • debug — Verbose output including capability registration details, notification/progress events, and data flow decisions
  • info — Standard operation: connections, capability calls, handled dialogs
  • warn — Non-fatal issues: browser disconnect, session errors during shutdown
  • error — Failures: capability errors, browser crashes

Error Handling

The bridge implements:
  • Exponential backoff for retryable errors
  • Graceful degradation
  • Browser crash recovery
  • Session reconnection
See Error Handling Guide for details.

Testing

Run the bridge:

Limitations and Known Issues

One connection at a time. The bridge supports a single browser/session. Connecting to a new app disconnects from the current one. There is no session pooling. Headless by default. The browser runs in headless mode by default. Some ABP capabilities (authenticated sessions, GPU/WebGPU, permissions prompts) may require a visible browser window. Set ABP_HEADLESS=false for headful mode when needed. Dynamic tool registration is unreliable. Claude Code does not reliably expose dynamically registered MCP tools to the LLM. The abp_call static tool is the reliable method. Dynamic tools are kept as a progressive enhancement for other MCP clients. Elicitation is v1 (auto-respond). When an ABP app requests input from the agent (elicitation), the bridge auto-responds with defaults or cancels. Full support for routing elicitation requests to the user via MCP sampling is planned for a future version. Print override timing. The BrowserEventGuard overrides window.print() via page.evaluate() after the page has loaded. If the app captures a reference to window.print during its initial script execution (before the guard attaches), the override won’t intercept the call. The fix is to use page.evaluateOnNewDocument() — this is tracked as a pending improvement. Browser profile. The bridge launches Chromium with a fresh profile each time. It does not reuse the user’s existing browser profile, which means authenticated sessions (cookies, localStorage) from the user’s regular browser are not available. See Troubleshooting for workarounds.

Next Steps

Data Flow

DataFlowManager routing logic

Error Handling

Error codes and recovery strategies

Client-Side Implementation

Build your own client

MCP Bridge Quick Start

Use the bridge