Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/feature-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Flagship feature. C++ services + `dao://dao-agent` WebUI + vendor runtime.
| ☐ | Default-off process-global local MCP server | `src/dao/.../mcp/dao_mcp_{service,transport,connection,protocol,runtime_files}.*`, `dao_pref_names.*`, `browser_prefs_mcp.cc.patch`, `chrome_browser_main_extra_parts_profiles.cc.patch` | 🔴 | Run `DaoMcpServiceBrowserTest.*`, `DaoMcpProtocolTest.*`, and `DaoMcpRuntimeFilesTest.*`; verify browser-IO-thread listener ownership, owner-only runtime permissions, nonce rotation, same-UID authentication, protocol/version/line limits, 64-request/8 MiB per-connection and bounded aggregate unconsumed-ingress credits, terminal-request logical closing before later same-batch tools, aggregate write backpressure, bounded graceful-close drains, 32-client admission, serialized approval prompts, concurrent different-tab control, same-tab exclusion, idle hello/catalog discovery beyond the approval timeout without a prompt or disconnect, exact last-active-window selection and approval on the first tool call, pre-approval catalog access even when connection begins on Dao Settings, re-entrant approval cancellation denial, approval plus lease ordering, cancellation, optional non-tab `tab_id` schema/routing, isolated concurrent tab contexts, default-target compatibility after MCP switch/open, unknown-target fail-closed behavior, and complete per-connection lease/runtime cleanup after disconnect, disable, and shutdown |
| ☐ | Settings MCP master switch, connection, quick setup, and Stop | `src/dao/.../mcp/dao_mcp_settings_handler.{h,cc}`, `resources/settings/dao_page/dao_page.{html,ts}.patch`, `webui/settings/settings_ui.cc.patch` | 🟡 | Run `DaoMcpInstallCommandTest.*`, `DaoMcpSettingsHandlerTest.*`, `DaoMcpSettingsPageBrowserTest.*`, and `DaoPage`; verify one header/connection/enabled-only-setup card, responsive selector/copy alignment, and text status updates through `dao-mcp-status-changed`. The switch must write process-global Local State rather than `prefs.dao`; client details and Stop appear only for an active authorized lease. Confirm setup is absent while disabled; when enabled it defaults to Codex and switches to user-scoped Claude Code or Generic MCP. CLI previews stay single-line, Generic MCP preview and clipboard are identical Chromium-native three-space pretty JSON, and malformed Generic JSON fails closed without changing the clipboard. Also verify option-specific feedback, POSIX-safe helper and current user-data-directory arguments, Debug/custom-profile endpoint binding, stale preview rejection, listener cleanup, and absence of the standalone configuration button. |
| ☐ | Native MCP stdio helper and macOS app bundling | `src/dao/.../mcp/helper/`, `dao_mcp_helper_browsertest.cc`, `dao_version.gni`, `chrome/BUILD_mcp_helper.gn.patch` | 🔴 | Run `DaoMcpHelperBrowserTest.*`; verify all 29 tools survive catalog adaptation, MCP `2025-11-25` and Codex-compatible `2025-06-18` negotiation with initialized gating, the `codex/tool-catalog-cache.cacheable=false` compatibility capability, server-wide instructions that prefer Dao MCP, establish the initial target with `list_tabs`, preserve it across follow-ups, route ambiguous open/click/select requests through `get_accessibility_tree` and `click_by_ref`, and reserve `switch_tab` for explicit browser-tab navigation; verify adapted per-tool descriptions do not repeat tab-discovery guidance, plus string/numeric IDs, object/scalar/list `structuredContent`, real screenshot MIME, `isError` failures, cancellation with no late response, disabled-browser stderr determinism, JSON-only stdout, and executable copies at both the build output and `Dao.app/Contents/Helpers/dao-mcp` |
| ☐ | Local MCP approval, controlled-tab indicator, Stop, and peer-agent busy UX | `dao_mcp_approval_dialog.{h,cc}`, `dao_mcp_control_banner_view.{h,cc}`, `dao_address_bar_view.{h,cc}`, `dao_mcp_service.{h,cc}`, `dao_agent_ui.{h,cc}`, `pi_tool_adapter.ts` | 🔴 | Run `DaoMcpApprovalDialogTest.*`, `DaoMcpControlBannerTest.*`, `DaoMcpPeerLeaseTest.*`, `pi_tool_adapter.test.ts`, and `dao_chat_view.test.ts`; verify serialized localized prompts with reported client/version, available verified PID, window, and Profile rendering, exact native Browser activation when a prompt arrives behind another application, the 60-second unanswered-request timeout, no default Allow action, deny/close/parent destruction exactly once and fail closed, robot-button visibility only for the active controlled tab in the authorized normal Browser, popup client/target/count details, no extra content row, clickable per-connection Stop, lease release/disconnect transitions without disturbing other clients, chat continuity, different-tab parallelism, and same-tab pre-CDP `AGENT_CONTROL_BUSY` browser-tool failures |
| ☐ | Local MCP approval, controlled-tab indicator, Stop, and peer-agent busy UX | `dao_mcp_approval_dialog.{h,cc}`, `dao_mcp_control_banner_view.{h,cc}`, `dao_address_bar_view.{h,cc}`, `dao_mcp_service.{h,cc}`, `ui/webui/dao_sidebar_ui.{h,cc}`, `resources/sidebar/{dao_tab_item.ts,sidebar_bridge.ts}`, `dao_agent_ui.{h,cc}`, `pi_tool_adapter.ts` | 🔴 | Run `DaoMcpApprovalDialogTest.*`, `DaoMcpControlBannerTest.*`, `DaoMcpPeerLeaseTest.*`, `tab_item.test.ts`, `pi_tool_adapter.test.ts`, and `dao_chat_view.test.ts`; verify serialized localized prompts with reported client/version, available verified PID, window, and Profile rendering, exact native Browser activation when a prompt arrives behind another application, the 60-second unanswered-request timeout, no default Allow action, deny/close/parent destruction exactly once and fail closed, address-bar robot visibility only for the active controlled tab in the authorized normal Browser, a sidebar robot for every controlled tab with the close action revealed on pointer hover or keyboard focus without layout shift, prompt target add/removal updates after switch/open, popup client/target/count details, no extra content row, clickable per-connection Stop, lease release/disconnect transitions without disturbing other clients, chat continuity, different-tab parallelism, and same-tab pre-CDP `AGENT_CONTROL_BUSY` browser-tool failures |
| ☐ | MCP isolated-target eligibility and lifecycle | `automation/dao_browser_target_policy.{h,cc}`, `mcp/dao_mcp_session_lifecycle_monitor.{h,cc}`, `dao_mcp_end_to_end_browsertest.cc`, `dao_mcp_service.{h,cc}` | 🔴 | Run `DaoMcpEndToEndBrowserTest.*` and the lifecycle filters in `DaoMcpServiceBrowserTest.*`; verify HTTP/HTTPS/literal blank/web-hosted PDF allow, popup/OTR/Guest/internal/extension/DevTools/Agent WebUI/file/data/custom rejection for execution without blocking catalog discovery, exact-owner `TARGET_GONE`, no active-tab fallback, pre-mutation forbidden switch rejection, per-target cancellation and cleanup without disturbing sibling contexts, last-target/Browser/Profile terminal cleanup, no Ready/Disabled status during enabled logical closing, and connection-slot release only after the affected socket disconnects |
| ☐ | MCP startup, packaging, protocol, UI, and rebinding regression sweep | `browser_prefs_mcp.cc.patch`, `chrome_browser_main_extra_parts_profiles*.patch`, `chrome/BUILD_mcp_helper.gn.patch`, `mcp/`, `dao_mcp_approval_dialog.*`, `dao_mcp_control_banner_view.*`, `dao_address_bar_view.*`, Settings Dao page patches | 🔴 | After Chromium upgrades, verify Local State registration and clean startup/shutdown, owner-only Unix socket/metadata plus helper executable packaging, protocol framing/version/8 MiB and ingress/write bounds, per-tab DevTools attach/cancel/detach, approval and address-bar indicator/popup layout, Settings switch/status/enabled-only quick setup/Copy/Stop, stable tab identity across reorder/restore/WebContents replacement, optional `tab_id` routing, and complete target rebinding/cleanup |
| ☐ | Agent page, selection, element-context, element-screenshot, and PDF-text attachments | `src/dao/.../agent/dao_chat_view.ts`, `dao_page_capture.ts`, `dao_agent_ui.cc` | — | Composer can attach current page, selected text, picked element DOM context, picked element screenshot, and PDF text without losing existing chips |
Expand Down
2 changes: 1 addition & 1 deletion docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ The stack includes: **LLM tool calling**, **long-term memory** (SQLite + FTS5),
- **Hardened local transport** (`mcp/dao_mcp_transport.{h,cc}`, `dao_mcp_connection.{h,cc}`, `dao_mcp_runtime_files.{h,mm}`) — The server owns all listener and connection I/O on Chromium's browser IO thread, uses a non-abstract Unix domain socket inside the user-data MCP directory, enforces owner-only directory/socket/metadata permissions, authenticates the peer UID plus a fresh 256-bit nonce, admits at most 32 external clients, and removes runtime artifacts on disable or shutdown. Atomic `runtime.json` metadata publishes the socket and nonce only inside that private directory. Per-connection and aggregate IO-thread credits bound requests posted but not yet consumed by the UI thread, terminal responses synchronously close only that connection's logical request gate, aggregate write budgets fail closed under backpressure, and graceful close has bounded request and write-drain deadlines.
- **Versioned NDJSON protocol** (`mcp/dao_mcp_protocol.{h,cc}`) — Protocol version 1 supports `hello`, `tools/list`, `tools/call`, and `tools/cancel`, with an 8 MiB line ceiling, 64-request/8 MiB pending-ingress budget, and structured errors. Catalog discovery is available before approval, while execution waits for approval and the external automation lease.
- **Central external-target eligibility and MCP lifecycle policy** (`automation/dao_browser_target_policy.{h,cc}`, `mcp/dao_mcp_session_lifecycle_monitor.{h,cc}`) — Every MCP target stays pinned to its exact tab in the approved normal Browser and regular Profile, with no eligible-tab or active-tab fallback. HTTP, HTTPS, literal `about:blank`, and web-hosted PDFs are allowed; popup, Incognito, Guest, internal, extension, DevTools, Agent WebUI, file, data, and custom-scheme targets are rejected. Target destruction or forbidden navigation cancels and removes only that tab's work, lock, overlay, and CDP state; losing the last target, the Browser, or the Profile closes only the affected logical connection and releases its leases.
- **Exact-window approval and control UX** (`dao_mcp_approval_dialog.{h,cc}`, `dao_mcp_control_banner_view.{h,cc}`, `dao_address_bar_view.{h,cc}`) — Execution leases display localized, fail-closed Dao system dialogs one at a time in the exact normal Browser selected for approval, with sanitized reported client metadata, verified PID when available, window, Profile, and current-login warning. Before showing a prompt, Dao activates that native Browser window so approval requests arriving while Dao is behind another application come to the foreground; unanswered prompts time out after 60 seconds. Allow is intentionally not the default action. A robot button immediately before the URL pill appears only when the active tab is controlled; its popup shows that connection's client, version, verified PID, current target, controlled-tab count, and Stop. Stop cancels that connection's external work, releases its leases, and closes it without inserting a control row above page content.
- **Exact-window approval and control UX** (`dao_mcp_approval_dialog.{h,cc}`, `dao_mcp_control_banner_view.{h,cc}`, `dao_address_bar_view.{h,cc}`, `ui/webui/dao_sidebar_ui.{h,cc}`, `resources/sidebar/dao_tab_item.ts`) — Execution leases display localized, fail-closed Dao system dialogs one at a time in the exact normal Browser selected for approval, with sanitized reported client metadata, verified PID when available, window, Profile, and current-login warning. Before showing a prompt, Dao activates that native Browser window so approval requests arriving while Dao is behind another application come to the foreground; unanswered prompts time out after 60 seconds. Allow is intentionally not the default action. A robot button immediately before the URL pill appears only when the active tab is controlled; every controlled tab also shows a quiet robot in its sidebar close-button slot, replaced by the normal close button on pointer hover or keyboard focus. The address-bar popup shows that connection's client, version, verified PID, current target, controlled-tab count, and Stop. Stop cancels that connection's external work, releases its leases, and closes it without inserting a control row above page content.
- **Per-tab peer contention** — The browser-automation lease is exclusive per tab, so different Codex or Dao Agent sessions can operate different tabs concurrently. A second browser-tool session targeting an already controlled tab waits or fails with the stable retryable busy error before CDP execution; chat and non-browser tools remain available.
- **Native stdio MCP helper** (`mcp/helper/`) — The standalone `dao-mcp` executable speaks newline-delimited JSON-RPC and negotiates MCP `2025-11-25` or `2025-06-18` over stdin/stdout, discovers the authenticated browser endpoint from the active user-data directory, and bridges MCP string or numeric request IDs to bounded browser IPC IDs. Its initialization response opts out of Codex's shared tool-catalog cache and directs MCP clients to prefer Dao tools for Dao Browser work, use `list_tabs` to establish the initial target, preserve that target across follow-ups, and treat ambiguous open/click/select requests as page-local accessibility-tree interactions. `switch_tab` is reserved for explicit browser-tab navigation, while exported tool descriptions retain only the general Dao MCP preference. It maps the 29-tool catalog to MCP annotations, normalized object/scalar/list results to text plus object `structuredContent`, screenshot media to image content with its real MIME type, and tool failures to `isError: true`. Cancellation is forwarded to the browser and late responses are discarded. Stdout remains protocol-only; unavailable-browser diagnostics are deterministic stderr output.
- **29-tool external scope** — The shared native catalog contains 30 Dao Agent browser tools; MCP exposes 29 and excludes only the Agent-specific `resolve_element_context`. Agent memory, skill, workspace, and web-provider tools are not part of the local MCP server.
Expand Down
28 changes: 19 additions & 9 deletions src/dao/browser/mcp/dao_mcp_service.cc
Original file line number Diff line number Diff line change
Expand Up @@ -746,9 +746,12 @@ void DaoMcpService::OnConnectionClosed(uint64_t runtime_generation,
if (connection == connections_.end()) {
return;
}
const bool had_controlled_target = std::ranges::any_of(
connection->second->target_contexts,
[](const auto& entry) { return entry.second->lease.has_value(); });
ResetConnectionState(*connection->second);
connections_.erase(connection);
UpdateStatus();
UpdateStatus(had_controlled_target);
}

