1use rmcp::ErrorData as McpError;
39use rmcp::handler::server::{router::prompt::PromptRouter, tool::ToolRouter, wrapper::Parameters};
40use rmcp::model::{
41 Annotated, CallToolResult, CompletionInfo, CompleteRequestParam, CompleteResult,
42 Content, ErrorCode, GetPromptRequestParam, GetPromptResult,
43 ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult,
44 PaginatedRequestParam, PromptMessage, PromptMessageRole,
45 RawResource, ReadResourceRequestParam, ReadResourceResult,
46 ResourceContents, ServerCapabilities, ServerInfo, SetLevelRequestParam,
47};
48use rmcp::schemars::JsonSchema;
49use rmcp::service::RequestContext;
50use rmcp::{prompt, prompt_handler, prompt_router, tool, tool_handler, tool_router, RoleServer};
51use serde::{Deserialize, Serialize};
52
53use std::collections::{BTreeMap, HashMap, HashSet};
54use std::sync::Arc;
55use std::path::PathBuf;
56
57use rpfm_ipc::helpers::DataSource;
58use rpfm_ipc::messages::{Command, Response};
59use rpfm_lib::files::{ContainerPath, RFile, RFileDecoded};
60use rpfm_telemetry::sentry;
61
62use crate::session::{Session, recv_response};
63
64macro_rules! send_and_respond {
73 ($self:expr, $tool_name:expr, $cmd:expr) => {{
74 let tx_ctx = sentry::TransactionContext::new(
75 &format!("tools/call {}", $tool_name),
76 "mcp.server",
77 );
78 let tx = sentry::start_transaction(tx_ctx);
79 tx.set_data("mcp.method.name", sentry::protocol::Value::from("tools/call"));
80 tx.set_data("mcp.tool.name", sentry::protocol::Value::from($tool_name));
81 tx.set_data("mcp.transport", sentry::protocol::Value::from("streamable-http"));
82
83 sentry::configure_scope(|scope| scope.set_span(Some(tx.clone().into())));
84
85 let mut receiver = $self.session.send($cmd);
86 let response = recv_response(&mut receiver).await;
87
88 tx.finish();
89
90 let is_error = matches!(&response, Response::Error(_));
91
92 let json = serde_json::to_string(&response).map_err(|e| McpError {
93 code: ErrorCode::INTERNAL_ERROR,
94 message: format!("Failed to serialize response: {e}").into(),
95 data: None,
96 })?;
97
98 if is_error {
99 Ok(CallToolResult::error(vec![Content::text(json)]))
100 } else {
101 Ok(CallToolResult::success(vec![Content::text(json)]))
102 }
103 }};
104}
105
106fn resource(uri: &str, name: &str, description: &str, mime_type: &str) -> Annotated<RawResource> {
108 let mut raw = RawResource::new(uri, name);
109 raw.description = Some(description.into());
110 raw.mime_type = Some(mime_type.into());
111 Annotated { raw, annotations: None }
112}
113
114macro_rules! parse_json {
120 ($input:expr) => {
121 match serde_json::from_str($input) {
122 Ok(v) => v,
123 Err(e) => return Ok(CallToolResult::error(vec![Content::text(format!("Invalid JSON parameter: {e}"))])),
124 }
125 };
126}
127
128#[derive(Clone)]
142pub struct McpServer {
143 session: Arc<Session>,
145 tool_router: ToolRouter<Self>,
147 prompt_router: PromptRouter<Self>,
149}
150
151#[derive(Debug, Deserialize, JsonSchema, Serialize)]
154#[schemars(description = "Call any IPC command directly.")]
155pub struct CallCommandArgs {
156 pub command: String,
158}
159
160#[derive(Debug, Deserialize, JsonSchema, Serialize)]
161pub struct OpenPackfilesArgs {
162 pub paths: Vec<PathBuf>,
164}
165
166#[derive(Debug, Deserialize, JsonSchema, Serialize)]
167pub struct SetGameSelectedArgs {
168 pub game_name: String,
170 pub rebuild_dependencies: bool,
172}
173
174#[derive(Debug, Deserialize, JsonSchema, Serialize)]
175pub struct TsvExportArgs {
176 pub pack_key: String,
178 pub tsv_path: PathBuf,
180 pub table_path: String,
182}
183
184#[derive(Debug, Deserialize, JsonSchema, Serialize)]
185pub struct TsvImportArgs {
186 pub pack_key: String,
188 pub tsv_path: PathBuf,
190 pub table_path: String,
192}
193
194#[derive(Debug, Deserialize, JsonSchema, Serialize)]
195pub struct DecodePackedFileArgs {
196 pub pack_key: String,
198 pub path: String,
200 pub source: DataSource,
202}
203
204#[derive(Debug, Deserialize, JsonSchema, Serialize)]
207pub struct PathArg {
208 pub path: PathBuf,
210}
211
212#[derive(Debug, Deserialize, JsonSchema, Serialize)]
215pub struct PackKeyArg {
216 pub pack_key: String,
218}
219
220#[derive(Debug, Deserialize, JsonSchema, Serialize)]
221pub struct PackKeyBoolArg {
222 pub pack_key: String,
224 pub value: bool,
226}
227
228#[derive(Debug, Deserialize, JsonSchema, Serialize)]
229pub struct PackKeyStringArg {
230 pub pack_key: String,
232 pub value: String,
234}
235
236#[derive(Debug, Deserialize, JsonSchema, Serialize)]
237pub struct PackKeyStringsArg {
238 pub pack_key: String,
240 pub values: Vec<String>,
242}
243
244#[derive(Debug, Deserialize, JsonSchema, Serialize)]
245pub struct PackKeyPathArg {
246 pub pack_key: String,
248 pub path: PathBuf,
250}
251
252#[derive(Debug, Deserialize, JsonSchema, Serialize)]
255pub struct SetPackFileTypeArgs {
256 pub pack_key: String,
258 pub pack_file_type: String,
260}
261
262#[derive(Debug, Deserialize, JsonSchema, Serialize)]
263pub struct ChangeCompressionFormatArgs {
264 pub pack_key: String,
266 pub format: String,
268}
269
270#[derive(Debug, Deserialize, JsonSchema, Serialize)]
271pub struct BoolArg {
272 pub value: bool,
274}
275
276#[derive(Debug, Deserialize, JsonSchema, Serialize)]
277pub struct SetPackSettingsArgs {
278 pub pack_key: String,
280 pub settings: String,
282}
283
284#[derive(Debug, Deserialize, JsonSchema, Serialize)]
285pub struct SetDependencyPackFilesListArgs {
286 pub pack_key: String,
288 pub list: String,
290}
291
292#[derive(Debug, Deserialize, JsonSchema, Serialize)]
295pub struct NewPackedFileArgs {
296 pub pack_key: String,
298 pub path: String,
300 pub new_file: String,
302}
303
304#[derive(Debug, Deserialize, JsonSchema, Serialize)]
305pub struct AddPackedFilesArgs {
306 pub pack_key: String,
308 pub source_paths: Vec<PathBuf>,
310 pub destination_paths: String,
312 pub ignore_paths: Option<Vec<PathBuf>>,
314}
315
316#[derive(Debug, Deserialize, JsonSchema, Serialize)]
317pub struct AddPackedFilesFromPackFileArgs {
318 pub pack_key: String,
320 pub source_pack_path: String,
322 pub container_paths: String,
324}
325
326#[derive(Debug, Deserialize, JsonSchema, Serialize)]
327pub struct AddPackedFilesFromPackFileToAnimpackArgs {
328 pub pack_key: String,
330 pub animpack_path: String,
332 pub container_paths: String,
334}
335
336#[derive(Debug, Deserialize, JsonSchema, Serialize)]
337pub struct AddPackedFilesFromAnimpackArgs {
338 pub pack_key: String,
340 pub source: DataSource,
342 pub animpack_path: String,
344 pub container_paths: String,
346}
347
348#[derive(Debug, Deserialize, JsonSchema, Serialize)]
349pub struct ContainerPathsArg {
350 pub pack_key: String,
352 pub paths: String,
354}
355
356#[derive(Debug, Deserialize, JsonSchema, Serialize)]
357pub struct DeleteFromAnimpackArgs {
358 pub pack_key: String,
360 pub animpack_path: String,
362 pub container_paths: String,
364}
365
366#[derive(Debug, Deserialize, JsonSchema, Serialize)]
367pub struct ExtractPackedFilesArgs {
368 pub pack_key: String,
370 pub source_paths: String,
372 pub destination_path: PathBuf,
374 pub export_as_tsv: bool,
376}
377
378#[derive(Debug, Deserialize, JsonSchema, Serialize)]
379pub struct RenamePackedFilesArgs {
380 pub pack_key: String,
382 pub renames: String,
384}
385
386#[derive(Debug, Deserialize, JsonSchema, Serialize)]
387pub struct CopyOrCutPackedFilesArgs {
388 pub paths_by_pack: String,
390}
391
392#[derive(Debug, Deserialize, JsonSchema, Serialize)]
393pub struct PastePackedFilesArgs {
394 pub pack_key: String,
396 pub destination_path: String,
398}
399
400#[derive(Debug, Deserialize, JsonSchema, Serialize)]
401pub struct DuplicatePackedFilesArgs {
402 pub pack_key: String,
404 pub paths: String,
406}
407
408#[derive(Debug, Deserialize, JsonSchema, Serialize)]
409pub struct SavePackedFileFromViewArgs {
410 pub pack_key: String,
412 pub path: String,
414 pub data: String,
416}
417
418#[derive(Debug, Deserialize, JsonSchema, Serialize)]
419pub struct SavePackedFileFromExternalViewArgs {
420 pub pack_key: String,
422 pub internal_path: String,
424 pub external_path: PathBuf,
426}
427
428#[derive(Debug, Deserialize, JsonSchema, Serialize)]
429pub struct SavePackedFilesToPackFileAndCleanArgs {
430 pub pack_key: String,
432 pub files: String,
434 pub optimize: bool,
436}
437
438#[derive(Debug, Deserialize, JsonSchema, Serialize)]
439pub struct StringArg {
440 pub value: String,
442}
443
444#[derive(Debug, Deserialize, JsonSchema, Serialize)]
445pub struct OpenPackedFileInExternalProgramArgs {
446 pub pack_key: String,
448 pub source: DataSource,
450 pub container_path: String,
452}
453
454#[derive(Debug, Deserialize, JsonSchema, Serialize)]
455pub struct StringsArg {
456 pub values: Vec<String>,
458}
459
460#[derive(Debug, Deserialize, JsonSchema, Serialize)]
463pub struct ImportDependenciesArgs {
464 pub pack_key: String,
466 pub paths: String,
468}
469
470#[derive(Debug, Deserialize, JsonSchema, Serialize)]
471pub struct GetRFilesFromAllSourcesArgs {
472 pub paths: String,
474 pub lowercase: bool,
476}
477
478#[derive(Debug, Deserialize, JsonSchema, Serialize)]
479pub struct ContainerPathArg {
480 pub path: String,
482}
483
484#[derive(Debug, Deserialize, JsonSchema, Serialize)]
487pub struct GlobalSearchArgs {
488 pub pack_key: String,
490 pub search: String,
492}
493
494#[derive(Debug, Deserialize, JsonSchema, Serialize)]
495pub struct GlobalSearchReplaceMatchesArgs {
496 pub pack_key: String,
498 pub search: String,
500 pub matches: String,
502}
503
504#[derive(Debug, Deserialize, JsonSchema, Serialize)]
505pub struct SearchReferencesArgs {
506 pub pack_key: String,
508 pub reference_map: String,
510 pub value: String,
512}
513
514#[derive(Debug, Deserialize, JsonSchema, Serialize)]
515pub struct GetReferenceDataFromDefinitionArgs {
516 pub pack_key: String,
518 pub table_name: String,
520 pub definition: String,
522 pub force: bool,
524}
525
526#[derive(Debug, Deserialize, JsonSchema, Serialize)]
527pub struct GoToDefinitionArgs {
528 pub pack_key: String,
530 pub table_name: String,
532 pub column_name: String,
534 pub values: Vec<String>,
536}
537
538#[derive(Debug, Deserialize, JsonSchema, Serialize)]
541pub struct SaveSchemaArgs {
542 pub schema: String,
544}
545
546#[derive(Debug, Deserialize, JsonSchema, Serialize)]
547pub struct StringI32Args {
548 pub name: String,
550 pub version: i32,
552}
553
554#[derive(Debug, Deserialize, JsonSchema, Serialize)]
555pub struct ReferencingColumnsForDefinitionArgs {
556 pub table_name: String,
558 pub definition: String,
560}
561
562#[derive(Debug, Deserialize, JsonSchema, Serialize)]
563pub struct DefinitionArg {
564 pub definition: String,
566}
567
568#[derive(Debug, Deserialize, JsonSchema, Serialize)]
569pub struct SchemaPatchArgs {
570 pub patches: String,
572}
573
574#[derive(Debug, Deserialize, JsonSchema, Serialize)]
577pub struct MergeFilesArgs {
578 pub pack_key: String,
580 pub paths: String,
582 pub merged_path: String,
584 pub delete_source: bool,
586}
587
588#[derive(Debug, Deserialize, JsonSchema, Serialize)]
589pub struct CascadeEditionArgs {
590 pub pack_key: String,
592 pub table_name: String,
594 pub definition: String,
596 pub changes: String,
598}
599
600#[derive(Debug, Deserialize, JsonSchema, Serialize)]
601pub struct AddKeysToKeyDeletesArgs {
602 pub pack_key: String,
604 pub table_file_name: String,
606 pub key_table_name: String,
608 pub keys: HashSet<String>,
610}
611
612#[derive(Debug, Deserialize, JsonSchema, Serialize)]
615pub struct DiagnosticsCheckArgs {
616 pub ignored: Vec<String>,
618 pub check_ak_only_refs: bool,
620}
621
622#[derive(Debug, Deserialize, JsonSchema, Serialize)]
623pub struct DiagnosticsUpdateArgs {
624 pub diagnostics: String,
626 pub paths: String,
628 pub check_ak_only_refs: bool,
630}
631
632#[derive(Debug, Deserialize, JsonSchema, Serialize)]
635pub struct AddNoteArgs {
636 pub pack_key: String,
638 pub note: String,
640}
641
642#[derive(Debug, Deserialize, JsonSchema, Serialize)]
643pub struct DeleteNoteArgs {
644 pub pack_key: String,
646 pub path: String,
648 pub id: u64,
650}
651
652#[derive(Debug, Deserialize, JsonSchema, Serialize)]
655pub struct OptimizePackFileArgs {
656 pub pack_key: String,
658 pub options: String,
660}
661
662#[derive(Debug, Deserialize, JsonSchema, Serialize)]
665pub struct SettingsSetBoolArgs {
666 pub key: String,
668 pub value: bool,
670}
671
672#[derive(Debug, Deserialize, JsonSchema, Serialize)]
673pub struct SettingsSetI32Args {
674 pub key: String,
676 pub value: i32,
678}
679
680#[derive(Debug, Deserialize, JsonSchema, Serialize)]
681pub struct SettingsSetF32Args {
682 pub key: String,
684 pub value: f32,
686}
687
688#[derive(Debug, Deserialize, JsonSchema, Serialize)]
689pub struct SettingsSetStringArgs {
690 pub key: String,
692 pub value: String,
694}
695
696#[derive(Debug, Deserialize, JsonSchema, Serialize)]
697pub struct SettingsSetPathBufArgs {
698 pub key: String,
700 pub value: PathBuf,
702}
703
704#[derive(Debug, Deserialize, JsonSchema, Serialize)]
705pub struct SettingsSetVecStringArgs {
706 pub key: String,
708 pub value: Vec<String>,
710}
711
712#[derive(Debug, Deserialize, JsonSchema, Serialize)]
713pub struct SettingsSetVecRawArgs {
714 pub key: String,
716 pub value: Vec<u8>,
718}
719
720#[derive(Debug, Deserialize, JsonSchema, Serialize)]
723pub struct InitializeMyModFolderArgs {
724 pub name: String,
726 pub game: String,
728 pub sublime: bool,
730 pub vscode: bool,
732 pub gitignore: Option<String>,
734}
735
736#[derive(Debug, Deserialize, JsonSchema, Serialize)]
737pub struct PackMapArgs {
738 pub pack_key: String,
740 pub tile_maps: Vec<PathBuf>,
742 pub tiles: String,
744}
745
746#[derive(Debug, Deserialize, JsonSchema, Serialize)]
747pub struct BuildStarposArgs {
748 pub pack_key: String,
750 pub campaign_id: String,
752 pub process_hlp_spd: bool,
754}
755
756#[derive(Debug, Deserialize, JsonSchema, Serialize)]
757pub struct UpdateAnimIdsArgs {
758 pub pack_key: String,
760 pub starting_id: i32,
762 pub offset: i32,
764}
765
766#[derive(Debug, Deserialize, JsonSchema, Serialize)]
767pub struct ExportRigidToGltfArgs {
768 pub rigid_model: String,
770 pub output_path: String,
772}
773
774#[derive(Debug, Deserialize, JsonSchema, Serialize)]
775pub struct SetVideoFormatArgs {
776 pub pack_key: String,
778 pub path: String,
780 pub format: String,
782}
783
784#[derive(Debug, Deserialize, JsonSchema, Serialize)]
785pub struct GetPackTranslationArgs {
786 pub pack_key: String,
788 pub language: String,
790}
791
792#[tool_handler]
797#[prompt_handler]
798impl rmcp::ServerHandler for McpServer {
799 fn get_info(&self) -> ServerInfo {
800 ServerInfo {
801 instructions: Some("\
802This is the MCP server for RPFM (Rusted PackFile Manager), a tool for modding Total War games by \
803Creative Assembly. It lets you read, edit, create, and manage PackFiles (.pack) — the archive \
804format used by all modern Total War titles.
805
806## Key Concepts
807
808- **PackFile**: An archive containing game data files (DB tables, localisation, textures, models, etc.). \
809 Mods are distributed as PackFiles.
810- **pack_key**: When you open one or more PackFiles, each gets a unique key string. Use `list_open_packs` \
811 to discover available keys. Most tools require a `pack_key` parameter.
812- **DataSource**: Where data lives — `\"PackFile\"` (the user's mod), `\"GameFiles\"` (vanilla game data), \
813 `\"ParentFiles\"` (dependency mods), `\"AssKitFiles\"` (Assembly Kit data), `\"ExternalFile\"` (disk file).
814- **ContainerPath**: A path inside a pack — either `{\"File\": \"db/land_units_tables/my_table\"}` or \
815 `{\"Folder\": \"db/land_units_tables\"}`. Use an empty string for root folder.
816
817## Required Initialization Sequence
818
8191. **Set the game** — Call `set_game_selected` with the game key (e.g. `\"warhammer_3\"`) and \
820 `rebuild_dependencies: true`. This loads schemas and vanilla data.
8212. **Open a pack** — Call `open_packfiles` with filesystem path(s). Note the returned pack key(s).
8223. **Verify schema** — Call `is_schema_loaded`; if false, call `update_schemas` first.
823
824## Supported Games
825
826Valid game keys: `pharaoh_dynasties`, `pharaoh`, `warhammer_3`, `troy`, `three_kingdoms`, \
827`warhammer_2`, `warhammer`, `thrones_of_britannia`, `attila`, `rome_2`, `shogun_2`, `napoleon`, \
828`empire`, `arena`.
829
830## Common File Path Conventions
831
832- DB tables: `db/<table_name>/<file_name>` (e.g. `db/land_units_tables/my_mod`)
833- Localisation: `text/db/<file_name>.loc`
834- Scripts: `script/<path>.lua`
835- Images: `ui/<path>.png`
836
837## Pack File Types (PFHFileType)
838
839`\"Boot\"`, `\"Release\"`, `\"Patch\"`, `\"Mod\"` (default for mods), `\"Movie\"`.
840
841## Compression Formats
842
843`\"None\"` (default), `\"Lzma1\"` (legacy), `\"Lz4\"` (WH3 6.2+), `\"Zstd\"` (WH3 6.2+).
844
845## Creating New Files (NewFile)
846
847- DB table: `{\"DB\": [\"file_name\", \"table_name\", version]}` — e.g. `{\"DB\": [\"my_mod\", \"land_units_tables\", 0]}`
848- Loc file: `{\"Loc\": \"file_name\"}`
849- Text file: `{\"Text\": [\"file_name\", \"Plain\"]}` — formats: `\"Plain\"`, `\"Html\"`, `\"Xml\"`, `\"Lua\"`, `\"Cpp\"`, `\"Json\"`, `\"Markdown\"`, `\"Smithy\"`
850- AnimPack: `{\"AnimPack\": \"file_name\"}`
851- PortraitSettings: `{\"PortraitSettings\": [\"file_name\", version, [[\"entry_key\", \"entry_value\"]]]}`
852- VMD: `{\"VMD\": \"file_name\"}`
853- WSModel: `{\"WSModel\": \"file_name\"}`
854
855## Resources
856
857Use `resources/list` and `resources/read` to browse reference data: valid enum values, game lists, \
858and example JSON payloads without needing tool calls.
859
860## Responses
861
862All tool responses are JSON-serialized. On failure, an error message is returned instead of the expected data.
863".into()),
864 capabilities: ServerCapabilities::builder()
865 .enable_tools()
866 .enable_prompts()
867 .enable_resources()
868 .enable_completions()
869 .enable_logging()
870 .build(),
871 ..Default::default()
872 }
873 }
874
875 async fn list_resources(
880 &self,
881 _request: Option<PaginatedRequestParam>,
882 _context: RequestContext<RoleServer>,
883 ) -> Result<ListResourcesResult, McpError> {
884 let resources = vec![
885 resource("rpfm://games", "games", "List of all supported Total War game keys.", "application/json"),
886 resource("rpfm://enums/PFHFileType", "PFHFileType", "Valid PackFile type values (Boot, Release, Patch, Mod, Movie).", "application/json"),
887 resource("rpfm://enums/CompressionFormat", "CompressionFormat", "Valid compression format values (None, Lzma1, Lz4, Zstd).", "application/json"),
888 resource("rpfm://enums/DataSource", "DataSource", "Valid data source values indicating where data comes from.", "application/json"),
889 resource("rpfm://enums/ContainerPath", "ContainerPath", "ContainerPath enum variants with JSON examples.", "application/json"),
890 resource("rpfm://enums/NewFile", "NewFile", "NewFile enum variants for creating files inside packs, with JSON examples.", "application/json"),
891 resource("rpfm://enums/SupportedFormats", "SupportedFormats", "Valid video format values (CaVp8, Ivf).", "application/json"),
892 resource("rpfm://examples/global_search", "GlobalSearch example", "Example JSON for the GlobalSearch struct used by search tools.", "application/json"),
893 resource("rpfm://examples/optimizer_options", "OptimizerOptions example", "Example JSON for OptimizerOptions with all boolean fields.", "application/json"),
894 resource("rpfm://reference/initialization", "Initialization guide", "Step-by-step guide for initializing the RPFM MCP server session.", "text/plain"),
895 resource("rpfm://reference/path_conventions", "Path conventions", "Common file path conventions inside Total War PackFiles.", "text/plain"),
896 ];
897 Ok(ListResourcesResult {
898 resources,
899 ..Default::default()
900 })
901 }
902
903 async fn list_resource_templates(
904 &self,
905 _request: Option<PaginatedRequestParam>,
906 _context: RequestContext<RoleServer>,
907 ) -> Result<ListResourceTemplatesResult, McpError> {
908 Ok(ListResourceTemplatesResult {
909 resource_templates: vec![],
910 ..Default::default()
911 })
912 }
913
914 async fn read_resource(
915 &self,
916 request: ReadResourceRequestParam,
917 _context: RequestContext<RoleServer>,
918 ) -> Result<ReadResourceResult, McpError> {
919 let uri = &request.uri;
920 let content = match uri.as_str() {
921 "rpfm://games" => serde_json::json!({
922 "supported_games": [
923 {"key": "pharaoh_dynasties", "display_name": "Total War: Pharaoh Dynasties"},
924 {"key": "pharaoh", "display_name": "Total War: Pharaoh"},
925 {"key": "warhammer_3", "display_name": "Total War: Warhammer III"},
926 {"key": "troy", "display_name": "A Total War Saga: Troy"},
927 {"key": "three_kingdoms", "display_name": "Total War: Three Kingdoms"},
928 {"key": "warhammer_2", "display_name": "Total War: Warhammer II"},
929 {"key": "warhammer", "display_name": "Total War: Warhammer"},
930 {"key": "thrones_of_britannia", "display_name": "A Total War Saga: Thrones of Britannia"},
931 {"key": "attila", "display_name": "Total War: Attila"},
932 {"key": "rome_2", "display_name": "Total War: Rome II"},
933 {"key": "shogun_2", "display_name": "Total War: Shogun 2"},
934 {"key": "napoleon", "display_name": "Total War: Napoleon"},
935 {"key": "empire", "display_name": "Total War: Empire"},
936 {"key": "arena", "display_name": "Total War: Arena"}
937 ]
938 }).to_string(),
939
940 "rpfm://enums/PFHFileType" => serde_json::json!({
941 "enum": "PFHFileType",
942 "description": "The type/priority of a PackFile. Games load packs in type order (Boot first, Movie last).",
943 "variants": [
944 {"name": "Boot", "value": 0, "description": "Core game boot files, loaded first."},
945 {"name": "Release", "value": 1, "description": "Main game data files."},
946 {"name": "Patch", "value": 2, "description": "Official patch and update files."},
947 {"name": "Mod", "value": 3, "description": "User mod files. This is the default for mods."},
948 {"name": "Movie", "value": 4, "description": "Cinematic and always-loaded files, loaded last."}
949 ],
950 "json_example": "\"Mod\""
951 }).to_string(),
952
953 "rpfm://enums/CompressionFormat" => serde_json::json!({
954 "enum": "CompressionFormat",
955 "description": "Compression algorithm for pack file data.",
956 "variants": [
957 {"name": "None", "description": "No compression (default)."},
958 {"name": "Lzma1", "description": "Legacy LZMA compression (all PFH5 games)."},
959 {"name": "Lz4", "description": "LZ4 compression (Warhammer 3 v6.2+)."},
960 {"name": "Zstd", "description": "Zstandard compression (Warhammer 3 v6.2+)."}
961 ],
962 "json_example": "\"None\""
963 }).to_string(),
964
965 "rpfm://enums/DataSource" => serde_json::json!({
966 "enum": "DataSource",
967 "description": "Identifies where data comes from when working with files.",
968 "variants": [
969 {"name": "PackFile", "description": "Data from the user's currently open pack (mod files)."},
970 {"name": "GameFiles", "description": "Data from vanilla game files."},
971 {"name": "ParentFiles", "description": "Data from parent/dependency pack files."},
972 {"name": "AssKitFiles", "description": "Data from the Assembly Kit (modding tools)."},
973 {"name": "ExternalFile", "description": "Data from an external file on disk."}
974 ],
975 "json_example": "\"PackFile\""
976 }).to_string(),
977
978 "rpfm://enums/ContainerPath" => serde_json::json!({
979 "enum": "ContainerPath",
980 "description": "A path reference inside a PackFile, pointing to either a file or a folder.",
981 "variants": [
982 {
983 "name": "File",
984 "description": "Path to a single file inside the pack.",
985 "json_example": {"File": "db/land_units_tables/my_table"}
986 },
987 {
988 "name": "Folder",
989 "description": "Path to a folder inside the pack. Use empty string for root.",
990 "json_example": {"Folder": "db/land_units_tables"}
991 }
992 ],
993 "usage_notes": "Most tools accept a JSON array of ContainerPath objects, e.g. [{\"File\": \"path1\"}, {\"Folder\": \"path2\"}]"
994 }).to_string(),
995
996 "rpfm://enums/NewFile" => serde_json::json!({
997 "enum": "NewFile",
998 "description": "Specifies what type of file to create inside a pack.",
999 "variants": [
1000 {
1001 "name": "DB",
1002 "description": "Create a new DB table. Args: [file_name, table_name, version].",
1003 "json_example": {"DB": ["my_mod", "land_units_tables", 0]}
1004 },
1005 {
1006 "name": "Loc",
1007 "description": "Create a new localisation file. Arg: file_name.",
1008 "json_example": {"Loc": "my_mod"}
1009 },
1010 {
1011 "name": "Text",
1012 "description": "Create a new text file. Args: [file_name, format]. Formats: Bat, Cpp, Html, Hlsl, Json, Js, Css, Lua, Markdown, Plain, Python, Sql, Xml, Yaml.",
1013 "json_example": {"Text": ["my_script", "Lua"]}
1014 },
1015 {
1016 "name": "AnimPack",
1017 "description": "Create a new AnimPack file. Arg: file_name.",
1018 "json_example": {"AnimPack": "my_anim"}
1019 },
1020 {
1021 "name": "PortraitSettings",
1022 "description": "Create a new portrait settings file. Args: [file_name, version, entries].",
1023 "json_example": {"PortraitSettings": ["my_portraits", 3, []]}
1024 },
1025 {
1026 "name": "VMD",
1027 "description": "Create a new VMD file. Arg: file_name.",
1028 "json_example": {"VMD": "my_vmd"}
1029 },
1030 {
1031 "name": "WSModel",
1032 "description": "Create a new WSModel file. Arg: file_name.",
1033 "json_example": {"WSModel": "my_model"}
1034 }
1035 ]
1036 }).to_string(),
1037
1038 "rpfm://enums/SupportedFormats" => serde_json::json!({
1039 "enum": "SupportedFormats",
1040 "description": "Video format options for CA VP8 video files.",
1041 "variants": [
1042 {"name": "CaVp8", "description": "CA's custom VP8 format (default)."},
1043 {"name": "Ivf", "description": "Standard VP8 IVF format."}
1044 ],
1045 "json_example": "\"CaVp8\""
1046 }).to_string(),
1047
1048 "rpfm://examples/global_search" => serde_json::json!({
1049 "description": "Example GlobalSearch JSON for use with global_search, global_search_replace_all, etc.",
1050 "example": {
1051 "pattern": "old_unit_name",
1052 "replace_text": "new_unit_name",
1053 "case_sensitive": false,
1054 "use_regex": false,
1055 "sources": [{"Pack": "my_mod.pack"}],
1056 "search_on": {
1057 "anim": false, "anim_fragment_battle": false, "anim_pack": false,
1058 "anims_table": false, "atlas": false, "audio": false, "bmd": false,
1059 "db": true, "esf": false, "group_formations": false, "image": false,
1060 "loc": true, "matched_combat": false, "pack": false,
1061 "portrait_settings": false, "rigid_model": false, "sound_bank": false,
1062 "text": true, "uic": false, "unit_variant": false, "unknown": false,
1063 "video": false, "schema": false
1064 },
1065 "matches": {
1066 "anim": [], "anim_fragment_battle": [], "anim_pack": [],
1067 "anims_table": [], "atlas": [], "audio": [], "bmd": [],
1068 "db": [], "esf": [], "group_formations": [], "image": [],
1069 "loc": [], "matched_combat": [], "pack": [],
1070 "portrait_settings": [], "rigid_model": [], "sound_bank": [],
1071 "text": [], "uic": [], "unit_variant": [], "unknown": [],
1072 "video": [], "schema": {"matches": []}
1073 },
1074 "game_key": "warhammer_3"
1075 },
1076 "notes": "The `matches` field is populated by the search results. When calling `global_search`, pass it empty. The `sources` field uses SearchSource: {\"Pack\": \"key\"}, \"ParentFiles\", \"GameFiles\", \"AssKitFiles\"."
1077 }).to_string(),
1078
1079 "rpfm://examples/optimizer_options" => serde_json::json!({
1080 "description": "OptimizerOptions struct with all boolean fields for pack optimization.",
1081 "example": {
1082 "pack_remove_itm_files": true,
1083 "pack_apply_compression": true,
1084 "db_import_datacores_into_twad_key_deletes": false,
1085 "db_optimize_datacored_tables": false,
1086 "table_remove_duplicated_entries": true,
1087 "table_remove_itm_entries": true,
1088 "table_remove_itnr_entries": true,
1089 "table_remove_empty_file": true,
1090 "text_remove_unused_xml_map_folders": false,
1091 "text_remove_unused_xml_prefab_folder": false,
1092 "text_remove_agf_files": false,
1093 "text_remove_model_statistics_files": false,
1094 "pts_remove_unused_art_sets": false,
1095 "pts_remove_unused_variants": false,
1096 "pts_remove_empty_masks": false,
1097 "pts_remove_empty_file": false
1098 },
1099 "field_descriptions": {
1100 "pack_remove_itm_files": "Remove files identical to vanilla (Identical To Master).",
1101 "pack_apply_compression": "Apply the most modern compression format the active game supports (overriding the pack's configured one), so the next save compresses the files.",
1102 "db_import_datacores_into_twad_key_deletes": "Import datacored tables into TWAD key deletes.",
1103 "db_optimize_datacored_tables": "Optimize datacored tables.",
1104 "table_remove_duplicated_entries": "Remove duplicate rows in tables.",
1105 "table_remove_itm_entries": "Remove rows identical to vanilla.",
1106 "table_remove_itnr_entries": "Remove rows identical to vanilla that are not referenced.",
1107 "table_remove_empty_file": "Remove tables with no rows.",
1108 "text_remove_unused_xml_map_folders": "Remove unused XML files in map folders.",
1109 "text_remove_unused_xml_prefab_folder": "Remove unused XML files in prefab folders.",
1110 "text_remove_agf_files": "Remove AGF files.",
1111 "text_remove_model_statistics_files": "Remove model statistics files.",
1112 "pts_remove_unused_art_sets": "Remove unused art sets in portrait settings.",
1113 "pts_remove_unused_variants": "Remove unused variants in portrait settings.",
1114 "pts_remove_empty_masks": "Remove empty masks in portrait settings.",
1115 "pts_remove_empty_file": "Remove empty portrait settings files."
1116 }
1117 }).to_string(),
1118
1119 "rpfm://reference/initialization" => "\
1120RPFM MCP Server Initialization Guide
1121=====================================
1122
1123Before you can work with PackFiles, you must initialize the server session:
1124
1125Step 1: Set the game
1126 Call: set_game_selected(game_name: \"warhammer_3\", rebuild_dependencies: true)
1127 This loads the correct schemas and vanilla game data for the selected title.
1128 Valid game keys: pharaoh_dynasties, pharaoh, warhammer_3, troy, three_kingdoms,
1129 warhammer_2, warhammer, thrones_of_britannia, attila, rome_2, shogun_2,
1130 napoleon, empire, arena.
1131
1132Step 2: Verify schema is loaded
1133 Call: is_schema_loaded()
1134 If it returns false, call update_schemas() to download the latest schemas.
1135
1136Step 3: Open a PackFile
1137 Call: open_packfiles(paths: [\"/path/to/my_mod.pack\"])
1138 The response returns pack info including the pack_key you'll use for all
1139 subsequent operations.
1140
1141Step 4: Verify dependencies (optional but recommended)
1142 Call: is_there_a_dependency_database(value: true)
1143 If false, call generate_dependencies_cache() to build the dependency database.
1144
1145After initialization, use list_open_packs() to see all open pack keys at any time.
1146".to_string(),
1147
1148 "rpfm://reference/path_conventions" => "\
1149Total War PackFile Path Conventions
1150====================================
1151
1152Files inside PackFiles follow specific path conventions:
1153
1154DB Tables:
1155 db/<table_name>/<file_name>
1156 Example: db/land_units_tables/my_mod
1157 Example: db/unit_stats_land_tables/custom_units
1158
1159Localisation (Loc) files:
1160 text/db/<file_name>.loc
1161 text/<file_name>.loc
1162 Example: text/db/my_mod.loc
1163
1164Scripts:
1165 script/<path>.lua
1166 script/campaign/mod/<script_name>.lua
1167 Example: script/campaign/mod/my_mod_script.lua
1168
1169UI Images:
1170 ui/<path>.png
1171 Path may vary depending on the purpose of the image.
1172
1173Models and Animations:
1174 variantmeshes/<path>
1175 animations/<path>
1176 Example: variantmeshes/wh_variantmodels/hu1/my_unit/my_unit.wsmodel
1177
1178Audio:
1179 audio/<path>.bnk
1180
1181Maps:
1182 terrain/tiles/battle/<map_name>/
1183".to_string(),
1184
1185 _ => {
1186 return Err(McpError {
1187 code: ErrorCode::INVALID_PARAMS,
1188 message: format!("Unknown resource URI: {uri}").into(),
1189 data: None,
1190 });
1191 }
1192 };
1193
1194 Ok(ReadResourceResult {
1195 contents: vec![ResourceContents::text(content, uri.clone())],
1196 })
1197 }
1198
1199 async fn complete(
1204 &self,
1205 request: CompleteRequestParam,
1206 _context: RequestContext<RoleServer>,
1207 ) -> Result<CompleteResult, McpError> {
1208 let argument_name = &request.argument.name;
1209 let partial = &request.argument.value;
1210
1211 let candidates: Vec<String> = match argument_name.as_str() {
1212 "game_name" | "game_key" | "game" => {
1213 let games = vec![
1214 "pharaoh_dynasties", "pharaoh", "warhammer_3", "troy",
1215 "three_kingdoms", "warhammer_2", "warhammer",
1216 "thrones_of_britannia", "attila", "rome_2", "shogun_2",
1217 "napoleon", "empire", "arena",
1218 ];
1219 games.into_iter()
1220 .filter(|g| g.starts_with(partial))
1221 .map(String::from)
1222 .collect()
1223 },
1224 "pack_file_type" => {
1225 let types = vec!["\"Boot\"", "\"Release\"", "\"Patch\"", "\"Mod\"", "\"Movie\""];
1226 types.into_iter()
1227 .filter(|t| t.starts_with(partial))
1228 .map(String::from)
1229 .collect()
1230 },
1231 "format" => {
1232 let formats = vec![
1234 "\"None\"", "\"Lzma1\"", "\"Lz4\"", "\"Zstd\"",
1235 "\"CaVp8\"", "\"Ivf\"",
1236 ];
1237 formats.into_iter()
1238 .filter(|f| f.starts_with(partial))
1239 .map(String::from)
1240 .collect()
1241 },
1242 "source" => {
1243 let sources = vec![
1244 "\"PackFile\"", "\"GameFiles\"", "\"ParentFiles\"",
1245 "\"AssKitFiles\"", "\"ExternalFile\"",
1246 ];
1247 sources.into_iter()
1248 .filter(|s| s.starts_with(partial))
1249 .map(String::from)
1250 .collect()
1251 },
1252 _ => vec![],
1253 };
1254
1255 let total = candidates.len() as u32;
1256 let values: Vec<String> = candidates.into_iter().take(100).collect();
1257 let has_more = total > 100;
1258
1259 Ok(CompleteResult {
1260 completion: CompletionInfo {
1261 values,
1262 total: Some(total),
1263 has_more: Some(has_more),
1264 },
1265 })
1266 }
1267
1268 async fn set_level(
1273 &self,
1274 _request: SetLevelRequestParam,
1275 _context: RequestContext<RoleServer>,
1276 ) -> Result<(), McpError> {
1277 Ok(())
1281 }
1282}
1283
1284#[tool_router]
1285impl McpServer {
1286
1287 pub fn new(session: Arc<Session>) -> Self {
1288 Self {
1289 session,
1290 tool_router: McpServer::tool_router(),
1291 prompt_router: McpServer::prompt_router(),
1292 }
1293 }
1294
1295 #[tool(name = "call_command", description = "Call any IPC command directly. Use this for commands not yet wrapped as named tools.")]
1300 pub async fn call_command(&self, params: Parameters<CallCommandArgs>) -> Result<CallToolResult, McpError> {
1301 let command: Command = parse_json!(¶ms.0.command);
1302 send_and_respond!(self, "call_command", command)
1303 }
1304
1305 #[tool(description = "Create a new empty PackFile.")]
1310 pub async fn new_pack(&self) -> Result<CallToolResult, McpError> {
1311 send_and_respond!(self, "new_pack", Command::NewPack)
1312 }
1313
1314 #[tool(description = "Open one or more PackFiles. Returns the info about the open pack.")]
1315 pub async fn open_packfiles(&self, params: Parameters<OpenPackfilesArgs>) -> Result<CallToolResult, McpError> {
1316 send_and_respond!(self, "open_packfiles", Command::OpenPackFiles(params.0.paths))
1317 }
1318
1319 #[tool(description = "Save the pack identified by `pack_key`.")]
1320 pub async fn save_packfile(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1321 send_and_respond!(self, "save_packfile", Command::SavePack(params.0.pack_key))
1322 }
1323
1324 #[tool(description = "Close the pack identified by `pack_key` without saving. Any unsaved changes will be lost.")]
1325 pub async fn close_pack(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1326 send_and_respond!(self, "close_pack", Command::ClosePack(params.0.pack_key))
1327 }
1328
1329 #[tool(description = "Save the pack identified by `pack_key` to a new path.")]
1330 pub async fn save_pack_as(&self, params: Parameters<PackKeyPathArg>) -> Result<CallToolResult, McpError> {
1331 send_and_respond!(self, "save_pack_as", Command::SavePackAs(params.0.pack_key, params.0.path))
1332 }
1333
1334 #[tool(description = "Clean the pack identified by `pack_key` from corrupted files and save to a path. Use if normal save fails.")]
1335 pub async fn clean_and_save_pack_as(&self, params: Parameters<PackKeyPathArg>) -> Result<CallToolResult, McpError> {
1336 send_and_respond!(self, "clean_and_save_pack_as", Command::CleanAndSavePackAs(params.0.pack_key, params.0.path))
1337 }
1338
1339 #[tool(description = "Trigger a backup autosave for the pack identified by `pack_key`.")]
1340 pub async fn trigger_backup_autosave(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1341 send_and_respond!(self, "trigger_backup_autosave", Command::TriggerBackupAutosave(params.0.pack_key))
1342 }
1343
1344 #[tool(description = "Open all CA (vanilla) PackFiles for the selected game as one merged PackFile.")]
1345 pub async fn load_all_ca_pack_files(&self) -> Result<CallToolResult, McpError> {
1346 send_and_respond!(self, "load_all_ca_pack_files", Command::LoadAllCAPackFiles)
1347 }
1348
1349 #[tool(description = "Set the type of the pack identified by `pack_key`. Valid PFHFileType values: \"Boot\", \"Release\", \"Patch\", \"Mod\", \"Movie\". Example: pack_file_type = \"\\\"Mod\\\"\"")]
1354 pub async fn set_pack_file_type(&self, params: Parameters<SetPackFileTypeArgs>) -> Result<CallToolResult, McpError> {
1355 let pfh_type = parse_json!(¶ms.0.pack_file_type);
1356 send_and_respond!(self, "set_pack_file_type", Command::SetPackFileType(params.0.pack_key, pfh_type))
1357 }
1358
1359 #[tool(description = "Change the compression format of the pack identified by `pack_key`. Valid formats: \"None\", \"Lzma1\" (legacy), \"Lz4\" (WH3 6.2+), \"Zstd\" (WH3 6.2+). Example: format = \"\\\"None\\\"\"")]
1360 pub async fn change_compression_format(&self, params: Parameters<ChangeCompressionFormatArgs>) -> Result<CallToolResult, McpError> {
1361 let format = parse_json!(¶ms.0.format);
1362 send_and_respond!(self, "change_compression_format", Command::ChangeCompressionFormat(params.0.pack_key, format))
1363 }
1364
1365 #[tool(description = "Change whether the pack index includes timestamps for the pack identified by `pack_key`.")]
1366 pub async fn change_index_includes_timestamp(&self, params: Parameters<PackKeyBoolArg>) -> Result<CallToolResult, McpError> {
1367 send_and_respond!(self, "change_index_includes_timestamp", Command::ChangeIndexIncludesTimestamp(params.0.pack_key, params.0.value))
1368 }
1369
1370 #[tool(description = "Get the file path of the pack identified by `pack_key`.")]
1371 pub async fn get_pack_file_path(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1372 send_and_respond!(self, "get_pack_file_path", Command::GetPackFilePath(params.0.pack_key))
1373 }
1374
1375 #[tool(description = "Get the file name of the pack identified by `pack_key`.")]
1376 pub async fn get_pack_file_name(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1377 send_and_respond!(self, "get_pack_file_name", Command::GetPackFileName(params.0.pack_key))
1378 }
1379
1380 #[tool(description = "Get the settings of the pack identified by `pack_key`.")]
1381 pub async fn get_pack_settings(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1382 send_and_respond!(self, "get_pack_settings", Command::GetPackSettings(params.0.pack_key))
1383 }
1384
1385 #[tool(description = "Set the settings of the pack identified by `pack_key`. The `settings` is a PackSettings JSON object containing pack-level configuration.")]
1386 pub async fn set_pack_settings(&self, params: Parameters<SetPackSettingsArgs>) -> Result<CallToolResult, McpError> {
1387 let settings = parse_json!(¶ms.0.settings);
1388 send_and_respond!(self, "set_pack_settings", Command::SetPackSettings(params.0.pack_key, settings))
1389 }
1390
1391 #[tool(description = "Get the list of PackFiles marked as dependencies of the pack identified by `pack_key`.")]
1392 pub async fn get_dependency_pack_files_list(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1393 send_and_respond!(self, "get_dependency_pack_files_list", Command::GetDependencyPackFilesList(params.0.pack_key))
1394 }
1395
1396 #[tool(description = "Set the list of PackFiles marked as dependencies for the pack identified by `pack_key`. The `list` is a JSON array of [enabled, pack_name] pairs, e.g. [[true, \"other_mod.pack\"], [false, \"disabled_mod.pack\"]].")]
1397 pub async fn set_dependency_pack_files_list(&self, params: Parameters<SetDependencyPackFilesListArgs>) -> Result<CallToolResult, McpError> {
1398 let list = parse_json!(¶ms.0.list);
1399 send_and_respond!(self, "set_dependency_pack_files_list", Command::SetDependencyPackFilesList(params.0.pack_key, list))
1400 }
1401
1402 #[tool(description = "Decode a file from the pack identified by `pack_key`. The `path` is the internal file path (e.g. \"db/land_units_tables/my_mod\"). The `source` is the data source: \"PackFile\" (user mod), \"GameFiles\" (vanilla), \"ParentFiles\" (dependency mods), \"AssKitFiles\", or \"ExternalFile\". Returns the decoded file content as JSON (RFileDecoded).")]
1407 pub async fn decode_packed_file(&self, params: Parameters<DecodePackedFileArgs>) -> Result<CallToolResult, McpError> {
1408 send_and_respond!(self, "decode_packed_file", Command::DecodePackedFile(params.0.pack_key, params.0.path, params.0.source))
1409 }
1410
1411 #[tool(description = "Create a new file inside the pack identified by `pack_key`. The `path` is the destination path (e.g. \"db/land_units_tables/my_mod\"). NewFile types: {\"DB\": [\"file_name\", \"table_name\", version]}, {\"Loc\": \"name\"}, {\"Text\": [\"name\", \"Plain\"]}, {\"AnimPack\": \"name\"}, {\"VMD\": \"name\"}, {\"WSModel\": \"name\"}, {\"PortraitSettings\": [\"name\", version, []]}.")]
1412 pub async fn new_packed_file(&self, params: Parameters<NewPackedFileArgs>) -> Result<CallToolResult, McpError> {
1413 let new_file = parse_json!(¶ms.0.new_file);
1414 send_and_respond!(self, "new_packed_file", Command::NewPackedFile(params.0.pack_key, params.0.path, new_file))
1415 }
1416
1417 #[tool(description = "Add files from disk to the pack identified by `pack_key`. The `source_paths` are filesystem paths. The `destination_paths` is a JSON array of ContainerPath: [{\"File\": \"db/table/file\"}, {\"Folder\": \"ui/images\"}]. Optionally set `ignore_paths` to skip certain files.")]
1418 pub async fn add_packed_files(&self, params: Parameters<AddPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1419 let dest: Vec<ContainerPath> = parse_json!(¶ms.0.destination_paths);
1420 send_and_respond!(self, "add_packed_files", Command::AddPackedFiles(params.0.pack_key, params.0.source_paths, dest, params.0.ignore_paths))
1421 }
1422
1423 #[tool(description = "Add files from another PackFile to the pack identified by `pack_key`. The `source_pack_path` is the pack path. The `container_paths` is a JSON array of ContainerPath: [{\"File\": \"path\"}].")]
1424 pub async fn add_packed_files_from_pack_file(&self, params: Parameters<AddPackedFilesFromPackFileArgs>) -> Result<CallToolResult, McpError> {
1425 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1426 send_and_respond!(self, "add_packed_files_from_pack_file", Command::AddPackedFilesFromPackFile(params.0.pack_key, params.0.source_pack_path, paths))
1427 }
1428
1429 #[tool(description = "Add files from the pack identified by `pack_key` to an AnimPack. The `container_paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"animations/anim.anim\"}]. The `animpack_path` is the AnimPack's internal path.")]
1430 pub async fn add_packed_files_from_pack_file_to_animpack(&self, params: Parameters<AddPackedFilesFromPackFileToAnimpackArgs>) -> Result<CallToolResult, McpError> {
1431 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1432 send_and_respond!(self, "add_packed_files_from_pack_file_to_animpack", Command::AddPackedFilesFromPackFileToAnimpack(params.0.pack_key, params.0.animpack_path, paths))
1433 }
1434
1435 #[tool(description = "Add files from an AnimPack to the pack identified by `pack_key`. The `source` is the DataSource (\"PackFile\", \"GameFiles\", etc.). The `animpack_path` is the AnimPack's internal path. The `container_paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"animations/anim.anim\"}].")]
1436 pub async fn add_packed_files_from_animpack(&self, params: Parameters<AddPackedFilesFromAnimpackArgs>) -> Result<CallToolResult, McpError> {
1437 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1438 send_and_respond!(self, "add_packed_files_from_animpack", Command::AddPackedFilesFromAnimpack(params.0.pack_key, params.0.source, params.0.animpack_path, paths))
1439 }
1440
1441 #[tool(description = "Delete files from the pack identified by `pack_key`. The `paths` is a JSON array of ContainerPath: [{\"File\": \"path/to/file\"}, {\"Folder\": \"path/to/folder\"}].")]
1442 pub async fn delete_packed_files(&self, params: Parameters<ContainerPathsArg>) -> Result<CallToolResult, McpError> {
1443 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1444 send_and_respond!(self, "delete_packed_files", Command::DeletePackedFiles(params.0.pack_key, paths))
1445 }
1446
1447 #[tool(description = "Delete files from an AnimPack in the pack identified by `pack_key`. The `animpack_path` is the AnimPack's internal path. The `container_paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"animations/anim.anim\"}].")]
1448 pub async fn delete_from_animpack(&self, params: Parameters<DeleteFromAnimpackArgs>) -> Result<CallToolResult, McpError> {
1449 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1450 send_and_respond!(self, "delete_from_animpack", Command::DeleteFromAnimpack(params.0.pack_key, params.0.animpack_path, paths))
1451 }
1452
1453 #[tool(description = "Extract files from the pack identified by `pack_key` to disk. The `source_paths` is a JSON object mapping DataSource to ContainerPath arrays, e.g. {\"PackFile\": [{\"File\": \"db/table/file\"}]}. Set `export_as_tsv: true` to export tables as TSV files.")]
1454 pub async fn extract_packed_files(&self, params: Parameters<ExtractPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1455 let source: BTreeMap<DataSource, Vec<ContainerPath>> = parse_json!(¶ms.0.source_paths);
1456 send_and_respond!(self, "extract_packed_files", Command::ExtractPackedFiles(params.0.pack_key, source, params.0.destination_path, params.0.export_as_tsv))
1457 }
1458
1459 #[tool(description = "Rename files in the pack identified by `pack_key`. The `renames` is a JSON array of [old, new] ContainerPath pairs, e.g. [[{\"File\": \"old/path\"}, {\"File\": \"new/path\"}]].")]
1460 pub async fn rename_packed_files(&self, params: Parameters<RenamePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1461 let renames: Vec<(ContainerPath, ContainerPath)> = parse_json!(¶ms.0.renames);
1462 send_and_respond!(self, "rename_packed_files", Command::RenamePackedFiles(params.0.pack_key, renames))
1463 }
1464
1465 #[tool(description = "Copy files to the internal clipboard. The `paths_by_pack` is a JSON object mapping pack key to ContainerPath arrays, e.g. {\"my_pack.pack\": [{\"File\": \"db/table/file\"}]}. Use `paste_packed_files` to paste afterwards.")]
1466 pub async fn copy_packed_files(&self, params: Parameters<CopyOrCutPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1467 let paths_by_pack: BTreeMap<String, Vec<ContainerPath>> = parse_json!(¶ms.0.paths_by_pack);
1468 send_and_respond!(self, "copy_packed_files", Command::CopyPackedFiles(paths_by_pack))
1469 }
1470
1471 #[tool(description = "Cut files to the internal clipboard. Same as copy, but files will be removed from the source pack on paste. The `paths_by_pack` is a JSON object mapping pack key to ContainerPath arrays. Use `paste_packed_files` to paste afterwards.")]
1472 pub async fn cut_packed_files(&self, params: Parameters<CopyOrCutPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1473 let paths_by_pack: BTreeMap<String, Vec<ContainerPath>> = parse_json!(¶ms.0.paths_by_pack);
1474 send_and_respond!(self, "cut_packed_files", Command::CutPackedFiles(paths_by_pack))
1475 }
1476
1477 #[tool(description = "Paste files from the internal clipboard into the pack identified by `pack_key`. The `destination_path` is the folder path to paste into (empty string for root). Returns the added paths, any cut-deleted paths, and the source pack key.")]
1478 pub async fn paste_packed_files(&self, params: Parameters<PastePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1479 send_and_respond!(self, "paste_packed_files", Command::PastePackedFiles(params.0.pack_key, params.0.destination_path))
1480 }
1481
1482 #[tool(description = "Duplicate files in-place within the same pack. Files are cloned with a numeric suffix to avoid name collisions. The `paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"db/table/file\"}].")]
1483 pub async fn duplicate_packed_files(&self, params: Parameters<DuplicatePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1484 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1485 send_and_respond!(self, "duplicate_packed_files", Command::DuplicatePackedFiles(params.0.pack_key, paths))
1486 }
1487
1488 #[tool(description = "Save an edited decoded file back to the pack identified by `pack_key`. The `path` is the internal path (e.g. \"db/land_units_tables/my_mod\"). The `data` is the modified RFileDecoded JSON (same structure returned by `decode_packed_file`).")]
1489 pub async fn save_packed_file_from_view(&self, params: Parameters<SavePackedFileFromViewArgs>) -> Result<CallToolResult, McpError> {
1490 let data: RFileDecoded = parse_json!(¶ms.0.data);
1491 send_and_respond!(self, "save_packed_file_from_view", Command::SavePackedFileFromView(params.0.pack_key, params.0.path, data))
1492 }
1493
1494 #[tool(description = "Save a file from an external program back to the pack identified by `pack_key`.")]
1495 pub async fn save_packed_file_from_external_view(&self, params: Parameters<SavePackedFileFromExternalViewArgs>) -> Result<CallToolResult, McpError> {
1496 send_and_respond!(self, "save_packed_file_from_external_view", Command::SavePackedFileFromExternalView(params.0.pack_key, params.0.internal_path, params.0.external_path))
1497 }
1498
1499 #[tool(description = "Save files to the pack identified by `pack_key` and optionally optimize afterward. The `files` is a JSON array of RFile objects (as returned by decode/get operations). Set `optimize` to true to remove unchanged data after saving.")]
1500 pub async fn save_packed_files_to_pack_file_and_clean(&self, params: Parameters<SavePackedFilesToPackFileAndCleanArgs>) -> Result<CallToolResult, McpError> {
1501 let files: Vec<RFile> = parse_json!(¶ms.0.files);
1502 send_and_respond!(self, "save_packed_files_to_pack_file_and_clean", Command::SavePackedFilesToPackFileAndClean(params.0.pack_key, files, params.0.optimize))
1503 }
1504
1505 #[tool(description = "Get the raw binary data of a file in the pack identified by `pack_key`.")]
1506 pub async fn get_packed_file_raw_data(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1507 send_and_respond!(self, "get_packed_file_raw_data", Command::GetPackedFileRawData(params.0.pack_key, params.0.value))
1508 }
1509
1510 #[tool(description = "Open a file in the system's default program from the pack identified by `pack_key`. The `source` is the DataSource (\"PackFile\", \"GameFiles\", etc.). The `container_path` is a ContainerPath JSON, e.g. {\"File\": \"db/table/file\"}.")]
1511 pub async fn open_packed_file_in_external_program(&self, params: Parameters<OpenPackedFileInExternalProgramArgs>) -> Result<CallToolResult, McpError> {
1512 let cp: ContainerPath = parse_json!(¶ms.0.container_path);
1513 send_and_respond!(self, "open_packed_file_in_external_program", Command::OpenPackedFileInExternalProgram(params.0.pack_key, params.0.source, cp))
1514 }
1515
1516 #[tool(description = "Open the folder containing the pack identified by `pack_key` in the file manager.")]
1517 pub async fn open_containing_folder(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1518 send_and_respond!(self, "open_containing_folder", Command::OpenContainingFolder(params.0.pack_key))
1519 }
1520
1521 #[tool(description = "Clean the decode cache for the provided paths in the pack identified by `pack_key`. The `paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"db/land_units_tables/my_mod\"}, {\"Folder\": \"db\"}].")]
1522 pub async fn clean_cache(&self, params: Parameters<ContainerPathsArg>) -> Result<CallToolResult, McpError> {
1523 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1524 send_and_respond!(self, "clean_cache", Command::CleanCache(params.0.pack_key, paths))
1525 }
1526
1527 #[tool(description = "Check if a folder exists in the pack identified by `pack_key`.")]
1528 pub async fn folder_exists(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1529 send_and_respond!(self, "folder_exists", Command::FolderExists(params.0.pack_key, params.0.value))
1530 }
1531
1532 #[tool(description = "Check if a file exists in the pack identified by `pack_key`.")]
1533 pub async fn packed_file_exists(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1534 send_and_respond!(self, "packed_file_exists", Command::PackedFileExists(params.0.pack_key, params.0.value))
1535 }
1536
1537 #[tool(description = "Get the info of one or more files in the pack identified by `pack_key`.")]
1538 pub async fn get_packed_files_info(&self, params: Parameters<PackKeyStringsArg>) -> Result<CallToolResult, McpError> {
1539 send_and_respond!(self, "get_packed_files_info", Command::GetPackedFilesInfo(params.0.pack_key, params.0.values))
1540 }
1541
1542 #[tool(description = "Get the info of a single file in the pack identified by `pack_key`.")]
1543 pub async fn get_rfile_info(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1544 send_and_respond!(self, "get_rfile_info", Command::GetRFileInfo(params.0.pack_key, params.0.value))
1545 }
1546
1547 #[tool(description = "Get the currently selected game key.")]
1552 pub async fn get_game_selected(&self) -> Result<CallToolResult, McpError> {
1553 send_and_respond!(self, "get_game_selected", Command::GetGameSelected)
1554 }
1555
1556 #[tool(description = "Set the current game. Valid game keys: pharaoh_dynasties, pharaoh, warhammer_3, troy, three_kingdoms, warhammer_2, warhammer, thrones_of_britannia, attila, rome_2, shogun_2, napoleon, empire, arena. Set rebuild_dependencies to true on first call to load schemas and vanilla data.")]
1557 pub async fn set_game_selected(&self, params: Parameters<SetGameSelectedArgs>) -> Result<CallToolResult, McpError> {
1558 send_and_respond!(self, "set_game_selected", Command::SetGameSelected(params.0.game_name, params.0.rebuild_dependencies))
1559 }
1560
1561 #[tool(description = "Generate the dependencies cache for the selected game.")]
1566 pub async fn generate_dependencies_cache(&self) -> Result<CallToolResult, McpError> {
1567 send_and_respond!(self, "generate_dependencies_cache", Command::GenerateDependenciesCache)
1568 }
1569
1570 #[tool(description = "Rebuild dependencies. Pass true for full rebuild, false for mod-specific only.")]
1571 pub async fn rebuild_dependencies(&self, params: Parameters<BoolArg>) -> Result<CallToolResult, McpError> {
1572 send_and_respond!(self, "rebuild_dependencies", Command::RebuildDependencies(params.0.value))
1573 }
1574
1575 #[tool(description = "Check if there is a dependency database loaded. Pass true to ensure AssKit data is included.")]
1576 pub async fn is_there_a_dependency_database(&self, params: Parameters<BoolArg>) -> Result<CallToolResult, McpError> {
1577 send_and_respond!(self, "is_there_a_dependency_database", Command::IsThereADependencyDatabase(params.0.value))
1578 }
1579
1580 #[tool(description = "Get the table names of all DB files in dependency PackFiles.")]
1581 pub async fn get_table_list_from_dependency_pack_file(&self) -> Result<CallToolResult, McpError> {
1582 send_and_respond!(self, "get_table_list_from_dependency_pack_file", Command::GetTableListFromDependencyPackFile)
1583 }
1584
1585 #[tool(description = "Get custom table names (start_pos_, twad_ prefixes) from the schema.")]
1586 pub async fn get_custom_table_list(&self) -> Result<CallToolResult, McpError> {
1587 send_and_respond!(self, "get_custom_table_list", Command::GetCustomTableList)
1588 }
1589
1590 #[tool(description = "Get the version of a table from the dependency database.")]
1591 pub async fn get_table_version_from_dependency_pack_file(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1592 send_and_respond!(self, "get_table_version_from_dependency_pack_file", Command::GetTableVersionFromDependencyPackFile(params.0.value))
1593 }
1594
1595 #[tool(description = "Get the definition of a table from the dependency database.")]
1596 pub async fn get_table_definition_from_dependency_pack_file(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1597 send_and_respond!(self, "get_table_definition_from_dependency_pack_file", Command::GetTableDefinitionFromDependencyPackFile(params.0.value))
1598 }
1599
1600 #[tool(description = "Get table data from dependencies by table name.")]
1601 pub async fn get_tables_from_dependencies(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1602 send_and_respond!(self, "get_tables_from_dependencies", Command::GetTablesFromDependencies(params.0.value))
1603 }
1604
1605 #[tool(description = "Import files from dependencies into the pack identified by `pack_key`. The `paths` is a JSON object mapping DataSource to ContainerPath arrays, e.g. {\"GameFiles\": [{\"File\": \"db/table/file\"}]}.")]
1606 pub async fn import_dependencies_to_open_pack_file(&self, params: Parameters<ImportDependenciesArgs>) -> Result<CallToolResult, McpError> {
1607 let paths: BTreeMap<DataSource, Vec<ContainerPath>> = parse_json!(¶ms.0.paths);
1608 send_and_respond!(self, "import_dependencies_to_open_pack_file", Command::ImportDependenciesToOpenPackFile(params.0.pack_key, paths))
1609 }
1610
1611 #[tool(description = "Get files from all known sources (PackFile, GameFiles, ParentFiles). The `paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"db/land_units_tables/some_file\"}]. Set `lowercase` to true to normalize path casing.")]
1612 pub async fn get_rfiles_from_all_sources(&self, params: Parameters<GetRFilesFromAllSourcesArgs>) -> Result<CallToolResult, McpError> {
1613 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1614 send_and_respond!(self, "get_rfiles_from_all_sources", Command::GetRFilesFromAllSources(paths, params.0.lowercase))
1615 }
1616
1617 #[tool(description = "Get all file names under a path prefix across all data sources (PackFile, GameFiles, ParentFiles). The `path` is a ContainerPath JSON, e.g. {\"Folder\": \"db/land_units_tables\"} to list all files under that folder.")]
1618 pub async fn get_packed_files_names_starting_with_path_from_all_sources(&self, params: Parameters<ContainerPathArg>) -> Result<CallToolResult, McpError> {
1619 let path: ContainerPath = parse_json!(¶ms.0.path);
1620 send_and_respond!(self, "get_packed_files_names_starting_with_path_from_all_sources", Command::GetPackedFilesNamesStartingWitPathFromAllSources(path))
1621 }
1622
1623 #[tool(description = "Get local art set IDs from campaign_character_arts_tables in the pack identified by `pack_key`.")]
1624 pub async fn local_art_set_ids(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1625 send_and_respond!(self, "local_art_set_ids", Command::LocalArtSetIds(params.0.pack_key))
1626 }
1627
1628 #[tool(description = "Get art set IDs from dependencies' campaign_character_arts_tables.")]
1629 pub async fn dependencies_art_set_ids(&self) -> Result<CallToolResult, McpError> {
1630 send_and_respond!(self, "dependencies_art_set_ids", Command::DependenciesArtSetIds)
1631 }
1632
1633 #[tool(description = "Run a global search across the pack identified by `pack_key`. The `search` is a GlobalSearch JSON with fields: pattern (string), replace_text (string), case_sensitive (bool), use_regex (bool), search_on ({db: bool, loc: bool, text: bool, ...}), sources ([{\"Pack\": \"key\"}]), game_key (string). See the `rpfm://examples/global_search` resource for a full example.")]
1638 pub async fn global_search(&self, params: Parameters<GlobalSearchArgs>) -> Result<CallToolResult, McpError> {
1639 let search = parse_json!(¶ms.0.search);
1640 send_and_respond!(self, "global_search", Command::GlobalSearch(params.0.pack_key, search))
1641 }
1642
1643 #[tool(description = "Replace specific matches in a global search for the pack identified by `pack_key`. The `search` is the same GlobalSearch JSON used in `global_search` (see `rpfm://examples/global_search` resource). The `matches` is a JSON array of MatchHolder objects from the search results — include only the matches you want to replace.")]
1644 pub async fn global_search_replace_matches(&self, params: Parameters<GlobalSearchReplaceMatchesArgs>) -> Result<CallToolResult, McpError> {
1645 let search = parse_json!(¶ms.0.search);
1646 let matches = parse_json!(¶ms.0.matches);
1647 send_and_respond!(self, "global_search_replace_matches", Command::GlobalSearchReplaceMatches(params.0.pack_key, search, matches))
1648 }
1649
1650 #[tool(description = "Replace all matches in a global search for the pack identified by `pack_key`. The `search` is a GlobalSearch JSON with the `replace_text` field set to the replacement string. See `rpfm://examples/global_search` resource for the full structure.")]
1651 pub async fn global_search_replace_all(&self, params: Parameters<GlobalSearchArgs>) -> Result<CallToolResult, McpError> {
1652 let search = parse_json!(¶ms.0.search);
1653 send_and_respond!(self, "global_search_replace_all", Command::GlobalSearchReplaceAll(params.0.pack_key, search))
1654 }
1655
1656 #[tool(description = "Find all references to a value in the pack identified by `pack_key`. The `reference_map` is a JSON object mapping table names to column name arrays, e.g. {\"land_units_tables\": [\"key\", \"unit\"]}. The `value` is the string to search for across those columns.")]
1657 pub async fn search_references(&self, params: Parameters<SearchReferencesArgs>) -> Result<CallToolResult, McpError> {
1658 let map: HashMap<String, Vec<String>> = parse_json!(¶ms.0.reference_map);
1659 send_and_respond!(self, "search_references", Command::SearchReferences(params.0.pack_key, map, params.0.value))
1660 }
1661
1662 #[tool(description = "Get valid reference values for columns in a table definition for the pack identified by `pack_key`. The `definition` is a Definition JSON (as returned by `get_table_definition_from_dependency_pack_file`). Set `force` to true to regenerate cached reference data.")]
1663 pub async fn get_reference_data_from_definition(&self, params: Parameters<GetReferenceDataFromDefinitionArgs>) -> Result<CallToolResult, McpError> {
1664 let def = parse_json!(¶ms.0.definition);
1665 send_and_respond!(self, "get_reference_data_from_definition", Command::GetReferenceDataFromDefinition(params.0.pack_key, params.0.table_name, def, params.0.force))
1666 }
1667
1668 #[tool(description = "Go to the definition of a reference in the pack identified by `pack_key`. Provide table name, column name, and values to search.")]
1669 pub async fn go_to_definition(&self, params: Parameters<GoToDefinitionArgs>) -> Result<CallToolResult, McpError> {
1670 send_and_respond!(self, "go_to_definition", Command::GoToDefinition(params.0.pack_key, params.0.table_name, params.0.column_name, params.0.values))
1671 }
1672
1673 #[tool(description = "Go to a loc key's location in the pack identified by `pack_key`.")]
1674 pub async fn go_to_loc(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1675 send_and_respond!(self, "go_to_loc", Command::GoToLoc(params.0.pack_key, params.0.value))
1676 }
1677
1678 #[tool(description = "Get the source data of a loc key in the pack identified by `pack_key`.")]
1679 pub async fn get_source_data_from_loc_key(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1680 send_and_respond!(self, "get_source_data_from_loc_key", Command::GetSourceDataFromLocKey(params.0.pack_key, params.0.value))
1681 }
1682
1683 #[tool(description = "Save the provided schema to disk. The `schema` is the full Schema JSON object (as returned by `get_schema`). Use this after modifying definitions or applying patches.")]
1688 pub async fn save_schema(&self, params: Parameters<SaveSchemaArgs>) -> Result<CallToolResult, McpError> {
1689 let schema = parse_json!(¶ms.0.schema);
1690 send_and_respond!(self, "save_schema", Command::SaveSchema(schema))
1691 }
1692
1693 #[tool(description = "Update the currently loaded schema with data from the game's Assembly Kit.")]
1694 pub async fn update_current_schema_from_asskit(&self) -> Result<CallToolResult, McpError> {
1695 send_and_respond!(self, "update_current_schema_from_asskit", Command::UpdateCurrentSchemaFromAssKit)
1696 }
1697
1698 #[tool(description = "Update schemas from the remote repository.")]
1699 pub async fn update_schemas(&self) -> Result<CallToolResult, McpError> {
1700 send_and_respond!(self, "update_schemas", Command::UpdateSchemas)
1701 }
1702
1703 #[tool(description = "Check if a schema is currently loaded.")]
1704 pub async fn is_schema_loaded(&self) -> Result<CallToolResult, McpError> {
1705 send_and_respond!(self, "is_schema_loaded", Command::IsSchemaLoaded)
1706 }
1707
1708 #[tool(description = "Get the current schema.")]
1709 pub async fn get_schema(&self) -> Result<CallToolResult, McpError> {
1710 send_and_respond!(self, "get_schema", Command::Schema)
1711 }
1712
1713 #[tool(description = "Get all definitions for a table name.")]
1714 pub async fn definitions_by_table_name(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1715 send_and_respond!(self, "definitions_by_table_name", Command::DefinitionsByTableName(params.0.value))
1716 }
1717
1718 #[tool(description = "Get a specific definition by table name and version.")]
1719 pub async fn definition_by_table_name_and_version(&self, params: Parameters<StringI32Args>) -> Result<CallToolResult, McpError> {
1720 send_and_respond!(self, "definition_by_table_name_and_version", Command::DefinitionByTableNameAndVersion(params.0.name, params.0.version))
1721 }
1722
1723 #[tool(description = "Delete a definition by table name and version.")]
1724 pub async fn delete_definition(&self, params: Parameters<StringI32Args>) -> Result<CallToolResult, McpError> {
1725 send_and_respond!(self, "delete_definition", Command::DeleteDefinition(params.0.name, params.0.version))
1726 }
1727
1728 #[tool(description = "Get columns from other tables that reference the given table's definition. The `definition` is a Definition JSON (as returned by `get_table_definition_from_dependency_pack_file` or `definitions_by_table_name`).")]
1729 pub async fn referencing_columns_for_definition(&self, params: Parameters<ReferencingColumnsForDefinitionArgs>) -> Result<CallToolResult, McpError> {
1730 let def = parse_json!(¶ms.0.definition);
1731 send_and_respond!(self, "referencing_columns_for_definition", Command::ReferencingColumnsForDefinition(params.0.table_name, def))
1732 }
1733
1734 #[tool(description = "Get the processed fields from a definition with bitwise expansion and enum conversions applied (useful for display). The `definition` is a Definition JSON (as returned by `get_table_definition_from_dependency_pack_file`).")]
1735 pub async fn fields_processed(&self, params: Parameters<DefinitionArg>) -> Result<CallToolResult, McpError> {
1736 let def = parse_json!(¶ms.0.definition);
1737 send_and_respond!(self, "fields_processed", Command::FieldsProcessed(def))
1738 }
1739
1740 #[tool(description = "Save local schema patches to customize column metadata without modifying the upstream schema. The `patches` is a JSON object mapping table names to DefinitionPatch objects, e.g. {\"land_units_tables\": {\"field_patches\": {...}}}.")]
1741 pub async fn save_local_schema_patch(&self, params: Parameters<SchemaPatchArgs>) -> Result<CallToolResult, McpError> {
1742 let patches = parse_json!(¶ms.0.patches);
1743 send_and_respond!(self, "save_local_schema_patch", Command::SaveLocalSchemaPatch(patches))
1744 }
1745
1746 #[tool(description = "Remove local schema patches for a table.")]
1747 pub async fn remove_local_schema_patches_for_table(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1748 send_and_respond!(self, "remove_local_schema_patches_for_table", Command::RemoveLocalSchemaPatchesForTable(params.0.value))
1749 }
1750
1751 #[tool(description = "Remove local schema patches for a specific field in a table.")]
1752 pub async fn remove_local_schema_patches_for_table_and_field(&self, params: Parameters<SettingsSetStringArgs>) -> Result<CallToolResult, McpError> {
1753 send_and_respond!(self, "remove_local_schema_patches_for_table_and_field", Command::RemoveLocalSchemaPatchesForTableAndField(params.0.key, params.0.value))
1754 }
1755
1756 #[tool(description = "Import a schema patch from an external source. The `patches` is a JSON object mapping table names to DefinitionPatch objects (same format as `save_local_schema_patch`).")]
1757 pub async fn import_schema_patch(&self, params: Parameters<SchemaPatchArgs>) -> Result<CallToolResult, McpError> {
1758 let patches = parse_json!(¶ms.0.patches);
1759 send_and_respond!(self, "import_schema_patch", Command::ImportSchemaPatch(patches))
1760 }
1761
1762 #[tool(description = "Merge multiple compatible tables into one in the pack identified by `pack_key`. The `paths` is a JSON array of ContainerPath for the tables to merge, e.g. [{\"File\": \"db/land_units_tables/table1\"}, {\"File\": \"db/land_units_tables/table2\"}]. The `merged_path` is the destination path. Set `delete_source` to true to remove the original files.")]
1767 pub async fn merge_files(&self, params: Parameters<MergeFilesArgs>) -> Result<CallToolResult, McpError> {
1768 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1769 send_and_respond!(self, "merge_files", Command::MergeFiles(params.0.pack_key, paths, params.0.merged_path, params.0.delete_source))
1770 }
1771
1772 #[tool(description = "Update a table to the latest schema version in the pack identified by `pack_key`. The `value` is a ContainerPath JSON, e.g. {\"File\": \"db/land_units_tables/my_mod\"}.")]
1773 pub async fn update_table(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1774 let path: ContainerPath = parse_json!(¶ms.0.value);
1775 send_and_respond!(self, "update_table", Command::UpdateTable(params.0.pack_key, path))
1776 }
1777
1778 #[tool(description = "Trigger a cascade edition on all referenced data in the pack identified by `pack_key`. When a key value changes, this propagates the change to all referencing tables. The `definition` is a Definition JSON for the source table. The `changes` is a JSON array of [field, old_value, new_value] tuples, e.g. [[field_json, \"old_key\", \"new_key\"]].")]
1779 pub async fn cascade_edition(&self, params: Parameters<CascadeEditionArgs>) -> Result<CallToolResult, McpError> {
1780 let def = parse_json!(¶ms.0.definition);
1781 let changes = parse_json!(¶ms.0.changes);
1782 send_and_respond!(self, "cascade_edition", Command::CascadeEdition(params.0.pack_key, params.0.table_name, def, changes))
1783 }
1784
1785 #[tool(description = "Get table paths by table name from the pack identified by `pack_key`.")]
1786 pub async fn get_tables_by_table_name(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1787 send_and_respond!(self, "get_tables_by_table_name", Command::GetTablesByTableName(params.0.pack_key, params.0.value))
1788 }
1789
1790 #[tool(description = "Add keys to the key_deletes table in the pack identified by `pack_key`.")]
1791 pub async fn add_keys_to_key_deletes(&self, params: Parameters<AddKeysToKeyDeletesArgs>) -> Result<CallToolResult, McpError> {
1792 send_and_respond!(self, "add_keys_to_key_deletes", Command::AddKeysToKeyDeletes(params.0.pack_key, params.0.table_file_name, params.0.key_table_name, params.0.keys))
1793 }
1794
1795 #[tool(description = "Export a table from the pack identified by `pack_key` to a TSV file.")]
1796 pub async fn export_tsv(&self, params: Parameters<TsvExportArgs>) -> Result<CallToolResult, McpError> {
1797 send_and_respond!(self, "export_tsv", Command::ExportTSV(params.0.pack_key, params.0.table_path, params.0.tsv_path, DataSource::PackFile))
1798 }
1799
1800 #[tool(description = "Import a TSV file to a table in the pack identified by `pack_key`.")]
1801 pub async fn import_tsv(&self, params: Parameters<TsvImportArgs>) -> Result<CallToolResult, McpError> {
1802 send_and_respond!(self, "import_tsv", Command::ImportTSV(params.0.pack_key, params.0.table_path, params.0.tsv_path))
1803 }
1804
1805 #[tool(description = "Run a full diagnostics check over all open packs.")]
1810 pub async fn diagnostics_check(&self, params: Parameters<DiagnosticsCheckArgs>) -> Result<CallToolResult, McpError> {
1811 send_and_respond!(self, "diagnostics_check", Command::DiagnosticsCheck(params.0.ignored, params.0.check_ak_only_refs))
1812 }
1813
1814 #[tool(description = "Update diagnostics incrementally for changed files across all open packs. The `diagnostics` is the Diagnostics JSON from a previous `diagnostics_check` call. The `paths` is a JSON array of ContainerPath for the files that changed, e.g. [{\"File\": \"db/land_units_tables/my_mod\"}].")]
1815 pub async fn diagnostics_update(&self, params: Parameters<DiagnosticsUpdateArgs>) -> Result<CallToolResult, McpError> {
1816 let diag = parse_json!(¶ms.0.diagnostics);
1817 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1818 send_and_respond!(self, "diagnostics_update", Command::DiagnosticsUpdate(diag, paths, params.0.check_ak_only_refs))
1819 }
1820
1821 #[tool(description = "Add a line to the ignored diagnostics list for the pack identified by `pack_key`.")]
1822 pub async fn add_line_to_pack_ignored_diagnostics(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1823 send_and_respond!(self, "add_line_to_pack_ignored_diagnostics", Command::AddLineToPackIgnoredDiagnostics(params.0.pack_key, params.0.value))
1824 }
1825
1826 #[tool(description = "Export missing table definitions for the pack identified by `pack_key` to a file (for debugging).")]
1827 pub async fn get_missing_definitions(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1828 send_and_respond!(self, "get_missing_definitions", Command::GetMissingDefinitions(params.0.pack_key))
1829 }
1830
1831 #[tool(description = "Get all notes under a path in the pack identified by `pack_key`.")]
1836 pub async fn notes_for_path(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1837 send_and_respond!(self, "notes_for_path", Command::NotesForPath(params.0.pack_key, params.0.value))
1838 }
1839
1840 #[tool(description = "Add a note to the pack identified by `pack_key`. The `note` is a Note JSON object with fields: path (string — the file or folder path to attach the note to), id (u64), text (string — the note content).")]
1841 pub async fn add_note(&self, params: Parameters<AddNoteArgs>) -> Result<CallToolResult, McpError> {
1842 let note = parse_json!(¶ms.0.note);
1843 send_and_respond!(self, "add_note", Command::AddNote(params.0.pack_key, note))
1844 }
1845
1846 #[tool(description = "Delete a note by path and ID in the pack identified by `pack_key`.")]
1847 pub async fn delete_note(&self, params: Parameters<DeleteNoteArgs>) -> Result<CallToolResult, McpError> {
1848 send_and_respond!(self, "delete_note", Command::DeleteNote(params.0.pack_key, params.0.path, params.0.id))
1849 }
1850
1851 #[tool(description = "Optimize the pack identified by `pack_key` by removing unchanged/duplicate data. The `options` is an OptimizerOptions JSON with boolean fields: pack_remove_itm_files, table_remove_duplicated_entries, table_remove_itm_entries, table_remove_itnr_entries, table_remove_empty_file, db_optimize_datacored_tables, etc. See the `rpfm://examples/optimizer_options` resource for all fields.")]
1856 pub async fn optimize_pack_file(&self, params: Parameters<OptimizePackFileArgs>) -> Result<CallToolResult, McpError> {
1857 let options = parse_json!(¶ms.0.options);
1858 send_and_respond!(self, "optimize_pack_file", Command::OptimizePackFile(params.0.pack_key, options))
1859 }
1860
1861 #[tool(description = "Get the default optimizer options.")]
1862 pub async fn get_optimizer_options(&self) -> Result<CallToolResult, McpError> {
1863 send_and_respond!(self, "get_optimizer_options", Command::OptimizerOptions)
1864 }
1865
1866 #[tool(description = "Check if there is an RPFM update available.")]
1871 pub async fn check_updates(&self) -> Result<CallToolResult, McpError> {
1872 send_and_respond!(self, "check_updates", Command::CheckUpdates)
1873 }
1874
1875 #[tool(description = "Check if there is a schema update available.")]
1876 pub async fn check_schema_updates(&self) -> Result<CallToolResult, McpError> {
1877 send_and_respond!(self, "check_schema_updates", Command::CheckSchemaUpdates)
1878 }
1879
1880 #[tool(description = "Check for Lua autogen updates.")]
1881 pub async fn check_lua_autogen_updates(&self) -> Result<CallToolResult, McpError> {
1882 send_and_respond!(self, "check_lua_autogen_updates", Command::CheckLuaAutogenUpdates)
1883 }
1884
1885 #[tool(description = "Check for Empire/Napoleon Assembly Kit updates.")]
1886 pub async fn check_empire_and_napoleon_ak_updates(&self) -> Result<CallToolResult, McpError> {
1887 send_and_respond!(self, "check_empire_and_napoleon_ak_updates", Command::CheckEmpireAndNapoleonAKUpdates)
1888 }
1889
1890 #[tool(description = "Check for translation updates.")]
1891 pub async fn check_translations_updates(&self) -> Result<CallToolResult, McpError> {
1892 send_and_respond!(self, "check_translations_updates", Command::CheckTranslationsUpdates)
1893 }
1894
1895 #[tool(description = "Update the Lua autogen repository.")]
1896 pub async fn update_lua_autogen(&self) -> Result<CallToolResult, McpError> {
1897 send_and_respond!(self, "update_lua_autogen", Command::UpdateLuaAutogen)
1898 }
1899
1900 #[tool(description = "Update the program to the latest version.")]
1901 pub async fn update_main_program(&self) -> Result<CallToolResult, McpError> {
1902 send_and_respond!(self, "update_main_program", Command::UpdateMainProgram)
1903 }
1904
1905 #[tool(description = "Update the Empire/Napoleon Assembly Kit files.")]
1906 pub async fn update_empire_and_napoleon_ak(&self) -> Result<CallToolResult, McpError> {
1907 send_and_respond!(self, "update_empire_and_napoleon_ak", Command::UpdateEmpireAndNapoleonAK)
1908 }
1909
1910 #[tool(description = "Update the translations repository.")]
1911 pub async fn update_translations(&self) -> Result<CallToolResult, McpError> {
1912 send_and_respond!(self, "update_translations", Command::UpdateTranslations)
1913 }
1914
1915 #[tool(description = "Get a boolean setting value by key.")]
1920 pub async fn settings_get_bool(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1921 send_and_respond!(self, "settings_get_bool", Command::SettingsGetBool(params.0.value))
1922 }
1923
1924 #[tool(description = "Get an i32 setting value by key.")]
1925 pub async fn settings_get_i32(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1926 send_and_respond!(self, "settings_get_i32", Command::SettingsGetI32(params.0.value))
1927 }
1928
1929 #[tool(description = "Get an f32 setting value by key.")]
1930 pub async fn settings_get_f32(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1931 send_and_respond!(self, "settings_get_f32", Command::SettingsGetF32(params.0.value))
1932 }
1933
1934 #[tool(description = "Get a string setting value by key.")]
1935 pub async fn settings_get_string(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1936 send_and_respond!(self, "settings_get_string", Command::SettingsGetString(params.0.value))
1937 }
1938
1939 #[tool(description = "Get a PathBuf setting value by key.")]
1940 pub async fn settings_get_path_buf(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1941 send_and_respond!(self, "settings_get_path_buf", Command::SettingsGetPathBuf(params.0.value))
1942 }
1943
1944 #[tool(description = "Get a Vec<String> setting value by key.")]
1945 pub async fn settings_get_vec_string(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1946 send_and_respond!(self, "settings_get_vec_string", Command::SettingsGetVecString(params.0.value))
1947 }
1948
1949 #[tool(description = "Get a raw bytes setting value by key.")]
1950 pub async fn settings_get_vec_raw(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1951 send_and_respond!(self, "settings_get_vec_raw", Command::SettingsGetVecRaw(params.0.value))
1952 }
1953
1954 #[tool(description = "Get all settings at once (bool, i32, f32, string, raw_data, and vec_string maps).")]
1955 pub async fn settings_get_all(&self) -> Result<CallToolResult, McpError> {
1956 send_and_respond!(self, "settings_get_all", Command::SettingsGetAll)
1957 }
1958
1959 #[tool(description = "Set a boolean setting value.")]
1964 pub async fn settings_set_bool(&self, params: Parameters<SettingsSetBoolArgs>) -> Result<CallToolResult, McpError> {
1965 send_and_respond!(self, "settings_set_bool", Command::SettingsSetBool(params.0.key, params.0.value))
1966 }
1967
1968 #[tool(description = "Set an i32 setting value.")]
1969 pub async fn settings_set_i32(&self, params: Parameters<SettingsSetI32Args>) -> Result<CallToolResult, McpError> {
1970 send_and_respond!(self, "settings_set_i32", Command::SettingsSetI32(params.0.key, params.0.value))
1971 }
1972
1973 #[tool(description = "Set an f32 setting value.")]
1974 pub async fn settings_set_f32(&self, params: Parameters<SettingsSetF32Args>) -> Result<CallToolResult, McpError> {
1975 send_and_respond!(self, "settings_set_f32", Command::SettingsSetF32(params.0.key, params.0.value))
1976 }
1977
1978 #[tool(description = "Set a string setting value.")]
1979 pub async fn settings_set_string(&self, params: Parameters<SettingsSetStringArgs>) -> Result<CallToolResult, McpError> {
1980 send_and_respond!(self, "settings_set_string", Command::SettingsSetString(params.0.key, params.0.value))
1981 }
1982
1983 #[tool(description = "Set a PathBuf setting value.")]
1984 pub async fn settings_set_path_buf(&self, params: Parameters<SettingsSetPathBufArgs>) -> Result<CallToolResult, McpError> {
1985 send_and_respond!(self, "settings_set_path_buf", Command::SettingsSetPathBuf(params.0.key, params.0.value))
1986 }
1987
1988 #[tool(description = "Set a Vec<String> setting value.")]
1989 pub async fn settings_set_vec_string(&self, params: Parameters<SettingsSetVecStringArgs>) -> Result<CallToolResult, McpError> {
1990 send_and_respond!(self, "settings_set_vec_string", Command::SettingsSetVecString(params.0.key, params.0.value))
1991 }
1992
1993 #[tool(description = "Set a raw bytes setting value.")]
1994 pub async fn settings_set_vec_raw(&self, params: Parameters<SettingsSetVecRawArgs>) -> Result<CallToolResult, McpError> {
1995 send_and_respond!(self, "settings_set_vec_raw", Command::SettingsSetVecRaw(params.0.key, params.0.value))
1996 }
1997
1998 #[tool(description = "Backup the current settings to memory.")]
1999 pub async fn backup_settings(&self) -> Result<CallToolResult, McpError> {
2000 send_and_respond!(self, "backup_settings", Command::BackupSettings)
2001 }
2002
2003 #[tool(description = "Clear all settings and reset to defaults.")]
2004 pub async fn clear_settings(&self) -> Result<CallToolResult, McpError> {
2005 send_and_respond!(self, "clear_settings", Command::ClearSettings)
2006 }
2007
2008 #[tool(description = "Restore settings from the backup.")]
2009 pub async fn restore_backup_settings(&self) -> Result<CallToolResult, McpError> {
2010 send_and_respond!(self, "restore_backup_settings", Command::RestoreBackupSettings)
2011 }
2012
2013 #[tool(description = "Get the config path.")]
2018 pub async fn config_path(&self) -> Result<CallToolResult, McpError> {
2019 send_and_respond!(self, "config_path", Command::ConfigPath)
2020 }
2021
2022 #[tool(description = "Get the Assembly Kit path for the current game.")]
2023 pub async fn assembly_kit_path(&self) -> Result<CallToolResult, McpError> {
2024 send_and_respond!(self, "assembly_kit_path", Command::AssemblyKitPath)
2025 }
2026
2027 #[tool(description = "Get the backup autosave path.")]
2028 pub async fn backup_autosave_path(&self) -> Result<CallToolResult, McpError> {
2029 send_and_respond!(self, "backup_autosave_path", Command::BackupAutosavePath)
2030 }
2031
2032 #[tool(description = "Get the old Assembly Kit data path.")]
2033 pub async fn old_ak_data_path(&self) -> Result<CallToolResult, McpError> {
2034 send_and_respond!(self, "old_ak_data_path", Command::OldAkDataPath)
2035 }
2036
2037 #[tool(description = "Get the schemas path.")]
2038 pub async fn schemas_path(&self) -> Result<CallToolResult, McpError> {
2039 send_and_respond!(self, "schemas_path", Command::SchemasPath)
2040 }
2041
2042 #[tool(description = "Get the table profiles path.")]
2043 pub async fn table_profiles_path(&self) -> Result<CallToolResult, McpError> {
2044 send_and_respond!(self, "table_profiles_path", Command::TableProfilesPath)
2045 }
2046
2047 #[tool(description = "Get the translations local path.")]
2048 pub async fn translations_local_path(&self) -> Result<CallToolResult, McpError> {
2049 send_and_respond!(self, "translations_local_path", Command::TranslationsLocalPath)
2050 }
2051
2052 #[tool(description = "Get the dependencies cache path.")]
2053 pub async fn dependencies_cache_path(&self) -> Result<CallToolResult, McpError> {
2054 send_and_respond!(self, "dependencies_cache_path", Command::DependenciesCachePath)
2055 }
2056
2057 #[tool(description = "Clear a config path.")]
2058 pub async fn settings_clear_path(&self, params: Parameters<PathArg>) -> Result<CallToolResult, McpError> {
2059 send_and_respond!(self, "settings_clear_path", Command::SettingsClearPath(params.0.path))
2060 }
2061
2062 #[tool(description = "Get the info about the pack identified by `pack_key` and the list of files it contains.")]
2067 pub async fn open_pack_info(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2068 send_and_respond!(self, "open_pack_info", Command::GetPackFileDataForTreeView(params.0.pack_key))
2069 }
2070
2071 #[tool(description = "Initialize a MyMod folder for mod development.")]
2072 pub async fn initialize_my_mod_folder(&self, params: Parameters<InitializeMyModFolderArgs>) -> Result<CallToolResult, McpError> {
2073 send_and_respond!(self, "initialize_my_mod_folder", Command::InitializeMyModFolder(params.0.name, params.0.game, params.0.sublime, params.0.vscode, params.0.gitignore))
2074 }
2075
2076 #[tool(description = "Live export the pack identified by `pack_key` to the game folder for testing.")]
2077 pub async fn live_export(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2078 send_and_respond!(self, "live_export", Command::LiveExport(params.0.pack_key))
2079 }
2080
2081 #[tool(description = "Patch the SiegeAI of a Siege Map in the pack identified by `pack_key` for Warhammer games.")]
2082 pub async fn patch_siege_ai(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2083 send_and_respond!(self, "patch_siege_ai", Command::PatchSiegeAI(params.0.pack_key))
2084 }
2085
2086 #[tool(description = "Pack map tiles into the pack identified by `pack_key`. The `tile_maps` is a list of tile map file paths on disk. The `tiles` is a JSON array of [path, name] pairs, e.g. [[\"/path/to/tile\", \"tile_name\"]].")]
2087 pub async fn pack_map(&self, params: Parameters<PackMapArgs>) -> Result<CallToolResult, McpError> {
2088 let tiles: Vec<(PathBuf, String)> = parse_json!(¶ms.0.tiles);
2089 send_and_respond!(self, "pack_map", Command::PackMap(params.0.pack_key, params.0.tile_maps, tiles))
2090 }
2091
2092 #[tool(description = "Generate all missing loc entries for the pack identified by `pack_key`.")]
2093 pub async fn generate_missing_loc_data(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2094 send_and_respond!(self, "generate_missing_loc_data", Command::GenerateMissingLocData(params.0.pack_key))
2095 }
2096
2097 #[tool(description = "Get pack translation data for a language from the pack identified by `pack_key`.")]
2098 pub async fn get_pack_translation(&self, params: Parameters<GetPackTranslationArgs>) -> Result<CallToolResult, McpError> {
2099 send_and_respond!(self, "get_pack_translation", Command::GetPackTranslation(params.0.pack_key, params.0.language))
2100 }
2101
2102 #[tool(description = "Get campaign IDs for starpos building in the pack identified by `pack_key`.")]
2103 pub async fn build_starpos_get_campaign_ids(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2104 send_and_respond!(self, "build_starpos_get_campaign_ids", Command::BuildStarposGetCampaingIds(params.0.pack_key))
2105 }
2106
2107 #[tool(description = "Check if victory conditions file exists for starpos building in the pack identified by `pack_key`.")]
2108 pub async fn build_starpos_check_victory_conditions(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2109 send_and_respond!(self, "build_starpos_check_victory_conditions", Command::BuildStarposCheckVictoryConditions(params.0.pack_key))
2110 }
2111
2112 #[tool(description = "Build starpos (pre-processing step) for the pack identified by `pack_key`.")]
2113 pub async fn build_starpos(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2114 send_and_respond!(self, "build_starpos", Command::BuildStarpos(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2115 }
2116
2117 #[tool(description = "Build starpos (post-processing step) for the pack identified by `pack_key`.")]
2118 pub async fn build_starpos_post(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2119 send_and_respond!(self, "build_starpos_post", Command::BuildStarposPost(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2120 }
2121
2122 #[tool(description = "Clean up starpos temporary files for the pack identified by `pack_key`.")]
2123 pub async fn build_starpos_cleanup(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2124 send_and_respond!(self, "build_starpos_cleanup", Command::BuildStarposCleanup(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2125 }
2126
2127 #[tool(description = "Update animation IDs with an offset in the pack identified by `pack_key`.")]
2128 pub async fn update_anim_ids(&self, params: Parameters<UpdateAnimIdsArgs>) -> Result<CallToolResult, McpError> {
2129 send_and_respond!(self, "update_anim_ids", Command::UpdateAnimIds(params.0.pack_key, params.0.starting_id, params.0.offset))
2130 }
2131
2132 #[tool(description = "Get animation paths by skeleton name.")]
2133 pub async fn get_anim_paths_by_skeleton_name(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
2134 send_and_respond!(self, "get_anim_paths_by_skeleton_name", Command::GetAnimPathsBySkeletonName(params.0.value))
2135 }
2136
2137 #[tool(description = "Export a RigidModel to glTF format. The `rigid_model` is a RigidModel JSON object (as returned by decoding a .rigid_model_v2 file with `decode_packed_file`). The `output_path` is the destination file path on disk.")]
2138 pub async fn export_rigid_to_gltf(&self, params: Parameters<ExportRigidToGltfArgs>) -> Result<CallToolResult, McpError> {
2139 let rigid = parse_json!(¶ms.0.rigid_model);
2140 send_and_respond!(self, "export_rigid_to_gltf", Command::ExportRigidToGltf(rigid, params.0.output_path))
2141 }
2142
2143 #[tool(description = "Change the format of a ca_vp8 video file in the pack identified by `pack_key`. Valid formats: \"CaVp8\" (CA custom VP8) or \"Ivf\" (standard VP8 IVF).")]
2144 pub async fn set_video_format(&self, params: Parameters<SetVideoFormatArgs>) -> Result<CallToolResult, McpError> {
2145 let format = parse_json!(¶ms.0.format);
2146 send_and_respond!(self, "set_video_format", Command::SetVideoFormat(params.0.pack_key, params.0.path, format))
2147 }
2148
2149 #[tool(description = "List all currently open packs with their keys and metadata. Use this to get valid pack_key values for other tools.")]
2154 pub async fn list_open_packs(&self) -> Result<CallToolResult, McpError> {
2155 send_and_respond!(self, "list_open_packs", Command::ListOpenPacks)
2156 }
2157
2158 #[tool(description = "Close all currently open packs without saving. Any unsaved changes will be lost.")]
2163 pub async fn close_all_packs(&self) -> Result<CallToolResult, McpError> {
2164 send_and_respond!(self, "close_all_packs", Command::CloseAllPacks)
2165 }
2166
2167}
2168
2169#[prompt_router]
2174impl McpServer {
2175
2176 #[prompt(name = "open_and_inspect_pack", description = "Walk through opening a PackFile and inspecting its contents.")]
2177 pub async fn open_and_inspect_pack(&self) -> Vec<PromptMessage> {
2178 vec![PromptMessage::new_text(
2179 PromptMessageRole::User,
2180 "\
2181You are an assistant helping the user inspect a Total War PackFile using the RPFM MCP server.
2182
2183Follow these steps in order:
2184
21851. **Open the pack** – Call `open_packfiles` with the filesystem path(s) the user provides.
2186 The response contains one or more pack keys; remember them for subsequent calls.
2187
21882. **Select the game** – Call `set_game_selected` with the correct game key (e.g. `\"warhammer_3\"`)
2189 and `rebuild_dependencies: true` so that schemas and dependency data are loaded.
2190
21913. **List pack contents** – Call `open_pack_info` with the pack key to get the full file tree.
2192 Present the tree to the user in a readable format.
2193
21944. **Decode specific files** – When the user asks about a file, call `decode_packed_file` with the
2195 pack key, the internal path (e.g. `\"db/land_units_tables/my_table\"`), and
2196 `source: \"PackFile\"`. The decoded JSON will contain the table rows, schema, etc.
2197
21985. **Inspect metadata** – Use `get_pack_settings`, `get_pack_file_name`, or
2199 `get_dependency_pack_files_list` to answer questions about the pack itself.
2200
2201Important notes:
2202- Always call `list_open_packs` if you are unsure which pack key to use.
2203- If a file fails to decode, check `is_schema_loaded`; if false, call `update_schemas` first.
2204- When done, optionally call `close_pack` to free resources.
2205",
2206 )]
2207 }
2208
2209 #[prompt(name = "edit_db_table", description = "Guide for reading, modifying, and saving a DB table inside a pack.")]
2210 pub async fn edit_db_table(&self) -> Vec<PromptMessage> {
2211 vec![PromptMessage::new_text(
2212 PromptMessageRole::User,
2213 "\
2214You are an assistant helping the user edit a DB table inside a Total War PackFile.
2215
2216Workflow:
2217
22181. **Open the pack** – `open_packfiles` → note the `pack_key`.
22192. **Set the game** – `set_game_selected` with `rebuild_dependencies: true`.
22203. **Decode the table** – `decode_packed_file` with the DB path
2221 (e.g. `\"db/unit_stats_land_tables/my_table\"`) and `source: \"PackFile\"`.
2222 The response is an `RFileDecoded` JSON containing the table data and definition.
22234. **Modify rows** – Edit the decoded JSON: add, remove, or change rows/cells.
2224 Each row is typically a list of `DecodedData` values matching the table's
2225 fields processed list (retrievable via the `FieldsProcessed` message).
22265. **Save back** – Call `save_packed_file_from_view` with the pack key, the same path,
2227 and the modified `RFileDecoded` JSON as the `data` parameter.
22286. **Save the pack** – Call `save_packfile` (or `save_pack_as` for a new path).
2229
2230Tips:
2231- Use `get_table_definition_from_dependency_pack_file` to see the expected column schema.
2232- Use `get_reference_data_from_definition` to discover valid values for referenced columns.
2233- After saving, you can run `diagnostics_check` to validate the pack.
2234",
2235 )]
2236 }
2237
2238 #[prompt(name = "create_new_mod", description = "Step-by-step guide for creating a new mod PackFile from scratch.")]
2239 pub async fn create_new_mod(&self) -> Vec<PromptMessage> {
2240 vec![PromptMessage::new_text(
2241 PromptMessageRole::User,
2242 "\
2243You are an assistant helping the user create a new Total War mod from scratch.
2244
2245Workflow:
2246
22471. **Set the game** – `set_game_selected` with the target game key and
2248 `rebuild_dependencies: true`.
2249
22502. **Create the pack** – `new_pack` returns a new empty pack and its pack key.
2251
22523. **Set pack type** – `set_pack_file_type` to `\"Mod\"` (the standard type for mods).
2253
22544. **Add DB tables** – For each table you need:
2255 a. Call `new_packed_file` with the pack key, the path (e.g. `\"db/land_units_tables/my_mod\"`),
2256 and the `new_file` JSON set to `\"DB\"` with the table name.
2257 b. Decode, edit, and save as described in the `edit_db_table` workflow.
2258
22595. **Add Loc files** – For localisation:
2260 a. `new_packed_file` with path `\"text/db/my_mod.loc\"` and `new_file` set to `\"Loc\"`.
2261 b. Decode, add key/value rows, and save.
2262
22636. **Add other files** – Use `add_packed_files` to import assets from disk (images, models, etc.).
2264
22657. **Save the pack** – `save_pack_as` to write the final `.pack` file to disk.
2266
2267Optional steps:
2268- `initialize_my_mod_folder` to set up a mod development folder with IDE support.
2269- `optimize_pack_file` to strip unchanged rows that match vanilla data.
2270- `diagnostics_check` to validate everything before release.
2271",
2272 )]
2273 }
2274
2275 #[prompt(name = "search_and_replace", description = "Find and replace values across all files in a pack.")]
2276 pub async fn search_and_replace(&self) -> Vec<PromptMessage> {
2277 vec![PromptMessage::new_text(
2278 PromptMessageRole::User,
2279 "\
2280You are an assistant helping the user search for and replace data across a PackFile.
2281
2282Workflow:
2283
22841. **Open the pack** and **set the game** (see `open_and_inspect_pack` prompt).
2285
22862. **Run a global search** – Call `global_search` with the pack key and a `GlobalSearch`
2287 JSON object. The search object specifies the pattern, whether to use regex, which file
2288 types to include (DB, Loc, Text), and the replacement string.
2289
22903. **Review matches** – The response contains all matches grouped by file.
2291 Present them to the user for review.
2292
22934. **Replace selectively** – Call `global_search_replace_matches` with the same search
2294 object and a `Vec<MatchHolder>` containing only the matches the user approved.
2295
22965. **Or replace all** – If the user confirms a blanket replace, call
2297 `global_search_replace_all` with the search object.
2298
22996. **Save** – `save_packfile` to persist changes.
2300
2301Related tools:
2302- `search_references` – Find all rows that reference a specific value across tables.
2303- `go_to_definition` – Jump to where a referenced key is defined.
2304- `go_to_loc` – Find the loc entry for a given key.
2305",
2306 )]
2307 }
2308
2309 #[prompt(name = "manage_dependencies", description = "Set up and work with game dependencies and vanilla data.")]
2310 pub async fn manage_dependencies(&self) -> Vec<PromptMessage> {
2311 vec![PromptMessage::new_text(
2312 PromptMessageRole::User,
2313 "\
2314You are an assistant helping the user work with dependency data (vanilla game files).
2315
2316Workflow:
2317
23181. **Set the game** – `set_game_selected` with `rebuild_dependencies: true`.
2319
23202. **Check dependency database** – `is_there_a_dependency_database` with `true` to verify
2321 that game data (including Assembly Kit data) is loaded.
2322 If it returns false, call `generate_dependencies_cache` first.
2323
23243. **Browse vanilla tables** – `get_table_list_from_dependency_pack_file` returns all
2325 DB table names from the vanilla game files.
2326
23274. **Read vanilla data** – `get_tables_from_dependencies` with a table name to get
2328 all rows from vanilla for that table.
2329
23305. **Get definitions** – `get_table_definition_from_dependency_pack_file` to get the
2331 schema definition for any table.
2332
23336. **Import from vanilla** – `import_dependencies_to_open_pack_file` to copy specific
2334 files from vanilla into your mod pack.
2335
23367. **Open CA packs** – `load_all_ca_pack_files` opens all vanilla packs as one merged
2337 read-only pack for full browsing.
2338
23398. **Cross-source lookups** – `get_rfiles_from_all_sources` retrieves files by path
2340 from PackFile, GameFiles, and ParentFiles simultaneously.
2341
2342Tips:
2343- Use `get_packed_files_names_starting_with_path_from_all_sources` to discover files
2344 under a given path prefix across all sources.
2345- `set_dependency_pack_files_list` lets you mark other mods as dependencies of your pack.
2346",
2347 )]
2348 }
2349
2350 #[prompt(name = "run_diagnostics", description = "Validate a pack and fix common issues.")]
2351 pub async fn run_diagnostics(&self) -> Vec<PromptMessage> {
2352 vec![PromptMessage::new_text(
2353 PromptMessageRole::User,
2354 "\
2355You are an assistant helping the user validate a Total War mod PackFile.
2356
2357Workflow:
2358
23591. **Open the pack** and **set the game** with `rebuild_dependencies: true`.
2360
23612. **Generate dependencies** – If dependencies have not been generated yet,
2362 call `generate_dependencies` to build the dependency data needed for diagnostics.
2363
23643. **Run full diagnostics** – `diagnostics_check` with an empty `ignored` list
2365 and `check_ak_only_refs: false` (or `true` to include Assembly Kit references).
2366 The response contains all warnings and errors grouped by category.
2367
23684. **Review results** – Present the diagnostic results to the user, grouped by severity.
2369 Common issues include:
2370 - Invalid references (a column references a key that does not exist)
2371 - Duplicate keys
2372 - Empty loc entries
2373 - Outdated table versions
2374
23755. **Fix issues** – For each issue:
2376 - Decode the affected file with `decode_packed_file`.
2377 - Apply the fix (correct a reference, remove a duplicate row, etc.).
2378 - Save with `save_packed_file_from_view`.
2379
23806. **Ignore false positives** – Use `add_line_to_pack_ignored_diagnostics` to suppress
2381 specific diagnostic lines that are intentional.
2382
23837. **Re-check** – After fixes, call `diagnostics_check` again to confirm all issues
2384 are resolved.
2385
23868. **Optimize** – Optionally run `optimize_pack_file` to remove rows that are identical
2387 to vanilla, reducing pack size.
2388",
2389 )]
2390 }
2391
2392 #[prompt(name = "schema_operations", description = "Work with table schemas: inspect, update, and patch definitions.")]
2393 pub async fn schema_operations(&self) -> Vec<PromptMessage> {
2394 vec![PromptMessage::new_text(
2395 PromptMessageRole::User,
2396 "\
2397You are an assistant helping the user manage RPFM table schemas.
2398
2399Workflow:
2400
24011. **Check schema status** – `is_schema_loaded` to verify a schema is loaded.
2402 If not, call `update_schemas` to download the latest from the repository.
2403
24042. **Get the full schema** – `get_schema` returns the entire schema object.
2405
24063. **Inspect a table definition** – `definitions_by_table_name` with a table name
2407 returns all known versions. Use `definition_by_table_name_and_version` for a
2408 specific version.
2409
24104. **See processed fields** – `fields_processed` takes a Definition JSON and returns
2411 fields with bitwise expansion and enum conversions applied (useful for display).
2412
24135. **Find referencing columns** – `referencing_columns_for_definition` shows which
2414 other tables reference a given table's columns.
2415
24166. **Patch a definition** – To customise column metadata (descriptions, references,
2417 default values) without modifying the upstream schema:
2418 a. Build a `HashMap<String, DefinitionPatch>` with your changes.
2419 b. Call `save_local_schema_patch` to persist it locally.
2420 c. Use `remove_local_schema_patches_for_table` or
2421 `remove_local_schema_patches_for_table_and_field` to undo patches.
2422
24237. **Import patches** – `import_schema_patch` applies a patch from another source.
2424
24258. **Update from Assembly Kit** – `update_current_schema_from_asskit` merges
2426 definition data from the game's Assembly Kit into the loaded schema.
2427
24289. **Save the schema** – `save_schema` writes the current in-memory schema to disk.
2429",
2430 )]
2431 }
2432
2433 #[prompt(name = "file_operations", description = "Add, remove, rename, extract, and move files within packs.")]
2434 pub async fn file_operations(&self) -> Vec<PromptMessage> {
2435 vec![PromptMessage::new_text(
2436 PromptMessageRole::User,
2437 "\
2438You are an assistant helping the user manage files inside a Total War PackFile.
2439
2440Common operations:
2441
2442**Add files from disk:**
2443- `add_packed_files` – Import files from the filesystem into the pack. Provide source
2444 filesystem paths and destination `ContainerPath` entries as JSON.
2445
2446**Add files from another pack:**
2447- `add_packed_files_from_pack_file` – Copy files between two open packs.
2448
2449**Create new files:**
2450- `new_packed_file` – Create a blank DB table, Loc file, or other file type inside the pack.
2451
2452**Delete files:**
2453- `delete_packed_files` – Remove files by their `ContainerPath` list.
2454
2455**Rename / move files:**
2456- `rename_packed_files` – Pass a list of `(old_path, new_path)` tuples.
2457
2458**Copy / Cut / Paste / Duplicate:**
2459- `copy_packed_files` – Copy files to the internal clipboard for later pasting.
2460- `cut_packed_files` – Cut files to the internal clipboard (removed from source on paste).
2461- `paste_packed_files` – Paste clipboard contents into a pack at the given folder path.
2462- `duplicate_packed_files` – Clone files in-place with a numeric suffix.
2463
2464**Extract to disk:**
2465- `extract_packed_files` – Export files from the pack to a folder on disk.
2466 Set `export_as_tsv: true` to export tables as TSV files.
2467
2468**AnimPack operations:**
2469- `add_packed_files_from_pack_file_to_animpack` – Add files to an AnimPack.
2470- `add_packed_files_from_animpack` – Extract files from an AnimPack.
2471- `delete_from_animpack` – Remove files from an AnimPack.
2472
2473**File info:**
2474- `get_packed_files_info` / `get_rfile_info` – Get metadata about files.
2475- `folder_exists` / `packed_file_exists` – Check if a path exists.
2476- `get_packed_file_raw_data` – Get the raw binary content of a file.
2477
2478**Merge tables:**
2479- `merge_files` – Combine multiple compatible tables into one.
2480
2481**External editing:**
2482- `open_packed_file_in_external_program` – Open a file in the system's default editor.
2483- `save_packed_file_from_external_view` – Re-import after external editing.
2484
2485Always call `save_packfile` or `save_pack_as` when done to persist changes.
2486",
2487 )]
2488 }
2489
2490 #[prompt(name = "troubleshooting", description = "Diagnose and fix common issues with RPFM and PackFiles.")]
2491 pub async fn troubleshooting(&self) -> Vec<PromptMessage> {
2492 vec![PromptMessage::new_text(
2493 PromptMessageRole::User,
2494 "\
2495You are an assistant helping the user troubleshoot common RPFM and PackFile issues.
2496
2497## Common Issues and Solutions
2498
2499### 1. Schema not loaded
2500**Symptom**: Files fail to decode, or `decode_packed_file` returns raw data.
2501**Solution**:
2502- Call `is_schema_loaded()` – if false, call `update_schemas()`.
2503- Make sure `set_game_selected` was called with `rebuild_dependencies: true`.
2504
2505### 2. Dependencies not available
2506**Symptom**: References show as invalid, diagnostics report missing keys.
2507**Solution**:
2508- Call `is_there_a_dependency_database(true)` – if false, call `generate_dependencies_cache()`.
2509- Ensure the game path is configured correctly in settings.
2510
2511### 3. Pack won't save
2512**Symptom**: `save_packfile` returns an error.
2513**Solution**:
2514- Check if the file is read-only or locked by another process.
2515- Try `save_pack_as` to a different path.
2516- As a last resort, use `clean_and_save_pack_as` to recover from corruption.
2517
2518### 4. Table version mismatch
2519**Symptom**: Table data looks wrong or has missing columns after a game update.
2520**Solution**:
2521- Call `update_schemas()` to get the latest table definitions.
2522- Use `update_table` to migrate the table to the current version.
2523- Check `get_table_definition_from_dependency_pack_file` for the expected schema.
2524
2525### 5. Wrong game selected
2526**Symptom**: Tables decode with wrong columns or fail to decode, dependencies are for a different game.
2527**Solution**:
2528- Call `get_game_selected()` to verify the current game.
2529- Call `set_game_selected` with the correct game key and `rebuild_dependencies: true`.
2530
2531### 6. Diagnostics show many reference errors
2532**Symptom**: `diagnostics_check` reports hundreds of invalid references.
2533**Solution**:
2534- Ensure dependencies are loaded (`is_there_a_dependency_database(true)`).
2535- Check if the pack depends on other mods via `get_dependency_pack_files_list`.
2536- Some references are Assembly Kit only; re-run with `check_ak_only_refs: true`.
2537- Use `add_line_to_pack_ignored_diagnostics` for intentional deviations.
2538
2539### Diagnostic Tools
2540- `diagnostics_check` – Full pack validation.
2541- `get_game_selected` – Verify game context.
2542- `is_schema_loaded` – Check schema status.
2543- `is_there_a_dependency_database` – Check dependency database status.
2544- `list_open_packs` – Verify which packs are open.
2545- `config_path` / `schemas_path` – Verify RPFM paths.
2546",
2547 )]
2548 }
2549
2550 #[prompt(name = "tsv_workflow", description = "Import and export tables as TSV files for batch editing in spreadsheets.")]
2551 pub async fn tsv_workflow(&self) -> Vec<PromptMessage> {
2552 vec![PromptMessage::new_text(
2553 PromptMessageRole::User,
2554 "\
2555You are an assistant helping the user work with TSV (Tab-Separated Values) files for batch editing \
2556Total War mod data in spreadsheets.
2557
2558## Export Workflow (Pack → TSV → Spreadsheet)
2559
25601. **Open the pack** and **set the game** with `rebuild_dependencies: true`.
2561
25622. **Export a single table as TSV**:
2563 Call `export_tsv` with:
2564 - `pack_key`: the pack key
2565 - `tsv_path`: destination path on disk (e.g. `/home/user/my_table.tsv`)
2566 - `table_path`: the internal path (e.g. `db/land_units_tables/my_mod`)
2567
25683. **Export all tables as TSV**:
2569 Call `extract_packed_files` with `export_as_tsv: true`.
2570 This exports all tables in the pack as TSV files to the destination folder.
2571
25724. **Edit in a spreadsheet**: Open the TSV file in LibreOffice Calc, Excel, or Google Sheets.
2573 - Keep the header rows intact (they contain schema metadata).
2574 - Tab-separated values — do not change the delimiter.
2575
2576## Import Workflow (Spreadsheet → TSV → Pack)
2577
25781. **Save the spreadsheet as TSV** (tab-delimited, UTF-8 encoding).
2579
25802. **Import the TSV back**:
2581 Call `import_tsv` with:
2582 - `pack_key`: the target pack key
2583 - `tsv_path`: path to the TSV file on disk
2584 - `table_path`: the internal path where the table should go
2585
25863. **Verify**: Call `decode_packed_file` to confirm the data imported correctly.
2587
25884. **Save the pack**: Call `save_packfile` to persist changes.
2589
2590## Tips
2591- TSV files include metadata headers that RPFM uses for schema matching.
2592 Do not delete or modify these header rows.
2593- Use `get_table_definition_from_dependency_pack_file` to understand column types
2594 before editing.
2595- After import, run `diagnostics_check` to validate references.
2596",
2597 )]
2598 }
2599
2600 #[prompt(name = "translation_workflow", description = "Work with localisation and translation data in PackFiles.")]
2601 pub async fn translation_workflow(&self) -> Vec<PromptMessage> {
2602 vec![PromptMessage::new_text(
2603 PromptMessageRole::User,
2604 "\
2605You are an assistant helping the user work with localisation (translation) data in Total War mods.
2606
2607## Understanding Loc Files
2608
2609Loc files contain key-value pairs for in-game text. Each entry has:
2610- A **key** (unique identifier referenced by DB tables)
2611- A **value** (the displayed text in the game)
2612
2613## Viewing Existing Translations
2614
26151. **Open the pack** and **set the game**.
2616
26172. **Decode a loc file**:
2618 Call `decode_packed_file` with the loc file path (e.g. `text/db/my_mod.loc`)
2619 and `source: \"PackFile\"`.
2620
26213. **Get translation overview**:
2622 Call `get_pack_translation` with the pack key and a language code
2623 (e.g. `\"en\"`, `\"fr\"`, `\"de\"`, `\"es\"`, `\"it\"`, `\"zh\"`, `\"ru\"`, etc.).
2624
2625## Creating New Translations
2626
26271. **Create a new loc file**:
2628 Call `new_packed_file` with path `\"text/db/my_mod.loc\"` and
2629 `new_file = {\"Loc\": \"my_mod\"}`.
2630
26312. **Decode it**: `decode_packed_file` to get the empty structure.
2632
26333. **Add entries**: Modify the decoded JSON to add key-value rows.
2634 Each row is typically `[\"key_string\", \"Displayed text in game\"]`.
2635
26364. **Save back**: `save_packed_file_from_view` with the modified data.
2637
2638## Generating Missing Loc Data
2639
2640Call `generate_missing_loc_data` with the pack key to auto-generate
2641loc entries for DB fields that reference loc keys but don't have entries yet.
2642
2643## Finding Loc Keys
2644
2645- Use `go_to_loc` with a loc key to find its source loc file.
2646- Use `get_source_data_from_loc_key` to find where a loc key is referenced.
2647- Use `global_search` with `search_on.loc: true` to search across all loc files.
2648
2649## Tips
2650- Loc keys follow naming conventions like `<table>_<loc_column_name>_<keys_concatenated>`.
2651- Use `search_references` to find all DB columns that reference a specific loc key.
2652- After adding translations, run `diagnostics_check` to verify all references.
2653",
2654 )]
2655 }
2656}