Skip to content

Commit f4024ad

Browse files
fix(mcp): validate enum-like string params in RediSearch tools (#864)
1 parent 144983d commit f4024ad

1 file changed

Lines changed: 58 additions & 3 deletions

File tree

crates/redisctl-mcp/src/tools/redis/search.rs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ fn format_kv_pairs(values: &[redis::Value]) -> Vec<String> {
2828
pub struct FieldDefinition {
2929
/// Field name (or JSONPath for JSON indexes)
3030
pub name: String,
31-
/// Field type: TEXT, NUMERIC, TAG, GEO, VECTOR
31+
/// Field type: TEXT, NUMERIC, TAG, GEO, VECTOR, GEOSHAPE
3232
pub field_type: String,
3333
/// Make field sortable (enables SORTBY in queries)
3434
#[serde(default)]
@@ -50,7 +50,30 @@ pub struct FieldDefinition {
5050
pub alias: Option<String>,
5151
}
5252

53+
const VALID_FIELD_TYPES: &[&str] = &["TEXT", "NUMERIC", "TAG", "GEO", "VECTOR", "GEOSHAPE"];
54+
5355
impl FieldDefinition {
56+
fn validate(&self) -> Result<(), McpError> {
57+
let ft = self.field_type.to_uppercase();
58+
if !VALID_FIELD_TYPES.contains(&ft.as_str()) {
59+
return Err(McpError::tool(format!(
60+
"Invalid field_type '{}' for field '{}'. Valid types: {}",
61+
self.field_type,
62+
self.name,
63+
VALID_FIELD_TYPES.join(", "),
64+
)));
65+
}
66+
if let Some(ref sep) = self.separator
67+
&& sep.chars().count() != 1
68+
{
69+
return Err(McpError::tool(format!(
70+
"Invalid separator '{}' for field '{}'. Must be a single character",
71+
sep, self.name,
72+
)));
73+
}
74+
Ok(())
75+
}
76+
5477
fn to_args(&self) -> Vec<String> {
5578
let mut args = vec![self.name.clone()];
5679
if let Some(ref alias) = self.alias {
@@ -179,6 +202,16 @@ database_tool!(read_only, ft_search, "redis_ft_search",
179202
#[serde(default)]
180203
pub withscores: bool,
181204
} => |conn, input| {
205+
if let Some(ref order) = input.sortby_order {
206+
let upper = order.to_uppercase();
207+
if upper != "ASC" && upper != "DESC" {
208+
return Err(McpError::tool(format!(
209+
"Invalid sortby_order '{}'. Valid values: ASC, DESC",
210+
order,
211+
)));
212+
}
213+
}
214+
182215
let mut cmd = redis::cmd("FT.SEARCH");
183216
cmd.arg(&input.index).arg(&input.query);
184217

@@ -352,9 +385,17 @@ database_tool!(read_only, ft_profile, "redis_ft_profile",
352385
/// Query to profile
353386
pub query: String,
354387
} => |conn, input| {
388+
let command_upper = input.command.to_uppercase();
389+
if command_upper != "SEARCH" && command_upper != "AGGREGATE" {
390+
return Err(McpError::tool(format!(
391+
"Invalid command '{}'. Valid values: SEARCH, AGGREGATE",
392+
input.command,
393+
)));
394+
}
395+
355396
let result: Vec<redis::Value> = redis::cmd("FT.PROFILE")
356397
.arg(&input.index)
357-
.arg(input.command.to_uppercase())
398+
.arg(&command_upper)
358399
.arg("QUERY")
359400
.arg(&input.query)
360401
.query_async(&mut conn)
@@ -364,7 +405,7 @@ database_tool!(read_only, ft_profile, "redis_ft_profile",
364405
// FT.PROFILE returns [results, profile_data]
365406
let mut output = format!(
366407
"Profile for {} '{}' on '{}':\n\n",
367-
input.command.to_uppercase(), input.query, input.index
408+
command_upper, input.query, input.index
368409
);
369410
for (i, val) in result.iter().enumerate() {
370411
output.push_str(&format!("[{}]: {}\n", i, format_value(val)));
@@ -475,6 +516,18 @@ database_tool!(write, ft_create, "redis_ft_create",
475516
if input.schema.is_empty() {
476517
return Err(McpError::tool("schema must contain at least one field definition"));
477518
}
519+
if let Some(ref on) = input.on {
520+
let upper = on.to_uppercase();
521+
if upper != "HASH" && upper != "JSON" {
522+
return Err(McpError::tool(format!(
523+
"Invalid 'on' value '{}'. Valid values: HASH, JSON",
524+
on,
525+
)));
526+
}
527+
}
528+
for field in &input.schema {
529+
field.validate()?;
530+
}
478531

479532
let mut cmd = redis::cmd("FT.CREATE");
480533
cmd.arg(&input.index);
@@ -521,6 +574,8 @@ database_tool!(write, ft_alter, "redis_ft_alter",
521574
/// Field definition to add
522575
pub field: FieldDefinition,
523576
} => |conn, input| {
577+
input.field.validate()?;
578+
524579
let mut cmd = redis::cmd("FT.ALTER");
525580
cmd.arg(&input.index).arg("SCHEMA").arg("ADD");
526581
for arg in input.field.to_args() {

0 commit comments

Comments
 (0)