Skip to content

Programmatic SDK

For most users Ghostvault is driven by an AI agent (Claude Desktop, Cursor, ZCode, Cline). But sometimes you want to call its tools from a plain Python script — a cron job that checks Gmail, a scraping pipeline, an end-to-end test. The ghostvault-sdk package is a thin async client that wraps the MCP tool calls so you don't have to spell out tool names or JSON-encode arguments yourself.

Install

The SDK is an optional extra:

pip install -e ".[sdk]"

This installs mcp (the client library) and exposes ghostvault_sdk.GhostvaultClient.

The GhostvaultClient class

A single async context manager that connects to a Ghostvault MCP server. Source: sdk/ghostvault_sdk/client.py.

from ghostvault_sdk import GhostvaultClient

Constructor

GhostvaultClient(
    *,
    command: str | list[str] | None = None,  # stdio: command to launch the server
    url: str | None = None,                   # http: URL of a running server
    env: dict[str, str] | None = None,        # stdio: env vars for the subprocess
)

Pass either command (stdio) or url (HTTP), not both:

  • stdio (default) — launches the server as a subprocess. The default command is ["python", "-m", "ghostvault"]. Pass env={...} to set GHOSTVAULT_* variables on the subprocess.
  • HTTP — connects to an already-running server. See Remote (HTTP transport).

The client must be used as an async context manager — that's what opens the MCP transport and runs the initialize handshake.

async with GhostvaultClient() as gv:
    ...

call(tool, arguments=None)

Low-level escape hatch: call any MCP tool by name with a dict of arguments. Returns the raw tool result (a string, or a list of content blocks for screenshot tools).

result = await gv.call("gv_list_accounts")

You usually don't need call — the convenience wrappers below cover every tool.

Wrapper methods

All wrappers are coroutines. They map 1:1 to the gv_* MCP tools in the Tool reference. The returned values are the raw tool results (strings or content blocks) — for tools that return JSON text, parse with json.loads.

Accounts & lifecycle

Method Maps to Notes
await gv.list_accounts() gv_list_accounts
await gv.create_account(name=..., os=..., preset=..., locale=..., timezone=..., humanize=..., block_webrtc=..., block_webgl=..., webgl_config=..., fonts=..., addons=..., disable_coop=..., block_images=..., device_profile=..., device_config=..., proxy=..., provider=...) gv_create_account Only name is required; everything else is optional and None means "use the default".
await gv.update_account_config(account_id, ...) gv_update_account_config Same advanced/device knobs as create_account, plus reset_keys and reset_device_keys.
await gv.sign_in(account_id) gv_sign_in Blocks until login completes or times out.
await gv.open_account(account_id) gv_open_account Reuses saved session (no login window).
await gv.switch_account(account_id) gv_switch_account
await gv.lock_account(account_id) gv_lock_account Close + re-encrypt.
await gv.sign_out(account_id) gv_sign_out Invalidates the Google session server-side.
await gv.delete_account(account_id) gv_delete_account Removes profile + DB row.

Anonymous scraping (ephemeral)

Method Maps to Notes
await gv.open_ephemeral(url=None, *, headless=None, proxy=None, block_images=None) gv_open_ephemeral Returns a session id; all browser tools work on it unchanged.
await gv.close_ephemeral(session_id=None) gv_close_ephemeral Defaults to the active session.

Browser interaction

Method Maps to Notes
await gv.open_url(url, *, wait_until="domcontentloaded") gv_open_url
await gv.get_page_content(*, include_screenshot=False, max_chars=20000) gv_get_page_content Returns text + optional PNG.
await gv.screenshot(*, full_page=False) gv_screenshot Returns an MCP Image block.
await gv.click_element(target, *, timeout_ms=10000, humanize=None, delay_after_ms=0) gv_click_element humanize resolves under the 3-layer policy.
await gv.fill_input(target, value, *, timeout_ms=10000, humanize=None, typing_speed_wpm=None, typing_variance=0.35, mistake_rate=None, delay_after_ms=0) gv_fill_input Set humanize=True for login forms.
await gv.press_key(key, *, modifiers=None, hold_ms=0, delay_after_ms=0) gv_press_key e.g. key="Enter", modifiers=["Control"].
await gv.scroll(*, direction="down", amount=3, humanize=True, delay_after_ms=0) gv_scroll Good for reCAPTCHA v3 warm-up.
await gv.read_inbox(*, limit=20) gv_read_gmail

