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 browsercallCapabilityByName(name, params)— Called byabp_call(looks up by dot-notation name)callCapability(toolName, params)— Called by dynamic tools (looks up byabp_prefixed name)state— Current connection state (status, url, app, session, capabilities, error)registeredCapabilities— Full list with manifest + runtime detailsonToolsChanged— Callback invoked when capabilities change (triggersnotifications/tools/list_changed)
executeCapability(), the orchestrator:
- Checks page health via
guard.isPageAlive - Arms the guard to track side effects (
guard.armForCall()) - Executes the ABP call
- Checks if the response already has binary data (
dataFlow.hasBinaryResponse()) - If no binary data, checks the guard for intercepted outputs (print PDF, downloads)
- 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. Whenoptions.extensionPathis provided, adds--load-extensionand--disable-extensions-exceptflags to load a Chrome extension.getExtensionId()— Pollsbrowser.targets()for achrome-extension://URL and extracts the 32-character extension ID. Uses 200ms polling interval withbrowserTimeoutas deadline. Used during the extension connect flow.navigate(url)— Goes to the URL (HTTP orchrome-extension://), waits fornetworkidle2evaluate(fn, ...args)— Runs code in the browser context, with detection for detached frames and closed targetsexposeFunction(name, fn)— Exposes a Node.js function to the browser (used for notifications/progress/elicitation)currentBrowser— Accessor for theBrowserinstance (used byBrowserEventGuardfor permissions and CDP)currentPage— Accessor for the currentPageinstanceclose()— Closes the browser, handles errors gracefully- Listens for the browser
disconnectedevent 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 forwindow.abpto be defined on the pageinitialize()— Callswindow.abp.initialize()with bridge identity and feature flagslistCapabilities()— Callswindow.abp.listCapabilities()for full capability detailscall(name, params)— Callswindow.abp.call()with the capability name, params, and timeoutshutdown()— Callswindow.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.markdownToHtmlbecomesabp_convert_markdownToHtml(dots replaced with underscores, prefixed withabp_) - When the runtime provides capabilities not in the manifest, they are added
- When the runtime provides schemas that the manifest lacked, they are merged
register(capabilities)— Register capabilities from manifestget(name)— Get capability details by ABP namegetByToolName(toolName)— Get capability details by MCP tool namelist()— 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
BinaryDataobjects by checking both binary MIME types andencoding: '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 byABPBridgeManagerto decide whether to check the guard) - Error responses are returned inline (small diagnostic text, not content)
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
onPageLostcallback - Permission auto-granting: auto-grants
notificationsviabrowserContext.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)
armForCall()— Resets per-call tracking (print flag, dialog list, download tracker)collectInterceptedOutputs(capabilityName)— After the call, checks what happened:- If
window.__abpPrintCalledis true, generates a PDF viapage.pdf()(with CDPPage.printToPDFfallback for headful mode) - Collects completed downloads
- Reports handled dialogs as informational entries
- If
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:
- Exposes Node-side handler callbacks into the browser via
page.exposeFunction()(e.g.,__abpOnNotification,__abpOnProgress,__abpOnElicitation) - Registers them with the ABP runtime via
page.evaluate()(e.g.,window.abp.onNotification(n => window.__abpOnNotification(n))) - Checks for existence of each ABP method before registering (defensive)
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 ofurl,extensionPath, orextensionId. WhenextensionIdis used, the bridge downloads the extension from the Chrome Web Store automatically.abp_disconnect()— Disconnect and cleanupabp_status()— Show connection status and capabilitiesabp_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-> Toolabp_convert_markdownToHtml - Capability
export.pdf-> Toolabp_export_pdf
- Fetch manifest
- Parse capabilities
- Generate MCP tool definitions
- 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. TheBrowserEventGuard intercepts these side effects and handles them automatically:
| Event | How it’s handled | Impact |
|---|---|---|
alert() dialog | Auto-dismissed | Prevents blocking |
confirm() dialog | Auto-accepted (non-destructive default) | Prevents blocking |
prompt() dialog | Auto-dismissed | Prevents blocking |
beforeunload dialog | Auto-accepted (allows navigation) | Prevents blocking |
| Page crash | Sets error flag, notifies bridge | Clear error message instead of cryptic “Target closed” |
| Page close | Sets error flag, notifies bridge | Clear error message on next abp_call |
| Permission requests | Auto-grants notifications | Capabilities work without manual permission clicks |
| Browser downloads | Intercepted via CDP, tracked to output directory | Downloaded files are captured and reported |
window.print() | Replaced with flag-setting no-op | Triggers PDF generation (see below) |
Print Interception and PDF Generation
When an ABP app callswindow.print() (common for “export to PDF” capabilities), the guard:
- The
window.print()override (set up at connect time) setswindow.__abpPrintCalled = trueinstead of opening the print dialog - After the capability call returns,
collectInterceptedOutputs()detects the flag - Generates a real PDF using
page.pdf({ printBackground: true, format: 'A4' })(headless mode) or CDPPage.printToPDF(headful fallback) - Saves the PDF to the output directory and returns the file path
"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).abp_connect while already connected automatically disconnects first.
Extension Connect Flow
Whenabp_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:
- 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:| Variable | Default | Description |
|---|---|---|
ABP_OUTPUT_DIR | <os.tmpdir()>/abp-mcp-bridge | Directory where output files are saved. Created automatically. |
ABP_HEADLESS | true | Set to false for headful (visible) mode. Headful is needed for capabilities requiring a visible browser window (authenticated sessions, GPU, permissions). |
ABP_BROWSER_TIMEOUT | 30000 | Timeout in milliseconds for browser launch and page navigation. |
ABP_CALL_TIMEOUT | 60000 | Default timeout in milliseconds for individual capability calls. |
ABP_DOWNLOAD_TIMEOUT | 30000 | Maximum time in milliseconds to wait for browser-initiated downloads. |
ABP_LOG_LEVEL | info | Minimum log level. One of: debug, info, warn, error. All logs go to stderr. |
Logging
All logs go to stderr (never stdout — stdout is reserved for JSON-RPC). Each message is timestamped in ISO 8601 format:debug, info, warn, error) controlled by ABP_LOG_LEVEL:
debug— Verbose output including capability registration details, notification/progress events, and data flow decisionsinfo— Standard operation: connections, capability calls, handled dialogswarn— Non-fatal issues: browser disconnect, session errors during shutdownerror— Failures: capability errors, browser crashes
Error Handling
The bridge implements:- Exponential backoff for retryable errors
- Graceful degradation
- Browser crash recovery
- Session reconnection
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. SetABP_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.