DaoMcpService::ConnectionState* DaoMcpService::FindConnection(
Expand Down Expand Up @@ -1440,8 +1443,12 @@ base::expected<void, DaoToolError> DaoMcpService::AcquireTargetLease(
}

void DaoMcpService::RejectConnection(ConnectionState& connection,
DaoToolError error) {
DaoToolError error,
bool notify_if_status_unchanged) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
notify_if_status_unchanged |= std::ranges::any_of(
connection.target_contexts,
[](const auto& entry) { return entry.second->lease.has_value(); });
connection.hello_timer.Stop();
connection.approval_timer.Stop();
connection.lease_retry_timer.Stop();
Expand Down Expand Up @@ -1474,7 +1481,7 @@ void DaoMcpService::RejectConnection(ConnectionState& connection,
connection.client_info.reset();
connection.connection_id.clear();
connection.approval_deadline = base::TimeTicks();
UpdateStatus();
UpdateStatus(notify_if_status_unchanged);
}

void DaoMcpService::OnTargetInvalidated(uint64_t connection_generation,
Expand All @@ -1489,6 +1496,7 @@ void DaoMcpService::OnTargetInvalidated(uint64_t connection_generation,
if (context == connection->target_contexts.end()) {
return;
}
const bool was_controlled = context->second->lease.has_value();
FailPendingCallsForTarget(*connection, target_id, error);
context->second->tool_executor->CancelAll(error);
context->second->tool_executor->ClearSessionState(
Expand All @@ -1502,10 +1510,10 @@ void DaoMcpService::OnTargetInvalidated(uint64_t connection_generation,
}
if (connection->target_contexts.empty() &&
connection->tab_tool_sessions.empty()) {
RejectConnection(*connection, std::move(error));
RejectConnection(*connection, std::move(error), was_controlled);
return;
}
UpdateStatus();
UpdateStatus(was_controlled);
}

void DaoMcpService::FailPendingCalls(ConnectionState& connection,
Expand Down Expand Up @@ -1664,7 +1672,7 @@ void DaoMcpService::OnToolCallComplete(uint64_t connection_generation,
SerializeDaoBrowserToolResult(std::move(result)));
}
if (target_set_changed) {
UpdateStatus();
UpdateStatus(/*notify_if_unchanged=*/true);
}
if (!connection->closing && connection->target_contexts.empty() &&
connection->tab_tool_sessions.empty()) {
Expand All @@ -1679,7 +1687,7 @@ void DaoMcpService::NotifyStatusObservers() {
status_observers_.Notify(status_);
}

void DaoMcpService::UpdateStatus() {
void DaoMcpService::UpdateStatus(bool notify_if_unchanged) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DaoMcpServiceStatus next;
next.state = listener_active_ || listener_start_pending_
Expand Down Expand Up @@ -1707,10 +1715,12 @@ void DaoMcpService::UpdateStatus() {
next.client->name == status_.client->name &&
next.client->version == status_.client->version &&
next.client->verified_pid == status_.client->verified_pid));
if (unchanged) {
if (unchanged && !notify_if_unchanged) {
return;
}
status_ = std::move(next);
if (!unchanged) {
status_ = std::move(next);
}
NotifyStatusObservers();
}

Expand Down
6 changes: 4 additions & 2 deletions src/dao/browser/mcp/dao_mcp_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ class DaoMcpService {
ConnectionState& connection,
TargetContext& context,
bool allow_uncommitted_url = false);
void RejectConnection(ConnectionState& connection, DaoToolError error);
void RejectConnection(ConnectionState& connection,
DaoToolError error,
bool notify_if_status_unchanged = false);
void OnTargetInvalidated(uint64_t connection_generation,
std::string target_id,
DaoToolError error);
Expand All @@ -240,7 +242,7 @@ class DaoMcpService {
DaoBrowserToolResult result);

void NotifyStatusObservers();
void UpdateStatus();
void UpdateStatus(bool notify_if_unchanged = false);

raw_ptr<PrefService> local_state_ = nullptr;
base::FilePath user_data_dir_;
Expand Down
Loading
Loading