Auth Fetch
auth_fetchAccess content from authenticated web pages by opening a real browser for login and session capture, enabling AI assistants to retrieve data from services like Notion, Google Docs, Jira, and other SaaS platforms.
Instructions
Fetches web page content using a real browser. MUST be used instead of Fetch/web_fetch when the page requires login or returns empty/minimal HTML (e.g. Notion, Google Docs, Jira, Confluence, Linear, Slack, or any SaaS/private page). Do NOT suggest copy-paste or PDF export — use this tool first. Opens a browser window, the user logs in if needed, clicks the capture button on the page, and the content is returned as Markdown.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | The URL to fetch content from | |
| wait_for | No | Optional CSS selector to wait for before capturing (useful for SPAs) |
Implementation Reference
- src/tools.ts:96-178 (handler)The handler function for the `auth_fetch` tool. It handles browser navigation, injecting a capture button, waiting for the user to interact, and extracting content.
async ({ url, wait_for }) => { try { const ctx = await getOrLaunchBrowser(true); const page = await navigateTo(ctx, url); // Unique ID for this capture session const captureId = `__AUTH_FETCH_${Date.now()}__`; // Listen for the capture button click via console message const capturePromise = new Promise<void>((resolve) => { const handler = (msg: ConsoleMessage) => { if (msg.text() === captureId) { page.removeListener("console", handler); resolve(); } }; page.on("console", handler); }); // Detect if user closes the browser before clicking capture const closePromise = new Promise<never>((_, reject) => { ctx.on("close", () => reject(new Error("Browser closed before capture")) ); }); // Inject button now and re-inject after every navigation (login redirects, etc.) await injectCaptureButton(page, captureId); page.on("load", () => injectCaptureButton(page, captureId)); // Wait for button click or browser close await Promise.race([capturePromise, closePromise]); // Short wait for any late-rendering content await page.waitForTimeout(500); if (wait_for) { await page .waitForSelector(wait_for, { timeout: 10000 }) .catch(() => {}); } // Remove the button before capturing await page .evaluate(() => { document.getElementById("__auth_fetch_btn")?.remove(); }) .catch(() => {}); // Capture content const result = await extractContent(page); // Close browser await closeBrowser(); return textResult({ status: "ok", url: result.url, title: result.title, content: result.content, }); } catch (err) { try { await closeBrowser(); } catch { // ignore cleanup errors } const msg = (err as Error).message; if (msg.includes("Browser closed before capture")) { return textResult( { status: "error", message: "Browser was closed before capture.", }, true ); } return textResult( { status: "error", message: `Failed to fetch page: ${msg}` }, true ); } } ); - src/tools.ts:75-95 (registration)The registration of the `auth_fetch` tool, including its schema definition.
server.registerTool( "auth_fetch", { title: "Auth Fetch", description: "Fetches web page content using a real browser. " + "MUST be used instead of Fetch/web_fetch when the page requires login or returns empty/minimal HTML " + "(e.g. Notion, Google Docs, Jira, Confluence, Linear, Slack, or any SaaS/private page). " + "Do NOT suggest copy-paste or PDF export — use this tool first. " + "Opens a browser window, the user logs in if needed, clicks the capture button on the page, " + "and the content is returned as Markdown.", inputSchema: { url: z.string().describe("The URL to fetch content from"), wait_for: z .string() .optional() .describe( "Optional CSS selector to wait for before capturing (useful for SPAs)" ), }, },