wowlab_sentinel/mcp/tools/
mod.rs1mod docs;
2mod effects;
3mod labels;
4mod loadout;
5mod parse_simc;
6mod query;
7mod rotations;
8
9use super::schema;
10use crate::mcp::mcp_tool;
11
12#[derive(Debug, thiserror::Error)]
13#[error("{kind}")]
14pub(super) struct ToolError {
15 kind: ToolErrorKind,
16}
17
18#[derive(Debug, thiserror::Error)]
19enum ToolErrorKind {
20 #[error("{message}")]
21 Message { message: String },
22}
23
24impl From<String> for ToolError {
25 fn from(message: String) -> Self {
26 Self {
27 kind: ToolErrorKind::Message { message },
28 }
29 }
30}
31
32impl From<&str> for ToolError {
33 fn from(message: &str) -> Self {
34 message.to_owned().into()
35 }
36}
37
38mcp_tool!(
39 get_docs,
40 1,
41 "Read project documentation. Returns the index by default, pass `full=true` for full content.",
42 crate::mcp::GetDocsInput,
43 tips = ["Use `get_docs` first to understand the project before querying game data."],
44 |srv, params| async { docs::handle(&srv.mcp, params).await }
45);
46
47mcp_tool!(
48 list_tables,
49 2,
50 "List all tables with category, description, and column count.",
51 crate::mcp::EmptyInput,
52 tips = ["Call `list_tables` before `get_schema` to avoid large responses."],
53 |srv, _params| async {
54 Ok(crate::mcp::json_result(
55 srv.mcp.schema.list_tables_compact(),
56 ))
57 }
58);
59
60mcp_tool!(
61 get_schema,
62 3,
63 "Get column details for a table. Pass a `table` name to see columns and types, omit for all.",
64 schema::GetSchemaInput,
65 tips = [],
66 |srv, params| async {
67 srv.mcp
68 .schema
69 .get_schema(params.table.as_deref())
70 .map(crate::mcp::json_result)
71 .map_err(|e| crate::mcp::mcp_error(crate::telemetry::McpTool::GetSchema, &e))
72 }
73);
74
75mcp_tool!(
76 query,
77 4,
78 "Fetch rows from a game data table with filters, sorting, and pagination.",
79 query::TableQuery,
80 tips = [
81 "`op=contains` for fuzzy name search, `op=in` for ID lists.",
82 "All filters are AND-ed. Use `query_batch` for multiple tables.",
83 "Rows include every column. For tables with many `json` columns (see `get_schema`), run `query_count` first or narrow the `limit` — large payloads can be rejected by some MCP clients.",
84 ],
85 |srv, params| async { query::handle_query(&srv.db, &srv.mcp.schema, params).await }
86);
87
88mcp_tool!(
89 query_batch,
90 5,
91 "Execute multiple queries in one request (max 50). Each runs independently.",
92 query::TableBatchQuery,
93 tips = [],
94 |srv, params| async { query::handle_query_batch(&srv.db, &srv.mcp.schema, params).await }
95);
96
97mcp_tool!(
98 query_count,
99 6,
100 "Count rows matching filters without returning row data. Same filters as `query`.",
101 query::CountQuery,
102 tips = ["Use `query_count` to check result size before fetching rows."],
103 |srv, params| async { query::handle_query_count(&srv.db, &srv.mcp.schema, params).await }
104);
105
106mcp_tool!(
107 resolve_effects,
108 7,
109 "Resolve spell and item effects with trigger spell chains (max 50 IDs).",
110 effects::ResolveEffectsInput,
111 tips = ["Use `resolve_effects` to understand what a spell or item does mechanically."],
112 |srv, params| async { effects::handle(&srv.db, params).await }
113);
114
115mcp_tool!(
116 decode_loadout,
117 8,
118 "Decode a base64 talent loadout string into structured talent data with spell names.",
119 loadout::DecodeLoadoutInput,
120 tips = ["Talent strings come from SimC `talents=` lines or game client export."],
121 |srv, params| async { loadout::handle(&srv.db, params).await }
122);
123
124mcp_tool!(
125 get_rotation,
126 9,
127 "Fetch a rotation script by ID, optionally at a specific version.",
128 rotations::GetRotationInput,
129 tips = ["Use `list_rotations` to find rotation IDs, then `get_rotation` to fetch the script."],
130 |srv, params| async { rotations::handle_get(&srv.db, params).await }
131);
132
133mcp_tool!(
134 list_spell_labels,
135 10,
136 "List all known spell label IDs with their names. Use these IDs to filter `game.spells` by the `labels` jsonb column (e.g. `op=jsonb_array_contains, value=20` for Rogue spells).",
137 labels::ListSpellLabelsInput,
138 tips = [
139 "Filter spells by label: query `game.spells` with `column=labels, op=jsonb_array_contains, value=<label_id>`."
140 ],
141 |_srv, params| async { labels::handle(params).await }
142);
143
144mcp_tool!(
145 list_rotations,
146 11,
147 "List accessible rotation scripts with optional filters by spec and name.",
148 rotations::ListRotationsInput,
149 tips = [
150 "Use `list_rotations` to find available rotations, then `get_rotation` to fetch the script."
151 ],
152 |srv, params| async { rotations::handle_list(&srv.db, params).await }
153);
154
155mcp_tool!(
156 parse_simc,
157 12,
158 "Parse a SimulationCraft profile string into structured character, equipment, talents, and metadata.",
159 parse_simc::ParseSimcInput,
160 tips = [
161 "Use `parse_simc` to ingest a user-pasted `/simc` export before querying or simulating it."
162 ],
163 |_srv, params| async { parse_simc::handle(params).await }
164);