Debug

Method Maps to Notes
await gv.get_logs(*, n=50) gv_get_logs Recent entries from the action log.

Private/public gate

Method Maps to Notes
await gv.get_auth_status() gv_get_auth_status
await gv.setup_password() gv_setup_password Opens a browser window — needs a display.
await gv.unlock(password) gv_unlock
await gv.logout() gv_logout Lock the gate.
await gv.make_private(account_id) gv_make_private
await gv.make_public(account_id) gv_make_public

How it launches the server (stdio)

When you don't pass url=, the client spawns the server as a subprocess:

GhostvaultClient()  # spawns: python -m ghostvault

The default command is ["python", "-m", "ghostvault"]. Two gotchas:

  1. python must be on PATH for the subprocess. The client checks with shutil.which and raises FileNotFoundError with a helpful message if not. Pass an absolute path via command= to be safe:
GhostvaultClient(command=["/abs/path/to/.venv/bin/python", "-m", "ghostvault"])
  1. Env vars (like GHOSTVAULT_HEADLESS) don't auto-propagate from your shell — pass them explicitly:
GhostvaultClient(env={"GHOSTVAULT_HEADLESS": "true"})

Connecting to a remote HTTP server

If Ghostvault is already running on a server (e.g. GHOSTVAULT_TRANSPORT=http), skip the subprocess and point the client at the URL:

async with GhostvaultClient(url="http://10.0.0.5:8765/mcp") as gv:
    accounts = await gv.list_accounts()

See Remote (HTTP transport) for how to start the server in HTTP mode.

A complete example script

This is a minimal end-to-end: create an account, sign in, open a URL, read the page, clean up. Save as my_script.py and run with python my_script.py.

import asyncio
import json
from ghostvault_sdk import GhostvaultClient


def _text(result) -> str:
    """Extract text from an MCP tool result (handles str or content blocks)."""
    if isinstance(result, str):
        return result
    # SDK returns a list of content blocks for some tools — join text ones.
    parts = []
    for block in result:
        if hasattr(block, "text"):
            parts.append(block.text)
        elif isinstance(block, dict) and "text" in block:
            parts.append(block["text"])
    return "".join(parts)


async def main() -> None:
    # Launch the server as a subprocess in headful mode so we can do first-time login.
    async with GhostvaultClient(
        command=["/abs/path/to/.venv/bin/python", "-m", "ghostvault"],
        env={"GHOSTVAULT_HEADLESS": "false"},
    ) as gv:
        # 1. Create an account slot with a realistic hardware profile.
        created = await gv.create_account(
            name="Work",
            device_profile="macbook-pro-14-m2",
            locale="en-US",
            timezone="America/New_York",
        )
        data = json.loads(_text(created))
        account_id = data["id"]
        print(f"Created account {account_id}")
        if data.get("consistency_warnings"):
            print("  warnings:", data["consistency_warnings"])

        # 2. Sign in (opens a browser window — you complete login + 2FA).
        await gv.sign_in(account_id)
        print("Signed in.")

        # 3. Open a URL in the account's session.
        await gv.open_url("https://mail.google.com")

        # 4. Read the page text + take a screenshot.
        content = await gv.get_page_content(include_screenshot=True)
        print(_text(content)[:500])

        # 5. Read Gmail.
        inbox = await gv.read_inbox(limit=5)
        print(_text(inbox))

        # 6. Lock the account (close browser, keep session).
        await gv.lock_account(account_id)
        print("Locked.")


if __name__ == "__main__":
    asyncio.run(main())

More examples

The repo ships a full set of runnable scripts under examples/scripts/ — see the Examples index for a categorized list.