Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ cargo tauri build

Requires: Rust stable, Node.js 20+, and platform-specific Tauri dependencies ([see Tauri prerequisites](https://v2.tauri.app/start/prerequisites/)).

If you want the desktop app to control a live Chrome session through Chrome DevTools MCP, keep a local Node/npm install available at runtime. OpenPlanter shells out to `npx -y chrome-devtools-mcp@latest`; it does not bundle the server or launch Chrome for you.

## CLI Agent

The Python CLI agent can be used independently of the desktop app.
Expand All @@ -71,6 +73,8 @@ Or run a single task headlessly:
openplanter-agent --task "Cross-reference vendor payments against lobbying disclosures and flag overlaps" --workspace ./data
```

Chrome DevTools MCP support in the CLI/TUI also uses local `npx`, so install Node.js 20+ if you want to enable Chrome tools there.

### Docker

```bash
Expand Down Expand Up @@ -142,6 +146,53 @@ The agent has access to 20 tools, organized around its investigation workflow:

In **recursive mode** (the default), the agent spawns sub-agents via `subtask` and `execute` to parallelize entity resolution, cross-dataset linking, and evidence-chain construction across large investigations.

When Chrome DevTools MCP is enabled, OpenPlanter discovers Chrome's published MCP tools at solve start and appends them natively to the built-in tool set for the main agent, recursive subtasks, and execute flows.

## Chrome DevTools MCP

OpenPlanter can attach to the official Chrome DevTools MCP server and reuse an active Chrome debugging session. The integration is native in both runtimes, but the server itself is still the upstream package started locally through `npx`.

### Requirements

- Node.js and npm available on your `PATH`
- Chrome 144 or newer
- Remote debugging enabled in Chrome at `chrome://inspect/#remote-debugging`

### How OpenPlanter Connects

- Auto-connect mode: OpenPlanter starts `chrome-devtools-mcp` with `--autoConnect` and reuses a running Chrome session after you approve Chrome's debugging prompt.
- Browser URL mode: OpenPlanter passes `--browserUrl <endpoint>` to attach to an existing remote debugging endpoint. This takes precedence over auto-connect when configured.
- Channel selection: `stable` is the default channel; you can switch to `beta`, `dev`, or `canary` when needed.

If Chrome MCP cannot start because Node/npm is missing, Chrome remote debugging is disabled, or Chrome is not available, OpenPlanter keeps running with its built-in tools and reports Chrome MCP as `unavailable`.

### Desktop Usage

Use the desktop slash command:

```text
/chrome status
/chrome on
/chrome off
/chrome auto --save
/chrome url http://127.0.0.1:9222 --save
/chrome channel beta --save
```

The sidebar and `/status` output both show the current Chrome MCP runtime state.

### CLI Usage

Use per-run flags:

```bash
openplanter-agent --chrome-mcp --chrome-auto-connect
openplanter-agent --chrome-mcp --chrome-browser-url http://127.0.0.1:9222
openplanter-agent --chrome-mcp --chrome-channel beta
```

The TUI also supports `/chrome status|on|off|auto|url <endpoint>|channel <stable|beta|dev|canary> [--save]`.

## CLI Reference

```
Expand Down Expand Up @@ -181,6 +232,10 @@ OPENPLANTER_WORKSPACE=workspace
| `--provider NAME` | `auto`, `openai`, `anthropic`, `openrouter`, `cerebras`, `ollama` |
| `--model NAME` | Model name or `newest` to auto-select |
| `--reasoning-effort LEVEL` | `low`, `medium`, `high`, or `none` |
| `--chrome-mcp` / `--no-chrome-mcp` | Enable or disable native Chrome DevTools MCP tools |
| `--chrome-auto-connect` / `--no-chrome-auto-connect` | Use Chrome MCP auto-connect or require an explicit browser URL |
| `--chrome-browser-url URL` | Attach Chrome MCP to an existing remote debugging browser URL |
| `--chrome-channel CHANNEL` | Chrome release channel for auto-connect: `stable`, `beta`, `dev`, `canary` |
| `--list-models` | Fetch available models from the provider API |

### Execution
Expand All @@ -204,7 +259,7 @@ OPENPLANTER_WORKSPACE=workspace

### Persistent Defaults

Use `--default-model`, `--default-reasoning-effort`, or per-provider variants like `--default-model-openai` to save workspace defaults to `.openplanter/settings.json`. View them with `--show-settings`.
Use `--default-model`, `--default-reasoning-effort`, Chrome MCP slash commands with `--save`, or per-provider variants like `--default-model-openai` to save workspace defaults to `.openplanter/settings.json`. View them with `--show-settings`.

## Configuration

Expand Down
100 changes: 99 additions & 1 deletion agent/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,40 @@ def build_parser() -> argparse.ArgumentParser:
parser.add_argument("--openrouter-api-key", help="OpenRouter API key override.")
parser.add_argument("--cerebras-api-key", help="Cerebras API key override.")
parser.add_argument("--exa-api-key", help="Exa API key override.")
parser.add_argument(
"--chrome-mcp",
dest="chrome_mcp_enabled",
action="store_true",
help="Enable native Chrome DevTools MCP tools for this run.",
)
parser.add_argument(
"--no-chrome-mcp",
dest="chrome_mcp_enabled",
action="store_false",
help="Disable native Chrome DevTools MCP tools for this run.",
)
parser.add_argument(
"--chrome-auto-connect",
dest="chrome_auto_connect",
action="store_true",
help="Ask the Chrome DevTools MCP server to auto-connect to a running Chrome instance.",
)
parser.add_argument(
"--no-chrome-auto-connect",
dest="chrome_auto_connect",
action="store_false",
help="Disable Chrome MCP auto-connect and rely on --chrome-browser-url instead.",
)
parser.add_argument(
"--chrome-browser-url",
help="Remote debugging browser URL for Chrome DevTools MCP (preferred over auto-connect).",
)
parser.add_argument(
"--chrome-channel",
choices=["stable", "beta", "dev", "canary"],
help="Chrome channel to target when Chrome MCP auto-connect is used.",
)
parser.set_defaults(chrome_mcp_enabled=None, chrome_auto_connect=None)
parser.add_argument("--voyage-api-key", help="Voyage API key override.")
parser.add_argument(
"--configure-keys",
Expand Down Expand Up @@ -167,7 +201,7 @@ def _resolve_provider(requested: str, creds: CredentialBundle) -> str:
return "openrouter"
if creds.cerebras_api_key:
return "cerebras"
return "openai"
return "anthropic"


def _print_models(cfg: AgentConfig, requested_provider: str) -> int:
Expand Down Expand Up @@ -329,6 +363,16 @@ def _apply_runtime_overrides(cfg: AgentConfig, args: argparse.Namespace, creds:
cfg.model = args.model
if args.reasoning_effort:
cfg.reasoning_effort = None if args.reasoning_effort == "none" else args.reasoning_effort
if args.chrome_mcp_enabled is not None:
cfg.chrome_mcp_enabled = bool(args.chrome_mcp_enabled)
if args.chrome_auto_connect is not None:
cfg.chrome_mcp_auto_connect = bool(args.chrome_auto_connect)
if args.chrome_browser_url is not None:
cfg.chrome_mcp_browser_url = args.chrome_browser_url.strip() or None
if cfg.chrome_mcp_browser_url:
cfg.chrome_mcp_enabled = True
if args.chrome_channel:
cfg.chrome_mcp_channel = args.chrome_channel
if args.recursive:
cfg.recursive = True
if args.acceptance_criteria:
Expand Down Expand Up @@ -419,6 +463,40 @@ def _apply_persistent_settings(
and settings.default_reasoning_effort
):
cfg.reasoning_effort = settings.default_reasoning_effort
if (
args.chrome_mcp_enabled is None
and os.getenv("OPENPLANTER_CHROME_MCP_ENABLED") is None
and settings.chrome_mcp_enabled is not None
):
cfg.chrome_mcp_enabled = settings.chrome_mcp_enabled
if (
args.chrome_auto_connect is None
and os.getenv("OPENPLANTER_CHROME_MCP_AUTO_CONNECT") is None
and settings.chrome_mcp_auto_connect is not None
):
cfg.chrome_mcp_auto_connect = settings.chrome_mcp_auto_connect
if (
args.chrome_browser_url is None
and os.getenv("OPENPLANTER_CHROME_MCP_BROWSER_URL") is None
and settings.chrome_mcp_browser_url
):
cfg.chrome_mcp_browser_url = settings.chrome_mcp_browser_url
if (
args.chrome_channel is None
and os.getenv("OPENPLANTER_CHROME_MCP_CHANNEL") is None
and settings.chrome_mcp_channel
):
cfg.chrome_mcp_channel = settings.chrome_mcp_channel
if (
os.getenv("OPENPLANTER_CHROME_MCP_CONNECT_TIMEOUT_SEC") is None
and settings.chrome_mcp_connect_timeout_sec is not None
):
cfg.chrome_mcp_connect_timeout_sec = settings.chrome_mcp_connect_timeout_sec
if (
os.getenv("OPENPLANTER_CHROME_MCP_RPC_TIMEOUT_SEC") is None
and settings.chrome_mcp_rpc_timeout_sec is not None
):
cfg.chrome_mcp_rpc_timeout_sec = settings.chrome_mcp_rpc_timeout_sec

return settings

Expand All @@ -432,6 +510,24 @@ def _print_settings(settings: PersistentSettings) -> None:
print(f" default_model_openrouter: {settings.default_model_openrouter or '(unset)'}")
print(f" default_model_cerebras: {settings.default_model_cerebras or '(unset)'}")
print(f" default_model_ollama: {settings.default_model_ollama or '(unset)'}")
print(
" chrome_mcp_enabled: "
f"{settings.chrome_mcp_enabled if settings.chrome_mcp_enabled is not None else '(unset)'}"
)
print(
" chrome_mcp_auto_connect: "
f"{settings.chrome_mcp_auto_connect if settings.chrome_mcp_auto_connect is not None else '(unset)'}"
)
print(f" chrome_mcp_browser_url: {settings.chrome_mcp_browser_url or '(unset)'}")
print(f" chrome_mcp_channel: {settings.chrome_mcp_channel or '(unset)'}")
print(
" chrome_mcp_connect_timeout_sec: "
f"{settings.chrome_mcp_connect_timeout_sec if settings.chrome_mcp_connect_timeout_sec is not None else '(unset)'}"
)
print(
" chrome_mcp_rpc_timeout_sec: "
f"{settings.chrome_mcp_rpc_timeout_sec if settings.chrome_mcp_rpc_timeout_sec is not None else '(unset)'}"
)


def _has_non_interactive_command(args: argparse.Namespace) -> bool:
Expand Down Expand Up @@ -566,6 +662,7 @@ def main() -> None:

engine = build_engine(cfg)
model_name = _get_model_display_name(engine)
chrome_status = engine.tools.chrome_mcp_status()

try:
runtime = SessionRuntime.bootstrap(
Expand All @@ -585,6 +682,7 @@ def main() -> None:
if cfg.reasoning_effort:
startup_info["Reasoning"] = cfg.reasoning_effort
startup_info["Mode"] = "recursive" if cfg.recursive else "flat"
startup_info["ChromeMCP"] = f"{chrome_status.status}: {chrome_status.detail}"
startup_info["Workspace"] = str(cfg.workspace)
startup_info["WorkspaceSource"] = workspace_resolution.source
if workspace_resolution.guardrail_action != "none":
Expand Down
6 changes: 6 additions & 0 deletions agent/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@ def build_engine(cfg: AgentConfig) -> RLMEngine:
mistral_transcription_chunk_overlap_seconds=cfg.mistral_transcription_chunk_overlap_seconds,
mistral_transcription_max_chunks=cfg.mistral_transcription_max_chunks,
mistral_transcription_request_timeout_sec=cfg.mistral_transcription_request_timeout_sec,
chrome_mcp_enabled=cfg.chrome_mcp_enabled,
chrome_mcp_auto_connect=cfg.chrome_mcp_auto_connect,
chrome_mcp_browser_url=cfg.chrome_mcp_browser_url,
chrome_mcp_channel=cfg.chrome_mcp_channel,
chrome_mcp_connect_timeout_sec=cfg.chrome_mcp_connect_timeout_sec,
chrome_mcp_rpc_timeout_sec=cfg.chrome_mcp_rpc_timeout_sec,
max_observation_chars=cfg.max_observation_chars,
)

Expand Down
Loading