chore(release): secrets-mcp 0.4.0
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:
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user