1use rmcp::ErrorData as McpError;
39use rmcp::handler::server::{router::prompt::PromptRouter, tool::ToolRouter, wrapper::Parameters};
40use rmcp::model::{
41 CallToolResult, CompletionInfo, CompleteRequestParams, CompleteResult,
42 ContentBlock, ErrorCode, ListResourcesResult, ListResourceTemplatesResult,
43 PaginatedRequestParams, PromptMessage,
44 ReadResourceRequestParams, ReadResourceResult, ReadResourceResponse,
45 Resource, ResourceContents, Role, ServerCapabilities, ServerInfo,
46};
47use rmcp::service::RequestContext;
48use rmcp::{prompt, prompt_handler, prompt_router, tool, tool_handler, tool_router, RoleServer};
49use schemars::JsonSchema;
50use serde::{Deserialize, Serialize};
51
52use std::collections::{BTreeMap, HashMap, HashSet};
53use std::sync::Arc;
54use std::path::PathBuf;
55
56use rpfm_extensions::merge::MergeOptions;
57use rpfm_extensions::translator::DEFAULT_SRC_LANG;
58
59use rpfm_ipc::helpers::DataSource;
60use rpfm_ipc::messages::{Command, Response};
61use rpfm_lib::files::{ContainerPath, RFile, RFileDecoded};
62use rpfm_telemetry::sentry;
63
64use crate::session::{Session, recv_response};
65
66macro_rules! send_and_respond {
75 ($self:expr, $tool_name:expr, $cmd:expr) => {{
76 let tx_ctx = sentry::TransactionContext::new(
77 &format!("tools/call {}", $tool_name),
78 "mcp.server",
79 );
80 let tx = sentry::start_transaction(tx_ctx);
81 tx.set_data("mcp.method.name", sentry::protocol::Value::from("tools/call"));
82 tx.set_data("mcp.tool.name", sentry::protocol::Value::from($tool_name));
83 tx.set_data("mcp.transport", sentry::protocol::Value::from("streamable-http"));
84
85 sentry::configure_scope(|scope| scope.set_span(Some(tx.clone().into())));
86
87 let mut receiver = $self.session.send($cmd);
88 let response = recv_response(&mut receiver).await;
89
90 tx.finish();
91
92 let is_error = matches!(&response, Response::Error(_));
93
94 let json = serde_json::to_string(&response).map_err(|e| McpError {
95 code: ErrorCode::INTERNAL_ERROR,
96 message: format!("Failed to serialize response: {e}").into(),
97 data: None,
98 })?;
99
100 if is_error {
101 Ok(CallToolResult::error(vec![ContentBlock::text(json)]))
102 } else {
103 Ok(CallToolResult::success(vec![ContentBlock::text(json)]))
104 }
105 }};
106}
107
108fn resource(uri: &str, name: &str, description: &str, mime_type: &str) -> Resource {
110 Resource::new(uri, name)
111 .with_description(description)
112 .with_mime_type(mime_type)
113}
114
115macro_rules! parse_json {
121 ($input:expr) => {
122 match serde_json::from_str($input) {
123 Ok(v) => v,
124 Err(e) => return Ok(CallToolResult::error(vec![ContentBlock::text(format!("Invalid JSON parameter: {e}"))])),
125 }
126 };
127}
128
129#[derive(Clone)]
143pub struct McpServer {
144 session: Arc<Session>,
146 tool_router: ToolRouter<Self>,
148 prompt_router: PromptRouter<Self>,
150}
151
152#[derive(Debug, Deserialize, JsonSchema, Serialize)]
155#[schemars(description = "Call any IPC command directly.")]
156pub struct CallCommandArgs {
157 pub command: String,
159}
160
161#[derive(Debug, Deserialize, JsonSchema, Serialize)]
162pub struct OpenPackfilesArgs {
163 pub paths: Vec<PathBuf>,
165}
166
167#[derive(Debug, Deserialize, JsonSchema, Serialize)]
168pub struct SetGameSelectedArgs {
169 pub game_name: String,
171 pub rebuild_dependencies: bool,
173}
174
175#[derive(Debug, Deserialize, JsonSchema, Serialize)]
176pub struct TsvExportArgs {
177 pub pack_key: String,
179 pub tsv_path: PathBuf,
181 pub table_path: String,
183}
184
185#[derive(Debug, Deserialize, JsonSchema, Serialize)]
186pub struct TsvImportArgs {
187 pub pack_key: String,
189 pub tsv_path: PathBuf,
191 pub table_path: String,
193}
194
195#[derive(Debug, Deserialize, JsonSchema, Serialize)]
196pub struct DecodePackedFileArgs {
197 pub pack_key: String,
199 pub path: String,
201 pub source: DataSource,
203}
204
205#[derive(Debug, Deserialize, JsonSchema, Serialize)]
208pub struct PathArg {
209 pub path: PathBuf,
211}
212
213#[derive(Debug, Deserialize, JsonSchema, Serialize)]
214pub struct TableColumnArgs {
215 pub table_name: String,
217 pub column_name: String,
219}
220
221#[derive(Debug, Deserialize, JsonSchema, Serialize)]
224pub struct PackKeyArg {
225 pub pack_key: String,
227}
228
229#[derive(Debug, Deserialize, JsonSchema, Serialize)]
230pub struct PackKeyBoolArg {
231 pub pack_key: String,
233 pub value: bool,
235}
236
237#[derive(Debug, Deserialize, JsonSchema, Serialize)]
238pub struct PackKeyStringArg {
239 pub pack_key: String,
241 pub value: String,
243}
244
245#[derive(Debug, Deserialize, JsonSchema, Serialize)]
246pub struct PackKeyStringsArg {
247 pub pack_key: String,
249 pub values: Vec<String>,
251}
252
253#[derive(Debug, Deserialize, JsonSchema, Serialize)]
254pub struct PackKeyPathArg {
255 pub pack_key: String,
257 pub path: PathBuf,
259}
260
261#[derive(Debug, Deserialize, JsonSchema, Serialize)]
264pub struct SetPackFileTypeArgs {
265 pub pack_key: String,
267 pub pack_file_type: String,
269}
270
271#[derive(Debug, Deserialize, JsonSchema, Serialize)]
272pub struct ChangeCompressionFormatArgs {
273 pub pack_key: String,
275 pub format: String,
277}
278
279#[derive(Debug, Deserialize, JsonSchema, Serialize)]
280pub struct BoolArg {
281 pub value: bool,
283}
284
285#[derive(Debug, Deserialize, JsonSchema, Serialize)]
286pub struct SetPackSettingsArgs {
287 pub pack_key: String,
289 pub settings: String,
291}
292
293#[derive(Debug, Deserialize, JsonSchema, Serialize)]
294pub struct SetDependencyPackFilesListArgs {
295 pub pack_key: String,
297 pub list: String,
299}
300
301#[derive(Debug, Deserialize, JsonSchema, Serialize)]
304pub struct NewPackedFileArgs {
305 pub pack_key: String,
307 pub path: String,
309 pub new_file: String,
311}
312
313#[derive(Debug, Deserialize, JsonSchema, Serialize)]
314pub struct AddPackedFilesArgs {
315 pub pack_key: String,
317 pub source_paths: Vec<PathBuf>,
319 pub destination_paths: String,
321 pub ignore_paths: Option<Vec<PathBuf>>,
323}
324
325#[derive(Debug, Deserialize, JsonSchema, Serialize)]
326pub struct AddPackedFilesFromPackFileArgs {
327 pub pack_key: String,
329 pub source_pack_path: String,
331 pub container_paths: String,
333}
334
335#[derive(Debug, Deserialize, JsonSchema, Serialize)]
336pub struct AddPackedFilesFromPackFileToAnimpackArgs {
337 pub source_pack_key: String,
339 pub pack_key: String,
341 pub animpack_path: String,
343 pub container_paths: String,
345}
346
347#[derive(Debug, Deserialize, JsonSchema, Serialize)]
348pub struct AddPackedFilesFromAnimpackArgs {
349 pub anim_pack_key: String,
351 pub pack_key: String,
353 pub source: DataSource,
355 pub animpack_path: String,
357 pub container_paths: String,
359}
360
361#[derive(Debug, Deserialize, JsonSchema, Serialize)]
362pub struct ContainerPathsArg {
363 pub pack_key: String,
365 pub paths: String,
367}
368
369#[derive(Debug, Deserialize, JsonSchema, Serialize)]
370pub struct DeleteFromAnimpackArgs {
371 pub pack_key: String,
373 pub animpack_path: String,
375 pub container_paths: String,
377}
378
379#[derive(Debug, Deserialize, JsonSchema, Serialize)]
380pub struct ExtractPackedFilesArgs {
381 pub pack_key: String,
383 pub source_paths: String,
385 pub destination_path: PathBuf,
387 pub export_as_tsv: bool,
389}
390
391#[derive(Debug, Deserialize, JsonSchema, Serialize)]
392pub struct RenamePackedFilesArgs {
393 pub pack_key: String,
395 pub renames: String,
397}
398
399#[derive(Debug, Deserialize, JsonSchema, Serialize)]
400pub struct CopyOrCutPackedFilesArgs {
401 pub paths_by_pack: String,
403}
404
405#[derive(Debug, Deserialize, JsonSchema, Serialize)]
406pub struct PastePackedFilesArgs {
407 pub pack_key: String,
409 pub destination_path: String,
411}
412
413#[derive(Debug, Deserialize, JsonSchema, Serialize)]
414pub struct DuplicatePackedFilesArgs {
415 pub pack_key: String,
417 pub paths: String,
419}
420
421#[derive(Debug, Deserialize, JsonSchema, Serialize)]
422pub struct SavePackedFileFromViewArgs {
423 pub pack_key: String,
425 pub path: String,
427 pub data: String,
429}
430
431#[derive(Debug, Deserialize, JsonSchema, Serialize)]
432pub struct SavePackedFileFromExternalViewArgs {
433 pub pack_key: String,
435 pub internal_path: String,
437 pub external_path: PathBuf,
439}
440
441#[derive(Debug, Deserialize, JsonSchema, Serialize)]
442pub struct SavePackedFilesToPackFileAndCleanArgs {
443 pub pack_key: String,
445 pub files: String,
447 pub optimize: bool,
449}
450
451#[derive(Debug, Deserialize, JsonSchema, Serialize)]
452pub struct StringArg {
453 pub value: String,
455}
456
457#[derive(Debug, Deserialize, JsonSchema, Serialize)]
458pub struct OpenPackedFileInExternalProgramArgs {
459 pub pack_key: String,
461 pub source: DataSource,
463 pub container_path: String,
465}
466
467#[derive(Debug, Deserialize, JsonSchema, Serialize)]
468pub struct StringsArg {
469 pub values: Vec<String>,
471}
472
473#[derive(Debug, Deserialize, JsonSchema, Serialize)]
476pub struct ImportDependenciesArgs {
477 pub pack_key: String,
479 pub paths: String,
481}
482
483#[derive(Debug, Deserialize, JsonSchema, Serialize)]
484pub struct GetRFilesFromAllSourcesArgs {
485 pub paths: String,
487 pub lowercase: bool,
489}
490
491#[derive(Debug, Deserialize, JsonSchema, Serialize)]
492pub struct ContainerPathArg {
493 pub path: String,
495}
496
497#[derive(Debug, Deserialize, JsonSchema, Serialize)]
500pub struct GlobalSearchArgs {
501 pub pack_key: String,
503 pub search: String,
505}
506
507#[derive(Debug, Deserialize, JsonSchema, Serialize)]
508pub struct GlobalSearchReplaceMatchesArgs {
509 pub pack_key: String,
511 pub search: String,
513 pub matches: String,
515}
516
517#[derive(Debug, Deserialize, JsonSchema, Serialize)]
518pub struct SearchReferencesArgs {
519 pub pack_key: String,
521 pub reference_map: String,
523 pub value: String,
525}
526
527#[derive(Debug, Deserialize, JsonSchema, Serialize)]
528pub struct GetReferenceDataFromDefinitionArgs {
529 pub pack_key: String,
531 pub table_name: String,
533 pub definition: String,
535 pub force: bool,
537}
538
539#[derive(Debug, Deserialize, JsonSchema, Serialize)]
540pub struct GoToDefinitionArgs {
541 pub pack_key: String,
543 pub table_name: String,
545 pub column_name: String,
547 pub values: Vec<String>,
549}
550
551#[derive(Debug, Deserialize, JsonSchema, Serialize)]
554pub struct SaveSchemaArgs {
555 pub schema: String,
557}
558
559#[derive(Debug, Deserialize, JsonSchema, Serialize)]
560pub struct StringI32Args {
561 pub name: String,
563 pub version: i32,
565}
566
567#[derive(Debug, Deserialize, JsonSchema, Serialize)]
568pub struct ReferencingColumnsForDefinitionArgs {
569 pub table_name: String,
571 pub definition: String,
573}
574
575#[derive(Debug, Deserialize, JsonSchema, Serialize)]
576pub struct DefinitionArg {
577 pub definition: String,
579}
580
581#[derive(Debug, Deserialize, JsonSchema, Serialize)]
582pub struct SchemaPatchArgs {
583 pub patches: String,
585}
586
587#[derive(Debug, Deserialize, JsonSchema, Serialize)]
590pub struct MergeFilesArgs {
591 pub pack_key: String,
593 pub paths: String,
595 pub merged_path: String,
597 pub delete_source: bool,
599 #[serde(default)]
603 pub delta_merge: bool,
604}
605
606#[derive(Debug, Deserialize, JsonSchema, Serialize)]
607pub struct CascadeEditionArgs {
608 pub pack_key: String,
610 pub table_name: String,
612 pub definition: String,
614 pub changes: String,
616}
617
618#[derive(Debug, Deserialize, JsonSchema, Serialize)]
619pub struct AddKeysToKeyDeletesArgs {
620 pub pack_key: String,
622 pub table_file_name: String,
624 pub key_table_name: String,
626 pub keys: HashSet<String>,
628}
629
630#[derive(Debug, Deserialize, JsonSchema, Serialize)]
633pub struct DiagnosticsCheckArgs {
634 pub ignored: Vec<String>,
636 pub check_ak_only_refs: bool,
638}
639
640#[derive(Debug, Deserialize, JsonSchema, Serialize)]
641pub struct LuaRunTestsArgs {
642 pub test_source: String,
644 pub campaign: Option<String>,
646}
647
648#[derive(Debug, Deserialize, JsonSchema, Serialize)]
649pub struct DiagnosticsUpdateArgs {
650 pub diagnostics: String,
652 pub paths: String,
654 pub check_ak_only_refs: bool,
656}
657
658#[derive(Debug, Deserialize, JsonSchema, Serialize)]
661pub struct AddNoteArgs {
662 pub pack_key: String,
664 pub note: String,
666}
667
668#[derive(Debug, Deserialize, JsonSchema, Serialize)]
669pub struct DeleteNoteArgs {
670 pub pack_key: String,
672 pub path: String,
674 pub id: u64,
676}
677
678#[derive(Debug, Deserialize, JsonSchema, Serialize)]
681pub struct OptimizePackFileArgs {
682 pub pack_key: String,
684 pub options: String,
686}
687
688#[derive(Debug, Deserialize, JsonSchema, Serialize)]
691pub struct SettingsSetBoolArgs {
692 pub key: String,
694 pub value: bool,
696}
697
698#[derive(Debug, Deserialize, JsonSchema, Serialize)]
699pub struct SettingsSetI32Args {
700 pub key: String,
702 pub value: i32,
704}
705
706#[derive(Debug, Deserialize, JsonSchema, Serialize)]
707pub struct SettingsSetF32Args {
708 pub key: String,
710 pub value: f32,
712}
713
714#[derive(Debug, Deserialize, JsonSchema, Serialize)]
715pub struct SettingsSetStringArgs {
716 pub key: String,
718 pub value: String,
720}
721
722#[derive(Debug, Deserialize, JsonSchema, Serialize)]
723pub struct SettingsSetPathBufArgs {
724 pub key: String,
726 pub value: PathBuf,
728}
729
730#[derive(Debug, Deserialize, JsonSchema, Serialize)]
731pub struct SettingsSetVecStringArgs {
732 pub key: String,
734 pub value: Vec<String>,
736}
737
738#[derive(Debug, Deserialize, JsonSchema, Serialize)]
739pub struct SettingsSetVecRawArgs {
740 pub key: String,
742 pub value: Vec<u8>,
744}
745
746#[derive(Debug, Deserialize, JsonSchema, Serialize)]
749pub struct InitializeMyModFolderArgs {
750 pub name: String,
752 pub game: String,
754 pub sublime: bool,
756 pub vscode: bool,
758 pub gitignore: Option<String>,
760}
761
762#[derive(Debug, Deserialize, JsonSchema, Serialize)]
763pub struct PackMapArgs {
764 pub pack_key: String,
766 pub tile_maps: Vec<PathBuf>,
768 pub tiles: String,
770}
771
772#[derive(Debug, Deserialize, JsonSchema, Serialize)]
773pub struct BuildStarposArgs {
774 pub pack_key: String,
776 pub campaign_id: String,
778 pub process_hlp_spd: bool,
780}
781
782#[derive(Debug, Deserialize, JsonSchema, Serialize)]
783pub struct UpdateAnimIdsArgs {
784 pub pack_key: String,
786 pub starting_id: i32,
788 pub offset: i32,
790}
791
792#[derive(Debug, Deserialize, JsonSchema, Serialize)]
793pub struct ExportRigidToGltfArgs {
794 pub rigid_model: String,
796 pub output_path: String,
798}
799
800#[derive(Debug, Deserialize, JsonSchema, Serialize)]
801pub struct SetVideoFormatArgs {
802 pub pack_key: String,
804 pub path: String,
806 pub format: String,
808}
809
810#[derive(Debug, Deserialize, JsonSchema, Serialize)]
811pub struct GetPackTranslationArgs {
812 pub pack_key: String,
814 #[serde(default = "default_src_lang")]
816 pub src_lang: String,
817 pub language: String,
819}
820
821#[derive(Debug, Deserialize, JsonSchema, Serialize)]
822pub struct SrcLangArg {
823 pub src_lang: String,
825}
826
827fn default_src_lang() -> String {
828 DEFAULT_SRC_LANG.to_owned()
829}
830
831#[tool_handler(router = self.tool_router)]
836#[prompt_handler(router = self.prompt_router)]
837impl rmcp::ServerHandler for McpServer {
838 fn get_info(&self) -> ServerInfo {
839 let capabilities = ServerCapabilities::builder()
840 .enable_tools()
841 .enable_prompts()
842 .enable_resources()
843 .enable_completions()
844 .build();
845
846 ServerInfo::new(capabilities).with_instructions("\
848This is the MCP server for RPFM (Rusted PackFile Manager), a tool for modding Total War games by \
849Creative Assembly. It lets you read, edit, create, and manage PackFiles (.pack) — the archive \
850format used by all modern Total War titles.
851
852## Key Concepts
853
854- **PackFile**: An archive containing game data files (DB tables, localisation, textures, models, etc.). \
855 Mods are distributed as PackFiles.
856- **pack_key**: When you open one or more PackFiles, each gets a unique key string. Use `list_open_packs` \
857 to discover available keys. Most tools require a `pack_key` parameter.
858- **DataSource**: Where data lives — `\"PackFile\"` (the user's mod), `\"GameFiles\"` (vanilla game data), \
859 `\"ParentFiles\"` (dependency mods), `\"AssKitFiles\"` (Assembly Kit data), `\"ExternalFile\"` (disk file).
860- **ContainerPath**: A path inside a pack — either `{\"File\": \"db/land_units_tables/my_table\"}` or \
861 `{\"Folder\": \"db/land_units_tables\"}`. Use an empty string for root folder.
862
863## Required Initialization Sequence
864
8651. **Set the game** — Call `set_game_selected` with the game key (e.g. `\"warhammer_3\"`) and \
866 `rebuild_dependencies: true`. This loads schemas and vanilla data.
8672. **Open a pack** — Call `open_packfiles` with filesystem path(s). Note the returned pack key(s).
8683. **Verify schema** — Call `is_schema_loaded`; if false, call `update_schemas` first.
869
870## Supported Games
871
872Valid game keys: `pharaoh_dynasties`, `pharaoh`, `warhammer_3`, `troy`, `three_kingdoms`, \
873`warhammer_2`, `warhammer`, `thrones_of_britannia`, `attila`, `rome_2`, `shogun_2`, `napoleon`, \
874`empire`, `arena`.
875
876## Common File Path Conventions
877
878- DB tables: `db/<table_name>/<file_name>` (e.g. `db/land_units_tables/my_mod`)
879- Localisation: `text/db/<file_name>.loc`
880- Scripts: `script/<path>.lua`
881- Images: `ui/<path>.png`
882
883## Pack File Types (PFHFileType)
884
885`\"Boot\"`, `\"Release\"`, `\"Patch\"`, `\"Mod\"` (default for mods), `\"Movie\"`.
886
887## Compression Formats
888
889`\"None\"` (default), `\"Lzma1\"` (legacy), `\"Lz4\"` (WH3 6.2+), `\"Zstd\"` (WH3 6.2+).
890
891## Creating New Files (NewFile)
892
893- DB table: `{\"DB\": [\"file_name\", \"table_name\", version]}` — e.g. `{\"DB\": [\"my_mod\", \"land_units_tables\", 0]}`
894- Loc file: `{\"Loc\": \"file_name\"}`
895- Text file: `{\"Text\": [\"file_name\", \"Plain\"]}` — formats: `\"Plain\"`, `\"Html\"`, `\"Xml\"`, `\"Lua\"`, `\"Cpp\"`, `\"Json\"`, `\"Markdown\"`, `\"Smithy\"`
896- AnimPack: `{\"AnimPack\": \"file_name\"}`
897- PortraitSettings: `{\"PortraitSettings\": [\"file_name\", version, [[\"entry_key\", \"entry_value\"]]]}`
898- VMD: `{\"VMD\": \"file_name\"}`
899- WSModel: `{\"WSModel\": \"file_name\"}`
900
901## Resources
902
903Use `resources/list` and `resources/read` to browse reference data: valid enum values, game lists, \
904and example JSON payloads without needing tool calls.
905
906## Responses
907
908All tool responses are JSON-serialized. On failure, an error message is returned instead of the expected data.
909")
910 }
911
912 async fn list_resources(
917 &self,
918 _request: Option<PaginatedRequestParams>,
919 _context: RequestContext<RoleServer>,
920 ) -> Result<ListResourcesResult, McpError> {
921 let resources = vec![
922 resource("rpfm://games", "games", "List of all supported Total War game keys.", "application/json"),
923 resource("rpfm://enums/PFHFileType", "PFHFileType", "Valid PackFile type values (Boot, Release, Patch, Mod, Movie).", "application/json"),
924 resource("rpfm://enums/CompressionFormat", "CompressionFormat", "Valid compression format values (None, Lzma1, Lz4, Zstd).", "application/json"),
925 resource("rpfm://enums/DataSource", "DataSource", "Valid data source values indicating where data comes from.", "application/json"),
926 resource("rpfm://enums/ContainerPath", "ContainerPath", "ContainerPath enum variants with JSON examples.", "application/json"),
927 resource("rpfm://enums/NewFile", "NewFile", "NewFile enum variants for creating files inside packs, with JSON examples.", "application/json"),
928 resource("rpfm://enums/SupportedFormats", "SupportedFormats", "Valid video format values (CaVp8, Ivf).", "application/json"),
929 resource("rpfm://examples/global_search", "GlobalSearch example", "Example JSON for the GlobalSearch struct used by search tools.", "application/json"),
930 resource("rpfm://examples/optimizer_options", "OptimizerOptions example", "Example JSON for OptimizerOptions with all boolean fields.", "application/json"),
931 resource("rpfm://reference/initialization", "Initialization guide", "Step-by-step guide for initializing the RPFM MCP server session.", "text/plain"),
932 resource("rpfm://reference/path_conventions", "Path conventions", "Common file path conventions inside Total War PackFiles.", "text/plain"),
933 ];
934 Ok(ListResourcesResult {
935 resources,
936 ..Default::default()
937 })
938 }
939
940 async fn list_resource_templates(
941 &self,
942 _request: Option<PaginatedRequestParams>,
943 _context: RequestContext<RoleServer>,
944 ) -> Result<ListResourceTemplatesResult, McpError> {
945 Ok(ListResourceTemplatesResult {
946 resource_templates: vec![],
947 ..Default::default()
948 })
949 }
950
951 async fn read_resource(
952 &self,
953 request: ReadResourceRequestParams,
954 _context: RequestContext<RoleServer>,
955 ) -> Result<ReadResourceResponse, McpError> {
956 let uri = &request.uri;
957 let content = match uri.as_str() {
958 "rpfm://games" => serde_json::json!({
959 "supported_games": [
960 {"key": "pharaoh_dynasties", "display_name": "Total War: Pharaoh Dynasties"},
961 {"key": "pharaoh", "display_name": "Total War: Pharaoh"},
962 {"key": "warhammer_3", "display_name": "Total War: Warhammer III"},
963 {"key": "troy", "display_name": "A Total War Saga: Troy"},
964 {"key": "three_kingdoms", "display_name": "Total War: Three Kingdoms"},
965 {"key": "warhammer_2", "display_name": "Total War: Warhammer II"},
966 {"key": "warhammer", "display_name": "Total War: Warhammer"},
967 {"key": "thrones_of_britannia", "display_name": "A Total War Saga: Thrones of Britannia"},
968 {"key": "attila", "display_name": "Total War: Attila"},
969 {"key": "rome_2", "display_name": "Total War: Rome II"},
970 {"key": "shogun_2", "display_name": "Total War: Shogun 2"},
971 {"key": "napoleon", "display_name": "Total War: Napoleon"},
972 {"key": "empire", "display_name": "Total War: Empire"},
973 {"key": "arena", "display_name": "Total War: Arena"}
974 ]
975 }).to_string(),
976
977 "rpfm://enums/PFHFileType" => serde_json::json!({
978 "enum": "PFHFileType",
979 "description": "The type/priority of a PackFile. Games load packs in type order (Boot first, Movie last).",
980 "variants": [
981 {"name": "Boot", "value": 0, "description": "Core game boot files, loaded first."},
982 {"name": "Release", "value": 1, "description": "Main game data files."},
983 {"name": "Patch", "value": 2, "description": "Official patch and update files."},
984 {"name": "Mod", "value": 3, "description": "User mod files. This is the default for mods."},
985 {"name": "Movie", "value": 4, "description": "Cinematic and always-loaded files, loaded last."}
986 ],
987 "json_example": "\"Mod\""
988 }).to_string(),
989
990 "rpfm://enums/CompressionFormat" => serde_json::json!({
991 "enum": "CompressionFormat",
992 "description": "Compression algorithm for pack file data.",
993 "variants": [
994 {"name": "None", "description": "No compression (default)."},
995 {"name": "Lzma1", "description": "Legacy LZMA compression (all PFH5 games)."},
996 {"name": "Lz4", "description": "LZ4 compression (Warhammer 3 v6.2+)."},
997 {"name": "Zstd", "description": "Zstandard compression (Warhammer 3 v6.2+)."}
998 ],
999 "json_example": "\"None\""
1000 }).to_string(),
1001
1002 "rpfm://enums/DataSource" => serde_json::json!({
1003 "enum": "DataSource",
1004 "description": "Identifies where data comes from when working with files.",
1005 "variants": [
1006 {"name": "PackFile", "description": "Data from the user's currently open pack (mod files)."},
1007 {"name": "GameFiles", "description": "Data from vanilla game files."},
1008 {"name": "ParentFiles", "description": "Data from parent/dependency pack files."},
1009 {"name": "AssKitFiles", "description": "Data from the Assembly Kit (modding tools)."},
1010 {"name": "ExternalFile", "description": "Data from an external file on disk."}
1011 ],
1012 "json_example": "\"PackFile\""
1013 }).to_string(),
1014
1015 "rpfm://enums/ContainerPath" => serde_json::json!({
1016 "enum": "ContainerPath",
1017 "description": "A path reference inside a PackFile, pointing to either a file or a folder.",
1018 "variants": [
1019 {
1020 "name": "File",
1021 "description": "Path to a single file inside the pack.",
1022 "json_example": {"File": "db/land_units_tables/my_table"}
1023 },
1024 {
1025 "name": "Folder",
1026 "description": "Path to a folder inside the pack. Use empty string for root.",
1027 "json_example": {"Folder": "db/land_units_tables"}
1028 }
1029 ],
1030 "usage_notes": "Most tools accept a JSON array of ContainerPath objects, e.g. [{\"File\": \"path1\"}, {\"Folder\": \"path2\"}]"
1031 }).to_string(),
1032
1033 "rpfm://enums/NewFile" => serde_json::json!({
1034 "enum": "NewFile",
1035 "description": "Specifies what type of file to create inside a pack.",
1036 "variants": [
1037 {
1038 "name": "DB",
1039 "description": "Create a new DB table. Args: [file_name, table_name, version].",
1040 "json_example": {"DB": ["my_mod", "land_units_tables", 0]}
1041 },
1042 {
1043 "name": "Loc",
1044 "description": "Create a new localisation file. Arg: file_name.",
1045 "json_example": {"Loc": "my_mod"}
1046 },
1047 {
1048 "name": "Text",
1049 "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.",
1050 "json_example": {"Text": ["my_script", "Lua"]}
1051 },
1052 {
1053 "name": "AnimPack",
1054 "description": "Create a new AnimPack file. Arg: file_name.",
1055 "json_example": {"AnimPack": "my_anim"}
1056 },
1057 {
1058 "name": "PortraitSettings",
1059 "description": "Create a new portrait settings file. Args: [file_name, version, entries].",
1060 "json_example": {"PortraitSettings": ["my_portraits", 3, []]}
1061 },
1062 {
1063 "name": "VMD",
1064 "description": "Create a new VMD file. Arg: file_name.",
1065 "json_example": {"VMD": "my_vmd"}
1066 },
1067 {
1068 "name": "WSModel",
1069 "description": "Create a new WSModel file. Arg: file_name.",
1070 "json_example": {"WSModel": "my_model"}
1071 }
1072 ]
1073 }).to_string(),
1074
1075 "rpfm://enums/SupportedFormats" => serde_json::json!({
1076 "enum": "SupportedFormats",
1077 "description": "Video format options for CA VP8 video files.",
1078 "variants": [
1079 {"name": "CaVp8", "description": "CA's custom VP8 format (default)."},
1080 {"name": "Ivf", "description": "Standard VP8 IVF format."}
1081 ],
1082 "json_example": "\"CaVp8\""
1083 }).to_string(),
1084
1085 "rpfm://examples/global_search" => serde_json::json!({
1086 "description": "Example GlobalSearch JSON for use with global_search, global_search_replace_all, etc.",
1087 "example": {
1088 "pattern": "old_unit_name",
1089 "replace_text": "new_unit_name",
1090 "case_sensitive": false,
1091 "use_regex": false,
1092 "sources": [{"Pack": "my_mod.pack"}],
1093 "search_on": {
1094 "anim": false, "anim_fragment_battle": false, "anim_pack": false,
1095 "anims_table": false, "atlas": false, "audio": false, "bmd": false,
1096 "db": true, "esf": false, "group_formations": false, "image": false,
1097 "loc": true, "matched_combat": false, "pack": false,
1098 "portrait_settings": false, "rigid_model": false, "sound_bank": false,
1099 "text": true, "uic": false, "unit_variant": false, "unknown": false,
1100 "video": false, "schema": false
1101 },
1102 "matches": {
1103 "anim": [], "anim_fragment_battle": [], "anim_pack": [],
1104 "anims_table": [], "atlas": [], "audio": [], "bmd": [],
1105 "db": [], "esf": [], "group_formations": [], "image": [],
1106 "loc": [], "matched_combat": [], "pack": [],
1107 "portrait_settings": [], "rigid_model": [], "sound_bank": [],
1108 "text": [], "uic": [], "unit_variant": [], "unknown": [],
1109 "video": [], "schema": {"matches": []}
1110 },
1111 "game_key": "warhammer_3"
1112 },
1113 "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\"."
1114 }).to_string(),
1115
1116 "rpfm://examples/optimizer_options" => serde_json::json!({
1117 "description": "OptimizerOptions struct with all boolean fields for pack optimization.",
1118 "example": {
1119 "pack_remove_itm_files": true,
1120 "pack_apply_compression": true,
1121 "pack_apply_encryption": false,
1122 "pack_remove_duplicated_files": false,
1123 "db_import_datacores_into_twad_key_deletes": false,
1124 "db_optimize_datacored_tables": false,
1125 "table_remove_duplicated_entries": true,
1126 "table_remove_itm_entries": true,
1127 "table_remove_itnr_entries": true,
1128 "table_remove_empty_file": true,
1129 "text_remove_unused_xml_map_folders": false,
1130 "text_remove_unused_xml_prefab_folder": false,
1131 "text_remove_agf_files": false,
1132 "text_remove_model_statistics_files": false,
1133 "pts_remove_unused_art_sets": false,
1134 "pts_remove_unused_variants": false,
1135 "pts_remove_empty_masks": false,
1136 "pts_remove_empty_file": false
1137 },
1138 "field_descriptions": {
1139 "pack_remove_itm_files": "Remove files identical to vanilla (Identical To Master).",
1140 "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.",
1141 "pack_apply_encryption": "Enable both index and data encryption, so the next save encrypts the pack. No-op on packs older than PFH4.",
1142 "pack_remove_duplicated_files": "Remove case-insensitively duplicated files (same name ignoring casing) when their contents are identical, keeping the all-lowercase one or, failing that, the last one.",
1143 "db_import_datacores_into_twad_key_deletes": "Import datacored tables into TWAD key deletes.",
1144 "db_optimize_datacored_tables": "Optimize datacored tables.",
1145 "table_remove_duplicated_entries": "Remove duplicate rows in tables.",
1146 "table_remove_itm_entries": "Remove rows identical to vanilla.",
1147 "table_remove_itnr_entries": "Remove rows identical to vanilla that are not referenced.",
1148 "table_remove_empty_file": "Remove tables with no rows.",
1149 "text_remove_unused_xml_map_folders": "Remove unused XML files in map folders.",
1150 "text_remove_unused_xml_prefab_folder": "Remove unused XML files in prefab folders.",
1151 "text_remove_agf_files": "Remove AGF files.",
1152 "text_remove_model_statistics_files": "Remove model statistics files.",
1153 "pts_remove_unused_art_sets": "Remove unused art sets in portrait settings.",
1154 "pts_remove_unused_variants": "Remove unused variants in portrait settings.",
1155 "pts_remove_empty_masks": "Remove empty masks in portrait settings.",
1156 "pts_remove_empty_file": "Remove empty portrait settings files."
1157 }
1158 }).to_string(),
1159
1160 "rpfm://reference/initialization" => "\
1161RPFM MCP Server Initialization Guide
1162=====================================
1163
1164Before you can work with PackFiles, you must initialize the server session:
1165
1166Step 1: Set the game
1167 Call: set_game_selected(game_name: \"warhammer_3\", rebuild_dependencies: true)
1168 This loads the correct schemas and vanilla game data for the selected title.
1169 Valid game keys: pharaoh_dynasties, pharaoh, warhammer_3, troy, three_kingdoms,
1170 warhammer_2, warhammer, thrones_of_britannia, attila, rome_2, shogun_2,
1171 napoleon, empire, arena.
1172
1173Step 2: Verify schema is loaded
1174 Call: is_schema_loaded()
1175 If it returns false, call update_schemas() to download the latest schemas.
1176
1177Step 3: Open a PackFile
1178 Call: open_packfiles(paths: [\"/path/to/my_mod.pack\"])
1179 The response returns pack info including the pack_key you'll use for all
1180 subsequent operations.
1181
1182Step 4: Verify dependencies (optional but recommended)
1183 Call: is_there_a_dependency_database(value: true)
1184 If false, call generate_dependencies_cache() to build the dependency database.
1185
1186After initialization, use list_open_packs() to see all open pack keys at any time.
1187".to_string(),
1188
1189 "rpfm://reference/path_conventions" => "\
1190Total War PackFile Path Conventions
1191====================================
1192
1193Files inside PackFiles follow specific path conventions:
1194
1195DB Tables:
1196 db/<table_name>/<file_name>
1197 Example: db/land_units_tables/my_mod
1198 Example: db/unit_stats_land_tables/custom_units
1199
1200Localisation (Loc) files:
1201 text/db/<file_name>.loc
1202 text/<file_name>.loc
1203 Example: text/db/my_mod.loc
1204
1205Scripts:
1206 script/<path>.lua
1207 script/campaign/mod/<script_name>.lua
1208 Example: script/campaign/mod/my_mod_script.lua
1209 After editing a script, run `diagnostics_check`: it reports Lua syntax errors, invalid DB keys, unknown methods,
1210 wrong argument counts and unknown events (all but syntax errors need the game's Assembly Kit installed).
1211 To check what a script does, write tests for it and run them with `lua_run_tests`.
1212
1213UI Images:
1214 ui/<path>.png
1215 Path may vary depending on the purpose of the image.
1216
1217Models and Animations:
1218 variantmeshes/<path>
1219 animations/<path>
1220 Example: variantmeshes/wh_variantmodels/hu1/my_unit/my_unit.wsmodel
1221
1222Audio:
1223 audio/<path>.bnk
1224
1225Maps:
1226 terrain/tiles/battle/<map_name>/
1227".to_string(),
1228
1229 _ => {
1230 return Err(McpError {
1231 code: ErrorCode::INVALID_PARAMS,
1232 message: format!("Unknown resource URI: {uri}").into(),
1233 data: None,
1234 });
1235 }
1236 };
1237
1238 Ok(ReadResourceResult::new(vec![ResourceContents::text(content, uri.clone())]).into())
1239 }
1240
1241 async fn complete(
1246 &self,
1247 request: CompleteRequestParams,
1248 _context: RequestContext<RoleServer>,
1249 ) -> Result<CompleteResult, McpError> {
1250 let argument_name = &request.argument.name;
1251 let partial = &request.argument.value;
1252
1253 let candidates: Vec<String> = match argument_name.as_str() {
1254 "game_name" | "game_key" | "game" => {
1255 let games = vec![
1256 "pharaoh_dynasties", "pharaoh", "warhammer_3", "troy",
1257 "three_kingdoms", "warhammer_2", "warhammer",
1258 "thrones_of_britannia", "attila", "rome_2", "shogun_2",
1259 "napoleon", "empire", "arena",
1260 ];
1261 games.into_iter()
1262 .filter(|g| g.starts_with(partial))
1263 .map(String::from)
1264 .collect()
1265 },
1266 "pack_file_type" => {
1267 let types = vec!["\"Boot\"", "\"Release\"", "\"Patch\"", "\"Mod\"", "\"Movie\""];
1268 types.into_iter()
1269 .filter(|t| t.starts_with(partial))
1270 .map(String::from)
1271 .collect()
1272 },
1273 "format" => {
1274 let formats = vec![
1276 "\"None\"", "\"Lzma1\"", "\"Lz4\"", "\"Zstd\"",
1277 "\"CaVp8\"", "\"Ivf\"",
1278 ];
1279 formats.into_iter()
1280 .filter(|f| f.starts_with(partial))
1281 .map(String::from)
1282 .collect()
1283 },
1284 "source" => {
1285 let sources = vec![
1286 "\"PackFile\"", "\"GameFiles\"", "\"ParentFiles\"",
1287 "\"AssKitFiles\"", "\"ExternalFile\"",
1288 ];
1289 sources.into_iter()
1290 .filter(|s| s.starts_with(partial))
1291 .map(String::from)
1292 .collect()
1293 },
1294 _ => vec![],
1295 };
1296
1297 let total = candidates.len() as u32;
1298 let values: Vec<String> = candidates.into_iter().take(100).collect();
1299 let has_more = total > 100;
1300
1301 Ok(CompleteResult::new(CompletionInfo::with_pagination(
1302 values,
1303 Some(total),
1304 has_more,
1305 ).map_err(|e| McpError {
1306 code: ErrorCode::INTERNAL_ERROR,
1307 message: format!("Failed to build completion info: {e}").into(),
1308 data: None,
1309 })?))
1310 }
1311
1312}
1313
1314#[tool_router]
1315impl McpServer {
1316
1317 pub fn new(session: Arc<Session>) -> Self {
1318 Self {
1319 session,
1320 tool_router: McpServer::tool_router(),
1321 prompt_router: McpServer::prompt_router(),
1322 }
1323 }
1324
1325 #[tool(name = "call_command", description = "Call any IPC command directly. Use this for commands not yet wrapped as named tools.")]
1330 pub async fn call_command(&self, params: Parameters<CallCommandArgs>) -> Result<CallToolResult, McpError> {
1331 let command: Command = parse_json!(¶ms.0.command);
1332 send_and_respond!(self, "call_command", command)
1333 }
1334
1335 #[tool(description = "Create a new empty PackFile.")]
1340 pub async fn new_pack(&self) -> Result<CallToolResult, McpError> {
1341 send_and_respond!(self, "new_pack", Command::NewPack)
1342 }
1343
1344 #[tool(description = "Open one or more PackFiles. Returns the info about the open pack.")]
1345 pub async fn open_packfiles(&self, params: Parameters<OpenPackfilesArgs>) -> Result<CallToolResult, McpError> {
1346 send_and_respond!(self, "open_packfiles", Command::OpenPackFiles(params.0.paths))
1347 }
1348
1349 #[tool(description = "Save the pack identified by `pack_key`.")]
1350 pub async fn save_packfile(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1351 send_and_respond!(self, "save_packfile", Command::SavePack(params.0.pack_key))
1352 }
1353
1354 #[tool(description = "Close the pack identified by `pack_key` without saving. Any unsaved changes will be lost.")]
1355 pub async fn close_pack(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1356 send_and_respond!(self, "close_pack", Command::ClosePack(params.0.pack_key))
1357 }
1358
1359 #[tool(description = "Save the pack identified by `pack_key` to a new path.")]
1360 pub async fn save_pack_as(&self, params: Parameters<PackKeyPathArg>) -> Result<CallToolResult, McpError> {
1361 send_and_respond!(self, "save_pack_as", Command::SavePackAs(params.0.pack_key, params.0.path))
1362 }
1363
1364 #[tool(description = "Clean the pack identified by `pack_key` from corrupted files and save to a path. Use if normal save fails.")]
1365 pub async fn clean_and_save_pack_as(&self, params: Parameters<PackKeyPathArg>) -> Result<CallToolResult, McpError> {
1366 send_and_respond!(self, "clean_and_save_pack_as", Command::CleanAndSavePackAs(params.0.pack_key, params.0.path))
1367 }
1368
1369 #[tool(description = "Trigger a backup autosave for the pack identified by `pack_key`.")]
1370 pub async fn trigger_backup_autosave(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1371 send_and_respond!(self, "trigger_backup_autosave", Command::TriggerBackupAutosave(params.0.pack_key))
1372 }
1373
1374 #[tool(description = "Open all CA (vanilla) PackFiles for the selected game as one merged PackFile.")]
1375 pub async fn load_all_ca_pack_files(&self) -> Result<CallToolResult, McpError> {
1376 send_and_respond!(self, "load_all_ca_pack_files", Command::LoadAllCAPackFiles)
1377 }
1378
1379 #[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\\\"\"")]
1384 pub async fn set_pack_file_type(&self, params: Parameters<SetPackFileTypeArgs>) -> Result<CallToolResult, McpError> {
1385 let pfh_type = parse_json!(¶ms.0.pack_file_type);
1386 send_and_respond!(self, "set_pack_file_type", Command::SetPackFileType(params.0.pack_key, pfh_type))
1387 }
1388
1389 #[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\\\"\"")]
1390 pub async fn change_compression_format(&self, params: Parameters<ChangeCompressionFormatArgs>) -> Result<CallToolResult, McpError> {
1391 let format = parse_json!(¶ms.0.format);
1392 send_and_respond!(self, "change_compression_format", Command::ChangeCompressionFormat(params.0.pack_key, format))
1393 }
1394
1395 #[tool(description = "Change whether the pack index includes timestamps for the pack identified by `pack_key`.")]
1396 pub async fn change_index_includes_timestamp(&self, params: Parameters<PackKeyBoolArg>) -> Result<CallToolResult, McpError> {
1397 send_and_respond!(self, "change_index_includes_timestamp", Command::ChangeIndexIncludesTimestamp(params.0.pack_key, params.0.value))
1398 }
1399
1400 #[tool(description = "Change whether the pack index (file paths, sizes and timestamps) is encrypted for the pack identified by `pack_key`. Only PFH4 and newer packs support enabling it.")]
1401 pub async fn change_index_is_encrypted(&self, params: Parameters<PackKeyBoolArg>) -> Result<CallToolResult, McpError> {
1402 send_and_respond!(self, "change_index_is_encrypted", Command::ChangeIndexIsEncrypted(params.0.pack_key, params.0.value))
1403 }
1404
1405 #[tool(description = "Change whether the file data is encrypted for the pack identified by `pack_key`. Only PFH4 and newer packs support enabling it.")]
1406 pub async fn change_data_is_encrypted(&self, params: Parameters<PackKeyBoolArg>) -> Result<CallToolResult, McpError> {
1407 send_and_respond!(self, "change_data_is_encrypted", Command::ChangeDataIsEncrypted(params.0.pack_key, params.0.value))
1408 }
1409
1410 #[tool(description = "Get the file path of the pack identified by `pack_key`.")]
1411 pub async fn get_pack_file_path(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1412 send_and_respond!(self, "get_pack_file_path", Command::GetPackFilePath(params.0.pack_key))
1413 }
1414
1415 #[tool(description = "Get the file name of the pack identified by `pack_key`.")]
1416 pub async fn get_pack_file_name(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1417 send_and_respond!(self, "get_pack_file_name", Command::GetPackFileName(params.0.pack_key))
1418 }
1419
1420 #[tool(description = "Get the settings of the pack identified by `pack_key`.")]
1421 pub async fn get_pack_settings(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1422 send_and_respond!(self, "get_pack_settings", Command::GetPackSettings(params.0.pack_key))
1423 }
1424
1425 #[tool(description = "Set the settings of the pack identified by `pack_key`. The `settings` is a PackSettings JSON object containing pack-level configuration.")]
1426 pub async fn set_pack_settings(&self, params: Parameters<SetPackSettingsArgs>) -> Result<CallToolResult, McpError> {
1427 let settings = parse_json!(¶ms.0.settings);
1428 send_and_respond!(self, "set_pack_settings", Command::SetPackSettings(params.0.pack_key, settings))
1429 }
1430
1431 #[tool(description = "Get the list of PackFiles marked as dependencies of the pack identified by `pack_key`.")]
1432 pub async fn get_dependency_pack_files_list(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1433 send_and_respond!(self, "get_dependency_pack_files_list", Command::GetDependencyPackFilesList(params.0.pack_key))
1434 }
1435
1436 #[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\"]].")]
1437 pub async fn set_dependency_pack_files_list(&self, params: Parameters<SetDependencyPackFilesListArgs>) -> Result<CallToolResult, McpError> {
1438 let list = parse_json!(¶ms.0.list);
1439 send_and_respond!(self, "set_dependency_pack_files_list", Command::SetDependencyPackFilesList(params.0.pack_key, list))
1440 }
1441
1442 #[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).")]
1447 pub async fn decode_packed_file(&self, params: Parameters<DecodePackedFileArgs>) -> Result<CallToolResult, McpError> {
1448 send_and_respond!(self, "decode_packed_file", Command::DecodePackedFile(params.0.pack_key, params.0.path, params.0.source))
1449 }
1450
1451 #[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, []]}.")]
1452 pub async fn new_packed_file(&self, params: Parameters<NewPackedFileArgs>) -> Result<CallToolResult, McpError> {
1453 let new_file = parse_json!(¶ms.0.new_file);
1454 send_and_respond!(self, "new_packed_file", Command::NewPackedFile(params.0.pack_key, params.0.path, new_file))
1455 }
1456
1457 #[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.")]
1458 pub async fn add_packed_files(&self, params: Parameters<AddPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1459 let dest: Vec<ContainerPath> = parse_json!(¶ms.0.destination_paths);
1460 send_and_respond!(self, "add_packed_files", Command::AddPackedFiles(params.0.pack_key, params.0.source_paths, dest, params.0.ignore_paths))
1461 }
1462
1463 #[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\"}].")]
1464 pub async fn add_packed_files_from_pack_file(&self, params: Parameters<AddPackedFilesFromPackFileArgs>) -> Result<CallToolResult, McpError> {
1465 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1466 send_and_respond!(self, "add_packed_files_from_pack_file", Command::AddPackedFilesFromPackFile(params.0.pack_key, params.0.source_pack_path, paths))
1467 }
1468
1469 #[tool(description = "Copy files from the pack identified by `source_pack_key` into an AnimPack owned by `pack_key` (the two may differ). The `container_paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"animations/anim.anim\"}]. The `animpack_path` is the AnimPack's internal path.")]
1470 pub async fn add_packed_files_from_pack_file_to_animpack(&self, params: Parameters<AddPackedFilesFromPackFileToAnimpackArgs>) -> Result<CallToolResult, McpError> {
1471 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1472 send_and_respond!(self, "add_packed_files_from_pack_file_to_animpack", Command::AddPackedFilesFromPackFileToAnimpack(params.0.source_pack_key, params.0.pack_key, params.0.animpack_path, paths))
1473 }
1474
1475 #[tool(description = "Copy files from an AnimPack owned by `anim_pack_key` into the destination pack `pack_key` (the two may differ). The `source` is the DataSource (\"PackFile\", \"GameFiles\", etc.); `anim_pack_key` is only used when it is \"PackFile\". The `animpack_path` is the AnimPack's internal path. The `container_paths` is a JSON array of ContainerPath, e.g. [{\"File\": \"animations/anim.anim\"}].")]
1476 pub async fn add_packed_files_from_animpack(&self, params: Parameters<AddPackedFilesFromAnimpackArgs>) -> Result<CallToolResult, McpError> {
1477 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1478 send_and_respond!(self, "add_packed_files_from_animpack", Command::AddPackedFilesFromAnimpack(params.0.anim_pack_key, params.0.pack_key, params.0.source, params.0.animpack_path, paths))
1479 }
1480
1481 #[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\"}].")]
1482 pub async fn delete_packed_files(&self, params: Parameters<ContainerPathsArg>) -> Result<CallToolResult, McpError> {
1483 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1484 send_and_respond!(self, "delete_packed_files", Command::DeletePackedFiles(params.0.pack_key, paths))
1485 }
1486
1487 #[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\"}].")]
1488 pub async fn delete_from_animpack(&self, params: Parameters<DeleteFromAnimpackArgs>) -> Result<CallToolResult, McpError> {
1489 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.container_paths);
1490 send_and_respond!(self, "delete_from_animpack", Command::DeleteFromAnimpack(params.0.pack_key, params.0.animpack_path, paths))
1491 }
1492
1493 #[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.")]
1494 pub async fn extract_packed_files(&self, params: Parameters<ExtractPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1495 let source: BTreeMap<DataSource, Vec<ContainerPath>> = parse_json!(¶ms.0.source_paths);
1496 send_and_respond!(self, "extract_packed_files", Command::ExtractPackedFiles(params.0.pack_key, source, params.0.destination_path, params.0.export_as_tsv))
1497 }
1498
1499 #[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\"}]].")]
1500 pub async fn rename_packed_files(&self, params: Parameters<RenamePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1501 let renames: Vec<(ContainerPath, ContainerPath)> = parse_json!(¶ms.0.renames);
1502 send_and_respond!(self, "rename_packed_files", Command::RenamePackedFiles(params.0.pack_key, renames))
1503 }
1504
1505 #[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.")]
1506 pub async fn copy_packed_files(&self, params: Parameters<CopyOrCutPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1507 let paths_by_pack: BTreeMap<String, Vec<ContainerPath>> = parse_json!(¶ms.0.paths_by_pack);
1508 send_and_respond!(self, "copy_packed_files", Command::CopyPackedFiles(paths_by_pack))
1509 }
1510
1511 #[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.")]
1512 pub async fn cut_packed_files(&self, params: Parameters<CopyOrCutPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1513 let paths_by_pack: BTreeMap<String, Vec<ContainerPath>> = parse_json!(¶ms.0.paths_by_pack);
1514 send_and_respond!(self, "cut_packed_files", Command::CutPackedFiles(paths_by_pack))
1515 }
1516
1517 #[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.")]
1518 pub async fn paste_packed_files(&self, params: Parameters<PastePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1519 send_and_respond!(self, "paste_packed_files", Command::PastePackedFiles(params.0.pack_key, params.0.destination_path))
1520 }
1521
1522 #[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\"}].")]
1523 pub async fn duplicate_packed_files(&self, params: Parameters<DuplicatePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1524 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1525 send_and_respond!(self, "duplicate_packed_files", Command::DuplicatePackedFiles(params.0.pack_key, paths))
1526 }
1527
1528 #[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`).")]
1529 pub async fn save_packed_file_from_view(&self, params: Parameters<SavePackedFileFromViewArgs>) -> Result<CallToolResult, McpError> {
1530 let data: RFileDecoded = parse_json!(¶ms.0.data);
1531 send_and_respond!(self, "save_packed_file_from_view", Command::SavePackedFileFromView(params.0.pack_key, params.0.path, data))
1532 }
1533
1534 #[tool(description = "Save a file from an external program back to the pack identified by `pack_key`.")]
1535 pub async fn save_packed_file_from_external_view(&self, params: Parameters<SavePackedFileFromExternalViewArgs>) -> Result<CallToolResult, McpError> {
1536 send_and_respond!(self, "save_packed_file_from_external_view", Command::SavePackedFileFromExternalView(params.0.pack_key, params.0.internal_path, params.0.external_path))
1537 }
1538
1539 #[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.")]
1540 pub async fn save_packed_files_to_pack_file_and_clean(&self, params: Parameters<SavePackedFilesToPackFileAndCleanArgs>) -> Result<CallToolResult, McpError> {
1541 let files: Vec<RFile> = parse_json!(¶ms.0.files);
1542 send_and_respond!(self, "save_packed_files_to_pack_file_and_clean", Command::SavePackedFilesToPackFileAndClean(params.0.pack_key, files, params.0.optimize))
1543 }
1544
1545 #[tool(description = "Get the raw binary data of a file in the pack identified by `pack_key`.")]
1546 pub async fn get_packed_file_raw_data(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1547 send_and_respond!(self, "get_packed_file_raw_data", Command::GetPackedFileRawData(params.0.pack_key, params.0.value))
1548 }
1549
1550 #[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\"}.")]
1551 pub async fn open_packed_file_in_external_program(&self, params: Parameters<OpenPackedFileInExternalProgramArgs>) -> Result<CallToolResult, McpError> {
1552 let cp: ContainerPath = parse_json!(¶ms.0.container_path);
1553 send_and_respond!(self, "open_packed_file_in_external_program", Command::OpenPackedFileInExternalProgram(params.0.pack_key, params.0.source, cp))
1554 }
1555
1556 #[tool(description = "Open the folder containing the pack identified by `pack_key` in the file manager.")]
1557 pub async fn open_containing_folder(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1558 send_and_respond!(self, "open_containing_folder", Command::OpenContainingFolder(params.0.pack_key))
1559 }
1560
1561 #[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\"}].")]
1562 pub async fn clean_cache(&self, params: Parameters<ContainerPathsArg>) -> Result<CallToolResult, McpError> {
1563 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1564 send_and_respond!(self, "clean_cache", Command::CleanCache(params.0.pack_key, paths))
1565 }
1566
1567 #[tool(description = "Check if a folder exists in the pack identified by `pack_key`.")]
1568 pub async fn folder_exists(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1569 send_and_respond!(self, "folder_exists", Command::FolderExists(params.0.pack_key, params.0.value))
1570 }
1571
1572 #[tool(description = "Check if a file exists in the pack identified by `pack_key`.")]
1573 pub async fn packed_file_exists(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1574 send_and_respond!(self, "packed_file_exists", Command::PackedFileExists(params.0.pack_key, params.0.value))
1575 }
1576
1577 #[tool(description = "Get the info of one or more files in the pack identified by `pack_key`.")]
1578 pub async fn get_packed_files_info(&self, params: Parameters<PackKeyStringsArg>) -> Result<CallToolResult, McpError> {
1579 send_and_respond!(self, "get_packed_files_info", Command::GetPackedFilesInfo(params.0.pack_key, params.0.values))
1580 }
1581
1582 #[tool(description = "Get the info of a single file in the pack identified by `pack_key`.")]
1583 pub async fn get_rfile_info(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1584 send_and_respond!(self, "get_rfile_info", Command::GetRFileInfo(params.0.pack_key, params.0.value))
1585 }
1586
1587 #[tool(description = "Get the currently selected game key.")]
1592 pub async fn get_game_selected(&self) -> Result<CallToolResult, McpError> {
1593 send_and_respond!(self, "get_game_selected", Command::GetGameSelected)
1594 }
1595
1596 #[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.")]
1597 pub async fn set_game_selected(&self, params: Parameters<SetGameSelectedArgs>) -> Result<CallToolResult, McpError> {
1598 send_and_respond!(self, "set_game_selected", Command::SetGameSelected(params.0.game_name, params.0.rebuild_dependencies))
1599 }
1600
1601 #[tool(description = "Generate the dependencies cache for the selected game. This can take a long time (more than 30 seconds), depending on your CPU and disk read speed. If the client is not careful, it can take enough time that the client may trigger a timeout.")]
1606 pub async fn generate_dependencies_cache(&self) -> Result<CallToolResult, McpError> {
1607 send_and_respond!(self, "generate_dependencies_cache", Command::GenerateDependenciesCache)
1608 }
1609
1610 #[tool(description = "Rebuild dependencies. Pass true for full rebuild, false for mod-specific only.")]
1611 pub async fn rebuild_dependencies(&self, params: Parameters<BoolArg>) -> Result<CallToolResult, McpError> {
1612 send_and_respond!(self, "rebuild_dependencies", Command::RebuildDependencies(params.0.value))
1613 }
1614
1615 #[tool(description = "Check if there is a dependency database loaded. Pass true to ensure AssKit data is included.")]
1616 pub async fn is_there_a_dependency_database(&self, params: Parameters<BoolArg>) -> Result<CallToolResult, McpError> {
1617 send_and_respond!(self, "is_there_a_dependency_database", Command::IsThereADependencyDatabase(params.0.value))
1618 }
1619
1620 #[tool(description = "Get the table names of all DB files in dependency PackFiles.")]
1621 pub async fn get_table_list_from_dependency_pack_file(&self) -> Result<CallToolResult, McpError> {
1622 send_and_respond!(self, "get_table_list_from_dependency_pack_file", Command::GetTableListFromDependencyPackFile)
1623 }
1624
1625 #[tool(description = "Get custom table names (start_pos_, twad_ prefixes) from the schema.")]
1626 pub async fn get_custom_table_list(&self) -> Result<CallToolResult, McpError> {
1627 send_and_respond!(self, "get_custom_table_list", Command::GetCustomTableList)
1628 }
1629
1630 #[tool(description = "Get the version of a table from the dependency database.")]
1631 pub async fn get_table_version_from_dependency_pack_file(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1632 send_and_respond!(self, "get_table_version_from_dependency_pack_file", Command::GetTableVersionFromDependencyPackFile(params.0.value))
1633 }
1634
1635 #[tool(description = "Get the definition of a table from the dependency database. NOTE: the returned `fields` list is the raw on-disk field layout, not what row data looks like (e.g. colour columns are split into separate r/g/b fields here). Pass the definition to `fields_processed` to get the field list/count that rows must actually match.")]
1636 pub async fn get_table_definition_from_dependency_pack_file(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1637 send_and_respond!(self, "get_table_definition_from_dependency_pack_file", Command::GetTableDefinitionFromDependencyPackFile(params.0.value))
1638 }
1639
1640 #[tool(description = "Get table data from dependencies by table name. NOTE: each returned file's decoded `data` rows are shaped per the PROCESSED fields (colour groups merged, bitwise/enum expanded), but the `definition.fields` bundled in the same file is the RAW on-disk layout and will have a different length/order — do not zip row cells against `definition.fields`. Call `fields_processed` on that definition to get the field list that actually lines up with `data`.")]
1641 pub async fn get_tables_from_dependencies(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1642 send_and_respond!(self, "get_tables_from_dependencies", Command::GetTablesFromDependencies(params.0.value))
1643 }
1644
1645 #[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\"}]}.")]
1646 pub async fn import_dependencies_to_open_pack_file(&self, params: Parameters<ImportDependenciesArgs>) -> Result<CallToolResult, McpError> {
1647 let paths: BTreeMap<DataSource, Vec<ContainerPath>> = parse_json!(¶ms.0.paths);
1648 send_and_respond!(self, "import_dependencies_to_open_pack_file", Command::ImportDependenciesToOpenPackFile(params.0.pack_key, paths))
1649 }
1650
1651 #[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.")]
1652 pub async fn get_rfiles_from_all_sources(&self, params: Parameters<GetRFilesFromAllSourcesArgs>) -> Result<CallToolResult, McpError> {
1653 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1654 send_and_respond!(self, "get_rfiles_from_all_sources", Command::GetRFilesFromAllSources(paths, params.0.lowercase))
1655 }
1656
1657 #[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.")]
1658 pub async fn get_packed_files_names_starting_with_path_from_all_sources(&self, params: Parameters<ContainerPathArg>) -> Result<CallToolResult, McpError> {
1659 let path: ContainerPath = parse_json!(¶ms.0.path);
1660 send_and_respond!(self, "get_packed_files_names_starting_with_path_from_all_sources", Command::GetPackedFilesNamesStartingWitPathFromAllSources(path))
1661 }
1662
1663 #[tool(description = "Get local art set IDs from campaign_character_arts_tables in the pack identified by `pack_key`.")]
1664 pub async fn local_art_set_ids(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1665 send_and_respond!(self, "local_art_set_ids", Command::LocalArtSetIds(params.0.pack_key))
1666 }
1667
1668 #[tool(description = "Get art set IDs from dependencies' campaign_character_arts_tables.")]
1669 pub async fn dependencies_art_set_ids(&self) -> Result<CallToolResult, McpError> {
1670 send_and_respond!(self, "dependencies_art_set_ids", Command::DependenciesArtSetIds)
1671 }
1672
1673 #[tool(description = "Get the distinct values of the column `column_name` of the DB table `table_name` (like `factions_tables`), from the open packs, their parent packs and vanilla.")]
1674 pub async fn dependencies_column_values(&self, params: Parameters<TableColumnArgs>) -> Result<CallToolResult, McpError> {
1675 send_and_respond!(self, "dependencies_column_values", Command::DependenciesColumnValues(params.0.table_name, params.0.column_name))
1676 }
1677
1678 #[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.")]
1683 pub async fn global_search(&self, params: Parameters<GlobalSearchArgs>) -> Result<CallToolResult, McpError> {
1684 let search = parse_json!(¶ms.0.search);
1685 send_and_respond!(self, "global_search", Command::GlobalSearch(params.0.pack_key, search))
1686 }
1687
1688 #[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.")]
1689 pub async fn global_search_replace_matches(&self, params: Parameters<GlobalSearchReplaceMatchesArgs>) -> Result<CallToolResult, McpError> {
1690 let search = parse_json!(¶ms.0.search);
1691 let matches = parse_json!(¶ms.0.matches);
1692 send_and_respond!(self, "global_search_replace_matches", Command::GlobalSearchReplaceMatches(params.0.pack_key, search, matches))
1693 }
1694
1695 #[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.")]
1696 pub async fn global_search_replace_all(&self, params: Parameters<GlobalSearchArgs>) -> Result<CallToolResult, McpError> {
1697 let search = parse_json!(¶ms.0.search);
1698 send_and_respond!(self, "global_search_replace_all", Command::GlobalSearchReplaceAll(params.0.pack_key, search))
1699 }
1700
1701 #[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.")]
1702 pub async fn search_references(&self, params: Parameters<SearchReferencesArgs>) -> Result<CallToolResult, McpError> {
1703 let map: HashMap<String, Vec<String>> = parse_json!(¶ms.0.reference_map);
1704 send_and_respond!(self, "search_references", Command::SearchReferences(params.0.pack_key, map, params.0.value))
1705 }
1706
1707 #[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.")]
1708 pub async fn get_reference_data_from_definition(&self, params: Parameters<GetReferenceDataFromDefinitionArgs>) -> Result<CallToolResult, McpError> {
1709 let def = parse_json!(¶ms.0.definition);
1710 send_and_respond!(self, "get_reference_data_from_definition", Command::GetReferenceDataFromDefinition(params.0.pack_key, params.0.table_name, def, params.0.force))
1711 }
1712
1713 #[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.")]
1714 pub async fn go_to_definition(&self, params: Parameters<GoToDefinitionArgs>) -> Result<CallToolResult, McpError> {
1715 send_and_respond!(self, "go_to_definition", Command::GoToDefinition(params.0.pack_key, params.0.table_name, params.0.column_name, params.0.values))
1716 }
1717
1718 #[tool(description = "Go to a loc key's location in the pack identified by `pack_key`.")]
1719 pub async fn go_to_loc(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1720 send_and_respond!(self, "go_to_loc", Command::GoToLoc(params.0.pack_key, params.0.value))
1721 }
1722
1723 #[tool(description = "Get the source data of a loc key in the pack identified by `pack_key`.")]
1724 pub async fn get_source_data_from_loc_key(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1725 send_and_respond!(self, "get_source_data_from_loc_key", Command::GetSourceDataFromLocKey(params.0.pack_key, params.0.value))
1726 }
1727
1728 #[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.")]
1733 pub async fn save_schema(&self, params: Parameters<SaveSchemaArgs>) -> Result<CallToolResult, McpError> {
1734 let schema = parse_json!(¶ms.0.schema);
1735 send_and_respond!(self, "save_schema", Command::SaveSchema(schema))
1736 }
1737
1738 #[tool(description = "Update the currently loaded schema with data from the game's Assembly Kit.")]
1739 pub async fn update_current_schema_from_asskit(&self) -> Result<CallToolResult, McpError> {
1740 send_and_respond!(self, "update_current_schema_from_asskit", Command::UpdateCurrentSchemaFromAssKit)
1741 }
1742
1743 #[tool(description = "Update schemas from the remote repository.")]
1744 pub async fn update_schemas(&self) -> Result<CallToolResult, McpError> {
1745 send_and_respond!(self, "update_schemas", Command::UpdateSchemas)
1746 }
1747
1748 #[tool(description = "Check if a schema is currently loaded.")]
1749 pub async fn is_schema_loaded(&self) -> Result<CallToolResult, McpError> {
1750 send_and_respond!(self, "is_schema_loaded", Command::IsSchemaLoaded)
1751 }
1752
1753 #[tool(description = "Get the current schema.")]
1754 pub async fn get_schema(&self) -> Result<CallToolResult, McpError> {
1755 send_and_respond!(self, "get_schema", Command::Schema)
1756 }
1757
1758 #[tool(description = "Get all definitions for a table name. NOTE: the returned `fields` list is the raw on-disk field layout, not what row data looks like (e.g. colour columns are split into separate r/g/b fields here). Pass the definition to `fields_processed` to get the field list/count that rows must actually match.")]
1759 pub async fn definitions_by_table_name(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1760 send_and_respond!(self, "definitions_by_table_name", Command::DefinitionsByTableName(params.0.value))
1761 }
1762
1763 #[tool(description = "Get a specific definition by table name and version. NOTE: the returned `fields` list is the raw on-disk field layout, not what row data looks like (e.g. colour columns are split into separate r/g/b fields here). Do not use `fields.len()` to size a row for saving — pass this definition to `fields_processed` first to get the field list/count and types that rows must actually match.")]
1764 pub async fn definition_by_table_name_and_version(&self, params: Parameters<StringI32Args>) -> Result<CallToolResult, McpError> {
1765 send_and_respond!(self, "definition_by_table_name_and_version", Command::DefinitionByTableNameAndVersion(params.0.name, params.0.version))
1766 }
1767
1768 #[tool(description = "Delete a definition by table name and version.")]
1769 pub async fn delete_definition(&self, params: Parameters<StringI32Args>) -> Result<CallToolResult, McpError> {
1770 send_and_respond!(self, "delete_definition", Command::DeleteDefinition(params.0.name, params.0.version))
1771 }
1772
1773 #[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`).")]
1774 pub async fn referencing_columns_for_definition(&self, params: Parameters<ReferencingColumnsForDefinitionArgs>) -> Result<CallToolResult, McpError> {
1775 let def = parse_json!(¶ms.0.definition);
1776 send_and_respond!(self, "referencing_columns_for_definition", Command::ReferencingColumnsForDefinition(params.0.table_name, def))
1777 }
1778
1779 #[tool(description = "Get the processed fields from a definition, with bitwise expansion, enum conversions, and colour-group merging applied. Call this before building or validating row data: table rows must have exactly as many entries as `fields_processed` returns, NOT as many as the raw `fields` list on the Definition (e.g. `definition_by_table_name_and_version`/`definitions_by_table_name` return raw fields, where a colour split into r/g/b counts as 3 fields instead of the 1 merged field rows actually use). Saving a row built against the raw field count/types will fail. The `definition` is a Definition JSON (as returned by `get_table_definition_from_dependency_pack_file`).")]
1780 pub async fn fields_processed(&self, params: Parameters<DefinitionArg>) -> Result<CallToolResult, McpError> {
1781 let def = parse_json!(¶ms.0.definition);
1782 send_and_respond!(self, "fields_processed", Command::FieldsProcessed(def))
1783 }
1784
1785 #[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\": {...}}}.")]
1786 pub async fn save_local_schema_patch(&self, params: Parameters<SchemaPatchArgs>) -> Result<CallToolResult, McpError> {
1787 let patches = parse_json!(¶ms.0.patches);
1788 send_and_respond!(self, "save_local_schema_patch", Command::SaveLocalSchemaPatch(patches))
1789 }
1790
1791 #[tool(description = "Remove local schema patches for a table.")]
1792 pub async fn remove_local_schema_patches_for_table(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1793 send_and_respond!(self, "remove_local_schema_patches_for_table", Command::RemoveLocalSchemaPatchesForTable(params.0.value))
1794 }
1795
1796 #[tool(description = "Remove local schema patches for a specific field in a table.")]
1797 pub async fn remove_local_schema_patches_for_table_and_field(&self, params: Parameters<SettingsSetStringArgs>) -> Result<CallToolResult, McpError> {
1798 send_and_respond!(self, "remove_local_schema_patches_for_table_and_field", Command::RemoveLocalSchemaPatchesForTableAndField(params.0.key, params.0.value))
1799 }
1800
1801 #[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`).")]
1802 pub async fn import_schema_patch(&self, params: Parameters<SchemaPatchArgs>) -> Result<CallToolResult, McpError> {
1803 let patches = parse_json!(¶ms.0.patches);
1804 send_and_respond!(self, "import_schema_patch", Command::ImportSchemaPatch(patches))
1805 }
1806
1807 #[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. DB Tables can also be enabled for Delta Merging (if two or more tables edit the same row, it merges their changes into a single row).")]
1812 pub async fn merge_files(&self, params: Parameters<MergeFilesArgs>) -> Result<CallToolResult, McpError> {
1813 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1814 let mut options = MergeOptions::default();
1815 options.set_delta_merge(params.0.delta_merge);
1816 send_and_respond!(self, "merge_files", Command::MergeFiles(params.0.pack_key, paths, params.0.merged_path, params.0.delete_source, options))
1817 }
1818
1819 #[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\"}.")]
1820 pub async fn update_table(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1821 let path: ContainerPath = parse_json!(¶ms.0.value);
1822 send_and_respond!(self, "update_table", Command::UpdateTable(params.0.pack_key, path))
1823 }
1824
1825 #[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\"]].")]
1826 pub async fn cascade_edition(&self, params: Parameters<CascadeEditionArgs>) -> Result<CallToolResult, McpError> {
1827 let def = parse_json!(¶ms.0.definition);
1828 let changes = parse_json!(¶ms.0.changes);
1829 send_and_respond!(self, "cascade_edition", Command::CascadeEdition(params.0.pack_key, params.0.table_name, def, changes))
1830 }
1831
1832 #[tool(description = "Get table paths by table name from the pack identified by `pack_key`.")]
1833 pub async fn get_tables_by_table_name(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1834 send_and_respond!(self, "get_tables_by_table_name", Command::GetTablesByTableName(params.0.pack_key, params.0.value))
1835 }
1836
1837 #[tool(description = "Add keys to the key_deletes table in the pack identified by `pack_key`.")]
1838 pub async fn add_keys_to_key_deletes(&self, params: Parameters<AddKeysToKeyDeletesArgs>) -> Result<CallToolResult, McpError> {
1839 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))
1840 }
1841
1842 #[tool(description = "Export a table from the pack identified by `pack_key` to a TSV file.")]
1843 pub async fn export_tsv(&self, params: Parameters<TsvExportArgs>) -> Result<CallToolResult, McpError> {
1844 send_and_respond!(self, "export_tsv", Command::ExportTSV(params.0.pack_key, params.0.table_path, params.0.tsv_path, DataSource::PackFile))
1845 }
1846
1847 #[tool(description = "Import a TSV file to a table in the pack identified by `pack_key`.")]
1848 pub async fn import_tsv(&self, params: Parameters<TsvImportArgs>) -> Result<CallToolResult, McpError> {
1849 send_and_respond!(self, "import_tsv", Command::ImportTSV(params.0.pack_key, params.0.table_path, params.0.tsv_path))
1850 }
1851
1852 #[tool(description = "Run a full diagnostics check over all open packs.")]
1857 pub async fn diagnostics_check(&self, params: Parameters<DiagnosticsCheckArgs>) -> Result<CallToolResult, McpError> {
1858 send_and_respond!(self, "diagnostics_check", Command::DiagnosticsCheck(params.0.ignored, params.0.check_ak_only_refs))
1859 }
1860
1861 #[tool(description = "Run Lua tests against the scripts of all open packs, outside of the game. Scripts run in Lua 5.1 with the game's real script libraries; the game's engine is emulated with objects typed after the Assembly Kit's scripting docs, which record every call made on them. Needs the game's Assembly Kit.
1862
1863Test file API (a global `rpfm` table):
1864- Top-level code runs before the game boots, to set up the world: `local kislev = rpfm.faction { key = \"wh3_main_ksl_kislev\", is_human = true }`. Fields other than `key` are the values returned by the methods with the same name; lists like `region_list` can be plain arrays. Also `rpfm.region { key = ... }`, `rpfm.character { faction = kislev, ... }`, and `rpfm.object(\"TYPE_SCRIPT_INTERFACE\", methods)`. Factions and regions from the game's DB exist even if not set up.
1865- `rpfm.test(name, function)` registers a test. Each test runs in a fresh Lua state, after the libraries, the pack's mods (script/campaign/mod/) and the first tick have run.
1866- `rpfm.fire(event, { accessor = value, ... })` triggers an event, like `rpfm.fire(\"FactionTurnStart\", { faction = kislev })`.
1867- `rpfm.advance_time(seconds)` advances game time, triggering due time triggers, like the ones from `cm:callback`. `rpfm.end_turn()` plays a full round: `WorldStartRound`, `FactionRoundStart` for every faction, then for each faction in creation order its `FactionTurnStart`, the turn events of the regions and characters in its `region_list` and `character_list`, `FactionBeginTurnPhaseNormal`, `FactionAboutToEndTurn` and `FactionTurnEnd`.
1868- `rpfm.mock(object, method, value)` changes what a method of an engine object returns after boot, like `rpfm.mock(region, \"owning_faction\", kislev)`; `value` can be a function receiving the call's arguments. Scripts' own globals (like a mod's manager table) can be inspected and changed directly from tests.
1869- `rpfm.assert_called(method, args...)`, `rpfm.assert_not_called(method)`, `rpfm.calls_to(method)` and `rpfm.assert_equal(actual, expected)` check what the scripts did. Calls on `cm` are recorded by their method name, like `treasury_mod`.
1870
1871The report lists each test with its errors (including errors of the pack's scripts, and in listeners), the undocumented methods it called (whose results are placeholders), and the scripts' output.")]
1872 pub async fn lua_run_tests(&self, params: Parameters<LuaRunTestsArgs>) -> Result<CallToolResult, McpError> {
1873 send_and_respond!(self, "lua_run_tests", Command::LuaRunTests(params.0.test_source, params.0.campaign))
1874 }
1875
1876 #[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\"}].")]
1877 pub async fn diagnostics_update(&self, params: Parameters<DiagnosticsUpdateArgs>) -> Result<CallToolResult, McpError> {
1878 let diag = parse_json!(¶ms.0.diagnostics);
1879 let paths: Vec<ContainerPath> = parse_json!(¶ms.0.paths);
1880 send_and_respond!(self, "diagnostics_update", Command::DiagnosticsUpdate(diag, paths, params.0.check_ak_only_refs))
1881 }
1882
1883 #[tool(description = "Add a line to the ignored diagnostics list for the pack identified by `pack_key`.")]
1884 pub async fn add_line_to_pack_ignored_diagnostics(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1885 send_and_respond!(self, "add_line_to_pack_ignored_diagnostics", Command::AddLineToPackIgnoredDiagnostics(params.0.pack_key, params.0.value))
1886 }
1887
1888 #[tool(description = "Export missing table definitions for the pack identified by `pack_key` to a file (for debugging).")]
1889 pub async fn get_missing_definitions(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1890 send_and_respond!(self, "get_missing_definitions", Command::GetMissingDefinitions(params.0.pack_key))
1891 }
1892
1893 #[tool(description = "Get all notes under a path in the pack identified by `pack_key`.")]
1898 pub async fn notes_for_path(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1899 send_and_respond!(self, "notes_for_path", Command::NotesForPath(params.0.pack_key, params.0.value))
1900 }
1901
1902 #[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).")]
1903 pub async fn add_note(&self, params: Parameters<AddNoteArgs>) -> Result<CallToolResult, McpError> {
1904 let note = parse_json!(¶ms.0.note);
1905 send_and_respond!(self, "add_note", Command::AddNote(params.0.pack_key, note))
1906 }
1907
1908 #[tool(description = "Delete a note by path and ID in the pack identified by `pack_key`.")]
1909 pub async fn delete_note(&self, params: Parameters<DeleteNoteArgs>) -> Result<CallToolResult, McpError> {
1910 send_and_respond!(self, "delete_note", Command::DeleteNote(params.0.pack_key, params.0.path, params.0.id))
1911 }
1912
1913 #[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.")]
1918 pub async fn optimize_pack_file(&self, params: Parameters<OptimizePackFileArgs>) -> Result<CallToolResult, McpError> {
1919 let options = parse_json!(¶ms.0.options);
1920 send_and_respond!(self, "optimize_pack_file", Command::OptimizePackFile(params.0.pack_key, options))
1921 }
1922
1923 #[tool(description = "Get the default optimizer options.")]
1924 pub async fn get_optimizer_options(&self) -> Result<CallToolResult, McpError> {
1925 send_and_respond!(self, "get_optimizer_options", Command::OptimizerOptions)
1926 }
1927
1928 #[tool(description = "Check if there is an RPFM update available.")]
1933 pub async fn check_updates(&self) -> Result<CallToolResult, McpError> {
1934 send_and_respond!(self, "check_updates", Command::CheckUpdates)
1935 }
1936
1937 #[tool(description = "Check if there is a schema update available.")]
1938 pub async fn check_schema_updates(&self) -> Result<CallToolResult, McpError> {
1939 send_and_respond!(self, "check_schema_updates", Command::CheckSchemaUpdates)
1940 }
1941
1942 #[tool(description = "Check for Lua autogen updates.")]
1943 pub async fn check_lua_autogen_updates(&self) -> Result<CallToolResult, McpError> {
1944 send_and_respond!(self, "check_lua_autogen_updates", Command::CheckLuaAutogenUpdates)
1945 }
1946
1947 #[tool(description = "Check for Empire/Napoleon Assembly Kit updates.")]
1948 pub async fn check_empire_and_napoleon_ak_updates(&self) -> Result<CallToolResult, McpError> {
1949 send_and_respond!(self, "check_empire_and_napoleon_ak_updates", Command::CheckEmpireAndNapoleonAKUpdates)
1950 }
1951
1952 #[tool(description = "Check for translation updates.")]
1953 pub async fn check_translations_updates(&self) -> Result<CallToolResult, McpError> {
1954 send_and_respond!(self, "check_translations_updates", Command::CheckTranslationsUpdates)
1955 }
1956
1957 #[tool(description = "Update the Lua autogen repository.")]
1958 pub async fn update_lua_autogen(&self) -> Result<CallToolResult, McpError> {
1959 send_and_respond!(self, "update_lua_autogen", Command::UpdateLuaAutogen)
1960 }
1961
1962 #[tool(description = "Update the program to the latest version.")]
1963 pub async fn update_main_program(&self) -> Result<CallToolResult, McpError> {
1964 send_and_respond!(self, "update_main_program", Command::UpdateMainProgram)
1965 }
1966
1967 #[tool(description = "Update the Empire/Napoleon Assembly Kit files.")]
1968 pub async fn update_empire_and_napoleon_ak(&self) -> Result<CallToolResult, McpError> {
1969 send_and_respond!(self, "update_empire_and_napoleon_ak", Command::UpdateEmpireAndNapoleonAK)
1970 }
1971
1972 #[tool(description = "Update the translations repository.")]
1973 pub async fn update_translations(&self) -> Result<CallToolResult, McpError> {
1974 send_and_respond!(self, "update_translations", Command::UpdateTranslations)
1975 }
1976
1977 #[tool(description = "Get a boolean setting value by key.")]
1982 pub async fn settings_get_bool(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1983 send_and_respond!(self, "settings_get_bool", Command::SettingsGetBool(params.0.value))
1984 }
1985
1986 #[tool(description = "Get an i32 setting value by key.")]
1987 pub async fn settings_get_i32(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1988 send_and_respond!(self, "settings_get_i32", Command::SettingsGetI32(params.0.value))
1989 }
1990
1991 #[tool(description = "Get an f32 setting value by key.")]
1992 pub async fn settings_get_f32(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1993 send_and_respond!(self, "settings_get_f32", Command::SettingsGetF32(params.0.value))
1994 }
1995
1996 #[tool(description = "Get a string setting value by key.")]
1997 pub async fn settings_get_string(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1998 send_and_respond!(self, "settings_get_string", Command::SettingsGetString(params.0.value))
1999 }
2000
2001 #[tool(description = "Get a PathBuf setting value by key.")]
2002 pub async fn settings_get_path_buf(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
2003 send_and_respond!(self, "settings_get_path_buf", Command::SettingsGetPathBuf(params.0.value))
2004 }
2005
2006 #[tool(description = "Get a Vec<String> setting value by key.")]
2007 pub async fn settings_get_vec_string(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
2008 send_and_respond!(self, "settings_get_vec_string", Command::SettingsGetVecString(params.0.value))
2009 }
2010
2011 #[tool(description = "Get a raw bytes setting value by key.")]
2012 pub async fn settings_get_vec_raw(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
2013 send_and_respond!(self, "settings_get_vec_raw", Command::SettingsGetVecRaw(params.0.value))
2014 }
2015
2016 #[tool(description = "Get all settings at once (bool, i32, f32, string, raw_data, and vec_string maps).")]
2017 pub async fn settings_get_all(&self) -> Result<CallToolResult, McpError> {
2018 send_and_respond!(self, "settings_get_all", Command::SettingsGetAll)
2019 }
2020
2021 #[tool(description = "Set a boolean setting value.")]
2026 pub async fn settings_set_bool(&self, params: Parameters<SettingsSetBoolArgs>) -> Result<CallToolResult, McpError> {
2027 send_and_respond!(self, "settings_set_bool", Command::SettingsSetBool(params.0.key, params.0.value))
2028 }
2029
2030 #[tool(description = "Set an i32 setting value.")]
2031 pub async fn settings_set_i32(&self, params: Parameters<SettingsSetI32Args>) -> Result<CallToolResult, McpError> {
2032 send_and_respond!(self, "settings_set_i32", Command::SettingsSetI32(params.0.key, params.0.value))
2033 }
2034
2035 #[tool(description = "Set an f32 setting value.")]
2036 pub async fn settings_set_f32(&self, params: Parameters<SettingsSetF32Args>) -> Result<CallToolResult, McpError> {
2037 send_and_respond!(self, "settings_set_f32", Command::SettingsSetF32(params.0.key, params.0.value))
2038 }
2039
2040 #[tool(description = "Set a string setting value.")]
2041 pub async fn settings_set_string(&self, params: Parameters<SettingsSetStringArgs>) -> Result<CallToolResult, McpError> {
2042 send_and_respond!(self, "settings_set_string", Command::SettingsSetString(params.0.key, params.0.value))
2043 }
2044
2045 #[tool(description = "Set a PathBuf setting value.")]
2046 pub async fn settings_set_path_buf(&self, params: Parameters<SettingsSetPathBufArgs>) -> Result<CallToolResult, McpError> {
2047 send_and_respond!(self, "settings_set_path_buf", Command::SettingsSetPathBuf(params.0.key, params.0.value))
2048 }
2049
2050 #[tool(description = "Set a Vec<String> setting value.")]
2051 pub async fn settings_set_vec_string(&self, params: Parameters<SettingsSetVecStringArgs>) -> Result<CallToolResult, McpError> {
2052 send_and_respond!(self, "settings_set_vec_string", Command::SettingsSetVecString(params.0.key, params.0.value))
2053 }
2054
2055 #[tool(description = "Set a raw bytes setting value.")]
2056 pub async fn settings_set_vec_raw(&self, params: Parameters<SettingsSetVecRawArgs>) -> Result<CallToolResult, McpError> {
2057 send_and_respond!(self, "settings_set_vec_raw", Command::SettingsSetVecRaw(params.0.key, params.0.value))
2058 }
2059
2060 #[tool(description = "Backup the current settings to memory.")]
2061 pub async fn backup_settings(&self) -> Result<CallToolResult, McpError> {
2062 send_and_respond!(self, "backup_settings", Command::BackupSettings)
2063 }
2064
2065 #[tool(description = "Clear all settings and reset to defaults.")]
2066 pub async fn clear_settings(&self) -> Result<CallToolResult, McpError> {
2067 send_and_respond!(self, "clear_settings", Command::ClearSettings)
2068 }
2069
2070 #[tool(description = "Restore settings from the backup.")]
2071 pub async fn restore_backup_settings(&self) -> Result<CallToolResult, McpError> {
2072 send_and_respond!(self, "restore_backup_settings", Command::RestoreBackupSettings)
2073 }
2074
2075 #[tool(description = "Get the config path.")]
2080 pub async fn config_path(&self) -> Result<CallToolResult, McpError> {
2081 send_and_respond!(self, "config_path", Command::ConfigPath)
2082 }
2083
2084 #[tool(description = "Get the Assembly Kit path for the current game.")]
2085 pub async fn assembly_kit_path(&self) -> Result<CallToolResult, McpError> {
2086 send_and_respond!(self, "assembly_kit_path", Command::AssemblyKitPath)
2087 }
2088
2089 #[tool(description = "Get the backup autosave path.")]
2090 pub async fn backup_autosave_path(&self) -> Result<CallToolResult, McpError> {
2091 send_and_respond!(self, "backup_autosave_path", Command::BackupAutosavePath)
2092 }
2093
2094 #[tool(description = "Get the old Assembly Kit data path.")]
2095 pub async fn old_ak_data_path(&self) -> Result<CallToolResult, McpError> {
2096 send_and_respond!(self, "old_ak_data_path", Command::OldAkDataPath)
2097 }
2098
2099 #[tool(description = "Get the schemas path.")]
2100 pub async fn schemas_path(&self) -> Result<CallToolResult, McpError> {
2101 send_and_respond!(self, "schemas_path", Command::SchemasPath)
2102 }
2103
2104 #[tool(description = "Get the table profiles path.")]
2105 pub async fn table_profiles_path(&self) -> Result<CallToolResult, McpError> {
2106 send_and_respond!(self, "table_profiles_path", Command::TableProfilesPath)
2107 }
2108
2109 #[tool(description = "Get the translations local path.")]
2110 pub async fn translations_local_path(&self) -> Result<CallToolResult, McpError> {
2111 send_and_respond!(self, "translations_local_path", Command::TranslationsLocalPath)
2112 }
2113
2114 #[tool(description = "Get the dependencies cache path.")]
2115 pub async fn dependencies_cache_path(&self) -> Result<CallToolResult, McpError> {
2116 send_and_respond!(self, "dependencies_cache_path", Command::DependenciesCachePath)
2117 }
2118
2119 #[tool(description = "Clear a config path.")]
2120 pub async fn settings_clear_path(&self, params: Parameters<PathArg>) -> Result<CallToolResult, McpError> {
2121 send_and_respond!(self, "settings_clear_path", Command::SettingsClearPath(params.0.path))
2122 }
2123
2124 #[tool(description = "Get the info about the pack identified by `pack_key` and the list of files it contains.")]
2129 pub async fn open_pack_info(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2130 send_and_respond!(self, "open_pack_info", Command::GetPackFileDataForTreeView(params.0.pack_key))
2131 }
2132
2133 #[tool(description = "Initialize a MyMod folder for mod development.")]
2134 pub async fn initialize_my_mod_folder(&self, params: Parameters<InitializeMyModFolderArgs>) -> Result<CallToolResult, McpError> {
2135 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))
2136 }
2137
2138 #[tool(description = "Live export the pack identified by `pack_key` to the game folder for testing.")]
2139 pub async fn live_export(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2140 send_and_respond!(self, "live_export", Command::LiveExport(params.0.pack_key))
2141 }
2142
2143 #[tool(description = "Patch the SiegeAI of a Siege Map in the pack identified by `pack_key` for Warhammer games.")]
2144 pub async fn patch_siege_ai(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2145 send_and_respond!(self, "patch_siege_ai", Command::PatchSiegeAI(params.0.pack_key))
2146 }
2147
2148 #[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\"]].")]
2149 pub async fn pack_map(&self, params: Parameters<PackMapArgs>) -> Result<CallToolResult, McpError> {
2150 let tiles: Vec<(PathBuf, String)> = parse_json!(¶ms.0.tiles);
2151 send_and_respond!(self, "pack_map", Command::PackMap(params.0.pack_key, params.0.tile_maps, tiles))
2152 }
2153
2154 #[tool(description = "Generate all missing loc entries for the pack identified by `pack_key`.")]
2155 pub async fn generate_missing_loc_data(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2156 send_and_respond!(self, "generate_missing_loc_data", Command::GenerateMissingLocData(params.0.pack_key))
2157 }
2158
2159 #[tool(description = "Get pack translation data for a language from the pack identified by `pack_key`.")]
2160 pub async fn get_pack_translation(&self, params: Parameters<GetPackTranslationArgs>) -> Result<CallToolResult, McpError> {
2161 send_and_respond!(self, "get_pack_translation", Command::GetPackTranslation(params.0.pack_key, params.0.src_lang, params.0.language))
2162 }
2163
2164 #[tool(description = "Generate the vanilla texts of a source language from the game's locale packs. Returns whether vanilla texts for that language are available.")]
2165 pub async fn generate_vanilla_translation_source(&self, params: Parameters<SrcLangArg>) -> Result<CallToolResult, McpError> {
2166 send_and_respond!(self, "generate_vanilla_translation_source", Command::GenerateVanillaTranslationSource(params.0.src_lang))
2167 }
2168
2169 #[tool(description = "Get campaign IDs for starpos building in the pack identified by `pack_key`.")]
2170 pub async fn build_starpos_get_campaign_ids(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2171 send_and_respond!(self, "build_starpos_get_campaign_ids", Command::BuildStarposGetCampaingIds(params.0.pack_key))
2172 }
2173
2174 #[tool(description = "Check if victory conditions file exists for starpos building in the pack identified by `pack_key`.")]
2175 pub async fn build_starpos_check_victory_conditions(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2176 send_and_respond!(self, "build_starpos_check_victory_conditions", Command::BuildStarposCheckVictoryConditions(params.0.pack_key))
2177 }
2178
2179 #[tool(description = "Build starpos (pre-processing step) for the pack identified by `pack_key`.")]
2180 pub async fn build_starpos(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2181 send_and_respond!(self, "build_starpos", Command::BuildStarpos(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2182 }
2183
2184 #[tool(description = "Build starpos (post-processing step) for the pack identified by `pack_key`.")]
2185 pub async fn build_starpos_post(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2186 send_and_respond!(self, "build_starpos_post", Command::BuildStarposPost(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2187 }
2188
2189 #[tool(description = "Clean up starpos temporary files for the pack identified by `pack_key`.")]
2190 pub async fn build_starpos_cleanup(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2191 send_and_respond!(self, "build_starpos_cleanup", Command::BuildStarposCleanup(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2192 }
2193
2194 #[tool(description = "Update animation IDs with an offset in the pack identified by `pack_key`.")]
2195 pub async fn update_anim_ids(&self, params: Parameters<UpdateAnimIdsArgs>) -> Result<CallToolResult, McpError> {
2196 send_and_respond!(self, "update_anim_ids", Command::UpdateAnimIds(params.0.pack_key, params.0.starting_id, params.0.offset))
2197 }
2198
2199 #[tool(description = "Get animation paths by skeleton name.")]
2200 pub async fn get_anim_paths_by_skeleton_name(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
2201 send_and_respond!(self, "get_anim_paths_by_skeleton_name", Command::GetAnimPathsBySkeletonName(params.0.value))
2202 }
2203
2204 #[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.")]
2205 pub async fn export_rigid_to_gltf(&self, params: Parameters<ExportRigidToGltfArgs>) -> Result<CallToolResult, McpError> {
2206 let rigid = parse_json!(¶ms.0.rigid_model);
2207 send_and_respond!(self, "export_rigid_to_gltf", Command::ExportRigidToGltf(rigid, params.0.output_path))
2208 }
2209
2210 #[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).")]
2211 pub async fn set_video_format(&self, params: Parameters<SetVideoFormatArgs>) -> Result<CallToolResult, McpError> {
2212 let format = parse_json!(¶ms.0.format);
2213 send_and_respond!(self, "set_video_format", Command::SetVideoFormat(params.0.pack_key, params.0.path, format))
2214 }
2215
2216 #[tool(description = "List all currently open packs with their keys and metadata. Use this to get valid pack_key values for other tools.")]
2221 pub async fn list_open_packs(&self) -> Result<CallToolResult, McpError> {
2222 send_and_respond!(self, "list_open_packs", Command::ListOpenPacks)
2223 }
2224
2225 #[tool(description = "Close all currently open packs without saving. Any unsaved changes will be lost.")]
2230 pub async fn close_all_packs(&self) -> Result<CallToolResult, McpError> {
2231 send_and_respond!(self, "close_all_packs", Command::CloseAllPacks)
2232 }
2233
2234}
2235
2236#[prompt_router]
2241impl McpServer {
2242
2243 #[prompt(name = "open_and_inspect_pack", description = "Walk through opening a PackFile and inspecting its contents.")]
2244 pub async fn open_and_inspect_pack(&self) -> Vec<PromptMessage> {
2245 vec![PromptMessage::new_text(
2246 Role::User,
2247 "\
2248You are an assistant helping the user inspect a Total War PackFile using the RPFM MCP server.
2249
2250Follow these steps in order:
2251
22521. **Open the pack** – Call `open_packfiles` with the filesystem path(s) the user provides.
2253 The response contains one or more pack keys; remember them for subsequent calls.
2254
22552. **Select the game** – Call `set_game_selected` with the correct game key (e.g. `\"warhammer_3\"`)
2256 and `rebuild_dependencies: true` so that schemas and dependency data are loaded.
2257
22583. **List pack contents** – Call `open_pack_info` with the pack key to get the full file tree.
2259 Present the tree to the user in a readable format.
2260
22614. **Decode specific files** – When the user asks about a file, call `decode_packed_file` with the
2262 pack key, the internal path (e.g. `\"db/land_units_tables/my_table\"`), and
2263 `source: \"PackFile\"`. The decoded JSON will contain the table rows, schema, etc.
2264
22655. **Inspect metadata** – Use `get_pack_settings`, `get_pack_file_name`, or
2266 `get_dependency_pack_files_list` to answer questions about the pack itself.
2267
2268Important notes:
2269- Always call `list_open_packs` if you are unsure which pack key to use.
2270- If a file fails to decode, check `is_schema_loaded`; if false, call `update_schemas` first.
2271- When done, optionally call `close_pack` to free resources.
2272",
2273 )]
2274 }
2275
2276 #[prompt(name = "edit_db_table", description = "Guide for reading, modifying, and saving a DB table inside a pack.")]
2277 pub async fn edit_db_table(&self) -> Vec<PromptMessage> {
2278 vec![PromptMessage::new_text(
2279 Role::User,
2280 "\
2281You are an assistant helping the user edit a DB table inside a Total War PackFile.
2282
2283Workflow:
2284
22851. **Open the pack** – `open_packfiles` → note the `pack_key`.
22862. **Set the game** – `set_game_selected` with `rebuild_dependencies: true`.
22873. **Decode the table** – `decode_packed_file` with the DB path
2288 (e.g. `\"db/unit_stats_land_tables/my_table\"`) and `source: \"PackFile\"`.
2289 The response is an `RFileDecoded` JSON containing the table data and definition.
22904. **Modify rows** – Edit the decoded JSON: add, remove, or change rows/cells.
2291 Each row is typically a list of `DecodedData` values matching the table's
2292 fields processed list (retrievable via the `FieldsProcessed` message).
22935. **Save back** – Call `save_packed_file_from_view` with the pack key, the same path,
2294 and the modified `RFileDecoded` JSON as the `data` parameter.
22956. **Save the pack** – Call `save_packfile` (or `save_pack_as` for a new path).
2296
2297Tips:
2298- Use `get_table_definition_from_dependency_pack_file` to see the table's definition, but
2299 always run it through `fields_processed` before using its field list/count — the
2300 definition's raw `fields` do NOT match row shape (e.g. a colour column is split into
2301 separate r/g/b fields there); rows must match `fields_processed` exactly or saving
2302 will fail with a field-count or type error.
2303- Use `get_reference_data_from_definition` to discover valid values for referenced columns.
2304- After saving, you can run `diagnostics_check` to validate the pack.
2305",
2306 )]
2307 }
2308
2309 #[prompt(name = "create_new_mod", description = "Step-by-step guide for creating a new mod PackFile from scratch.")]
2310 pub async fn create_new_mod(&self) -> Vec<PromptMessage> {
2311 vec![PromptMessage::new_text(
2312 Role::User,
2313 "\
2314You are an assistant helping the user create a new Total War mod from scratch.
2315
2316Workflow:
2317
23181. **Set the game** – `set_game_selected` with the target game key and
2319 `rebuild_dependencies: true`.
2320
23212. **Create the pack** – `new_pack` returns a new empty pack and its pack key.
2322
23233. **Set pack type** – `set_pack_file_type` to `\"Mod\"` (the standard type for mods).
2324
23254. **Add DB tables** – For each table you need:
2326 a. Call `new_packed_file` with the pack key, the path (e.g. `\"db/land_units_tables/my_mod\"`),
2327 and the `new_file` JSON set to `\"DB\"` with the table name.
2328 b. Decode, edit, and save as described in the `edit_db_table` workflow.
2329
23305. **Add Loc files** – For localisation:
2331 a. `new_packed_file` with path `\"text/db/my_mod.loc\"` and `new_file` set to `\"Loc\"`.
2332 b. Decode, add key/value rows, and save.
2333
23346. **Add other files** – Use `add_packed_files` to import assets from disk (images, models, etc.).
2335
23367. **Save the pack** – `save_pack_as` to write the final `.pack` file to disk.
2337
2338Optional steps:
2339- `initialize_my_mod_folder` to set up a mod development folder with IDE support.
2340- `optimize_pack_file` to strip unchanged rows that match vanilla data.
2341- `diagnostics_check` to validate everything before release.
2342",
2343 )]
2344 }
2345
2346 #[prompt(name = "search_and_replace", description = "Find and replace values across all files in a pack.")]
2347 pub async fn search_and_replace(&self) -> Vec<PromptMessage> {
2348 vec![PromptMessage::new_text(
2349 Role::User,
2350 "\
2351You are an assistant helping the user search for and replace data across a PackFile.
2352
2353Workflow:
2354
23551. **Open the pack** and **set the game** (see `open_and_inspect_pack` prompt).
2356
23572. **Run a global search** – Call `global_search` with the pack key and a `GlobalSearch`
2358 JSON object. The search object specifies the pattern, whether to use regex, which file
2359 types to include (DB, Loc, Text), and the replacement string.
2360
23613. **Review matches** – The response contains all matches grouped by file.
2362 Present them to the user for review.
2363
23644. **Replace selectively** – Call `global_search_replace_matches` with the same search
2365 object and a `Vec<MatchHolder>` containing only the matches the user approved.
2366
23675. **Or replace all** – If the user confirms a blanket replace, call
2368 `global_search_replace_all` with the search object.
2369
23706. **Save** – `save_packfile` to persist changes.
2371
2372Related tools:
2373- `search_references` – Find all rows that reference a specific value across tables.
2374- `go_to_definition` – Jump to where a referenced key is defined.
2375- `go_to_loc` – Find the loc entry for a given key.
2376",
2377 )]
2378 }
2379
2380 #[prompt(name = "manage_dependencies", description = "Set up and work with game dependencies and vanilla data.")]
2381 pub async fn manage_dependencies(&self) -> Vec<PromptMessage> {
2382 vec![PromptMessage::new_text(
2383 Role::User,
2384 "\
2385You are an assistant helping the user work with dependency data (vanilla game files).
2386
2387Workflow:
2388
23891. **Set the game** – `set_game_selected` with `rebuild_dependencies: true`.
2390
23912. **Check dependency database** – `is_there_a_dependency_database` with `true` to verify
2392 that game data (including Assembly Kit data) is loaded.
2393 If it returns false, call `generate_dependencies_cache` first.
2394
23953. **Browse vanilla tables** – `get_table_list_from_dependency_pack_file` returns all
2396 DB table names from the vanilla game files.
2397
23984. **Read vanilla data** – `get_tables_from_dependencies` with a table name to get
2399 all rows from vanilla for that table.
2400
24015. **Get definitions** – `get_table_definition_from_dependency_pack_file` to get the
2402 schema definition for any table.
2403
24046. **Import from vanilla** – `import_dependencies_to_open_pack_file` to copy specific
2405 files from vanilla into your mod pack.
2406
24077. **Open CA packs** – `load_all_ca_pack_files` opens all vanilla packs as one merged
2408 read-only pack for full browsing.
2409
24108. **Cross-source lookups** – `get_rfiles_from_all_sources` retrieves files by path
2411 from PackFile, GameFiles, and ParentFiles simultaneously.
2412
2413Tips:
2414- Use `get_packed_files_names_starting_with_path_from_all_sources` to discover files
2415 under a given path prefix across all sources.
2416- `set_dependency_pack_files_list` lets you mark other mods as dependencies of your pack.
2417- The `definition` bundled in each file from `get_tables_from_dependencies` (and from
2418 `get_table_definition_from_dependency_pack_file`) lists RAW on-disk fields, which can
2419 have a different length/order than the actual decoded rows (e.g. colour columns are
2420 split into separate r/g/b fields there). Run it through `fields_processed` before
2421 matching it up against row cells or reusing it to build new rows.
2422",
2423 )]
2424 }
2425
2426 #[prompt(name = "run_diagnostics", description = "Validate a pack and fix common issues.")]
2427 pub async fn run_diagnostics(&self) -> Vec<PromptMessage> {
2428 vec![PromptMessage::new_text(
2429 Role::User,
2430 "\
2431You are an assistant helping the user validate a Total War mod PackFile.
2432
2433Workflow:
2434
24351. **Open the pack** and **set the game** with `rebuild_dependencies: true`.
2436
24372. **Generate dependencies** – If dependencies have not been generated yet,
2438 call `generate_dependencies` to build the dependency data needed for diagnostics.
2439
24403. **Run full diagnostics** – `diagnostics_check` with an empty `ignored` list
2441 and `check_ak_only_refs: false` (or `true` to include Assembly Kit references).
2442 The response contains all warnings and errors grouped by category.
2443
24444. **Review results** – Present the diagnostic results to the user, grouped by severity.
2445 Common issues include:
2446 - Invalid references (a column references a key that does not exist)
2447 - Duplicate keys
2448 - Empty loc entries
2449 - Outdated table versions
2450
24515. **Fix issues** – For each issue:
2452 - Decode the affected file with `decode_packed_file`.
2453 - Apply the fix (correct a reference, remove a duplicate row, etc.).
2454 - Save with `save_packed_file_from_view`.
2455
24566. **Ignore false positives** – Use `add_line_to_pack_ignored_diagnostics` to suppress
2457 specific diagnostic lines that are intentional.
2458
24597. **Re-check** – After fixes, call `diagnostics_check` again to confirm all issues
2460 are resolved.
2461
24628. **Optimize** – Optionally run `optimize_pack_file` to remove rows that are identical
2463 to vanilla, reducing pack size.
2464",
2465 )]
2466 }
2467
2468 #[prompt(name = "schema_operations", description = "Work with table schemas: inspect, update, and patch definitions.")]
2469 pub async fn schema_operations(&self) -> Vec<PromptMessage> {
2470 vec![PromptMessage::new_text(
2471 Role::User,
2472 "\
2473You are an assistant helping the user manage RPFM table schemas.
2474
2475Workflow:
2476
24771. **Check schema status** – `is_schema_loaded` to verify a schema is loaded.
2478 If not, call `update_schemas` to download the latest from the repository.
2479
24802. **Get the full schema** – `get_schema` returns the entire schema object.
2481
24823. **Inspect a table definition** – `definitions_by_table_name` with a table name
2483 returns all known versions. Use `definition_by_table_name_and_version` for a
2484 specific version.
2485
24864. **See processed fields** – `fields_processed` takes a Definition JSON and returns
2487 fields with bitwise expansion, enum conversions, and colour-group merging applied.
2488 This is required, not just cosmetic: `definitions_by_table_name` and
2489 `definition_by_table_name_and_version` return the raw on-disk field list (e.g. a
2490 colour column split into separate r/g/b fields), which has a different length/order
2491 than actual row data. Always call `fields_processed` before using a definition's
2492 field list/count to build or validate rows for saving.
2493
24945. **Find referencing columns** – `referencing_columns_for_definition` shows which
2495 other tables reference a given table's columns.
2496
24976. **Patch a definition** – To customise column metadata (descriptions, references,
2498 default values) without modifying the upstream schema:
2499 a. Build a `HashMap<String, DefinitionPatch>` with your changes.
2500 b. Call `save_local_schema_patch` to persist it locally.
2501 c. Use `remove_local_schema_patches_for_table` or
2502 `remove_local_schema_patches_for_table_and_field` to undo patches.
2503
25047. **Import patches** – `import_schema_patch` applies a patch from another source.
2505
25068. **Update from Assembly Kit** – `update_current_schema_from_asskit` merges
2507 definition data from the game's Assembly Kit into the loaded schema.
2508
25099. **Save the schema** – `save_schema` writes the current in-memory schema to disk.
2510",
2511 )]
2512 }
2513
2514 #[prompt(name = "file_operations", description = "Add, remove, rename, extract, and move files within packs.")]
2515 pub async fn file_operations(&self) -> Vec<PromptMessage> {
2516 vec![PromptMessage::new_text(
2517 Role::User,
2518 "\
2519You are an assistant helping the user manage files inside a Total War PackFile.
2520
2521Common operations:
2522
2523**Add files from disk:**
2524- `add_packed_files` – Import files from the filesystem into the pack. Provide source
2525 filesystem paths and destination `ContainerPath` entries as JSON.
2526
2527**Add files from another pack:**
2528- `add_packed_files_from_pack_file` – Copy files between two open packs.
2529
2530**Create new files:**
2531- `new_packed_file` – Create a blank DB table, Loc file, or other file type inside the pack.
2532
2533**Delete files:**
2534- `delete_packed_files` – Remove files by their `ContainerPath` list.
2535
2536**Rename / move files:**
2537- `rename_packed_files` – Pass a list of `(old_path, new_path)` tuples.
2538
2539**Copy / Cut / Paste / Duplicate:**
2540- `copy_packed_files` – Copy files to the internal clipboard for later pasting.
2541- `cut_packed_files` – Cut files to the internal clipboard (removed from source on paste).
2542- `paste_packed_files` – Paste clipboard contents into a pack at the given folder path.
2543- `duplicate_packed_files` – Clone files in-place with a numeric suffix.
2544
2545**Extract to disk:**
2546- `extract_packed_files` – Export files from the pack to a folder on disk.
2547 Set `export_as_tsv: true` to export tables as TSV files.
2548
2549**AnimPack operations:**
2550- `add_packed_files_from_pack_file_to_animpack` – Add files to an AnimPack.
2551- `add_packed_files_from_animpack` – Extract files from an AnimPack.
2552- `delete_from_animpack` – Remove files from an AnimPack.
2553
2554**File info:**
2555- `get_packed_files_info` / `get_rfile_info` – Get metadata about files.
2556- `folder_exists` / `packed_file_exists` – Check if a path exists.
2557- `get_packed_file_raw_data` – Get the raw binary content of a file.
2558
2559**Merge tables:**
2560- `merge_files` – Combine multiple compatible tables into one.
2561
2562**External editing:**
2563- `open_packed_file_in_external_program` – Open a file in the system's default editor.
2564- `save_packed_file_from_external_view` – Re-import after external editing.
2565
2566Always call `save_packfile` or `save_pack_as` when done to persist changes.
2567",
2568 )]
2569 }
2570
2571 #[prompt(name = "troubleshooting", description = "Diagnose and fix common issues with RPFM and PackFiles.")]
2572 pub async fn troubleshooting(&self) -> Vec<PromptMessage> {
2573 vec![PromptMessage::new_text(
2574 Role::User,
2575 "\
2576You are an assistant helping the user troubleshoot common RPFM and PackFile issues.
2577
2578## Common Issues and Solutions
2579
2580### 1. Schema not loaded
2581**Symptom**: Files fail to decode, or `decode_packed_file` returns raw data.
2582**Solution**:
2583- Call `is_schema_loaded()` – if false, call `update_schemas()`.
2584- Make sure `set_game_selected` was called with `rebuild_dependencies: true`.
2585
2586### 2. Dependencies not available
2587**Symptom**: References show as invalid, diagnostics report missing keys.
2588**Solution**:
2589- Call `is_there_a_dependency_database(true)` – if false, call `generate_dependencies_cache()`.
2590- Ensure the game path is configured correctly in settings.
2591
2592### 3. Pack won't save
2593**Symptom**: `save_packfile` returns an error.
2594**Solution**:
2595- Check if the file is read-only or locked by another process.
2596- Try `save_pack_as` to a different path.
2597- As a last resort, use `clean_and_save_pack_as` to recover from corruption.
2598
2599### 4. Table version mismatch
2600**Symptom**: Table data looks wrong or has missing columns after a game update.
2601**Solution**:
2602- Call `update_schemas()` to get the latest table definitions.
2603- Use `update_table` to migrate the table to the current version.
2604- Check `get_table_definition_from_dependency_pack_file` for the expected schema.
2605
2606### 5. Wrong game selected
2607**Symptom**: Tables decode with wrong columns or fail to decode, dependencies are for a different game.
2608**Solution**:
2609- Call `get_game_selected()` to verify the current game.
2610- Call `set_game_selected` with the correct game key and `rebuild_dependencies: true`.
2611
2612### 6. Diagnostics show many reference errors
2613**Symptom**: `diagnostics_check` reports hundreds of invalid references.
2614**Solution**:
2615- Ensure dependencies are loaded (`is_there_a_dependency_database(true)`).
2616- Check if the pack depends on other mods via `get_dependency_pack_files_list`.
2617- Some references are Assembly Kit only; re-run with `check_ak_only_refs: true`.
2618- Use `add_line_to_pack_ignored_diagnostics` for intentional deviations.
2619
2620### Diagnostic Tools
2621- `diagnostics_check` – Full pack validation.
2622- `get_game_selected` – Verify game context.
2623- `is_schema_loaded` – Check schema status.
2624- `is_there_a_dependency_database` – Check dependency database status.
2625- `list_open_packs` – Verify which packs are open.
2626- `config_path` / `schemas_path` – Verify RPFM paths.
2627",
2628 )]
2629 }
2630
2631 #[prompt(name = "tsv_workflow", description = "Import and export tables as TSV files for batch editing in spreadsheets.")]
2632 pub async fn tsv_workflow(&self) -> Vec<PromptMessage> {
2633 vec![PromptMessage::new_text(
2634 Role::User,
2635 "\
2636You are an assistant helping the user work with TSV (Tab-Separated Values) files for batch editing \
2637Total War mod data in spreadsheets.
2638
2639## Export Workflow (Pack → TSV → Spreadsheet)
2640
26411. **Open the pack** and **set the game** with `rebuild_dependencies: true`.
2642
26432. **Export a single table as TSV**:
2644 Call `export_tsv` with:
2645 - `pack_key`: the pack key
2646 - `tsv_path`: destination path on disk (e.g. `/home/user/my_table.tsv`)
2647 - `table_path`: the internal path (e.g. `db/land_units_tables/my_mod`)
2648
26493. **Export all tables as TSV**:
2650 Call `extract_packed_files` with `export_as_tsv: true`.
2651 This exports all tables in the pack as TSV files to the destination folder.
2652
26534. **Edit in a spreadsheet**: Open the TSV file in LibreOffice Calc, Excel, or Google Sheets.
2654 - Keep the header rows intact (they contain schema metadata).
2655 - Tab-separated values — do not change the delimiter.
2656
2657## Import Workflow (Spreadsheet → TSV → Pack)
2658
26591. **Save the spreadsheet as TSV** (tab-delimited, UTF-8 encoding).
2660
26612. **Import the TSV back**:
2662 Call `import_tsv` with:
2663 - `pack_key`: the target pack key
2664 - `tsv_path`: path to the TSV file on disk
2665 - `table_path`: the internal path where the table should go
2666
26673. **Verify**: Call `decode_packed_file` to confirm the data imported correctly.
2668
26694. **Save the pack**: Call `save_packfile` to persist changes.
2670
2671## Tips
2672- TSV files include metadata headers that RPFM uses for schema matching.
2673 Do not delete or modify these header rows.
2674- Use `get_table_definition_from_dependency_pack_file` to understand column types
2675 before editing.
2676- After import, run `diagnostics_check` to validate references.
2677",
2678 )]
2679 }
2680
2681 #[prompt(name = "translation_workflow", description = "Work with localisation and translation data in PackFiles.")]
2682 pub async fn translation_workflow(&self) -> Vec<PromptMessage> {
2683 vec![PromptMessage::new_text(
2684 Role::User,
2685 "\
2686You are an assistant helping the user work with localisation (translation) data in Total War mods.
2687
2688## Understanding Loc Files
2689
2690Loc files contain key-value pairs for in-game text. Each entry has:
2691- A **key** (unique identifier referenced by DB tables)
2692- A **value** (the displayed text in the game)
2693
2694## Viewing Existing Translations
2695
26961. **Open the pack** and **set the game**.
2697
26982. **Decode a loc file**:
2699 Call `decode_packed_file` with the loc file path (e.g. `text/db/my_mod.loc`)
2700 and `source: \"PackFile\"`.
2701
27023. **Get translation overview**:
2703 Call `get_pack_translation` with the pack key and a language code
2704 (e.g. `\"en\"`, `\"fr\"`, `\"de\"`, `\"es\"`, `\"it\"`, `\"zh\"`, `\"ru\"`, etc.).
2705
2706## Creating New Translations
2707
27081. **Create a new loc file**:
2709 Call `new_packed_file` with path `\"text/db/my_mod.loc\"` and
2710 `new_file = {\"Loc\": \"my_mod\"}`.
2711
27122. **Decode it**: `decode_packed_file` to get the empty structure.
2713
27143. **Add entries**: Modify the decoded JSON to add key-value rows.
2715 Each row is typically `[\"key_string\", \"Displayed text in game\"]`.
2716
27174. **Save back**: `save_packed_file_from_view` with the modified data.
2718
2719## Generating Missing Loc Data
2720
2721Call `generate_missing_loc_data` with the pack key to auto-generate
2722loc entries for DB fields that reference loc keys but don't have entries yet.
2723
2724## Finding Loc Keys
2725
2726- Use `go_to_loc` with a loc key to find its source loc file.
2727- Use `get_source_data_from_loc_key` to find where a loc key is referenced.
2728- Use `global_search` with `search_on.loc: true` to search across all loc files.
2729
2730## Tips
2731- Loc keys follow naming conventions like `<table>_<loc_column_name>_<keys_concatenated>`.
2732- Use `search_references` to find all DB columns that reference a specific loc key.
2733- After adding translations, run `diagnostics_check` to verify all references.
2734",
2735 )]
2736 }
2737}