chore(release): secrets-mcp 0.4.0
All checks were successful
Secrets MCP — Build & Release / 检查 / 构建 / 发版 (push) Successful in 4m19s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 6s

Bump version for the N:N entry_secrets data model and related MCP/Web
changes. Remove superseded SQL migration artifacts; rely on auto-migrate.
Add structured errors, taxonomy normalization, and web i18n helpers.

Made-with: Cursor
This commit is contained in:
voson
2026-04-04 17:58:12 +08:00
parent b99d821644
commit 1518388374
29 changed files with 2285 additions and 1260 deletions

View File

@@ -31,6 +31,7 @@ use secrets_core::service::{
};
use crate::auth::AuthUser;
use crate::error;
// ── MCP client-facing errors (no internal details) ───────────────────────────
@@ -50,6 +51,17 @@ fn mcp_err_internal_logged(
)
}
fn mcp_err_from_anyhow(
tool: &'static str,
user_id: Option<Uuid>,
err: anyhow::Error,
) -> rmcp::ErrorData {
if let Some(app_err) = err.downcast_ref::<secrets_core::error::AppError>() {
return error::app_error_to_mcp(app_err);
}
mcp_err_internal_logged(tool, user_id, err)
}
fn mcp_err_invalid_encryption_key_logged(err: impl std::fmt::Display) -> rmcp::ErrorData {
tracing::warn!(error = %err, "invalid X-Encryption-Key");
rmcp::ErrorData::invalid_request(
@@ -162,11 +174,17 @@ struct FindInput {
query: Option<String>,
#[schemars(description = "Exact folder filter (e.g. 'refining', 'ricnsmart')")]
folder: Option<String>,
#[schemars(description = "Exact type filter (e.g. 'server', 'service', 'person', 'key')")]
#[schemars(
description = "Exact type filter (recommended: 'server', 'service', 'person', 'document')"
)]
#[serde(rename = "type")]
entry_type: Option<String>,
#[schemars(description = "Exact name filter")]
#[schemars(description = "Exact name filter. For fuzzy matching use name_query instead.")]
name: Option<String>,
#[schemars(
description = "Fuzzy name filter (ILIKE, case-insensitive partial match). Use this instead of 'name' when you don't know the exact name."
)]
name_query: Option<String>,
#[schemars(description = "Tag filters (all must match)")]
tags: Option<Vec<String>>,
#[schemars(description = "Max results (default 20)")]
@@ -179,11 +197,17 @@ struct SearchInput {
query: Option<String>,
#[schemars(description = "Folder filter (e.g. 'refining', 'personal', 'family')")]
folder: Option<String>,
#[schemars(description = "Type filter (e.g. 'server', 'service', 'person', 'key')")]
#[schemars(
description = "Type filter (recommended: 'server', 'service', 'person', 'document')"
)]
#[serde(rename = "type")]
entry_type: Option<String>,
#[schemars(description = "Exact name to match")]
#[schemars(description = "Exact name to match. For fuzzy matching use name_query instead.")]
name: Option<String>,
#[schemars(
description = "Fuzzy name filter (ILIKE, case-insensitive partial match). Use this instead of 'name' when you don't know the exact name."
)]
name_query: Option<String>,
#[schemars(description = "Tag filters (all must match)")]
tags: Option<Vec<String>>,
#[schemars(description = "Return only summary fields (name/tags/notes/updated_at)")]
@@ -211,7 +235,7 @@ struct AddInput {
#[schemars(description = "Folder for organization (optional, e.g. 'personal', 'refining')")]
folder: Option<String>,
#[schemars(
description = "Type/category of this entry (optional, e.g. 'server', 'person', 'key')"
description = "Type/category of this entry (optional, recommended: 'server', 'service', 'person', 'document')"
)]
#[serde(rename = "type")]
entry_type: Option<String>,
@@ -233,6 +257,10 @@ struct AddInput {
description = "Secret fields as a JSON object {\"key\": \"value\"}. Merged with 'secrets' if both provided. Reminder: non-sensitive endpoint/address fields should go to metadata.address."
)]
secrets_obj: Option<Map<String, Value>>,
#[schemars(
description = "Secret types as {\"secret_name\": \"type\"}. Keys must match secret field names. Missing keys default to \"text\"."
)]
secret_types: Option<Map<String, Value>>,
#[schemars(
description = "Link existing secrets by secret name. Names must resolve uniquely under current user."
)]
@@ -273,6 +301,10 @@ struct UpdateInput {
description = "Secret fields to update/add as a JSON object {\"key\": \"value\"}. Merged with 'secrets' if both provided. Reminder: non-sensitive endpoint/address fields should go to metadata.address."
)]
secrets_obj: Option<Map<String, Value>>,
#[schemars(
description = "Secret types as {\"secret_name\": \"type\"}. Keys must match secret field names. Missing keys default to \"text\"."
)]
secret_types: Option<Map<String, Value>>,
#[schemars(description = "Secret field keys to remove")]
remove_secrets: Option<Vec<String>>,
}
@@ -412,6 +444,7 @@ impl SecretsService {
folder = input.folder.as_deref(),
entry_type = input.entry_type.as_deref(),
name = input.name.as_deref(),
name_query = input.name_query.as_deref(),
query = input.query.as_deref(),
"tool call start",
);
@@ -422,6 +455,7 @@ impl SecretsService {
folder: input.folder.as_deref(),
entry_type: input.entry_type.as_deref(),
name: input.name.as_deref(),
name_query: input.name_query.as_deref(),
tags: &tags,
query: input.query.as_deref(),
sort: "name",
@@ -499,6 +533,7 @@ impl SecretsService {
folder = input.folder.as_deref(),
entry_type = input.entry_type.as_deref(),
name = input.name.as_deref(),
name_query = input.name_query.as_deref(),
query = input.query.as_deref(),
"tool call start",
);
@@ -509,6 +544,7 @@ impl SecretsService {
folder: input.folder.as_deref(),
entry_type: input.entry_type.as_deref(),
name: input.name.as_deref(),
name_query: input.name_query.as_deref(),
tags: &tags,
query: input.query.as_deref(),
sort: input.sort.as_deref().unwrap_or("name"),
@@ -667,6 +703,11 @@ impl SecretsService {
if let Some(obj) = input.secrets_obj {
secrets.extend(map_to_kv_strings(obj));
}
let secret_types = input.secret_types.unwrap_or_default();
let secret_types_map: std::collections::HashMap<String, String> = secret_types
.into_iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_string())))
.collect();
let link_secret_names = input.link_secret_names.unwrap_or_default();
let folder = input.folder.as_deref().unwrap_or("");
let entry_type = input.entry_type.as_deref().unwrap_or("");
@@ -682,13 +723,14 @@ impl SecretsService {
tags: &tags,
meta_entries: &meta,
secret_entries: &secrets,
secret_types: &secret_types_map,
link_secret_names: &link_secret_names,
user_id: Some(user_id),
},
&user_key,
)
.await
.map_err(|e| mcp_err_internal_logged("secrets_add", Some(user_id), e))?;
.map_err(|e| mcp_err_from_anyhow("secrets_add", Some(user_id), e))?;
tracing::info!(
tool = "secrets_add",
@@ -745,6 +787,11 @@ impl SecretsService {
if let Some(obj) = input.secrets_obj {
secrets.extend(map_to_kv_strings(obj));
}
let secret_types = input.secret_types.unwrap_or_default();
let secret_types_map: std::collections::HashMap<String, String> = secret_types
.into_iter()
.filter_map(|(k, v)| v.as_str().map(|s| (k, s.to_string())))
.collect();
let remove_secrets = input.remove_secrets.unwrap_or_default();
let result = svc_update(
@@ -758,13 +805,14 @@ impl SecretsService {
meta_entries: &meta,
remove_meta: &remove_meta,
secret_entries: &secrets,
secret_types: &secret_types_map,
remove_secrets: &remove_secrets,
user_id: Some(user_id),
},
&user_key,
)
.await
.map_err(|e| mcp_err_internal_logged("secrets_update", Some(user_id), e))?;
.map_err(|e| mcp_err_from_anyhow("secrets_update", Some(user_id), e))?;
tracing::info!(
tool = "secrets_update",