Use a Profile from Other Tools
Most browser automation tools and AI browser agents can attach to a Chromium that is already running, over the Chrome DevTools Protocol (CDP). A running Veilus profile exposes that address, so you can keep the tool you already use and point it at a profile — with that profile’s fingerprint, proxy and cookies.
SDK for Node and Python
Section titled “SDK for Node and Python”If you write your own Playwright script, the veilus package finds the local API, launches the profile and connects Playwright to it in one call. It needs Veilus 0.2.9 or newer with the local API turned on, and an API token in VEILUS_API_TOKEN — see REST API.
npm install veilus playwright-coreimport { Veilus } from 'veilus';
const veilus = new Veilus();const [profile] = await veilus.listProfiles();
const { page, close } = await veilus.connect(profile.id);await page.goto('https://example.com');console.log(await page.title());await close(); // detaches; the profile keeps runningpip install "veilus[playwright]"import asynciofrom veilus import Veilus
async def main(): veilus = Veilus() profile = (await veilus.list_profiles())[0]
session = await veilus.connect(profile["id"]) await session.page.goto("https://example.com") print(await session.page.title()) await session.close() # detaches; the profile keeps running
asyncio.run(main())connect() reuses the profile if it is already running, otherwise launches it, and returns the profile’s own context and page. The full API is in the package README on npm and PyPI. For any other tool, read the profile’s CDP address yourself, as below.
Both examples were run on macOS on October 13, 2026, with veilus 0.1.0 from npm (Node 22, playwright-core 1.64.0) and from PyPI (Python 3.14, Playwright 1.63.0), against Veilus 0.2.10. After close(), the profile was still running.
Get the profile’s CDP address
Section titled “Get the profile’s CDP address”You need the local API turned on — see REST API for the port and token.
Launch the profile; the answer carries its cdpUrl:
curl -X POST \ -H "Authorization: Bearer $VEILUS_API_TOKEN" \ http://127.0.0.1:<port>/v1/profiles/<profile-id>/launch{ "pid": 56891, "cdpUrl": "http://127.0.0.1:61761" }A profile you opened from the app also has a cdpUrl while it runs: GET /v1/profiles lists it on each running profile. Over MCP, launch_profile and list_profiles return the same field.
The address changes every time the profile starts. Read it again after each launch.
Before you connect
Section titled “Before you connect”- The CDP address has no password. Any program on your machine can use it while the profile runs. It only listens on
127.0.0.1. - Use the browser as it is. Work in the profile’s existing window and context. Don’t create a new context with your own user agent, viewport or locale options — those override the profile’s values for that context.
- Disconnect, don’t close. Ending your script should detach from the browser. The profile stays open until you stop it from the app or with
POST /v1/profiles/<profile-id>/stop. - A connected tool is still a connected tool. Veilus keeps the profile’s identity; it can’t hide that an automation client is driving the page while one is attached.
Playwright
Section titled “Playwright”import { chromium } from 'playwright';
const browser = await chromium.connectOverCDP(cdpUrl);const context = browser.contexts()[0];const page = context.pages()[0] ?? (await context.newPage());await page.goto('https://example.com');console.log(await page.title());await browser.close(); // detaches; the profile keeps runningUse browser.contexts()[0], not browser.newContext().
Puppeteer
Section titled “Puppeteer”import puppeteer from 'puppeteer';
const browser = await puppeteer.connect({ browserURL: cdpUrl, defaultViewport: null });const [page] = await browser.pages();await page.goto('https://example.com');console.log(await page.title());await browser.disconnect();Keep defaultViewport: null. Without it, Puppeteer resizes the page to 800×600, and that size stays on the tab after Puppeteer disconnects.
playwright-mcp
Section titled “playwright-mcp”Point the server at the profile instead of letting it start its own browser:
{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp", "--cdp-endpoint", "http://127.0.0.1:61761"] } }}Replace the address with the profile’s current cdpUrl, and restart the MCP client after each profile launch.
agent-browser
Section titled “agent-browser”agent-browser connect takes the port part of cdpUrl:
agent-browser connect 61761agent-browser open https://example.comagent-browser get titlebrowser-use
Section titled “browser-use”from browser_use import Browser
browser = Browser(cdp_url=cdp_url, keep_alive=True, headless=False, no_viewport=True)Pass it to your agent as Agent(task=..., llm=..., browser=browser). keep_alive=True leaves the profile running when the agent finishes. headless=False and no_viewport=True stop browser-use from resizing the profile’s tabs when your script runs on a machine without a display. Full example: browser_use_example.py.
The example uses claude-haiku-5-5. browser-use 0.13.11 asks the model for a forced tool choice, and claude-sonnet-5-5 and claude-opus-5-5 reject that request.
Crawl4AI
Section titled “Crawl4AI”import asynciofrom crawl4ai import AsyncWebCrawler, BrowserConfig
async def main(): config = BrowserConfig( browser_mode="cdp", cdp_url=cdp_url, use_managed_browser=True, user_agent="" ) config.headers.pop("sec-ch-ua", None) async with AsyncWebCrawler(config=config) as crawler: result = await crawler.arun("https://example.com") print(result.metadata.get("title"))
asyncio.run(main())Keep all three settings. Without them, Crawl4AI 0.9.4 opens its own context and sends its own User-Agent and sec-ch-ua headers instead of the profile’s. Full example: crawl4ai_example.py.
Stagehand
Section titled “Stagehand”import { Stagehand } from '@browserbasehq/stagehand';import { z } from 'zod';
// Stagehand wants the browser's WebSocket endpoint, not the http:// address.const { webSocketDebuggerUrl } = await (await fetch(`${cdpUrl}/json/version`)).json();const stagehand = new Stagehand({ env: 'LOCAL', localBrowserLaunchOptions: { cdpUrl: webSocketDebuggerUrl }, model: 'anthropic/claude-sonnet-5-5',});await stagehand.init();const page = stagehand.context.pages()[0];await page.goto('https://example.com');const { title } = await stagehand.extract('the page title', z.object({ title: z.string() }));console.log(title);await stagehand.close({ force: false }); // detaches; the profile keeps runningUse stagehand.context.pages()[0] — the profile’s own tab. extract calls the model, so set ANTHROPIC_API_KEY. Full example: stagehand.mjs.
LangChain
Section titled “LangChain”from langchain_community.agent_toolkits import PlayWrightBrowserToolkitfrom playwright.async_api import async_playwright
async with async_playwright() as p: browser = await p.chromium.connect_over_cdp(cdp_url) toolkit = PlayWrightBrowserToolkit.from_browser(async_browser=browser) tools = {t.name: t for t in toolkit.get_tools()} await tools["navigate_browser"].arun({"url": "https://example.com"}) print(await browser.contexts[0].pages[-1].title())Calling a tool directly needs no LLM; hand toolkit.get_tools() to your agent when you want one to choose. Leaving the async with block disconnects; the profile keeps running. Full example: langchain_example.py.
Scrapling
Section titled “Scrapling”from playwright.async_api import async_playwrightfrom scrapling.parser import Selector
async with async_playwright() as pw: browser = await pw.chromium.connect_over_cdp(cdp_url) page = await browser.contexts[0].new_page() await page.goto("https://example.com") html = await page.content() await page.close()print(Selector(html).css("title::text").get())Playwright drives the profile’s browser and Scrapling parses the page. Scrapling’s own fetchers open a new browser context, which would not carry the profile’s cookies. Full example: scrapling_example.py.
Crawlee
Section titled “Crawlee”Crawlee starts its own browser by default. Give it a browser plugin that connects to the profile instead, and a controller that opens pages in the profile’s existing context and never closes it — the full plugin is about 30 lines: crawlee_example.py.
crawler = PlaywrightCrawler( browser_pool=BrowserPool(plugins=[VeilusPlugin(cdp_url)]))The example subclasses Crawlee’s PlaywrightBrowserController; check it again when you upgrade Crawlee.
Tested versions
Section titled “Tested versions”On October 14, 2026, every example in veilus/sdk/examples ran against a Veilus profile on macOS: it opened a page, read its title and left the profile running. Versions: Playwright 1.64.0, Puppeteer 25.13.0, playwright-mcp 0.0.83, agent-browser 0.39.1, Crawl4AI 0.9.4, LangChain (langchain-community) 0.4.2, Scrapling 0.4.6, Crawlee 1.10.4, browser-use 0.13.11, Stagehand 3.7.3. The current table is examples/TESTED.md.
On October 11, 2026, with Playwright, Puppeteer, playwright-mcp, agent-browser and browser-use, the page also reported the profile’s own user agent, screen size, time zone and language, and filled the browser window — no tool resized it, as long as Puppeteer was given defaultViewport: null. Newer releases of these tools may change their defaults — if a page suddenly reports a smaller size than its window, check the tool’s viewport option first.