Skip to main content

wowlab_sentinel/mcp/tools/
parse_simc.rs

1use schemars::JsonSchema;
2use serde::Deserialize;
3use wowlab_parsers::parse_simc;
4
5/// Input for the `parse_simc` MCP tool.
6#[derive(Debug, Default, Deserialize, JsonSchema)]
7#[schemars(
8    description = "Parse a SimulationCraft profile string into structured character, equipment, talents, and metadata."
9)]
10pub(crate) struct ParseSimcInput {
11    #[schemars(
12        description = "Raw SimC profile text, e.g. the output of `/simc` in-game or a saved .simc file."
13    )]
14    profile: String,
15}
16
17#[expect(
18    clippy::unused_async,
19    reason = "MCP route handlers share one asynchronous interface"
20)]
21pub(super) async fn handle(
22    params: ParseSimcInput,
23) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
24    let profile = parse_simc(&params.profile).map_err(|error| {
25        super::ToolError::invalid_params(format!(
26            "Failed to parse SimC profile: {error}. Pass the raw `/simc` export in the `profile` string; do not pass a JSON object or JSON-encode the profile text."
27        ))
28    });
29
30    Ok(crate::mcp::tool_result(
31        crate::telemetry::McpTool::ParseSimc,
32        profile,
33    ))
34}
35
36#[cfg(test)]
37mod tests {
38    use googletest::prelude::*;
39    use rmcp::model::ContentBlock;
40
41    use super::*;
42
43    #[gtest]
44    #[tokio::test]
45    async fn invalid_profile_error_explains_the_input_contract() -> Result<()> {
46        let result = handle(ParseSimcInput {
47            profile: r#"{"profile":"not raw SimC"}"#.to_owned(),
48        })
49        .await?;
50        let text = result
51            .content
52            .first()
53            .and_then(ContentBlock::as_text)
54            .or_fail()?;
55
56        verify_that!(result.is_error, some(eq(true)))?;
57        verify_that!(
58            text.text.as_str(),
59            contains_substring("Invalid token at byte 0")
60        )?;
61        verify_that!(
62            text.text.as_str(),
63            contains_substring("raw `/simc` export in the `profile` string")
64        )?;
65
66        verify_that!(
67            text.text.as_str(),
68            contains_substring("do not pass a JSON object")
69        )
70    }
71}