Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/cdcx-cli/src/cli_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ pub fn extract_params(matches: &clap::ArgMatches, params: &[ParamSchema]) -> ser
serde_json::json!(value_str)
}
}
"boolean" => match value_str.as_str() {
"true" | "1" => serde_json::json!(true),
"false" | "0" => serde_json::json!(false),
_ => serde_json::json!(value_str),
},
"json" => {
serde_json::from_str(value_str).unwrap_or_else(|_| serde_json::json!(value_str))
}
Expand Down
32 changes: 31 additions & 1 deletion crates/cdcx-cli/src/mcp/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ pub fn mcp_to_schema_groups(mcp_group: &str) -> Vec<&'static str> {
"fiat" => vec!["fiat"],
"stream" => vec!["stream"], // stream tools aren't in schema yet
"otc" => vec!["otc"],
"bot" => vec!["bot"],
"all" => vec![
"market", "account", "history", "trade", "advanced", "wallet", "fiat", "staking",
"margin", "otc",
"margin", "otc", "bot",
],
_ => vec![],
}
Expand Down Expand Up @@ -159,6 +160,7 @@ mod tests {
assert_eq!(mcp_to_schema_groups("account"), vec!["account", "history"]);
assert_eq!(mcp_to_schema_groups("funding"), vec!["wallet"]);
assert_eq!(mcp_to_schema_groups("fiat"), vec!["fiat"]);
assert_eq!(mcp_to_schema_groups("bot"), vec!["bot"]);
assert!(mcp_to_schema_groups("unknown").is_empty());
}

Expand Down Expand Up @@ -210,6 +212,7 @@ mod tests {
assert!(tools.iter().any(|t| t.name.starts_with("cdcx_account_")));
assert!(tools.iter().any(|t| t.name.starts_with("cdcx_funding_")));
assert!(tools.iter().any(|t| t.name.starts_with("cdcx_fiat_")));
assert!(tools.iter().any(|t| t.name.starts_with("cdcx_bot_")));

// Should have tools from all fixture groups
assert!(
Expand All @@ -219,6 +222,33 @@ mod tests {
);
}

#[test]
fn test_tool_generation_bot_group() {
let registry =
SchemaRegistry::from_fixture_with_overlays().expect("Failed to parse fixture");
let tools = generate_tools(&registry, &["bot".to_string()]);

assert!(tools.iter().any(|t| t.name == "cdcx_bot_create"));
assert!(tools.iter().any(|t| t.name == "cdcx_bot_terminate"));
assert!(tools.iter().any(|t| t.name == "cdcx_bot_list"));
assert!(tools.iter().any(|t| t.name == "cdcx_bot_executions"));

// terminate is dangerous — should have acknowledged param
let terminate_tool = tools
.iter()
.find(|t| t.name == "cdcx_bot_terminate")
.unwrap();
let schema_obj = terminate_tool.input_schema.as_ref();
let properties = schema_obj
.get("properties")
.and_then(|p| p.as_object())
.expect("Should have properties");
assert!(
properties.contains_key("acknowledged"),
"Dangerous tool should have acknowledged parameter"
);
}

#[test]
fn test_tool_generation_filters_groups() {
let registry = test_registry();
Expand Down
1 change: 1 addition & 0 deletions crates/cdcx-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ pub const MCP_SERVICE_GROUPS: &[(&str, &str)] = &[
("funding", "Withdrawals (dangerous)"),
("fiat", "Fiat operations (dangerous)"),
("otc", "OTC desk operations"),
("bot", "Trading bot management"),
("stream", "Real-time data streams"),
];

Expand Down
59 changes: 57 additions & 2 deletions crates/cdcx-core/src/openapi/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub fn tag_to_group(tag: &str) -> Option<&'static str> {
"Staking" => Some("staking"),
"Transaction History" => Some("history"),
"OTC RFQ for Taker" => Some("otc"),
"Trading Bot API" => Some("bot"),
_ => None,
}
}
Expand All @@ -57,6 +58,7 @@ pub fn group_description_for(group: &str) -> &'static str {
"staking" => "Staking endpoints",
"history" => "Transaction history endpoints",
"otc" => "OTC RFQ trading endpoints",
"bot" => "Automated trading bot management",
_ => "API endpoints",
}
}
Expand Down Expand Up @@ -151,6 +153,13 @@ pub fn derive_command_name(method: &str, group: &str) -> String {
name = rest.to_string();
}
}
"bot" => {
if let Some(rest) = name.strip_prefix("trading-bot-") {
name = rest.to_string();
} else if let Some(rest) = name.strip_suffix("-trading-bot") {
name = rest.to_string();
}
}
_ => {}
}

