feat(v3): migrate workspace to API, Tauri desktop, and v3 crates; remove legacy MCP stack
Some checks failed
Secrets v3 CI / 检查 (push) Has been cancelled
Some checks failed
Secrets v3 CI / 检查 (push) Has been cancelled
- Add apps/api, desktop Tauri shell, domain/application/crypto/device-auth/infrastructure-db - Replace desktop-daemon vault integration; drop secrets-core and secrets-mcp* - Ignore apps/desktop/dist and generated Tauri icons; document icon/dist steps in AGENTS.md - Apply rustfmt; fix clippy (collapsible_if, HTTP method as str)
This commit is contained in:
13
crates/client-integrations/Cargo.toml
Normal file
13
crates/client-integrations/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "secrets-client-integrations"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "secrets_client_integrations"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
162
crates/client-integrations/src/lib.rs
Normal file
162
crates/client-integrations/src/lib.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
use anyhow::{Context, Result};
|
||||
use serde_json::{Map, Value};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub trait ClientAdapter {
|
||||
fn client_name(&self) -> &'static str;
|
||||
fn config_path(&self) -> PathBuf;
|
||||
}
|
||||
|
||||
pub struct CursorAdapter;
|
||||
|
||||
impl ClientAdapter for CursorAdapter {
|
||||
fn client_name(&self) -> &'static str {
|
||||
"cursor"
|
||||
}
|
||||
|
||||
fn config_path(&self) -> PathBuf {
|
||||
default_home().join(".cursor").join("mcp.json")
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClaudeCodeAdapter;
|
||||
|
||||
impl ClientAdapter for ClaudeCodeAdapter {
|
||||
fn client_name(&self) -> &'static str {
|
||||
"claude-code"
|
||||
}
|
||||
|
||||
fn config_path(&self) -> PathBuf {
|
||||
default_home().join(".claude").join("mcp.json")
|
||||
}
|
||||
}
|
||||
|
||||
fn default_home() -> PathBuf {
|
||||
std::env::var_os("HOME")
|
||||
.or_else(|| std::env::var_os("USERPROFILE"))
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
}
|
||||
|
||||
pub fn has_managed_server(adapter: &dyn ClientAdapter, server_name: &str) -> Result<bool> {
|
||||
let path = adapter.config_path();
|
||||
let root = read_config_or_default(&path)?;
|
||||
Ok(root
|
||||
.get("mcpServers")
|
||||
.and_then(Value::as_object)
|
||||
.is_some_and(|servers| servers.contains_key(server_name)))
|
||||
}
|
||||
|
||||
pub fn upsert_managed_server(
|
||||
adapter: &dyn ClientAdapter,
|
||||
server_name: &str,
|
||||
server_config: Value,
|
||||
) -> Result<()> {
|
||||
let path = adapter.config_path();
|
||||
let mut root = read_config_or_default(&path)?;
|
||||
let root_object = ensure_object(&mut root);
|
||||
let mcp_servers = root_object
|
||||
.entry("mcpServers".to_string())
|
||||
.or_insert_with(|| Value::Object(Map::new()));
|
||||
let servers_object = ensure_object(mcp_servers);
|
||||
servers_object.insert(server_name.to_string(), server_config);
|
||||
write_config_atomically(&path, &root)
|
||||
}
|
||||
|
||||
fn read_config_or_default(path: &Path) -> Result<Value> {
|
||||
if !path.exists() {
|
||||
return Ok(Value::Object(Map::new()));
|
||||
}
|
||||
let raw =
|
||||
fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
|
||||
serde_json::from_str(&raw).with_context(|| format!("failed to parse {}", path.display()))
|
||||
}
|
||||
|
||||
fn write_config_atomically(path: &Path, value: &Value) -> Result<()> {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)
|
||||
.with_context(|| format!("failed to create {}", parent.display()))?;
|
||||
}
|
||||
let tmp_path = path.with_extension("json.tmp");
|
||||
let body = serde_json::to_string_pretty(value).context("failed to serialize mcp config")?;
|
||||
fs::write(&tmp_path, body)
|
||||
.with_context(|| format!("failed to write {}", tmp_path.display()))?;
|
||||
fs::rename(&tmp_path, path).with_context(|| format!("failed to replace {}", path.display()))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_object(value: &mut Value) -> &mut Map<String, Value> {
|
||||
if !value.is_object() {
|
||||
*value = Value::Object(Map::new());
|
||||
}
|
||||
value.as_object_mut().expect("object just ensured")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
struct TestAdapter {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl ClientAdapter for TestAdapter {
|
||||
fn client_name(&self) -> &'static str {
|
||||
"test"
|
||||
}
|
||||
|
||||
fn config_path(&self) -> PathBuf {
|
||||
self.path.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upsert_preserves_other_servers() {
|
||||
let unique = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("clock")
|
||||
.as_nanos();
|
||||
let base = std::env::temp_dir().join(format!("secrets-client-integrations-{unique}"));
|
||||
let adapter = TestAdapter {
|
||||
path: base.join("mcp.json"),
|
||||
};
|
||||
fs::create_dir_all(adapter.path.parent().expect("parent")).expect("mkdir");
|
||||
fs::write(
|
||||
&adapter.path,
|
||||
r#"{"mcpServers":{"postgres":{"command":"npx"},"secrets":{"url":"http://old"}}}"#,
|
||||
)
|
||||
.expect("seed config");
|
||||
|
||||
upsert_managed_server(
|
||||
&adapter,
|
||||
"secrets",
|
||||
serde_json::json!({
|
||||
"url": "http://127.0.0.1:9515/mcp"
|
||||
}),
|
||||
)
|
||||
.expect("upsert config");
|
||||
|
||||
let root: Value =
|
||||
serde_json::from_str(&fs::read_to_string(&adapter.path).expect("read back"))
|
||||
.expect("parse back");
|
||||
let servers = root
|
||||
.get("mcpServers")
|
||||
.and_then(Value::as_object)
|
||||
.expect("mcpServers object");
|
||||
assert!(servers.contains_key("postgres"));
|
||||
assert_eq!(
|
||||
servers
|
||||
.get("secrets")
|
||||
.and_then(Value::as_object)
|
||||
.and_then(|value| value.get("url"))
|
||||
.and_then(Value::as_str),
|
||||
Some("http://127.0.0.1:9515/mcp")
|
||||
);
|
||||
|
||||
let _ = fs::remove_dir_all(base);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user