Symptom:abp_connect returns “No ABP manifest link found”Diagnosis:
Copy
# 1. Check HTML has manifest linkcurl https://your-app.example.com | grep -i "abp-manifest"# Should find: <link rel="abp-manifest" href="...">
Solutions:1. Add manifest link to HTML:
Copy
<head> <link rel="abp-manifest" href="/abp.json"> <!-- Must be in <head>, not <body> --></head>
Important for framework apps: The manifest link must be in the server-rendered HTML. ABP clients discover the manifest via raw HTTP fetch (no JavaScript execution), so a link injected client-side via useEffect, onMounted, or similar lifecycle hooks will never be found. Use your framework’s server-side metadata API instead (e.g., generateMetadata() in Next.js App Router, useHead() in Nuxt). See ABP Implementation Guide — Framework Environments for a framework-by-framework table.
2. Check manifest is served:
Copy
curl https://your-app.example.com/abp.json | jq# Should return valid JSON
Symptom:abp_connect succeeds but no dynamic tools appearDiagnosis:1. Check browser console:
Open the browser window that appeared and check DevTools console for errors.2. Check window.abp exists:
In browser console:
Copy
console.log(window.abp);// Should show the ABP object, not undefined
Solutions:1. Vanilla HTML — load ABP runtime before app scripts:
Copy
<script src="/abp-runtime.js"></script> <!-- Load first --><script src="/app.js"></script> <!-- Then app scripts -->
2. Framework apps (React, Next.js, Vue, etc.) — assign at module scope:
Do NOT use lifecycle hooks (useEffect, onMounted, ngOnInit) to set window.abp — they run after hydration, which may be too late. Assign at module scope instead:
Copy
// Module scope -- runs when the bundle loads, before hydrationif (typeof window !== 'undefined') { window.abp = createAbpRuntime();}
See ABP Implementation Guide — Framework Environments for a full framework-by-framework guide.3. Check for JavaScript errors:
Fix any errors in your ABP runtime or app code.4. Use abp_call as a fallback:
Even if dynamic tools don’t appear, you can always invoke capabilities using abp_call. Pass the capability name as capability and its arguments as params:
Symptom: Capabilities fail with PERMISSION_DENIED errorCommon causes:
Running in headless mode
Browser window doesn’t have focus
User hasn’t granted permission
Solutions:1. Use headful mode:
Copy
abp_connect("http://localhost:3000") # Default is headful# Not:abp_connect("http://localhost:3000", { headless: true })
2. Ensure browser window has focus:
Click on the browser window before the capability is called.3. Grant permissions:For hardware capabilities (camera, microphone), the user must manually grant permission or the client must pre-grant via browser context.
All successful capability results are saved to files. Error responses are the only results returned inline (they are small diagnostic text, not content).
These are known limitations of the current MCP Bridge implementation. See MCP Bridge Architecture: Limitations for more detail.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.Headful mode required. Many ABP capabilities (authenticated sessions, GPU/WebGPU, permissions prompts, downloads) require a visible browser window. Headless mode (ABP_HEADLESS=true) will work for basic capabilities but not for the features that make ABP valuable.Dynamic tool registration is unreliable. Claude Code does not reliably expose dynamically registered MCP tools to the LLM. Use abp_call as the reliable fallback — it can invoke any capability by name.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. Authenticated sessions (cookies, localStorage) from the user’s regular browser are not available.