Skip to main content

wowlab_sentinel/mcp/tools/
loadout.rs

1use schemars::JsonSchema;
2use serde::Deserialize;
3use sqlx::PgPool;
4use wowlab_loadout::{EnrichedLoadout, apply_decoded_traits, decode_trait_loadout, enrich_loadout};
5use wowlab_types::data::TraitTreeFlat;
6
7/// Input for the talent loadout decoder MCP tool.
8#[derive(Debug, Default, Deserialize, JsonSchema)]
9#[schemars(
10    description = "Decode a WoW talent loadout string into selected talents with spell IDs, names, and ranks"
11)]
12pub(crate) struct DecodeLoadoutInput {
13    #[schemars(
14        description = "Base64-encoded talent loadout string (from SimC `talents=` or the game client export)"
15    )]
16    loadout: String,
17}
18
19/// Decode a talent loadout string and resolve talent names from the database.
20pub(super) async fn execute(
21    db: &PgPool,
22    input: DecodeLoadoutInput,
23) -> Result<EnrichedLoadout, super::ToolError> {
24    let decoded = decode_trait_loadout(&input.loadout)
25        .map_err(|e| format!("Failed to decode loadout string: {e}"))?;
26
27    let spec_id = i32::from(decoded.spec_id);
28
29    let row: (serde_json::Value,) =
30        sqlx::query_as("SELECT row_to_json(t) FROM game.specs_traits t WHERE spec_id = $1 LIMIT 1")
31            .bind(spec_id)
32            .fetch_one(db)
33            .await
34            .map_err(|e| format!("Failed to fetch trait tree for spec_id {spec_id}: {e}"))?;
35
36    let tree: TraitTreeFlat =
37        serde_json::from_value(row.0).map_err(|e| format!("Failed to parse trait tree: {e}"))?;
38
39    let with_selections = apply_decoded_traits(tree, &decoded);
40
41    Ok(enrich_loadout(&with_selections))
42}
43
44pub(super) async fn handle(
45    db: &PgPool,
46    params: DecodeLoadoutInput,
47) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
48    let result = execute(db, params)
49        .await
50        .map_err(|e| crate::mcp::mcp_error(crate::telemetry::McpTool::DecodeLoadout, &e))?;
51
52    Ok(crate::mcp::json_result(result))
53}