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
1 change: 1 addition & 0 deletions crates/agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,5 @@ windows-sys = { version = "0.61", features = [
"Win32_Foundation",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_System_Registry",
] }
2 changes: 1 addition & 1 deletion crates/agent/src/bin/agentdesktop-service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ mod windows {
))
.context("report Windows service running")?;

let stopping_status = status_handle.clone();
let stopping_status = status_handle;
let shutdown = async move {
if !*shutdown_receiver.borrow() {
shutdown_receiver
Expand Down
23 changes: 23 additions & 0 deletions crates/agent/src/provider/claude_desktop/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ fn discover_mcp_servers(managed: &Path) -> Vec<McpServer> {
})
.unwrap_or_default();

// On Windows the reconciler manages settings as machine policy registry
// values — the only place Claude Desktop reads them — so the managed
// servers are inventoried from there too.
#[cfg(windows)]
servers.extend(registry_managed_mcp_servers());

let mut paths = BTreeSet::new();
for home in metadata::user_home_dirs() {
paths.insert(home.join(".config/Claude/claude_desktop_config.json"));
Expand All @@ -118,6 +124,23 @@ fn discover_mcp_servers(managed: &Path) -> Vec<McpServer> {
servers
}

/// The managed MCP servers under Claude Desktop's Windows policy key, where
/// the reconciler writes `managedMcpServers` as a `REG_SZ` JSON string.
#[cfg(windows)]
fn registry_managed_mcp_servers() -> Vec<McpServer> {
let key = super::claude_desktop_policy_key();
crate::reconcile::registry::read_values(&key)
.ok()
.and_then(|values| values.get("managedMcpServers").cloned().flatten())
.map(|encoded| {
claude_code::managed_mcp_servers_from_value(
&Value::String(encoded),
&key.display_path(),
)
})
.unwrap_or_default()
}

/// Reads Claude Desktop's system-managed settings in the platform's native format — see
/// `default_claude_desktop_managed_settings_path`.
fn managed_settings(path: &Path) -> Option<Value> {
Expand Down
24 changes: 22 additions & 2 deletions crates/agent/src/provider/claude_desktop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use agentdesktop_core::model::Discovery;

use super::{Provider, ReconcileContext};
use crate::reconcile::ReconcilePlan;
#[cfg(windows)]
use crate::reconcile::registry::RegistryKey;

pub(super) mod discovery;
mod reconcile;
Expand All @@ -13,17 +15,31 @@ mod reconcile;
pub struct ClaudeDesktop {
pub managed_settings_path: PathBuf,
pub credential_helper_path: PathBuf,
/// The policy registry key the managed settings are reconciled into.
/// Production uses [`claude_desktop_policy_key`]; tests inject a
/// disposable `HKCU` key so they never touch live machine policy.
#[cfg(windows)]
pub(crate) policy_key: RegistryKey,
}

impl Default for ClaudeDesktop {
fn default() -> Self {
Self {
managed_settings_path: default_claude_desktop_managed_settings_path(),
credential_helper_path: default_claude_desktop_credential_helper_path(),
#[cfg(windows)]
policy_key: claude_desktop_policy_key(),
}
}
}

/// The live machine policy key Claude Desktop reads its managed settings
/// from on Windows.
#[cfg(windows)]
pub(crate) fn claude_desktop_policy_key() -> RegistryKey {
RegistryKey::local_machine(reconcile::POLICY_KEY)
}

impl ClaudeDesktop {
pub const ID: &'static str = "claude-desktop";
pub const DISPLAY_NAME: &'static str = "Claude Desktop";
Expand Down Expand Up @@ -59,9 +75,8 @@ impl Provider for ClaudeDesktop {
});
let plan = ReconcilePlan::default();
reconcile::plan(
&self.managed_settings_path,
self,
ctx.merge_user_settings,
&self.credential_helper_path,
&ctx.credential_helper,
&ctx.socket,
configured,
Expand All @@ -72,6 +87,11 @@ impl Provider for ClaudeDesktop {
}

/// Returns Claude Desktop's system-managed settings path.
///
/// On Windows Claude Desktop reads its managed configuration from the
/// `HKLM\SOFTWARE\Policies\Claude` registry key, never from a file, so the
/// reconciler targets that key and only removes what an earlier agentdesktop
/// wrote at this path.
#[cfg(not(target_os = "macos"))]
pub fn default_claude_desktop_managed_settings_path() -> PathBuf {
PathBuf::from("/etc/claude-desktop/managed-settings.json")
Expand Down
Loading