fix(mcp): remove secrets_find/add/update aliases; align docs and repair script
Some checks failed
Secrets v3 CI / 检查 (push) Failing after 2m16s
Some checks failed
Secrets v3 CI / 检查 (push) Failing after 2m16s
This commit is contained in:
@@ -153,12 +153,6 @@ http://127.0.0.1:9515/mcp
|
|||||||
|
|
||||||
- `secrets_env_map`
|
- `secrets_env_map`
|
||||||
|
|
||||||
兼容别名:
|
|
||||||
|
|
||||||
- `secrets_find`
|
|
||||||
- `secrets_add`
|
|
||||||
- `secrets_update`
|
|
||||||
|
|
||||||
### `target_exec`
|
### `target_exec`
|
||||||
|
|
||||||
`target_exec` 会显式读取 entry 当前 secrets 的真实值,并从 metadata / secrets 派生标准环境变量,例如:
|
`target_exec` 会显式读取 entry 当前 secrets 的真实值,并从 metadata / secrets 派生标准环境变量,例如:
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ cargo run -p secrets-desktop
|
|||||||
- `secrets_secret_add` / `secrets_secret_update` / `secrets_secret_delete`
|
- `secrets_secret_add` / `secrets_secret_update` / `secrets_secret_delete`
|
||||||
- `secrets_secret_history` / `secrets_secret_rollback`
|
- `secrets_secret_history` / `secrets_secret_rollback`
|
||||||
- `target_exec`
|
- `target_exec`
|
||||||
- 保留兼容别名:`secrets_find` / `secrets_add` / `secrets_update`
|
|
||||||
- 桌面端会自动把本地 daemon MCP 配置写入 `Cursor` 与 `Claude Code`
|
- 桌面端会自动把本地 daemon MCP 配置写入 `Cursor` 与 `Claude Code`
|
||||||
- 桌面端支持条目新建、搜索、按 type 筛选、元数据编辑、最近删除与恢复
|
- 桌面端支持条目新建、搜索、按 type 筛选、元数据编辑、最近删除与恢复
|
||||||
- 桌面端支持 secret 新增、编辑、删除、明文显示、真实复制、历史查看与回滚
|
- 桌面端支持 secret 新增、编辑、删除、明文显示、真实复制、历史查看与回滚
|
||||||
@@ -82,14 +81,6 @@ cargo test --locked
|
|||||||
| `secrets_secret_rollback` | 将单个本地 secret 回滚到指定版本 |
|
| `secrets_secret_rollback` | 将单个本地 secret 回滚到指定版本 |
|
||||||
| `target_exec` | 用本地对象的 metadata 和 secrets 生成 `TARGET_*` 环境变量并执行本地命令 |
|
| `target_exec` | 用本地对象的 metadata 和 secrets 生成 `TARGET_*` 环境变量并执行本地命令 |
|
||||||
|
|
||||||
### 兼容别名
|
|
||||||
|
|
||||||
以下旧名称仍可用,但内部已转发到 v3 工具:
|
|
||||||
|
|
||||||
- `secrets_find` -> `secrets_entry_find`
|
|
||||||
- `secrets_add` -> `secrets_entry_add`
|
|
||||||
- `secrets_update` -> `secrets_entry_update`
|
|
||||||
|
|
||||||
## AI 客户端配置
|
## AI 客户端配置
|
||||||
|
|
||||||
桌面端会自动把本地 daemon 写入以下配置:
|
桌面端会自动把本地 daemon 写入以下配置:
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ fn initialize_response(id: Value) -> Response {
|
|||||||
"version": env!("CARGO_PKG_VERSION"),
|
"version": env!("CARGO_PKG_VERSION"),
|
||||||
"title": "Secrets Desktop Daemon"
|
"title": "Secrets Desktop Daemon"
|
||||||
},
|
},
|
||||||
"instructions": "Preferred tools: secrets_entry_find, secrets_entry_get, secrets_entry_add, secrets_entry_update, secrets_entry_delete, secrets_entry_restore, secrets_secret_add, secrets_secret_update, secrets_secret_delete, secrets_secret_history, secrets_secret_rollback, and target_exec. All data is resolved from the desktop app's unlocked local vault session. Legacy aliases secrets_find, secrets_add, and secrets_update remain supported."
|
"instructions": "Preferred tools: secrets_entry_find, secrets_entry_get, secrets_entry_add, secrets_entry_update, secrets_entry_delete, secrets_entry_restore, secrets_secret_add, secrets_secret_update, secrets_secret_delete, secrets_secret_history, secrets_secret_rollback, and target_exec. All data is resolved from the desktop app's unlocked local vault session."
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Response::builder()
|
Response::builder()
|
||||||
@@ -290,48 +290,6 @@ fn tool_definitions() -> Vec<Value> {
|
|||||||
"required": ["target_ref", "command"]
|
"required": ["target_ref", "command"]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
json!({
|
|
||||||
"name": "secrets_find",
|
|
||||||
"description": "Legacy alias for secrets_entry_find.",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"query": { "type": ["string", "null"] },
|
|
||||||
"folder": { "type": ["string", "null"] },
|
|
||||||
"type": { "type": ["string", "null"] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
json!({
|
|
||||||
"name": "secrets_add",
|
|
||||||
"description": "Legacy alias for secrets_entry_add.",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"folder": { "type": "string" },
|
|
||||||
"name": { "type": "string" },
|
|
||||||
"type": { "type": ["string", "null"] },
|
|
||||||
"metadata": { "type": ["object", "null"] },
|
|
||||||
"secrets": { "type": ["array", "null"] }
|
|
||||||
},
|
|
||||||
"required": ["folder", "name"]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
json!({
|
|
||||||
"name": "secrets_update",
|
|
||||||
"description": "Legacy alias for secrets_entry_update.",
|
|
||||||
"inputSchema": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": { "type": "string" },
|
|
||||||
"folder": { "type": ["string", "null"] },
|
|
||||||
"name": { "type": ["string", "null"] },
|
|
||||||
"type": { "type": ["string", "null"] },
|
|
||||||
"metadata": { "type": ["object", "null"] }
|
|
||||||
},
|
|
||||||
"required": ["id"]
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +326,7 @@ fn revealed_secrets_to_env(secrets: &[SecretValueField]) -> HashMap<String, Valu
|
|||||||
|
|
||||||
async fn call_tool(state: &AppState, name: &str, arguments: Value) -> Result<Value> {
|
async fn call_tool(state: &AppState, name: &str, arguments: Value) -> Result<Value> {
|
||||||
match name {
|
match name {
|
||||||
"secrets_find" | "secrets_entry_find" => {
|
"secrets_entry_find" => {
|
||||||
let folder = arguments
|
let folder = arguments
|
||||||
.get("folder")
|
.get("folder")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
@@ -417,7 +375,7 @@ async fn call_tool(state: &AppState, name: &str, arguments: Value) -> Result<Val
|
|||||||
let secrets = fetch_revealed_entry_secrets(state, id).await?;
|
let secrets = fetch_revealed_entry_secrets(state, id).await?;
|
||||||
Ok(entry_detail_payload(&detail, Some(&secrets)))
|
Ok(entry_detail_payload(&detail, Some(&secrets)))
|
||||||
}
|
}
|
||||||
"secrets_add" | "secrets_entry_add" => {
|
"secrets_entry_add" => {
|
||||||
let folder = arguments
|
let folder = arguments
|
||||||
.get("folder")
|
.get("folder")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
@@ -451,7 +409,7 @@ async fn call_tool(state: &AppState, name: &str, arguments: Value) -> Result<Val
|
|||||||
.await
|
.await
|
||||||
.context("failed to decode create result")?)
|
.context("failed to decode create result")?)
|
||||||
}
|
}
|
||||||
"secrets_update" | "secrets_entry_update" => {
|
"secrets_entry_update" => {
|
||||||
let id = arguments
|
let id = arguments
|
||||||
.get("id")
|
.get("id")
|
||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ CSV format:
|
|||||||
019d...,api_key,sk-xxxx
|
019d...,api_key,sk-xxxx
|
||||||
019d...,password,hunter2
|
019d...,password,hunter2
|
||||||
|
|
||||||
The script groups rows by entry_id, then calls `secrets_update` with `secrets_obj`
|
The script groups rows by entry_id, then calls `secrets_entry_update` with `secrets_obj`
|
||||||
so the server re-encrypts the provided plaintext values with the current key.
|
so the server re-encrypts the provided plaintext values with the current key.
|
||||||
|
|
||||||
Warnings:
|
Warnings:
|
||||||
@@ -34,7 +34,7 @@ REQUIRED_COLUMNS = {"entry_id", "secret_name", "secret_value"}
|
|||||||
|
|
||||||
def parse_args() -> argparse.Namespace:
|
def parse_args() -> argparse.Namespace:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Repair secret ciphertexts by re-submitting plaintext via secrets_update."
|
description="Repair secret ciphertexts by re-submitting plaintext via secrets_entry_update."
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--csv",
|
"--csv",
|
||||||
@@ -225,7 +225,7 @@ def load_entry_index(
|
|||||||
"id": 999_001,
|
"id": 999_001,
|
||||||
"method": "tools/call",
|
"method": "tools/call",
|
||||||
"params": {
|
"params": {
|
||||||
"name": "secrets_find",
|
"name": "secrets_entry_find",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"limit": 1000,
|
"limit": 1000,
|
||||||
},
|
},
|
||||||
@@ -240,14 +240,14 @@ def load_entry_index(
|
|||||||
last = items[-1] if items else {"raw": body[:1000]}
|
last = items[-1] if items else {"raw": body[:1000]}
|
||||||
if status != 200:
|
if status != 200:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"secrets_find failed: status={status}, body={body[:500]}"
|
f"secrets_entry_find failed: status={status}, body={body[:500]}"
|
||||||
)
|
)
|
||||||
if "error" in last:
|
if "error" in last:
|
||||||
raise RuntimeError(f"secrets_find returned error: {last}")
|
raise RuntimeError(f"secrets_entry_find returned error: {last}")
|
||||||
|
|
||||||
content = last.get("result", {}).get("content", [])
|
content = last.get("result", {}).get("content", [])
|
||||||
if not content:
|
if not content:
|
||||||
raise RuntimeError("secrets_find returned no content")
|
raise RuntimeError("secrets_entry_find returned no content")
|
||||||
payload = json.loads(content[0]["text"])
|
payload = json.loads(content[0]["text"])
|
||||||
|
|
||||||
index: dict[str, tuple[str, str]] = {}
|
index: dict[str, tuple[str, str]] = {}
|
||||||
@@ -260,7 +260,7 @@ def load_entry_index(
|
|||||||
return index
|
return index
|
||||||
|
|
||||||
|
|
||||||
def call_secrets_update(
|
def call_secrets_entry_update(
|
||||||
url: str,
|
url: str,
|
||||||
auth: str,
|
auth: str,
|
||||||
encryption_key: str,
|
encryption_key: str,
|
||||||
@@ -277,7 +277,7 @@ def call_secrets_update(
|
|||||||
"id": request_id,
|
"id": request_id,
|
||||||
"method": "tools/call",
|
"method": "tools/call",
|
||||||
"params": {
|
"params": {
|
||||||
"name": "secrets_update",
|
"name": "secrets_entry_update",
|
||||||
"arguments": {
|
"arguments": {
|
||||||
"id": entry_id,
|
"id": entry_id,
|
||||||
"name": entry_name,
|
"name": entry_name,
|
||||||
@@ -296,7 +296,7 @@ def call_secrets_update(
|
|||||||
last = items[-1] if items else {"raw": body[:1000]}
|
last = items[-1] if items else {"raw": body[:1000]}
|
||||||
if status != 200:
|
if status != 200:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"secrets_update failed for {entry_id}: status={status}, body={body[:500]}"
|
f"secrets_entry_update failed for {entry_id}: status={status}, body={body[:500]}"
|
||||||
)
|
)
|
||||||
return last
|
return last
|
||||||
|
|
||||||
@@ -339,10 +339,10 @@ def main() -> int:
|
|||||||
try:
|
try:
|
||||||
if entry_id not in entry_index:
|
if entry_id not in entry_index:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
f"entry id not found in secrets_find results: {entry_id}"
|
f"entry id not found in secrets_entry_find results: {entry_id}"
|
||||||
)
|
)
|
||||||
entry_name, entry_folder = entry_index[entry_id]
|
entry_name, entry_folder = entry_index[entry_id]
|
||||||
result = call_secrets_update(
|
result = call_secrets_entry_update(
|
||||||
url,
|
url,
|
||||||
auth,
|
auth,
|
||||||
encryption_key,
|
encryption_key,
|
||||||
|
|||||||
Reference in New Issue
Block a user