Expand All @@ -160,7 +169,10 @@ pub fn derive_command_name(method: &str, group: &str) -> String {
/// Derives a safety tier from the method path.
pub fn derive_safety_tier(method: &str) -> &'static str {
// Dangerous operations
if method == "private/create-withdrawal" || method == "private/fiat/fiat-create-withdraw" {
if method == "private/create-withdrawal"
|| method == "private/fiat/fiat-create-withdraw"
|| method == "private/bot/terminate-trading-bot"
{
return "dangerous";
}

Expand Down Expand Up @@ -649,7 +661,14 @@ fn extract_parameters(
.get("description")
.and_then(|d| d.as_str())
.unwrap_or_default();
let raw_type = val.get("type").and_then(|t| t.as_str()).unwrap_or("string");
let raw_type =
val.get("type").and_then(|t| t.as_str()).unwrap_or_else(|| {
if val.get("oneOf").is_some() || val.get("anyOf").is_some() {
"object"
} else {
"string"
}
});
let mut is_required = required_list.contains(&name.to_string());

let enum_values = extract_enum_values(val, openapi_doc, schema_doc);
Expand Down Expand Up @@ -802,6 +821,7 @@ mod tests {
assert_eq!(tag_to_group("OTC RFQ for Taker"), Some("otc"));
assert_eq!(tag_to_group("Staking"), Some("staking"));
assert_eq!(tag_to_group("Fiat Wallet"), Some("fiat"));
assert_eq!(tag_to_group("Trading Bot API"), Some("bot"));
}

#[test]
Expand Down Expand Up @@ -1139,6 +1159,28 @@ mod tests {
derive_command_name("private/get-transactions", "history"),
"transactions"
);

// Bot
assert_eq!(
derive_command_name("private/bot/terminate-trading-bot", "bot"),
"terminate"
);
assert_eq!(
derive_command_name("private/bot/update-trading-bot", "bot"),
"update"
);
assert_eq!(
derive_command_name("private/bot/pause-trading-bot", "bot"),
"pause"
);
assert_eq!(
derive_command_name("private/bot/resume-trading-bot", "bot"),
"resume"
);
assert_eq!(
derive_command_name("private/bot/get-trading-bot-executions", "bot"),
"executions"
);
}

#[test]
Expand All @@ -1157,6 +1199,15 @@ mod tests {
"mutate"
);
assert_eq!(derive_safety_tier("private/user-balance"), "read");
assert_eq!(
derive_safety_tier("private/bot/terminate-trading-bot"),
"dangerous"
);
assert_eq!(
derive_safety_tier("private/bot/create-trading-bot"),
"mutate"
);
assert_eq!(derive_safety_tier("private/bot/get-trading-bots"), "read");
}

#[test]
Expand All @@ -1166,6 +1217,10 @@ mod tests {
"Public market data endpoints"
);
assert_eq!(group_description_for("otc"), "OTC RFQ trading endpoints");
assert_eq!(
group_description_for("bot"),
"Automated trading bot management"
);
assert_eq!(group_description_for("unknown"), "API endpoints");
}

Expand Down
38 changes: 36 additions & 2 deletions crates/cdcx-core/src/safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ impl SafetyTier {
"private/cancel-all-orders"
| "private/advanced/cancel-all-orders"
| "private/create-withdrawal"
| "private/fiat/fiat-create-withdraw" => Self::Dangerous,
| "private/fiat/fiat-create-withdraw"
| "private/bot/terminate-trading-bot" => Self::Dangerous,

// Mutate operations
"private/create-order"
Expand All @@ -50,7 +51,11 @@ impl SafetyTier {
| "private/staking/stake"
| "private/staking/unstake"
| "private/staking/convert"
| "private/create-subaccount-transfer" => Self::Mutate,
| "private/create-subaccount-transfer"
| "private/bot/create-trading-bot"
| "private/bot/update-trading-bot"
| "private/bot/pause-trading-bot"
| "private/bot/resume-trading-bot" => Self::Mutate,

// Everything else that's private is SensitiveRead
_ => Self::SensitiveRead,
Expand Down Expand Up @@ -150,6 +155,35 @@ mod tests {
SafetyTier::from_method("private/create-withdrawal"),
SafetyTier::Dangerous
);
// Bot endpoints
assert_eq!(
SafetyTier::from_method("private/bot/create-trading-bot"),
SafetyTier::Mutate
);
assert_eq!(
SafetyTier::from_method("private/bot/update-trading-bot"),
SafetyTier::Mutate
);
assert_eq!(
SafetyTier::from_method("private/bot/pause-trading-bot"),
SafetyTier::Mutate
);
assert_eq!(
SafetyTier::from_method("private/bot/resume-trading-bot"),
SafetyTier::Mutate
);
assert_eq!(
SafetyTier::from_method("private/bot/terminate-trading-bot"),
SafetyTier::Dangerous
);
assert_eq!(
SafetyTier::from_method("private/bot/get-trading-bots"),
SafetyTier::SensitiveRead
);
assert_eq!(
SafetyTier::from_method("private/bot/get-trading-bot-executions"),
SafetyTier::SensitiveRead
);
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions crates/cdcx-core/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ impl SchemaRegistry {
include_str!("../../../schemas/apis/staking.toml"),
include_str!("../../../schemas/apis/margin.toml"),
include_str!("../../../schemas/apis/history.toml"),
include_str!("../../../schemas/apis/bot.toml"),
]
.iter()
.map(|s| {
Expand Down
54 changes: 54 additions & 0 deletions schemas/apis/bot.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
[group]
name = "bot"

[[endpoints]]
method = "private/bot/create-trading-bot"
command = "create"

[[endpoints.params]]
name = "bot_type"
position = 0

[[endpoints]]
method = "private/bot/terminate-trading-bot"
command = "terminate"

[[endpoints.params]]
name = "bot_id"
position = 0

[[endpoints]]
method = "private/bot/update-trading-bot"
command = "update"

[[endpoints.params]]
name = "bot_id"
position = 0

[[endpoints]]
method = "private/bot/pause-trading-bot"
command = "pause"

[[endpoints.params]]
name = "bot_id"
position = 0

[[endpoints]]
method = "private/bot/resume-trading-bot"
command = "resume"

[[endpoints.params]]
name = "bot_id"
position = 0

[[endpoints]]
method = "private/bot/get-trading-bots"
command = "list"

[[endpoints]]
method = "private/bot/get-trading-bot-executions"
command = "executions"

[[endpoints.params]]
name = "bot_id"
position = 0
Loading
Loading