Skip to main content

rpfm_server/
server_mcp.rs

1//---------------------------------------------------------------------------//
2// Copyright (c) 2017-2026 Ismael Gutiérrez González. All rights reserved.
3//
4// This file is part of the Rusted PackFile Manager (RPFM) project,
5// which can be found here: https://github.com/Frodo45127/rpfm.
6//
7// This file is licensed under the MIT license, which can be found here:
8// https://github.com/Frodo45127/rpfm/blob/master/LICENSE.
9//---------------------------------------------------------------------------//
10
11//! [Model Context Protocol][mcp] server exposed at the `/mcp` endpoint.
12//!
13//! Wraps every [`Command`] the [`crate::background_thread`] dispatcher
14//! understands as an MCP **tool**, plus a handful of MCP **resources**
15//! (game lists, enum dumps, examples, reference docs) and **prompts** for
16//! common workflows ("open and inspect a pack", "edit a DB table",
17//! "manage dependencies", …). Each MCP client gets its own dedicated
18//! [`Session`] and [`McpServer`] — same isolation guarantees as the
19//! WebSocket clients.
20//!
21//! Each tool call:
22//!
23//! 1. Translates its `*Args` payload into a [`Command`] and ships it
24//!    through the session's
25//!    [`background_loop`](crate::background_thread::background_loop) via
26//!    the `send_and_respond!` helper.
27//! 2. Wraps the resulting [`Response`] back into a [`CallToolResult`].
28//!
29//! The `*Args` structs are the canonical schema for every tool. Their
30//! `JsonSchema` derive is what `rmcp` ships to clients to advertise tool
31//! arguments, so docstrings on individual fields show up directly in MCP
32//! tool listings.
33//!
34//! [mcp]: https://modelcontextprotocol.io/
35//! [`Session`]: crate::session::Session
36//! [`CallToolResult`]: rmcp::model::CallToolResult
37
38use rmcp::ErrorData as McpError;
39use rmcp::handler::server::{router::prompt::PromptRouter, tool::ToolRouter, wrapper::Parameters};
40use rmcp::model::{
41    Annotated, CallToolResult, CompletionInfo, CompleteRequestParams, CompleteResult,
42    Content, ErrorCode, GetPromptRequestParams, GetPromptResult,
43    ListPromptsResult, ListResourcesResult, ListResourceTemplatesResult,
44    PaginatedRequestParams, PromptMessage, PromptMessageRole,
45    RawResource, ReadResourceRequestParams, ReadResourceResult,
46    ResourceContents, ServerCapabilities, ServerInfo, SetLevelRequestParams,
47};
48use rmcp::schemars::JsonSchema;
49use rmcp::service::RequestContext;
50use rmcp::{prompt, prompt_handler, prompt_router, tool, tool_handler, tool_router, RoleServer};
51use serde::{Deserialize, Serialize};
52
53use std::collections::{BTreeMap, HashMap, HashSet};
54use std::sync::Arc;
55use std::path::PathBuf;
56
57use rpfm_extensions::merge::MergeOptions;
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
66//-------------------------------------------------------------------------------//
67//                              Helper macro
68//-------------------------------------------------------------------------------//
69
70/// Helper to send a command and return the JSON response.
71///
72/// Each tool call starts an independent Sentry transaction following the MCP tracing spec,
73/// so it gets reported regardless of the long-lived rmcp service span.
74macro_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![Content::text(json)]))
102        } else {
103            Ok(CallToolResult::success(vec![Content::text(json)]))
104        }
105    }};
106}
107
108/// Build an Annotated<RawResource> with common fields set.
109fn resource(uri: &str, name: &str, description: &str, mime_type: &str) -> Annotated<RawResource> {
110    let mut raw = RawResource::new(uri, name);
111    raw.description = Some(description.into());
112    raw.mime_type = Some(mime_type.into());
113    Annotated { raw, annotations: None }
114}
115
116/// Parse a JSON string into the expected type, returning a tool-level error on failure.
117///
118/// This is a macro (not a function) so that `return Ok(...)` exits the calling tool method,
119/// keeping invalid-JSON errors as tool results instead of protocol-level `McpError`s that
120/// would tear down the MCP session.
121macro_rules! parse_json {
122    ($input:expr) => {
123        match serde_json::from_str($input) {
124            Ok(v) => v,
125            Err(e) => return Ok(CallToolResult::error(vec![Content::text(format!("Invalid JSON parameter: {e}"))])),
126        }
127    };
128}
129
130//-------------------------------------------------------------------------------//
131//                              Enums & Structs
132//-------------------------------------------------------------------------------//
133
134/// MCP server bound to a single [`Session`].
135///
136/// One instance is constructed per MCP client connection by the
137/// `StreamableHttpService` factory wired in `main.rs`. The
138/// `tool_router` and `prompt_router` fields are built once at construction
139/// time from the `#[tool_router]` / `#[prompt_router]` attribute macros
140/// applied further down in this module.
141///
142/// Cheap to clone — only `Arc` and small router structs.
143#[derive(Clone)]
144pub struct McpServer {
145    /// The session this MCP client is bound to.
146    session: Arc<Session>,
147    /// The router auto-generated from `#[tool_router]` annotations.
148    tool_router: ToolRouter<Self>,
149    /// The router auto-generated from `#[prompt_router]` annotations.
150    prompt_router: PromptRouter<Self>,
151}
152
153// -- Generic / Existing Args --
154
155#[derive(Debug, Deserialize, JsonSchema, Serialize)]
156#[schemars(description = "Call any IPC command directly.")]
157pub struct CallCommandArgs {
158    /// The JSON representation of the Command enum.
159    pub command: String,
160}
161
162#[derive(Debug, Deserialize, JsonSchema, Serialize)]
163pub struct OpenPackfilesArgs {
164    /// The paths of the PackFiles to open.
165    pub paths: Vec<PathBuf>,
166}
167
168#[derive(Debug, Deserialize, JsonSchema, Serialize)]
169pub struct SetGameSelectedArgs {
170    /// The name of the game to select.
171    pub game_name: String,
172    /// Whether to rebuild dependencies.
173    pub rebuild_dependencies: bool,
174}
175
176#[derive(Debug, Deserialize, JsonSchema, Serialize)]
177pub struct TsvExportArgs {
178    /// The key of the target pack.
179    pub pack_key: String,
180    /// The path of the TSV file to export to.
181    pub tsv_path: PathBuf,
182    /// The path of the table to export.
183    pub table_path: String,
184}
185
186#[derive(Debug, Deserialize, JsonSchema, Serialize)]
187pub struct TsvImportArgs {
188    /// The key of the target pack.
189    pub pack_key: String,
190    /// The path of the TSV file to import from.
191    pub tsv_path: PathBuf,
192    /// The path of the table to import to.
193    pub table_path: String,
194}
195
196#[derive(Debug, Deserialize, JsonSchema, Serialize)]
197pub struct DecodePackedFileArgs {
198    /// The key of the target pack.
199    pub pack_key: String,
200    /// The path of the file inside the data source.
201    pub path: String,
202    /// The data source to decode from.
203    pub source: DataSource,
204}
205
206// -- Pack Lifecycle Args --
207
208#[derive(Debug, Deserialize, JsonSchema, Serialize)]
209pub struct PathArg {
210    /// The file path.
211    pub path: PathBuf,
212}
213
214// -- Pack Key Args (multi-pack support) --
215
216#[derive(Debug, Deserialize, JsonSchema, Serialize)]
217pub struct PackKeyArg {
218    /// The key of the target pack. Use `list_open_packs` to get available keys.
219    pub pack_key: String,
220}
221
222#[derive(Debug, Deserialize, JsonSchema, Serialize)]
223pub struct PackKeyBoolArg {
224    /// The key of the target pack.
225    pub pack_key: String,
226    /// A boolean value.
227    pub value: bool,
228}
229
230#[derive(Debug, Deserialize, JsonSchema, Serialize)]
231pub struct PackKeyStringArg {
232    /// The key of the target pack.
233    pub pack_key: String,
234    /// A string value.
235    pub value: String,
236}
237
238#[derive(Debug, Deserialize, JsonSchema, Serialize)]
239pub struct PackKeyStringsArg {
240    /// The key of the target pack.
241    pub pack_key: String,
242    /// A list of string values.
243    pub values: Vec<String>,
244}
245
246#[derive(Debug, Deserialize, JsonSchema, Serialize)]
247pub struct PackKeyPathArg {
248    /// The key of the target pack.
249    pub pack_key: String,
250    /// The file path.
251    pub path: PathBuf,
252}
253
254// -- Pack Metadata Args --
255
256#[derive(Debug, Deserialize, JsonSchema, Serialize)]
257pub struct SetPackFileTypeArgs {
258    /// The key of the target pack.
259    pub pack_key: String,
260    /// The JSON representation of the PFHFileType enum.
261    pub pack_file_type: String,
262}
263
264#[derive(Debug, Deserialize, JsonSchema, Serialize)]
265pub struct ChangeCompressionFormatArgs {
266    /// The key of the target pack.
267    pub pack_key: String,
268    /// The JSON representation of the CompressionFormat enum.
269    pub format: String,
270}
271
272#[derive(Debug, Deserialize, JsonSchema, Serialize)]
273pub struct BoolArg {
274    /// A boolean value.
275    pub value: bool,
276}
277
278#[derive(Debug, Deserialize, JsonSchema, Serialize)]
279pub struct SetPackSettingsArgs {
280    /// The key of the target pack.
281    pub pack_key: String,
282    /// The JSON representation of the PackSettings struct.
283    pub settings: String,
284}
285
286#[derive(Debug, Deserialize, JsonSchema, Serialize)]
287pub struct SetDependencyPackFilesListArgs {
288    /// The key of the target pack.
289    pub pack_key: String,
290    /// The JSON representation of Vec<(bool, String)> for the dependency list.
291    pub list: String,
292}
293
294// -- File Operations Args --
295
296#[derive(Debug, Deserialize, JsonSchema, Serialize)]
297pub struct NewPackedFileArgs {
298    /// The key of the target pack.
299    pub pack_key: String,
300    /// The path for the new file inside the pack.
301    pub path: String,
302    /// The JSON representation of the NewFile enum.
303    pub new_file: String,
304}
305
306#[derive(Debug, Deserialize, JsonSchema, Serialize)]
307pub struct AddPackedFilesArgs {
308    /// The key of the target pack.
309    pub pack_key: String,
310    /// The source filesystem paths.
311    pub source_paths: Vec<PathBuf>,
312    /// The JSON representation of Vec<ContainerPath> for destination paths.
313    pub destination_paths: String,
314    /// The optional paths to ignore (JSON representation of Option<Vec<PathBuf>>).
315    pub ignore_paths: Option<Vec<PathBuf>>,
316}
317
318#[derive(Debug, Deserialize, JsonSchema, Serialize)]
319pub struct AddPackedFilesFromPackFileArgs {
320    /// The key of the target pack.
321    pub pack_key: String,
322    /// The key of the source PackFile.
323    pub source_pack_path: String,
324    /// The JSON representation of Vec<ContainerPath> for files to add.
325    pub container_paths: String,
326}
327
328#[derive(Debug, Deserialize, JsonSchema, Serialize)]
329pub struct AddPackedFilesFromPackFileToAnimpackArgs {
330    /// The key of the source pack the files are copied from.
331    pub source_pack_key: String,
332    /// The key of the pack that owns the target AnimPack (may differ from the source).
333    pub pack_key: String,
334    /// The animpack path.
335    pub animpack_path: String,
336    /// The JSON representation of Vec<ContainerPath> for files to add.
337    pub container_paths: String,
338}
339
340#[derive(Debug, Deserialize, JsonSchema, Serialize)]
341pub struct AddPackedFilesFromAnimpackArgs {
342    /// The key of the pack that owns the AnimPack (only used when `source` is a PackFile).
343    pub anim_pack_key: String,
344    /// The key of the destination pack the files are copied into (may differ from the AnimPack's).
345    pub pack_key: String,
346    /// The data source to get the animpack from.
347    pub source: DataSource,
348    /// The animpack path.
349    pub animpack_path: String,
350    /// The JSON representation of Vec<ContainerPath> for files to add.
351    pub container_paths: String,
352}
353
354#[derive(Debug, Deserialize, JsonSchema, Serialize)]
355pub struct ContainerPathsArg {
356    /// The key of the target pack.
357    pub pack_key: String,
358    /// The JSON representation of Vec<ContainerPath>.
359    pub paths: String,
360}
361
362#[derive(Debug, Deserialize, JsonSchema, Serialize)]
363pub struct DeleteFromAnimpackArgs {
364    /// The key of the target pack.
365    pub pack_key: String,
366    /// The animpack path.
367    pub animpack_path: String,
368    /// The JSON representation of Vec<ContainerPath> for files to delete.
369    pub container_paths: String,
370}
371
372#[derive(Debug, Deserialize, JsonSchema, Serialize)]
373pub struct ExtractPackedFilesArgs {
374    /// The key of the target pack.
375    pub pack_key: String,
376    /// The JSON representation of BTreeMap<DataSource, Vec<ContainerPath>>.
377    pub source_paths: String,
378    /// The destination path on disk.
379    pub destination_path: PathBuf,
380    /// Whether to export tables as TSV.
381    pub export_as_tsv: bool,
382}
383
384#[derive(Debug, Deserialize, JsonSchema, Serialize)]
385pub struct RenamePackedFilesArgs {
386    /// The key of the target pack.
387    pub pack_key: String,
388    /// The JSON representation of Vec<(ContainerPath, ContainerPath)>.
389    pub renames: String,
390}
391
392#[derive(Debug, Deserialize, JsonSchema, Serialize)]
393pub struct CopyOrCutPackedFilesArgs {
394    /// A JSON object mapping pack key to ContainerPath arrays, e.g. {"my_pack.pack": [{"File": "db/table/file"}]}.
395    pub paths_by_pack: String,
396}
397
398#[derive(Debug, Deserialize, JsonSchema, Serialize)]
399pub struct PastePackedFilesArgs {
400    /// The key of the target pack to paste into.
401    pub pack_key: String,
402    /// The destination folder path inside the pack (use empty string for root).
403    pub destination_path: String,
404}
405
406#[derive(Debug, Deserialize, JsonSchema, Serialize)]
407pub struct DuplicatePackedFilesArgs {
408    /// The key of the target pack.
409    pub pack_key: String,
410    /// The JSON representation of Vec<ContainerPath> for files to duplicate.
411    pub paths: String,
412}
413
414#[derive(Debug, Deserialize, JsonSchema, Serialize)]
415pub struct SavePackedFileFromViewArgs {
416    /// The key of the target pack.
417    pub pack_key: String,
418    /// The path of the file inside the pack.
419    pub path: String,
420    /// The JSON representation of the RFileDecoded enum.
421    pub data: String,
422}
423
424#[derive(Debug, Deserialize, JsonSchema, Serialize)]
425pub struct SavePackedFileFromExternalViewArgs {
426    /// The key of the target pack.
427    pub pack_key: String,
428    /// The internal path of the file in the pack.
429    pub internal_path: String,
430    /// The external file path on disk.
431    pub external_path: PathBuf,
432}
433
434#[derive(Debug, Deserialize, JsonSchema, Serialize)]
435pub struct SavePackedFilesToPackFileAndCleanArgs {
436    /// The key of the target pack.
437    pub pack_key: String,
438    /// The JSON representation of Vec<RFile>.
439    pub files: String,
440    /// Whether to optimize after saving.
441    pub optimize: bool,
442}
443
444#[derive(Debug, Deserialize, JsonSchema, Serialize)]
445pub struct StringArg {
446    /// A string value.
447    pub value: String,
448}
449
450#[derive(Debug, Deserialize, JsonSchema, Serialize)]
451pub struct OpenPackedFileInExternalProgramArgs {
452    /// The key of the target pack.
453    pub pack_key: String,
454    /// The data source of the file.
455    pub source: DataSource,
456    /// The JSON representation of the ContainerPath.
457    pub container_path: String,
458}
459
460#[derive(Debug, Deserialize, JsonSchema, Serialize)]
461pub struct StringsArg {
462    /// A list of string values.
463    pub values: Vec<String>,
464}
465
466// -- Dependency Args --
467
468#[derive(Debug, Deserialize, JsonSchema, Serialize)]
469pub struct ImportDependenciesArgs {
470    /// The key of the target pack.
471    pub pack_key: String,
472    /// The JSON representation of BTreeMap<DataSource, Vec<ContainerPath>>.
473    pub paths: String,
474}
475
476#[derive(Debug, Deserialize, JsonSchema, Serialize)]
477pub struct GetRFilesFromAllSourcesArgs {
478    /// The JSON representation of Vec<ContainerPath>.
479    pub paths: String,
480    /// Whether to lowercase paths.
481    pub lowercase: bool,
482}
483
484#[derive(Debug, Deserialize, JsonSchema, Serialize)]
485pub struct ContainerPathArg {
486    /// The JSON representation of the ContainerPath.
487    pub path: String,
488}
489
490// -- Search Args --
491
492#[derive(Debug, Deserialize, JsonSchema, Serialize)]
493pub struct GlobalSearchArgs {
494    /// The key of the target pack.
495    pub pack_key: String,
496    /// The JSON representation of the GlobalSearch struct.
497    pub search: String,
498}
499
500#[derive(Debug, Deserialize, JsonSchema, Serialize)]
501pub struct GlobalSearchReplaceMatchesArgs {
502    /// The key of the target pack.
503    pub pack_key: String,
504    /// The JSON representation of the GlobalSearch struct.
505    pub search: String,
506    /// The JSON representation of Vec<MatchHolder>.
507    pub matches: String,
508}
509
510#[derive(Debug, Deserialize, JsonSchema, Serialize)]
511pub struct SearchReferencesArgs {
512    /// The key of the target pack.
513    pub pack_key: String,
514    /// The JSON representation of HashMap<String, Vec<String>>.
515    pub reference_map: String,
516    /// The value to search for.
517    pub value: String,
518}
519
520#[derive(Debug, Deserialize, JsonSchema, Serialize)]
521pub struct GetReferenceDataFromDefinitionArgs {
522    /// The key of the target pack.
523    pub pack_key: String,
524    /// The table name.
525    pub table_name: String,
526    /// The JSON representation of the Definition struct.
527    pub definition: String,
528    /// Force local reference regeneration.
529    pub force: bool,
530}
531
532#[derive(Debug, Deserialize, JsonSchema, Serialize)]
533pub struct GoToDefinitionArgs {
534    /// The key of the target pack.
535    pub pack_key: String,
536    /// The table name.
537    pub table_name: String,
538    /// The column name.
539    pub column_name: String,
540    /// The values to search for.
541    pub values: Vec<String>,
542}
543
544// -- Schema Args --
545
546#[derive(Debug, Deserialize, JsonSchema, Serialize)]
547pub struct SaveSchemaArgs {
548    /// The JSON representation of the Schema struct.
549    pub schema: String,
550}
551
552#[derive(Debug, Deserialize, JsonSchema, Serialize)]
553pub struct StringI32Args {
554    /// A string value (e.g., table name).
555    pub name: String,
556    /// An integer value (e.g., version).
557    pub version: i32,
558}
559
560#[derive(Debug, Deserialize, JsonSchema, Serialize)]
561pub struct ReferencingColumnsForDefinitionArgs {
562    /// The table name.
563    pub table_name: String,
564    /// The JSON representation of the Definition struct.
565    pub definition: String,
566}
567
568#[derive(Debug, Deserialize, JsonSchema, Serialize)]
569pub struct DefinitionArg {
570    /// The JSON representation of the Definition struct.
571    pub definition: String,
572}
573
574#[derive(Debug, Deserialize, JsonSchema, Serialize)]
575pub struct SchemaPatchArgs {
576    /// The JSON representation of HashMap<String, DefinitionPatch>.
577    pub patches: String,
578}
579
580// -- Table Ops Args --
581
582#[derive(Debug, Deserialize, JsonSchema, Serialize)]
583pub struct MergeFilesArgs {
584    /// The key of the target pack.
585    pub pack_key: String,
586    /// The JSON representation of Vec<ContainerPath> for files to merge.
587    pub paths: String,
588    /// The path for the merged file.
589    pub merged_path: String,
590    /// Whether to delete source files after merging.
591    pub delete_source: bool,
592    /// Merge rows by key instead of concatenating them. If some rows can't be reconciled
593    /// automatically, nothing is written and the response is `Response::MergeConflicts` instead.
594    /// Defaults to false.
595    #[serde(default)]
596    pub delta_merge: bool,
597}
598
599#[derive(Debug, Deserialize, JsonSchema, Serialize)]
600pub struct CascadeEditionArgs {
601    /// The key of the target pack.
602    pub pack_key: String,
603    /// The table name.
604    pub table_name: String,
605    /// The JSON representation of the Definition struct.
606    pub definition: String,
607    /// The JSON representation of Vec<(Field, String, String)> for field changes.
608    pub changes: String,
609}
610
611#[derive(Debug, Deserialize, JsonSchema, Serialize)]
612pub struct AddKeysToKeyDeletesArgs {
613    /// The key of the target pack.
614    pub pack_key: String,
615    /// The table file name.
616    pub table_file_name: String,
617    /// The key table name.
618    pub key_table_name: String,
619    /// The keys to add.
620    pub keys: HashSet<String>,
621}
622
623// -- Diagnostics Args --
624
625#[derive(Debug, Deserialize, JsonSchema, Serialize)]
626pub struct DiagnosticsCheckArgs {
627    /// The list of ignored diagnostics.
628    pub ignored: Vec<String>,
629    /// Whether to check AK-only references.
630    pub check_ak_only_refs: bool,
631}
632
633#[derive(Debug, Deserialize, JsonSchema, Serialize)]
634pub struct DiagnosticsUpdateArgs {
635    /// The JSON representation of the Diagnostics struct.
636    pub diagnostics: String,
637    /// The JSON representation of Vec<ContainerPath> for paths to check.
638    pub paths: String,
639    /// Whether to check AK-only references.
640    pub check_ak_only_refs: bool,
641}
642
643// -- Notes Args --
644
645#[derive(Debug, Deserialize, JsonSchema, Serialize)]
646pub struct AddNoteArgs {
647    /// The key of the target pack.
648    pub pack_key: String,
649    /// The JSON representation of the Note struct.
650    pub note: String,
651}
652
653#[derive(Debug, Deserialize, JsonSchema, Serialize)]
654pub struct DeleteNoteArgs {
655    /// The key of the target pack.
656    pub pack_key: String,
657    /// The path the note belongs to.
658    pub path: String,
659    /// The note ID.
660    pub id: u64,
661}
662
663// -- Optimization Args --
664
665#[derive(Debug, Deserialize, JsonSchema, Serialize)]
666pub struct OptimizePackFileArgs {
667    /// The key of the target pack.
668    pub pack_key: String,
669    /// The JSON representation of the OptimizerOptions struct.
670    pub options: String,
671}
672
673// -- Settings Args --
674
675#[derive(Debug, Deserialize, JsonSchema, Serialize)]
676pub struct SettingsSetBoolArgs {
677    /// The setting key.
678    pub key: String,
679    /// The boolean value.
680    pub value: bool,
681}
682
683#[derive(Debug, Deserialize, JsonSchema, Serialize)]
684pub struct SettingsSetI32Args {
685    /// The setting key.
686    pub key: String,
687    /// The integer value.
688    pub value: i32,
689}
690
691#[derive(Debug, Deserialize, JsonSchema, Serialize)]
692pub struct SettingsSetF32Args {
693    /// The setting key.
694    pub key: String,
695    /// The float value.
696    pub value: f32,
697}
698
699#[derive(Debug, Deserialize, JsonSchema, Serialize)]
700pub struct SettingsSetStringArgs {
701    /// The setting key.
702    pub key: String,
703    /// The string value.
704    pub value: String,
705}
706
707#[derive(Debug, Deserialize, JsonSchema, Serialize)]
708pub struct SettingsSetPathBufArgs {
709    /// The setting key.
710    pub key: String,
711    /// The path value.
712    pub value: PathBuf,
713}
714
715#[derive(Debug, Deserialize, JsonSchema, Serialize)]
716pub struct SettingsSetVecStringArgs {
717    /// The setting key.
718    pub key: String,
719    /// The list of string values.
720    pub value: Vec<String>,
721}
722
723#[derive(Debug, Deserialize, JsonSchema, Serialize)]
724pub struct SettingsSetVecRawArgs {
725    /// The setting key.
726    pub key: String,
727    /// The raw byte values.
728    pub value: Vec<u8>,
729}
730
731// -- Specialized Args --
732
733#[derive(Debug, Deserialize, JsonSchema, Serialize)]
734pub struct InitializeMyModFolderArgs {
735    /// The mod name.
736    pub name: String,
737    /// The game key.
738    pub game: String,
739    /// Whether to add Sublime Text support.
740    pub sublime: bool,
741    /// Whether to add VS Code support.
742    pub vscode: bool,
743    /// Optional gitignore template content.
744    pub gitignore: Option<String>,
745}
746
747#[derive(Debug, Deserialize, JsonSchema, Serialize)]
748pub struct PackMapArgs {
749    /// The key of the target pack.
750    pub pack_key: String,
751    /// The tile map paths.
752    pub tile_maps: Vec<PathBuf>,
753    /// The JSON representation of Vec<(PathBuf, String)> for tile path/name pairs.
754    pub tiles: String,
755}
756
757#[derive(Debug, Deserialize, JsonSchema, Serialize)]
758pub struct BuildStarposArgs {
759    /// The key of the target pack.
760    pub pack_key: String,
761    /// The campaign ID.
762    pub campaign_id: String,
763    /// Whether to process HLP/SPD data.
764    pub process_hlp_spd: bool,
765}
766
767#[derive(Debug, Deserialize, JsonSchema, Serialize)]
768pub struct UpdateAnimIdsArgs {
769    /// The key of the target pack.
770    pub pack_key: String,
771    /// The starting animation ID.
772    pub starting_id: i32,
773    /// The offset to apply.
774    pub offset: i32,
775}
776
777#[derive(Debug, Deserialize, JsonSchema, Serialize)]
778pub struct ExportRigidToGltfArgs {
779    /// The JSON representation of the RigidModel struct.
780    pub rigid_model: String,
781    /// The output path.
782    pub output_path: String,
783}
784
785#[derive(Debug, Deserialize, JsonSchema, Serialize)]
786pub struct SetVideoFormatArgs {
787    /// The key of the target pack.
788    pub pack_key: String,
789    /// The path of the video file in the pack.
790    pub path: String,
791    /// The JSON representation of the SupportedFormats enum.
792    pub format: String,
793}
794
795#[derive(Debug, Deserialize, JsonSchema, Serialize)]
796pub struct GetPackTranslationArgs {
797    /// The key of the target pack.
798    pub pack_key: String,
799    /// The language code.
800    pub language: String,
801}
802
803//-------------------------------------------------------------------------------//
804//                             Implementations
805//-------------------------------------------------------------------------------//
806
807#[tool_handler(router = self.tool_router)]
808#[prompt_handler(router = self.prompt_router)]
809impl rmcp::ServerHandler for McpServer {
810    fn get_info(&self) -> ServerInfo {
811        let capabilities = ServerCapabilities::builder()
812            .enable_tools()
813            .enable_prompts()
814            .enable_resources()
815            .enable_completions()
816            .enable_logging()
817            .build();
818
819        // `ServerInfo` is `#[non_exhaustive]` in rmcp, so it must be built through its constructor instead of a struct literal.
820        ServerInfo::new(capabilities).with_instructions("\
821This is the MCP server for RPFM (Rusted PackFile Manager), a tool for modding Total War games by \
822Creative Assembly. It lets you read, edit, create, and manage PackFiles (.pack) — the archive \
823format used by all modern Total War titles.
824
825## Key Concepts
826
827- **PackFile**: An archive containing game data files (DB tables, localisation, textures, models, etc.). \
828  Mods are distributed as PackFiles.
829- **pack_key**: When you open one or more PackFiles, each gets a unique key string. Use `list_open_packs` \
830  to discover available keys. Most tools require a `pack_key` parameter.
831- **DataSource**: Where data lives — `\"PackFile\"` (the user's mod), `\"GameFiles\"` (vanilla game data), \
832  `\"ParentFiles\"` (dependency mods), `\"AssKitFiles\"` (Assembly Kit data), `\"ExternalFile\"` (disk file).
833- **ContainerPath**: A path inside a pack — either `{\"File\": \"db/land_units_tables/my_table\"}` or \
834  `{\"Folder\": \"db/land_units_tables\"}`. Use an empty string for root folder.
835
836## Required Initialization Sequence
837
8381. **Set the game** — Call `set_game_selected` with the game key (e.g. `\"warhammer_3\"`) and \
839   `rebuild_dependencies: true`. This loads schemas and vanilla data.
8402. **Open a pack** — Call `open_packfiles` with filesystem path(s). Note the returned pack key(s).
8413. **Verify schema** — Call `is_schema_loaded`; if false, call `update_schemas` first.
842
843## Supported Games
844
845Valid game keys: `pharaoh_dynasties`, `pharaoh`, `warhammer_3`, `troy`, `three_kingdoms`, \
846`warhammer_2`, `warhammer`, `thrones_of_britannia`, `attila`, `rome_2`, `shogun_2`, `napoleon`, \
847`empire`, `arena`.
848
849## Common File Path Conventions
850
851- DB tables: `db/<table_name>/<file_name>` (e.g. `db/land_units_tables/my_mod`)
852- Localisation: `text/db/<file_name>.loc`
853- Scripts: `script/<path>.lua`
854- Images: `ui/<path>.png`
855
856## Pack File Types (PFHFileType)
857
858`\"Boot\"`, `\"Release\"`, `\"Patch\"`, `\"Mod\"` (default for mods), `\"Movie\"`.
859
860## Compression Formats
861
862`\"None\"` (default), `\"Lzma1\"` (legacy), `\"Lz4\"` (WH3 6.2+), `\"Zstd\"` (WH3 6.2+).
863
864## Creating New Files (NewFile)
865
866- DB table: `{\"DB\": [\"file_name\", \"table_name\", version]}` — e.g. `{\"DB\": [\"my_mod\", \"land_units_tables\", 0]}`
867- Loc file: `{\"Loc\": \"file_name\"}`
868- Text file: `{\"Text\": [\"file_name\", \"Plain\"]}` — formats: `\"Plain\"`, `\"Html\"`, `\"Xml\"`, `\"Lua\"`, `\"Cpp\"`, `\"Json\"`, `\"Markdown\"`, `\"Smithy\"`
869- AnimPack: `{\"AnimPack\": \"file_name\"}`
870- PortraitSettings: `{\"PortraitSettings\": [\"file_name\", version, [[\"entry_key\", \"entry_value\"]]]}`
871- VMD: `{\"VMD\": \"file_name\"}`
872- WSModel: `{\"WSModel\": \"file_name\"}`
873
874## Resources
875
876Use `resources/list` and `resources/read` to browse reference data: valid enum values, game lists, \
877and example JSON payloads without needing tool calls.
878
879## Responses
880
881All tool responses are JSON-serialized. On failure, an error message is returned instead of the expected data.
882")
883    }
884
885    //-----------------------------------------------------------------------//
886    // Resources
887    //-----------------------------------------------------------------------//
888
889    async fn list_resources(
890        &self,
891        _request: Option<PaginatedRequestParams>,
892        _context: RequestContext<RoleServer>,
893    ) -> Result<ListResourcesResult, McpError> {
894        let resources = vec![
895            resource("rpfm://games", "games", "List of all supported Total War game keys.", "application/json"),
896            resource("rpfm://enums/PFHFileType", "PFHFileType", "Valid PackFile type values (Boot, Release, Patch, Mod, Movie).", "application/json"),
897            resource("rpfm://enums/CompressionFormat", "CompressionFormat", "Valid compression format values (None, Lzma1, Lz4, Zstd).", "application/json"),
898            resource("rpfm://enums/DataSource", "DataSource", "Valid data source values indicating where data comes from.", "application/json"),
899            resource("rpfm://enums/ContainerPath", "ContainerPath", "ContainerPath enum variants with JSON examples.", "application/json"),
900            resource("rpfm://enums/NewFile", "NewFile", "NewFile enum variants for creating files inside packs, with JSON examples.", "application/json"),
901            resource("rpfm://enums/SupportedFormats", "SupportedFormats", "Valid video format values (CaVp8, Ivf).", "application/json"),
902            resource("rpfm://examples/global_search", "GlobalSearch example", "Example JSON for the GlobalSearch struct used by search tools.", "application/json"),
903            resource("rpfm://examples/optimizer_options", "OptimizerOptions example", "Example JSON for OptimizerOptions with all boolean fields.", "application/json"),
904            resource("rpfm://reference/initialization", "Initialization guide", "Step-by-step guide for initializing the RPFM MCP server session.", "text/plain"),
905            resource("rpfm://reference/path_conventions", "Path conventions", "Common file path conventions inside Total War PackFiles.", "text/plain"),
906        ];
907        Ok(ListResourcesResult {
908            resources,
909            ..Default::default()
910        })
911    }
912
913    async fn list_resource_templates(
914        &self,
915        _request: Option<PaginatedRequestParams>,
916        _context: RequestContext<RoleServer>,
917    ) -> Result<ListResourceTemplatesResult, McpError> {
918        Ok(ListResourceTemplatesResult {
919            resource_templates: vec![],
920            ..Default::default()
921        })
922    }
923
924    async fn read_resource(
925        &self,
926        request: ReadResourceRequestParams,
927        _context: RequestContext<RoleServer>,
928    ) -> Result<ReadResourceResult, McpError> {
929        let uri = &request.uri;
930        let content = match uri.as_str() {
931            "rpfm://games" => serde_json::json!({
932                "supported_games": [
933                    {"key": "pharaoh_dynasties", "display_name": "Total War: Pharaoh Dynasties"},
934                    {"key": "pharaoh", "display_name": "Total War: Pharaoh"},
935                    {"key": "warhammer_3", "display_name": "Total War: Warhammer III"},
936                    {"key": "troy", "display_name": "A Total War Saga: Troy"},
937                    {"key": "three_kingdoms", "display_name": "Total War: Three Kingdoms"},
938                    {"key": "warhammer_2", "display_name": "Total War: Warhammer II"},
939                    {"key": "warhammer", "display_name": "Total War: Warhammer"},
940                    {"key": "thrones_of_britannia", "display_name": "A Total War Saga: Thrones of Britannia"},
941                    {"key": "attila", "display_name": "Total War: Attila"},
942                    {"key": "rome_2", "display_name": "Total War: Rome II"},
943                    {"key": "shogun_2", "display_name": "Total War: Shogun 2"},
944                    {"key": "napoleon", "display_name": "Total War: Napoleon"},
945                    {"key": "empire", "display_name": "Total War: Empire"},
946                    {"key": "arena", "display_name": "Total War: Arena"}
947                ]
948            }).to_string(),
949
950            "rpfm://enums/PFHFileType" => serde_json::json!({
951                "enum": "PFHFileType",
952                "description": "The type/priority of a PackFile. Games load packs in type order (Boot first, Movie last).",
953                "variants": [
954                    {"name": "Boot", "value": 0, "description": "Core game boot files, loaded first."},
955                    {"name": "Release", "value": 1, "description": "Main game data files."},
956                    {"name": "Patch", "value": 2, "description": "Official patch and update files."},
957                    {"name": "Mod", "value": 3, "description": "User mod files. This is the default for mods."},
958                    {"name": "Movie", "value": 4, "description": "Cinematic and always-loaded files, loaded last."}
959                ],
960                "json_example": "\"Mod\""
961            }).to_string(),
962
963            "rpfm://enums/CompressionFormat" => serde_json::json!({
964                "enum": "CompressionFormat",
965                "description": "Compression algorithm for pack file data.",
966                "variants": [
967                    {"name": "None", "description": "No compression (default)."},
968                    {"name": "Lzma1", "description": "Legacy LZMA compression (all PFH5 games)."},
969                    {"name": "Lz4", "description": "LZ4 compression (Warhammer 3 v6.2+)."},
970                    {"name": "Zstd", "description": "Zstandard compression (Warhammer 3 v6.2+)."}
971                ],
972                "json_example": "\"None\""
973            }).to_string(),
974
975            "rpfm://enums/DataSource" => serde_json::json!({
976                "enum": "DataSource",
977                "description": "Identifies where data comes from when working with files.",
978                "variants": [
979                    {"name": "PackFile", "description": "Data from the user's currently open pack (mod files)."},
980                    {"name": "GameFiles", "description": "Data from vanilla game files."},
981                    {"name": "ParentFiles", "description": "Data from parent/dependency pack files."},
982                    {"name": "AssKitFiles", "description": "Data from the Assembly Kit (modding tools)."},
983                    {"name": "ExternalFile", "description": "Data from an external file on disk."}
984                ],
985                "json_example": "\"PackFile\""
986            }).to_string(),
987
988            "rpfm://enums/ContainerPath" => serde_json::json!({
989                "enum": "ContainerPath",
990                "description": "A path reference inside a PackFile, pointing to either a file or a folder.",
991                "variants": [
992                    {
993                        "name": "File",
994                        "description": "Path to a single file inside the pack.",
995                        "json_example": {"File": "db/land_units_tables/my_table"}
996                    },
997                    {
998                        "name": "Folder",
999                        "description": "Path to a folder inside the pack. Use empty string for root.",
1000                        "json_example": {"Folder": "db/land_units_tables"}
1001                    }
1002                ],
1003                "usage_notes": "Most tools accept a JSON array of ContainerPath objects, e.g. [{\"File\": \"path1\"}, {\"Folder\": \"path2\"}]"
1004            }).to_string(),
1005
1006            "rpfm://enums/NewFile" => serde_json::json!({
1007                "enum": "NewFile",
1008                "description": "Specifies what type of file to create inside a pack.",
1009                "variants": [
1010                    {
1011                        "name": "DB",
1012                        "description": "Create a new DB table. Args: [file_name, table_name, version].",
1013                        "json_example": {"DB": ["my_mod", "land_units_tables", 0]}
1014                    },
1015                    {
1016                        "name": "Loc",
1017                        "description": "Create a new localisation file. Arg: file_name.",
1018                        "json_example": {"Loc": "my_mod"}
1019                    },
1020                    {
1021                        "name": "Text",
1022                        "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.",
1023                        "json_example": {"Text": ["my_script", "Lua"]}
1024                    },
1025                    {
1026                        "name": "AnimPack",
1027                        "description": "Create a new AnimPack file. Arg: file_name.",
1028                        "json_example": {"AnimPack": "my_anim"}
1029                    },
1030                    {
1031                        "name": "PortraitSettings",
1032                        "description": "Create a new portrait settings file. Args: [file_name, version, entries].",
1033                        "json_example": {"PortraitSettings": ["my_portraits", 3, []]}
1034                    },
1035                    {
1036                        "name": "VMD",
1037                        "description": "Create a new VMD file. Arg: file_name.",
1038                        "json_example": {"VMD": "my_vmd"}
1039                    },
1040                    {
1041                        "name": "WSModel",
1042                        "description": "Create a new WSModel file. Arg: file_name.",
1043                        "json_example": {"WSModel": "my_model"}
1044                    }
1045                ]
1046            }).to_string(),
1047
1048            "rpfm://enums/SupportedFormats" => serde_json::json!({
1049                "enum": "SupportedFormats",
1050                "description": "Video format options for CA VP8 video files.",
1051                "variants": [
1052                    {"name": "CaVp8", "description": "CA's custom VP8 format (default)."},
1053                    {"name": "Ivf", "description": "Standard VP8 IVF format."}
1054                ],
1055                "json_example": "\"CaVp8\""
1056            }).to_string(),
1057
1058            "rpfm://examples/global_search" => serde_json::json!({
1059                "description": "Example GlobalSearch JSON for use with global_search, global_search_replace_all, etc.",
1060                "example": {
1061                    "pattern": "old_unit_name",
1062                    "replace_text": "new_unit_name",
1063                    "case_sensitive": false,
1064                    "use_regex": false,
1065                    "sources": [{"Pack": "my_mod.pack"}],
1066                    "search_on": {
1067                        "anim": false, "anim_fragment_battle": false, "anim_pack": false,
1068                        "anims_table": false, "atlas": false, "audio": false, "bmd": false,
1069                        "db": true, "esf": false, "group_formations": false, "image": false,
1070                        "loc": true, "matched_combat": false, "pack": false,
1071                        "portrait_settings": false, "rigid_model": false, "sound_bank": false,
1072                        "text": true, "uic": false, "unit_variant": false, "unknown": false,
1073                        "video": false, "schema": false
1074                    },
1075                    "matches": {
1076                        "anim": [], "anim_fragment_battle": [], "anim_pack": [],
1077                        "anims_table": [], "atlas": [], "audio": [], "bmd": [],
1078                        "db": [], "esf": [], "group_formations": [], "image": [],
1079                        "loc": [], "matched_combat": [], "pack": [],
1080                        "portrait_settings": [], "rigid_model": [], "sound_bank": [],
1081                        "text": [], "uic": [], "unit_variant": [], "unknown": [],
1082                        "video": [], "schema": {"matches": []}
1083                    },
1084                    "game_key": "warhammer_3"
1085                },
1086                "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\"."
1087            }).to_string(),
1088
1089            "rpfm://examples/optimizer_options" => serde_json::json!({
1090                "description": "OptimizerOptions struct with all boolean fields for pack optimization.",
1091                "example": {
1092                    "pack_remove_itm_files": true,
1093                    "pack_apply_compression": true,
1094                    "pack_remove_duplicated_files": false,
1095                    "db_import_datacores_into_twad_key_deletes": false,
1096                    "db_optimize_datacored_tables": false,
1097                    "table_remove_duplicated_entries": true,
1098                    "table_remove_itm_entries": true,
1099                    "table_remove_itnr_entries": true,
1100                    "table_remove_empty_file": true,
1101                    "text_remove_unused_xml_map_folders": false,
1102                    "text_remove_unused_xml_prefab_folder": false,
1103                    "text_remove_agf_files": false,
1104                    "text_remove_model_statistics_files": false,
1105                    "pts_remove_unused_art_sets": false,
1106                    "pts_remove_unused_variants": false,
1107                    "pts_remove_empty_masks": false,
1108                    "pts_remove_empty_file": false
1109                },
1110                "field_descriptions": {
1111                    "pack_remove_itm_files": "Remove files identical to vanilla (Identical To Master).",
1112                    "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.",
1113                    "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.",
1114                    "db_import_datacores_into_twad_key_deletes": "Import datacored tables into TWAD key deletes.",
1115                    "db_optimize_datacored_tables": "Optimize datacored tables.",
1116                    "table_remove_duplicated_entries": "Remove duplicate rows in tables.",
1117                    "table_remove_itm_entries": "Remove rows identical to vanilla.",
1118                    "table_remove_itnr_entries": "Remove rows identical to vanilla that are not referenced.",
1119                    "table_remove_empty_file": "Remove tables with no rows.",
1120                    "text_remove_unused_xml_map_folders": "Remove unused XML files in map folders.",
1121                    "text_remove_unused_xml_prefab_folder": "Remove unused XML files in prefab folders.",
1122                    "text_remove_agf_files": "Remove AGF files.",
1123                    "text_remove_model_statistics_files": "Remove model statistics files.",
1124                    "pts_remove_unused_art_sets": "Remove unused art sets in portrait settings.",
1125                    "pts_remove_unused_variants": "Remove unused variants in portrait settings.",
1126                    "pts_remove_empty_masks": "Remove empty masks in portrait settings.",
1127                    "pts_remove_empty_file": "Remove empty portrait settings files."
1128                }
1129            }).to_string(),
1130
1131            "rpfm://reference/initialization" => "\
1132RPFM MCP Server Initialization Guide
1133=====================================
1134
1135Before you can work with PackFiles, you must initialize the server session:
1136
1137Step 1: Set the game
1138    Call: set_game_selected(game_name: \"warhammer_3\", rebuild_dependencies: true)
1139    This loads the correct schemas and vanilla game data for the selected title.
1140    Valid game keys: pharaoh_dynasties, pharaoh, warhammer_3, troy, three_kingdoms,
1141    warhammer_2, warhammer, thrones_of_britannia, attila, rome_2, shogun_2,
1142    napoleon, empire, arena.
1143
1144Step 2: Verify schema is loaded
1145    Call: is_schema_loaded()
1146    If it returns false, call update_schemas() to download the latest schemas.
1147
1148Step 3: Open a PackFile
1149    Call: open_packfiles(paths: [\"/path/to/my_mod.pack\"])
1150    The response returns pack info including the pack_key you'll use for all
1151    subsequent operations.
1152
1153Step 4: Verify dependencies (optional but recommended)
1154    Call: is_there_a_dependency_database(value: true)
1155    If false, call generate_dependencies_cache() to build the dependency database.
1156
1157After initialization, use list_open_packs() to see all open pack keys at any time.
1158".to_string(),
1159
1160            "rpfm://reference/path_conventions" => "\
1161Total War PackFile Path Conventions
1162====================================
1163
1164Files inside PackFiles follow specific path conventions:
1165
1166DB Tables:
1167    db/<table_name>/<file_name>
1168    Example: db/land_units_tables/my_mod
1169    Example: db/unit_stats_land_tables/custom_units
1170
1171Localisation (Loc) files:
1172    text/db/<file_name>.loc
1173    text/<file_name>.loc
1174    Example: text/db/my_mod.loc
1175
1176Scripts:
1177    script/<path>.lua
1178    script/campaign/mod/<script_name>.lua
1179    Example: script/campaign/mod/my_mod_script.lua
1180
1181UI Images:
1182    ui/<path>.png
1183    Path may vary depending on the purpose of the image.
1184
1185Models and Animations:
1186    variantmeshes/<path>
1187    animations/<path>
1188    Example: variantmeshes/wh_variantmodels/hu1/my_unit/my_unit.wsmodel
1189
1190Audio:
1191    audio/<path>.bnk
1192
1193Maps:
1194    terrain/tiles/battle/<map_name>/
1195".to_string(),
1196
1197            _ => {
1198                return Err(McpError {
1199                    code: ErrorCode::INVALID_PARAMS,
1200                    message: format!("Unknown resource URI: {uri}").into(),
1201                    data: None,
1202                });
1203            }
1204        };
1205
1206        Ok(ReadResourceResult::new(vec![ResourceContents::text(content, uri.clone())]))
1207    }
1208
1209    //-----------------------------------------------------------------------//
1210    // Completions
1211    //-----------------------------------------------------------------------//
1212
1213    async fn complete(
1214        &self,
1215        request: CompleteRequestParams,
1216        _context: RequestContext<RoleServer>,
1217    ) -> Result<CompleteResult, McpError> {
1218        let argument_name = &request.argument.name;
1219        let partial = &request.argument.value;
1220
1221        let candidates: Vec<String> = match argument_name.as_str() {
1222            "game_name" | "game_key" | "game" => {
1223                let games = vec![
1224                    "pharaoh_dynasties", "pharaoh", "warhammer_3", "troy",
1225                    "three_kingdoms", "warhammer_2", "warhammer",
1226                    "thrones_of_britannia", "attila", "rome_2", "shogun_2",
1227                    "napoleon", "empire", "arena",
1228                ];
1229                games.into_iter()
1230                    .filter(|g| g.starts_with(partial))
1231                    .map(String::from)
1232                    .collect()
1233            },
1234            "pack_file_type" => {
1235                let types = vec!["\"Boot\"", "\"Release\"", "\"Patch\"", "\"Mod\"", "\"Movie\""];
1236                types.into_iter()
1237                    .filter(|t| t.starts_with(partial))
1238                    .map(String::from)
1239                    .collect()
1240            },
1241            "format" => {
1242                // Could be CompressionFormat or SupportedFormats depending on tool
1243                let formats = vec![
1244                    "\"None\"", "\"Lzma1\"", "\"Lz4\"", "\"Zstd\"",
1245                    "\"CaVp8\"", "\"Ivf\"",
1246                ];
1247                formats.into_iter()
1248                    .filter(|f| f.starts_with(partial))
1249                    .map(String::from)
1250                    .collect()
1251            },
1252            "source" => {
1253                let sources = vec![
1254                    "\"PackFile\"", "\"GameFiles\"", "\"ParentFiles\"",
1255                    "\"AssKitFiles\"", "\"ExternalFile\"",
1256                ];
1257                sources.into_iter()
1258                    .filter(|s| s.starts_with(partial))
1259                    .map(String::from)
1260                    .collect()
1261            },
1262            _ => vec![],
1263        };
1264
1265        let total = candidates.len() as u32;
1266        let values: Vec<String> = candidates.into_iter().take(100).collect();
1267        let has_more = total > 100;
1268
1269        Ok(CompleteResult::new(CompletionInfo {
1270            values,
1271            total: Some(total),
1272            has_more: Some(has_more),
1273        }))
1274    }
1275
1276    //-----------------------------------------------------------------------//
1277    // Logging
1278    //-----------------------------------------------------------------------//
1279
1280    async fn set_level(
1281        &self,
1282        _request: SetLevelRequestParams,
1283        _context: RequestContext<RoleServer>,
1284    ) -> Result<(), McpError> {
1285        // Acknowledge the logging level request. RPFM uses its own logging
1286        // infrastructure (rpfm_telemetry/sentry), so we accept the request but
1287        // don't change the internal log level.
1288        Ok(())
1289    }
1290}
1291
1292#[tool_router]
1293impl McpServer {
1294
1295    pub fn new(session: Arc<Session>) -> Self {
1296        Self {
1297            session,
1298            tool_router: McpServer::tool_router(),
1299            prompt_router: McpServer::prompt_router(),
1300        }
1301    }
1302
1303    //-----------------------------------------------------------------------//
1304    // Existing tools
1305    //-----------------------------------------------------------------------//
1306
1307    #[tool(name = "call_command", description = "Call any IPC command directly. Use this for commands not yet wrapped as named tools.")]
1308    pub async fn call_command(&self, params: Parameters<CallCommandArgs>) -> Result<CallToolResult, McpError> {
1309        let command: Command = parse_json!(&params.0.command);
1310        send_and_respond!(self, "call_command", command)
1311    }
1312
1313    //-----------------------------------------------------------------------//
1314    // Pack Lifecycle
1315    //-----------------------------------------------------------------------//
1316
1317    #[tool(description = "Create a new empty PackFile.")]
1318    pub async fn new_pack(&self) -> Result<CallToolResult, McpError> {
1319        send_and_respond!(self, "new_pack", Command::NewPack)
1320    }
1321
1322    #[tool(description = "Open one or more PackFiles. Returns the info about the open pack.")]
1323    pub async fn open_packfiles(&self, params: Parameters<OpenPackfilesArgs>) -> Result<CallToolResult, McpError> {
1324        send_and_respond!(self, "open_packfiles", Command::OpenPackFiles(params.0.paths))
1325    }
1326
1327    #[tool(description = "Save the pack identified by `pack_key`.")]
1328    pub async fn save_packfile(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1329        send_and_respond!(self, "save_packfile", Command::SavePack(params.0.pack_key))
1330    }
1331
1332    #[tool(description = "Close the pack identified by `pack_key` without saving. Any unsaved changes will be lost.")]
1333    pub async fn close_pack(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1334        send_and_respond!(self, "close_pack", Command::ClosePack(params.0.pack_key))
1335    }
1336
1337    #[tool(description = "Save the pack identified by `pack_key` to a new path.")]
1338    pub async fn save_pack_as(&self, params: Parameters<PackKeyPathArg>) -> Result<CallToolResult, McpError> {
1339        send_and_respond!(self, "save_pack_as", Command::SavePackAs(params.0.pack_key, params.0.path))
1340    }
1341
1342    #[tool(description = "Clean the pack identified by `pack_key` from corrupted files and save to a path. Use if normal save fails.")]
1343    pub async fn clean_and_save_pack_as(&self, params: Parameters<PackKeyPathArg>) -> Result<CallToolResult, McpError> {
1344        send_and_respond!(self, "clean_and_save_pack_as", Command::CleanAndSavePackAs(params.0.pack_key, params.0.path))
1345    }
1346
1347    #[tool(description = "Trigger a backup autosave for the pack identified by `pack_key`.")]
1348    pub async fn trigger_backup_autosave(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1349        send_and_respond!(self, "trigger_backup_autosave", Command::TriggerBackupAutosave(params.0.pack_key))
1350    }
1351
1352    #[tool(description = "Open all CA (vanilla) PackFiles for the selected game as one merged PackFile.")]
1353    pub async fn load_all_ca_pack_files(&self) -> Result<CallToolResult, McpError> {
1354        send_and_respond!(self, "load_all_ca_pack_files", Command::LoadAllCAPackFiles)
1355    }
1356
1357    //-----------------------------------------------------------------------//
1358    // Pack Metadata
1359    //-----------------------------------------------------------------------//
1360
1361    #[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\\\"\"")]
1362    pub async fn set_pack_file_type(&self, params: Parameters<SetPackFileTypeArgs>) -> Result<CallToolResult, McpError> {
1363        let pfh_type = parse_json!(&params.0.pack_file_type);
1364        send_and_respond!(self, "set_pack_file_type", Command::SetPackFileType(params.0.pack_key, pfh_type))
1365    }
1366
1367    #[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\\\"\"")]
1368    pub async fn change_compression_format(&self, params: Parameters<ChangeCompressionFormatArgs>) -> Result<CallToolResult, McpError> {
1369        let format = parse_json!(&params.0.format);
1370        send_and_respond!(self, "change_compression_format", Command::ChangeCompressionFormat(params.0.pack_key, format))
1371    }
1372
1373    #[tool(description = "Change whether the pack index includes timestamps for the pack identified by `pack_key`.")]
1374    pub async fn change_index_includes_timestamp(&self, params: Parameters<PackKeyBoolArg>) -> Result<CallToolResult, McpError> {
1375        send_and_respond!(self, "change_index_includes_timestamp", Command::ChangeIndexIncludesTimestamp(params.0.pack_key, params.0.value))
1376    }
1377
1378    #[tool(description = "Get the file path of the pack identified by `pack_key`.")]
1379    pub async fn get_pack_file_path(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1380        send_and_respond!(self, "get_pack_file_path", Command::GetPackFilePath(params.0.pack_key))
1381    }
1382
1383    #[tool(description = "Get the file name of the pack identified by `pack_key`.")]
1384    pub async fn get_pack_file_name(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1385        send_and_respond!(self, "get_pack_file_name", Command::GetPackFileName(params.0.pack_key))
1386    }
1387
1388    #[tool(description = "Get the settings of the pack identified by `pack_key`.")]
1389    pub async fn get_pack_settings(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1390        send_and_respond!(self, "get_pack_settings", Command::GetPackSettings(params.0.pack_key))
1391    }
1392
1393    #[tool(description = "Set the settings of the pack identified by `pack_key`. The `settings` is a PackSettings JSON object containing pack-level configuration.")]
1394    pub async fn set_pack_settings(&self, params: Parameters<SetPackSettingsArgs>) -> Result<CallToolResult, McpError> {
1395        let settings = parse_json!(&params.0.settings);
1396        send_and_respond!(self, "set_pack_settings", Command::SetPackSettings(params.0.pack_key, settings))
1397    }
1398
1399    #[tool(description = "Get the list of PackFiles marked as dependencies of the pack identified by `pack_key`.")]
1400    pub async fn get_dependency_pack_files_list(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1401        send_and_respond!(self, "get_dependency_pack_files_list", Command::GetDependencyPackFilesList(params.0.pack_key))
1402    }
1403
1404    #[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\"]].")]
1405    pub async fn set_dependency_pack_files_list(&self, params: Parameters<SetDependencyPackFilesListArgs>) -> Result<CallToolResult, McpError> {
1406        let list = parse_json!(&params.0.list);
1407        send_and_respond!(self, "set_dependency_pack_files_list", Command::SetDependencyPackFilesList(params.0.pack_key, list))
1408    }
1409
1410    //-----------------------------------------------------------------------//
1411    // File Operations
1412    //-----------------------------------------------------------------------//
1413
1414    #[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).")]
1415    pub async fn decode_packed_file(&self, params: Parameters<DecodePackedFileArgs>) -> Result<CallToolResult, McpError> {
1416        send_and_respond!(self, "decode_packed_file", Command::DecodePackedFile(params.0.pack_key, params.0.path, params.0.source))
1417    }
1418
1419    #[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, []]}.")]
1420    pub async fn new_packed_file(&self, params: Parameters<NewPackedFileArgs>) -> Result<CallToolResult, McpError> {
1421        let new_file = parse_json!(&params.0.new_file);
1422        send_and_respond!(self, "new_packed_file", Command::NewPackedFile(params.0.pack_key, params.0.path, new_file))
1423    }
1424
1425    #[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.")]
1426    pub async fn add_packed_files(&self, params: Parameters<AddPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1427        let dest: Vec<ContainerPath> = parse_json!(&params.0.destination_paths);
1428        send_and_respond!(self, "add_packed_files", Command::AddPackedFiles(params.0.pack_key, params.0.source_paths, dest, params.0.ignore_paths))
1429    }
1430
1431    #[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\"}].")]
1432    pub async fn add_packed_files_from_pack_file(&self, params: Parameters<AddPackedFilesFromPackFileArgs>) -> Result<CallToolResult, McpError> {
1433        let paths: Vec<ContainerPath> = parse_json!(&params.0.container_paths);
1434        send_and_respond!(self, "add_packed_files_from_pack_file", Command::AddPackedFilesFromPackFile(params.0.pack_key, params.0.source_pack_path, paths))
1435    }
1436
1437    #[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.")]
1438    pub async fn add_packed_files_from_pack_file_to_animpack(&self, params: Parameters<AddPackedFilesFromPackFileToAnimpackArgs>) -> Result<CallToolResult, McpError> {
1439        let paths: Vec<ContainerPath> = parse_json!(&params.0.container_paths);
1440        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))
1441    }
1442
1443    #[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\"}].")]
1444    pub async fn add_packed_files_from_animpack(&self, params: Parameters<AddPackedFilesFromAnimpackArgs>) -> Result<CallToolResult, McpError> {
1445        let paths: Vec<ContainerPath> = parse_json!(&params.0.container_paths);
1446        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))
1447    }
1448
1449    #[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\"}].")]
1450    pub async fn delete_packed_files(&self, params: Parameters<ContainerPathsArg>) -> Result<CallToolResult, McpError> {
1451        let paths: Vec<ContainerPath> = parse_json!(&params.0.paths);
1452        send_and_respond!(self, "delete_packed_files", Command::DeletePackedFiles(params.0.pack_key, paths))
1453    }
1454
1455    #[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\"}].")]
1456    pub async fn delete_from_animpack(&self, params: Parameters<DeleteFromAnimpackArgs>) -> Result<CallToolResult, McpError> {
1457        let paths: Vec<ContainerPath> = parse_json!(&params.0.container_paths);
1458        send_and_respond!(self, "delete_from_animpack", Command::DeleteFromAnimpack(params.0.pack_key, params.0.animpack_path, paths))
1459    }
1460
1461    #[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.")]
1462    pub async fn extract_packed_files(&self, params: Parameters<ExtractPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1463        let source: BTreeMap<DataSource, Vec<ContainerPath>> = parse_json!(&params.0.source_paths);
1464        send_and_respond!(self, "extract_packed_files", Command::ExtractPackedFiles(params.0.pack_key, source, params.0.destination_path, params.0.export_as_tsv))
1465    }
1466
1467    #[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\"}]].")]
1468    pub async fn rename_packed_files(&self, params: Parameters<RenamePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1469        let renames: Vec<(ContainerPath, ContainerPath)> = parse_json!(&params.0.renames);
1470        send_and_respond!(self, "rename_packed_files", Command::RenamePackedFiles(params.0.pack_key, renames))
1471    }
1472
1473    #[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.")]
1474    pub async fn copy_packed_files(&self, params: Parameters<CopyOrCutPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1475        let paths_by_pack: BTreeMap<String, Vec<ContainerPath>> = parse_json!(&params.0.paths_by_pack);
1476        send_and_respond!(self, "copy_packed_files", Command::CopyPackedFiles(paths_by_pack))
1477    }
1478
1479    #[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.")]
1480    pub async fn cut_packed_files(&self, params: Parameters<CopyOrCutPackedFilesArgs>) -> Result<CallToolResult, McpError> {
1481        let paths_by_pack: BTreeMap<String, Vec<ContainerPath>> = parse_json!(&params.0.paths_by_pack);
1482        send_and_respond!(self, "cut_packed_files", Command::CutPackedFiles(paths_by_pack))
1483    }
1484
1485    #[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.")]
1486    pub async fn paste_packed_files(&self, params: Parameters<PastePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1487        send_and_respond!(self, "paste_packed_files", Command::PastePackedFiles(params.0.pack_key, params.0.destination_path))
1488    }
1489
1490    #[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\"}].")]
1491    pub async fn duplicate_packed_files(&self, params: Parameters<DuplicatePackedFilesArgs>) -> Result<CallToolResult, McpError> {
1492        let paths: Vec<ContainerPath> = parse_json!(&params.0.paths);
1493        send_and_respond!(self, "duplicate_packed_files", Command::DuplicatePackedFiles(params.0.pack_key, paths))
1494    }
1495
1496    #[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`).")]
1497    pub async fn save_packed_file_from_view(&self, params: Parameters<SavePackedFileFromViewArgs>) -> Result<CallToolResult, McpError> {
1498        let data: RFileDecoded = parse_json!(&params.0.data);
1499        send_and_respond!(self, "save_packed_file_from_view", Command::SavePackedFileFromView(params.0.pack_key, params.0.path, data))
1500    }
1501
1502    #[tool(description = "Save a file from an external program back to the pack identified by `pack_key`.")]
1503    pub async fn save_packed_file_from_external_view(&self, params: Parameters<SavePackedFileFromExternalViewArgs>) -> Result<CallToolResult, McpError> {
1504        send_and_respond!(self, "save_packed_file_from_external_view", Command::SavePackedFileFromExternalView(params.0.pack_key, params.0.internal_path, params.0.external_path))
1505    }
1506
1507    #[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.")]
1508    pub async fn save_packed_files_to_pack_file_and_clean(&self, params: Parameters<SavePackedFilesToPackFileAndCleanArgs>) -> Result<CallToolResult, McpError> {
1509        let files: Vec<RFile> = parse_json!(&params.0.files);
1510        send_and_respond!(self, "save_packed_files_to_pack_file_and_clean", Command::SavePackedFilesToPackFileAndClean(params.0.pack_key, files, params.0.optimize))
1511    }
1512
1513    #[tool(description = "Get the raw binary data of a file in the pack identified by `pack_key`.")]
1514    pub async fn get_packed_file_raw_data(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1515        send_and_respond!(self, "get_packed_file_raw_data", Command::GetPackedFileRawData(params.0.pack_key, params.0.value))
1516    }
1517
1518    #[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\"}.")]
1519    pub async fn open_packed_file_in_external_program(&self, params: Parameters<OpenPackedFileInExternalProgramArgs>) -> Result<CallToolResult, McpError> {
1520        let cp: ContainerPath = parse_json!(&params.0.container_path);
1521        send_and_respond!(self, "open_packed_file_in_external_program", Command::OpenPackedFileInExternalProgram(params.0.pack_key, params.0.source, cp))
1522    }
1523
1524    #[tool(description = "Open the folder containing the pack identified by `pack_key` in the file manager.")]
1525    pub async fn open_containing_folder(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1526        send_and_respond!(self, "open_containing_folder", Command::OpenContainingFolder(params.0.pack_key))
1527    }
1528
1529    #[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\"}].")]
1530    pub async fn clean_cache(&self, params: Parameters<ContainerPathsArg>) -> Result<CallToolResult, McpError> {
1531        let paths: Vec<ContainerPath> = parse_json!(&params.0.paths);
1532        send_and_respond!(self, "clean_cache", Command::CleanCache(params.0.pack_key, paths))
1533    }
1534
1535    #[tool(description = "Check if a folder exists in the pack identified by `pack_key`.")]
1536    pub async fn folder_exists(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1537        send_and_respond!(self, "folder_exists", Command::FolderExists(params.0.pack_key, params.0.value))
1538    }
1539
1540    #[tool(description = "Check if a file exists in the pack identified by `pack_key`.")]
1541    pub async fn packed_file_exists(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1542        send_and_respond!(self, "packed_file_exists", Command::PackedFileExists(params.0.pack_key, params.0.value))
1543    }
1544
1545    #[tool(description = "Get the info of one or more files in the pack identified by `pack_key`.")]
1546    pub async fn get_packed_files_info(&self, params: Parameters<PackKeyStringsArg>) -> Result<CallToolResult, McpError> {
1547        send_and_respond!(self, "get_packed_files_info", Command::GetPackedFilesInfo(params.0.pack_key, params.0.values))
1548    }
1549
1550    #[tool(description = "Get the info of a single file in the pack identified by `pack_key`.")]
1551    pub async fn get_rfile_info(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1552        send_and_respond!(self, "get_rfile_info", Command::GetRFileInfo(params.0.pack_key, params.0.value))
1553    }
1554
1555    //-----------------------------------------------------------------------//
1556    // Game Selection
1557    //-----------------------------------------------------------------------//
1558
1559    #[tool(description = "Get the currently selected game key.")]
1560    pub async fn get_game_selected(&self) -> Result<CallToolResult, McpError> {
1561        send_and_respond!(self, "get_game_selected", Command::GetGameSelected)
1562    }
1563
1564    #[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.")]
1565    pub async fn set_game_selected(&self, params: Parameters<SetGameSelectedArgs>) -> Result<CallToolResult, McpError> {
1566        send_and_respond!(self, "set_game_selected", Command::SetGameSelected(params.0.game_name, params.0.rebuild_dependencies))
1567    }
1568
1569    //-----------------------------------------------------------------------//
1570    // Dependencies
1571    //-----------------------------------------------------------------------//
1572
1573    #[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.")]
1574    pub async fn generate_dependencies_cache(&self) -> Result<CallToolResult, McpError> {
1575        send_and_respond!(self, "generate_dependencies_cache", Command::GenerateDependenciesCache)
1576    }
1577
1578    #[tool(description = "Rebuild dependencies. Pass true for full rebuild, false for mod-specific only.")]
1579    pub async fn rebuild_dependencies(&self, params: Parameters<BoolArg>) -> Result<CallToolResult, McpError> {
1580        send_and_respond!(self, "rebuild_dependencies", Command::RebuildDependencies(params.0.value))
1581    }
1582
1583    #[tool(description = "Check if there is a dependency database loaded. Pass true to ensure AssKit data is included.")]
1584    pub async fn is_there_a_dependency_database(&self, params: Parameters<BoolArg>) -> Result<CallToolResult, McpError> {
1585        send_and_respond!(self, "is_there_a_dependency_database", Command::IsThereADependencyDatabase(params.0.value))
1586    }
1587
1588    #[tool(description = "Get the table names of all DB files in dependency PackFiles.")]
1589    pub async fn get_table_list_from_dependency_pack_file(&self) -> Result<CallToolResult, McpError> {
1590        send_and_respond!(self, "get_table_list_from_dependency_pack_file", Command::GetTableListFromDependencyPackFile)
1591    }
1592
1593    #[tool(description = "Get custom table names (start_pos_, twad_ prefixes) from the schema.")]
1594    pub async fn get_custom_table_list(&self) -> Result<CallToolResult, McpError> {
1595        send_and_respond!(self, "get_custom_table_list", Command::GetCustomTableList)
1596    }
1597
1598    #[tool(description = "Get the version of a table from the dependency database.")]
1599    pub async fn get_table_version_from_dependency_pack_file(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1600        send_and_respond!(self, "get_table_version_from_dependency_pack_file", Command::GetTableVersionFromDependencyPackFile(params.0.value))
1601    }
1602
1603    #[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.")]
1604    pub async fn get_table_definition_from_dependency_pack_file(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1605        send_and_respond!(self, "get_table_definition_from_dependency_pack_file", Command::GetTableDefinitionFromDependencyPackFile(params.0.value))
1606    }
1607
1608    #[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`.")]
1609    pub async fn get_tables_from_dependencies(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1610        send_and_respond!(self, "get_tables_from_dependencies", Command::GetTablesFromDependencies(params.0.value))
1611    }
1612
1613    #[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\"}]}.")]
1614    pub async fn import_dependencies_to_open_pack_file(&self, params: Parameters<ImportDependenciesArgs>) -> Result<CallToolResult, McpError> {
1615        let paths: BTreeMap<DataSource, Vec<ContainerPath>> = parse_json!(&params.0.paths);
1616        send_and_respond!(self, "import_dependencies_to_open_pack_file", Command::ImportDependenciesToOpenPackFile(params.0.pack_key, paths))
1617    }
1618
1619    #[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.")]
1620    pub async fn get_rfiles_from_all_sources(&self, params: Parameters<GetRFilesFromAllSourcesArgs>) -> Result<CallToolResult, McpError> {
1621        let paths: Vec<ContainerPath> = parse_json!(&params.0.paths);
1622        send_and_respond!(self, "get_rfiles_from_all_sources", Command::GetRFilesFromAllSources(paths, params.0.lowercase))
1623    }
1624
1625    #[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.")]
1626    pub async fn get_packed_files_names_starting_with_path_from_all_sources(&self, params: Parameters<ContainerPathArg>) -> Result<CallToolResult, McpError> {
1627        let path: ContainerPath = parse_json!(&params.0.path);
1628        send_and_respond!(self, "get_packed_files_names_starting_with_path_from_all_sources", Command::GetPackedFilesNamesStartingWitPathFromAllSources(path))
1629    }
1630
1631    #[tool(description = "Get local art set IDs from campaign_character_arts_tables in the pack identified by `pack_key`.")]
1632    pub async fn local_art_set_ids(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1633        send_and_respond!(self, "local_art_set_ids", Command::LocalArtSetIds(params.0.pack_key))
1634    }
1635
1636    #[tool(description = "Get art set IDs from dependencies' campaign_character_arts_tables.")]
1637    pub async fn dependencies_art_set_ids(&self) -> Result<CallToolResult, McpError> {
1638        send_and_respond!(self, "dependencies_art_set_ids", Command::DependenciesArtSetIds)
1639    }
1640
1641    //-----------------------------------------------------------------------//
1642    // Search
1643    //-----------------------------------------------------------------------//
1644
1645    #[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.")]
1646    pub async fn global_search(&self, params: Parameters<GlobalSearchArgs>) -> Result<CallToolResult, McpError> {
1647        let search = parse_json!(&params.0.search);
1648        send_and_respond!(self, "global_search", Command::GlobalSearch(params.0.pack_key, search))
1649    }
1650
1651    #[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.")]
1652    pub async fn global_search_replace_matches(&self, params: Parameters<GlobalSearchReplaceMatchesArgs>) -> Result<CallToolResult, McpError> {
1653        let search = parse_json!(&params.0.search);
1654        let matches = parse_json!(&params.0.matches);
1655        send_and_respond!(self, "global_search_replace_matches", Command::GlobalSearchReplaceMatches(params.0.pack_key, search, matches))
1656    }
1657
1658    #[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.")]
1659    pub async fn global_search_replace_all(&self, params: Parameters<GlobalSearchArgs>) -> Result<CallToolResult, McpError> {
1660        let search = parse_json!(&params.0.search);
1661        send_and_respond!(self, "global_search_replace_all", Command::GlobalSearchReplaceAll(params.0.pack_key, search))
1662    }
1663
1664    #[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.")]
1665    pub async fn search_references(&self, params: Parameters<SearchReferencesArgs>) -> Result<CallToolResult, McpError> {
1666        let map: HashMap<String, Vec<String>> = parse_json!(&params.0.reference_map);
1667        send_and_respond!(self, "search_references", Command::SearchReferences(params.0.pack_key, map, params.0.value))
1668    }
1669
1670    #[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.")]
1671    pub async fn get_reference_data_from_definition(&self, params: Parameters<GetReferenceDataFromDefinitionArgs>) -> Result<CallToolResult, McpError> {
1672        let def = parse_json!(&params.0.definition);
1673        send_and_respond!(self, "get_reference_data_from_definition", Command::GetReferenceDataFromDefinition(params.0.pack_key, params.0.table_name, def, params.0.force))
1674    }
1675
1676    #[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.")]
1677    pub async fn go_to_definition(&self, params: Parameters<GoToDefinitionArgs>) -> Result<CallToolResult, McpError> {
1678        send_and_respond!(self, "go_to_definition", Command::GoToDefinition(params.0.pack_key, params.0.table_name, params.0.column_name, params.0.values))
1679    }
1680
1681    #[tool(description = "Go to a loc key's location in the pack identified by `pack_key`.")]
1682    pub async fn go_to_loc(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1683        send_and_respond!(self, "go_to_loc", Command::GoToLoc(params.0.pack_key, params.0.value))
1684    }
1685
1686    #[tool(description = "Get the source data of a loc key in the pack identified by `pack_key`.")]
1687    pub async fn get_source_data_from_loc_key(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1688        send_and_respond!(self, "get_source_data_from_loc_key", Command::GetSourceDataFromLocKey(params.0.pack_key, params.0.value))
1689    }
1690
1691    //-----------------------------------------------------------------------//
1692    // Schema
1693    //-----------------------------------------------------------------------//
1694
1695    #[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.")]
1696    pub async fn save_schema(&self, params: Parameters<SaveSchemaArgs>) -> Result<CallToolResult, McpError> {
1697        let schema = parse_json!(&params.0.schema);
1698        send_and_respond!(self, "save_schema", Command::SaveSchema(schema))
1699    }
1700
1701    #[tool(description = "Update the currently loaded schema with data from the game's Assembly Kit.")]
1702    pub async fn update_current_schema_from_asskit(&self) -> Result<CallToolResult, McpError> {
1703        send_and_respond!(self, "update_current_schema_from_asskit", Command::UpdateCurrentSchemaFromAssKit)
1704    }
1705
1706    #[tool(description = "Update schemas from the remote repository.")]
1707    pub async fn update_schemas(&self) -> Result<CallToolResult, McpError> {
1708        send_and_respond!(self, "update_schemas", Command::UpdateSchemas)
1709    }
1710
1711    #[tool(description = "Check if a schema is currently loaded.")]
1712    pub async fn is_schema_loaded(&self) -> Result<CallToolResult, McpError> {
1713        send_and_respond!(self, "is_schema_loaded", Command::IsSchemaLoaded)
1714    }
1715
1716    #[tool(description = "Get the current schema.")]
1717    pub async fn get_schema(&self) -> Result<CallToolResult, McpError> {
1718        send_and_respond!(self, "get_schema", Command::Schema)
1719    }
1720
1721    #[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.")]
1722    pub async fn definitions_by_table_name(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1723        send_and_respond!(self, "definitions_by_table_name", Command::DefinitionsByTableName(params.0.value))
1724    }
1725
1726    #[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.")]
1727    pub async fn definition_by_table_name_and_version(&self, params: Parameters<StringI32Args>) -> Result<CallToolResult, McpError> {
1728        send_and_respond!(self, "definition_by_table_name_and_version", Command::DefinitionByTableNameAndVersion(params.0.name, params.0.version))
1729    }
1730
1731    #[tool(description = "Delete a definition by table name and version.")]
1732    pub async fn delete_definition(&self, params: Parameters<StringI32Args>) -> Result<CallToolResult, McpError> {
1733        send_and_respond!(self, "delete_definition", Command::DeleteDefinition(params.0.name, params.0.version))
1734    }
1735
1736    #[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`).")]
1737    pub async fn referencing_columns_for_definition(&self, params: Parameters<ReferencingColumnsForDefinitionArgs>) -> Result<CallToolResult, McpError> {
1738        let def = parse_json!(&params.0.definition);
1739        send_and_respond!(self, "referencing_columns_for_definition", Command::ReferencingColumnsForDefinition(params.0.table_name, def))
1740    }
1741
1742    #[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`).")]
1743    pub async fn fields_processed(&self, params: Parameters<DefinitionArg>) -> Result<CallToolResult, McpError> {
1744        let def = parse_json!(&params.0.definition);
1745        send_and_respond!(self, "fields_processed", Command::FieldsProcessed(def))
1746    }
1747
1748    #[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\": {...}}}.")]
1749    pub async fn save_local_schema_patch(&self, params: Parameters<SchemaPatchArgs>) -> Result<CallToolResult, McpError> {
1750        let patches = parse_json!(&params.0.patches);
1751        send_and_respond!(self, "save_local_schema_patch", Command::SaveLocalSchemaPatch(patches))
1752    }
1753
1754    #[tool(description = "Remove local schema patches for a table.")]
1755    pub async fn remove_local_schema_patches_for_table(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1756        send_and_respond!(self, "remove_local_schema_patches_for_table", Command::RemoveLocalSchemaPatchesForTable(params.0.value))
1757    }
1758
1759    #[tool(description = "Remove local schema patches for a specific field in a table.")]
1760    pub async fn remove_local_schema_patches_for_table_and_field(&self, params: Parameters<SettingsSetStringArgs>) -> Result<CallToolResult, McpError> {
1761        send_and_respond!(self, "remove_local_schema_patches_for_table_and_field", Command::RemoveLocalSchemaPatchesForTableAndField(params.0.key, params.0.value))
1762    }
1763
1764    #[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`).")]
1765    pub async fn import_schema_patch(&self, params: Parameters<SchemaPatchArgs>) -> Result<CallToolResult, McpError> {
1766        let patches = parse_json!(&params.0.patches);
1767        send_and_respond!(self, "import_schema_patch", Command::ImportSchemaPatch(patches))
1768    }
1769
1770    //-----------------------------------------------------------------------//
1771    // Table Operations
1772    //-----------------------------------------------------------------------//
1773
1774    #[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).")]
1775    pub async fn merge_files(&self, params: Parameters<MergeFilesArgs>) -> Result<CallToolResult, McpError> {
1776        let paths: Vec<ContainerPath> = parse_json!(&params.0.paths);
1777        let mut options = MergeOptions::default();
1778        options.set_delta_merge(params.0.delta_merge);
1779        send_and_respond!(self, "merge_files", Command::MergeFiles(params.0.pack_key, paths, params.0.merged_path, params.0.delete_source, options))
1780    }
1781
1782    #[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\"}.")]
1783    pub async fn update_table(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1784        let path: ContainerPath = parse_json!(&params.0.value);
1785        send_and_respond!(self, "update_table", Command::UpdateTable(params.0.pack_key, path))
1786    }
1787
1788    #[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\"]].")]
1789    pub async fn cascade_edition(&self, params: Parameters<CascadeEditionArgs>) -> Result<CallToolResult, McpError> {
1790        let def = parse_json!(&params.0.definition);
1791        let changes = parse_json!(&params.0.changes);
1792        send_and_respond!(self, "cascade_edition", Command::CascadeEdition(params.0.pack_key, params.0.table_name, def, changes))
1793    }
1794
1795    #[tool(description = "Get table paths by table name from the pack identified by `pack_key`.")]
1796    pub async fn get_tables_by_table_name(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1797        send_and_respond!(self, "get_tables_by_table_name", Command::GetTablesByTableName(params.0.pack_key, params.0.value))
1798    }
1799
1800    #[tool(description = "Add keys to the key_deletes table in the pack identified by `pack_key`.")]
1801    pub async fn add_keys_to_key_deletes(&self, params: Parameters<AddKeysToKeyDeletesArgs>) -> Result<CallToolResult, McpError> {
1802        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))
1803    }
1804
1805    #[tool(description = "Export a table from the pack identified by `pack_key` to a TSV file.")]
1806    pub async fn export_tsv(&self, params: Parameters<TsvExportArgs>) -> Result<CallToolResult, McpError> {
1807        send_and_respond!(self, "export_tsv", Command::ExportTSV(params.0.pack_key, params.0.table_path, params.0.tsv_path, DataSource::PackFile))
1808    }
1809
1810    #[tool(description = "Import a TSV file to a table in the pack identified by `pack_key`.")]
1811    pub async fn import_tsv(&self, params: Parameters<TsvImportArgs>) -> Result<CallToolResult, McpError> {
1812        send_and_respond!(self, "import_tsv", Command::ImportTSV(params.0.pack_key, params.0.table_path, params.0.tsv_path))
1813    }
1814
1815    //-----------------------------------------------------------------------//
1816    // Diagnostics
1817    //-----------------------------------------------------------------------//
1818
1819    #[tool(description = "Run a full diagnostics check over all open packs.")]
1820    pub async fn diagnostics_check(&self, params: Parameters<DiagnosticsCheckArgs>) -> Result<CallToolResult, McpError> {
1821        send_and_respond!(self, "diagnostics_check", Command::DiagnosticsCheck(params.0.ignored, params.0.check_ak_only_refs))
1822    }
1823
1824    #[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\"}].")]
1825    pub async fn diagnostics_update(&self, params: Parameters<DiagnosticsUpdateArgs>) -> Result<CallToolResult, McpError> {
1826        let diag = parse_json!(&params.0.diagnostics);
1827        let paths: Vec<ContainerPath> = parse_json!(&params.0.paths);
1828        send_and_respond!(self, "diagnostics_update", Command::DiagnosticsUpdate(diag, paths, params.0.check_ak_only_refs))
1829    }
1830
1831    #[tool(description = "Add a line to the ignored diagnostics list for the pack identified by `pack_key`.")]
1832    pub async fn add_line_to_pack_ignored_diagnostics(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1833        send_and_respond!(self, "add_line_to_pack_ignored_diagnostics", Command::AddLineToPackIgnoredDiagnostics(params.0.pack_key, params.0.value))
1834    }
1835
1836    #[tool(description = "Export missing table definitions for the pack identified by `pack_key` to a file (for debugging).")]
1837    pub async fn get_missing_definitions(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
1838        send_and_respond!(self, "get_missing_definitions", Command::GetMissingDefinitions(params.0.pack_key))
1839    }
1840
1841    //-----------------------------------------------------------------------//
1842    // Notes
1843    //-----------------------------------------------------------------------//
1844
1845    #[tool(description = "Get all notes under a path in the pack identified by `pack_key`.")]
1846    pub async fn notes_for_path(&self, params: Parameters<PackKeyStringArg>) -> Result<CallToolResult, McpError> {
1847        send_and_respond!(self, "notes_for_path", Command::NotesForPath(params.0.pack_key, params.0.value))
1848    }
1849
1850    #[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).")]
1851    pub async fn add_note(&self, params: Parameters<AddNoteArgs>) -> Result<CallToolResult, McpError> {
1852        let note = parse_json!(&params.0.note);
1853        send_and_respond!(self, "add_note", Command::AddNote(params.0.pack_key, note))
1854    }
1855
1856    #[tool(description = "Delete a note by path and ID in the pack identified by `pack_key`.")]
1857    pub async fn delete_note(&self, params: Parameters<DeleteNoteArgs>) -> Result<CallToolResult, McpError> {
1858        send_and_respond!(self, "delete_note", Command::DeleteNote(params.0.pack_key, params.0.path, params.0.id))
1859    }
1860
1861    //-----------------------------------------------------------------------//
1862    // Optimization
1863    //-----------------------------------------------------------------------//
1864
1865    #[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.")]
1866    pub async fn optimize_pack_file(&self, params: Parameters<OptimizePackFileArgs>) -> Result<CallToolResult, McpError> {
1867        let options = parse_json!(&params.0.options);
1868        send_and_respond!(self, "optimize_pack_file", Command::OptimizePackFile(params.0.pack_key, options))
1869    }
1870
1871    #[tool(description = "Get the default optimizer options.")]
1872    pub async fn get_optimizer_options(&self) -> Result<CallToolResult, McpError> {
1873        send_and_respond!(self, "get_optimizer_options", Command::OptimizerOptions)
1874    }
1875
1876    //-----------------------------------------------------------------------//
1877    // Updates
1878    //-----------------------------------------------------------------------//
1879
1880    #[tool(description = "Check if there is an RPFM update available.")]
1881    pub async fn check_updates(&self) -> Result<CallToolResult, McpError> {
1882        send_and_respond!(self, "check_updates", Command::CheckUpdates)
1883    }
1884
1885    #[tool(description = "Check if there is a schema update available.")]
1886    pub async fn check_schema_updates(&self) -> Result<CallToolResult, McpError> {
1887        send_and_respond!(self, "check_schema_updates", Command::CheckSchemaUpdates)
1888    }
1889
1890    #[tool(description = "Check for Lua autogen updates.")]
1891    pub async fn check_lua_autogen_updates(&self) -> Result<CallToolResult, McpError> {
1892        send_and_respond!(self, "check_lua_autogen_updates", Command::CheckLuaAutogenUpdates)
1893    }
1894
1895    #[tool(description = "Check for Empire/Napoleon Assembly Kit updates.")]
1896    pub async fn check_empire_and_napoleon_ak_updates(&self) -> Result<CallToolResult, McpError> {
1897        send_and_respond!(self, "check_empire_and_napoleon_ak_updates", Command::CheckEmpireAndNapoleonAKUpdates)
1898    }
1899
1900    #[tool(description = "Check for translation updates.")]
1901    pub async fn check_translations_updates(&self) -> Result<CallToolResult, McpError> {
1902        send_and_respond!(self, "check_translations_updates", Command::CheckTranslationsUpdates)
1903    }
1904
1905    #[tool(description = "Update the Lua autogen repository.")]
1906    pub async fn update_lua_autogen(&self) -> Result<CallToolResult, McpError> {
1907        send_and_respond!(self, "update_lua_autogen", Command::UpdateLuaAutogen)
1908    }
1909
1910    #[tool(description = "Update the program to the latest version.")]
1911    pub async fn update_main_program(&self) -> Result<CallToolResult, McpError> {
1912        send_and_respond!(self, "update_main_program", Command::UpdateMainProgram)
1913    }
1914
1915    #[tool(description = "Update the Empire/Napoleon Assembly Kit files.")]
1916    pub async fn update_empire_and_napoleon_ak(&self) -> Result<CallToolResult, McpError> {
1917        send_and_respond!(self, "update_empire_and_napoleon_ak", Command::UpdateEmpireAndNapoleonAK)
1918    }
1919
1920    #[tool(description = "Update the translations repository.")]
1921    pub async fn update_translations(&self) -> Result<CallToolResult, McpError> {
1922        send_and_respond!(self, "update_translations", Command::UpdateTranslations)
1923    }
1924
1925    //-----------------------------------------------------------------------//
1926    // Settings Getters
1927    //-----------------------------------------------------------------------//
1928
1929    #[tool(description = "Get a boolean setting value by key.")]
1930    pub async fn settings_get_bool(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1931        send_and_respond!(self, "settings_get_bool", Command::SettingsGetBool(params.0.value))
1932    }
1933
1934    #[tool(description = "Get an i32 setting value by key.")]
1935    pub async fn settings_get_i32(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1936        send_and_respond!(self, "settings_get_i32", Command::SettingsGetI32(params.0.value))
1937    }
1938
1939    #[tool(description = "Get an f32 setting value by key.")]
1940    pub async fn settings_get_f32(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1941        send_and_respond!(self, "settings_get_f32", Command::SettingsGetF32(params.0.value))
1942    }
1943
1944    #[tool(description = "Get a string setting value by key.")]
1945    pub async fn settings_get_string(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1946        send_and_respond!(self, "settings_get_string", Command::SettingsGetString(params.0.value))
1947    }
1948
1949    #[tool(description = "Get a PathBuf setting value by key.")]
1950    pub async fn settings_get_path_buf(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1951        send_and_respond!(self, "settings_get_path_buf", Command::SettingsGetPathBuf(params.0.value))
1952    }
1953
1954    #[tool(description = "Get a Vec<String> setting value by key.")]
1955    pub async fn settings_get_vec_string(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1956        send_and_respond!(self, "settings_get_vec_string", Command::SettingsGetVecString(params.0.value))
1957    }
1958
1959    #[tool(description = "Get a raw bytes setting value by key.")]
1960    pub async fn settings_get_vec_raw(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
1961        send_and_respond!(self, "settings_get_vec_raw", Command::SettingsGetVecRaw(params.0.value))
1962    }
1963
1964    #[tool(description = "Get all settings at once (bool, i32, f32, string, raw_data, and vec_string maps).")]
1965    pub async fn settings_get_all(&self) -> Result<CallToolResult, McpError> {
1966        send_and_respond!(self, "settings_get_all", Command::SettingsGetAll)
1967    }
1968
1969    //-----------------------------------------------------------------------//
1970    // Settings Setters
1971    //-----------------------------------------------------------------------//
1972
1973    #[tool(description = "Set a boolean setting value.")]
1974    pub async fn settings_set_bool(&self, params: Parameters<SettingsSetBoolArgs>) -> Result<CallToolResult, McpError> {
1975        send_and_respond!(self, "settings_set_bool", Command::SettingsSetBool(params.0.key, params.0.value))
1976    }
1977
1978    #[tool(description = "Set an i32 setting value.")]
1979    pub async fn settings_set_i32(&self, params: Parameters<SettingsSetI32Args>) -> Result<CallToolResult, McpError> {
1980        send_and_respond!(self, "settings_set_i32", Command::SettingsSetI32(params.0.key, params.0.value))
1981    }
1982
1983    #[tool(description = "Set an f32 setting value.")]
1984    pub async fn settings_set_f32(&self, params: Parameters<SettingsSetF32Args>) -> Result<CallToolResult, McpError> {
1985        send_and_respond!(self, "settings_set_f32", Command::SettingsSetF32(params.0.key, params.0.value))
1986    }
1987
1988    #[tool(description = "Set a string setting value.")]
1989    pub async fn settings_set_string(&self, params: Parameters<SettingsSetStringArgs>) -> Result<CallToolResult, McpError> {
1990        send_and_respond!(self, "settings_set_string", Command::SettingsSetString(params.0.key, params.0.value))
1991    }
1992
1993    #[tool(description = "Set a PathBuf setting value.")]
1994    pub async fn settings_set_path_buf(&self, params: Parameters<SettingsSetPathBufArgs>) -> Result<CallToolResult, McpError> {
1995        send_and_respond!(self, "settings_set_path_buf", Command::SettingsSetPathBuf(params.0.key, params.0.value))
1996    }
1997
1998    #[tool(description = "Set a Vec<String> setting value.")]
1999    pub async fn settings_set_vec_string(&self, params: Parameters<SettingsSetVecStringArgs>) -> Result<CallToolResult, McpError> {
2000        send_and_respond!(self, "settings_set_vec_string", Command::SettingsSetVecString(params.0.key, params.0.value))
2001    }
2002
2003    #[tool(description = "Set a raw bytes setting value.")]
2004    pub async fn settings_set_vec_raw(&self, params: Parameters<SettingsSetVecRawArgs>) -> Result<CallToolResult, McpError> {
2005        send_and_respond!(self, "settings_set_vec_raw", Command::SettingsSetVecRaw(params.0.key, params.0.value))
2006    }
2007
2008    #[tool(description = "Backup the current settings to memory.")]
2009    pub async fn backup_settings(&self) -> Result<CallToolResult, McpError> {
2010        send_and_respond!(self, "backup_settings", Command::BackupSettings)
2011    }
2012
2013    #[tool(description = "Clear all settings and reset to defaults.")]
2014    pub async fn clear_settings(&self) -> Result<CallToolResult, McpError> {
2015        send_and_respond!(self, "clear_settings", Command::ClearSettings)
2016    }
2017
2018    #[tool(description = "Restore settings from the backup.")]
2019    pub async fn restore_backup_settings(&self) -> Result<CallToolResult, McpError> {
2020        send_and_respond!(self, "restore_backup_settings", Command::RestoreBackupSettings)
2021    }
2022
2023    //-----------------------------------------------------------------------//
2024    // Path Queries
2025    //-----------------------------------------------------------------------//
2026
2027    #[tool(description = "Get the config path.")]
2028    pub async fn config_path(&self) -> Result<CallToolResult, McpError> {
2029        send_and_respond!(self, "config_path", Command::ConfigPath)
2030    }
2031
2032    #[tool(description = "Get the Assembly Kit path for the current game.")]
2033    pub async fn assembly_kit_path(&self) -> Result<CallToolResult, McpError> {
2034        send_and_respond!(self, "assembly_kit_path", Command::AssemblyKitPath)
2035    }
2036
2037    #[tool(description = "Get the backup autosave path.")]
2038    pub async fn backup_autosave_path(&self) -> Result<CallToolResult, McpError> {
2039        send_and_respond!(self, "backup_autosave_path", Command::BackupAutosavePath)
2040    }
2041
2042    #[tool(description = "Get the old Assembly Kit data path.")]
2043    pub async fn old_ak_data_path(&self) -> Result<CallToolResult, McpError> {
2044        send_and_respond!(self, "old_ak_data_path", Command::OldAkDataPath)
2045    }
2046
2047    #[tool(description = "Get the schemas path.")]
2048    pub async fn schemas_path(&self) -> Result<CallToolResult, McpError> {
2049        send_and_respond!(self, "schemas_path", Command::SchemasPath)
2050    }
2051
2052    #[tool(description = "Get the table profiles path.")]
2053    pub async fn table_profiles_path(&self) -> Result<CallToolResult, McpError> {
2054        send_and_respond!(self, "table_profiles_path", Command::TableProfilesPath)
2055    }
2056
2057    #[tool(description = "Get the translations local path.")]
2058    pub async fn translations_local_path(&self) -> Result<CallToolResult, McpError> {
2059        send_and_respond!(self, "translations_local_path", Command::TranslationsLocalPath)
2060    }
2061
2062    #[tool(description = "Get the dependencies cache path.")]
2063    pub async fn dependencies_cache_path(&self) -> Result<CallToolResult, McpError> {
2064        send_and_respond!(self, "dependencies_cache_path", Command::DependenciesCachePath)
2065    }
2066
2067    #[tool(description = "Clear a config path.")]
2068    pub async fn settings_clear_path(&self, params: Parameters<PathArg>) -> Result<CallToolResult, McpError> {
2069        send_and_respond!(self, "settings_clear_path", Command::SettingsClearPath(params.0.path))
2070    }
2071
2072    //-----------------------------------------------------------------------//
2073    // Specialized
2074    //-----------------------------------------------------------------------//
2075
2076    #[tool(description = "Get the info about the pack identified by `pack_key` and the list of files it contains.")]
2077    pub async fn open_pack_info(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2078        send_and_respond!(self, "open_pack_info", Command::GetPackFileDataForTreeView(params.0.pack_key))
2079    }
2080
2081    #[tool(description = "Initialize a MyMod folder for mod development.")]
2082    pub async fn initialize_my_mod_folder(&self, params: Parameters<InitializeMyModFolderArgs>) -> Result<CallToolResult, McpError> {
2083        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))
2084    }
2085
2086    #[tool(description = "Live export the pack identified by `pack_key` to the game folder for testing.")]
2087    pub async fn live_export(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2088        send_and_respond!(self, "live_export", Command::LiveExport(params.0.pack_key))
2089    }
2090
2091    #[tool(description = "Patch the SiegeAI of a Siege Map in the pack identified by `pack_key` for Warhammer games.")]
2092    pub async fn patch_siege_ai(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2093        send_and_respond!(self, "patch_siege_ai", Command::PatchSiegeAI(params.0.pack_key))
2094    }
2095
2096    #[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\"]].")]
2097    pub async fn pack_map(&self, params: Parameters<PackMapArgs>) -> Result<CallToolResult, McpError> {
2098        let tiles: Vec<(PathBuf, String)> = parse_json!(&params.0.tiles);
2099        send_and_respond!(self, "pack_map", Command::PackMap(params.0.pack_key, params.0.tile_maps, tiles))
2100    }
2101
2102    #[tool(description = "Generate all missing loc entries for the pack identified by `pack_key`.")]
2103    pub async fn generate_missing_loc_data(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2104        send_and_respond!(self, "generate_missing_loc_data", Command::GenerateMissingLocData(params.0.pack_key))
2105    }
2106
2107    #[tool(description = "Get pack translation data for a language from the pack identified by `pack_key`.")]
2108    pub async fn get_pack_translation(&self, params: Parameters<GetPackTranslationArgs>) -> Result<CallToolResult, McpError> {
2109        send_and_respond!(self, "get_pack_translation", Command::GetPackTranslation(params.0.pack_key, params.0.language))
2110    }
2111
2112    #[tool(description = "Get campaign IDs for starpos building in the pack identified by `pack_key`.")]
2113    pub async fn build_starpos_get_campaign_ids(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2114        send_and_respond!(self, "build_starpos_get_campaign_ids", Command::BuildStarposGetCampaingIds(params.0.pack_key))
2115    }
2116
2117    #[tool(description = "Check if victory conditions file exists for starpos building in the pack identified by `pack_key`.")]
2118    pub async fn build_starpos_check_victory_conditions(&self, params: Parameters<PackKeyArg>) -> Result<CallToolResult, McpError> {
2119        send_and_respond!(self, "build_starpos_check_victory_conditions", Command::BuildStarposCheckVictoryConditions(params.0.pack_key))
2120    }
2121
2122    #[tool(description = "Build starpos (pre-processing step) for the pack identified by `pack_key`.")]
2123    pub async fn build_starpos(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2124        send_and_respond!(self, "build_starpos", Command::BuildStarpos(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2125    }
2126
2127    #[tool(description = "Build starpos (post-processing step) for the pack identified by `pack_key`.")]
2128    pub async fn build_starpos_post(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2129        send_and_respond!(self, "build_starpos_post", Command::BuildStarposPost(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2130    }
2131
2132    #[tool(description = "Clean up starpos temporary files for the pack identified by `pack_key`.")]
2133    pub async fn build_starpos_cleanup(&self, params: Parameters<BuildStarposArgs>) -> Result<CallToolResult, McpError> {
2134        send_and_respond!(self, "build_starpos_cleanup", Command::BuildStarposCleanup(params.0.pack_key, params.0.campaign_id, params.0.process_hlp_spd))
2135    }
2136
2137    #[tool(description = "Update animation IDs with an offset in the pack identified by `pack_key`.")]
2138    pub async fn update_anim_ids(&self, params: Parameters<UpdateAnimIdsArgs>) -> Result<CallToolResult, McpError> {
2139        send_and_respond!(self, "update_anim_ids", Command::UpdateAnimIds(params.0.pack_key, params.0.starting_id, params.0.offset))
2140    }
2141
2142    #[tool(description = "Get animation paths by skeleton name.")]
2143    pub async fn get_anim_paths_by_skeleton_name(&self, params: Parameters<StringArg>) -> Result<CallToolResult, McpError> {
2144        send_and_respond!(self, "get_anim_paths_by_skeleton_name", Command::GetAnimPathsBySkeletonName(params.0.value))
2145    }
2146
2147    #[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.")]
2148    pub async fn export_rigid_to_gltf(&self, params: Parameters<ExportRigidToGltfArgs>) -> Result<CallToolResult, McpError> {
2149        let rigid = parse_json!(&params.0.rigid_model);
2150        send_and_respond!(self, "export_rigid_to_gltf", Command::ExportRigidToGltf(rigid, params.0.output_path))
2151    }
2152
2153    #[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).")]
2154    pub async fn set_video_format(&self, params: Parameters<SetVideoFormatArgs>) -> Result<CallToolResult, McpError> {
2155        let format = parse_json!(&params.0.format);
2156        send_and_respond!(self, "set_video_format", Command::SetVideoFormat(params.0.pack_key, params.0.path, format))
2157    }
2158
2159    //-----------------------------------------------------------------------//
2160    // Multi-Pack Management
2161    //-----------------------------------------------------------------------//
2162
2163    #[tool(description = "List all currently open packs with their keys and metadata. Use this to get valid pack_key values for other tools.")]
2164    pub async fn list_open_packs(&self) -> Result<CallToolResult, McpError> {
2165        send_and_respond!(self, "list_open_packs", Command::ListOpenPacks)
2166    }
2167
2168    //-----------------------------------------------------------------------//
2169    // Additional tools
2170    //-----------------------------------------------------------------------//
2171
2172    #[tool(description = "Close all currently open packs without saving. Any unsaved changes will be lost.")]
2173    pub async fn close_all_packs(&self) -> Result<CallToolResult, McpError> {
2174        send_and_respond!(self, "close_all_packs", Command::CloseAllPacks)
2175    }
2176
2177}
2178
2179//-------------------------------------------------------------------------------//
2180//                              MCP Prompts
2181//-------------------------------------------------------------------------------//
2182
2183#[prompt_router]
2184impl McpServer {
2185
2186    #[prompt(name = "open_and_inspect_pack", description = "Walk through opening a PackFile and inspecting its contents.")]
2187    pub async fn open_and_inspect_pack(&self) -> Vec<PromptMessage> {
2188        vec![PromptMessage::new_text(
2189            PromptMessageRole::User,
2190            "\
2191You are an assistant helping the user inspect a Total War PackFile using the RPFM MCP server.
2192
2193Follow these steps in order:
2194
21951. **Open the pack** – Call `open_packfiles` with the filesystem path(s) the user provides.
2196   The response contains one or more pack keys; remember them for subsequent calls.
2197
21982. **Select the game** – Call `set_game_selected` with the correct game key (e.g. `\"warhammer_3\"`)
2199   and `rebuild_dependencies: true` so that schemas and dependency data are loaded.
2200
22013. **List pack contents** – Call `open_pack_info` with the pack key to get the full file tree.
2202   Present the tree to the user in a readable format.
2203
22044. **Decode specific files** – When the user asks about a file, call `decode_packed_file` with the
2205   pack key, the internal path (e.g. `\"db/land_units_tables/my_table\"`), and
2206   `source: \"PackFile\"`. The decoded JSON will contain the table rows, schema, etc.
2207
22085. **Inspect metadata** – Use `get_pack_settings`, `get_pack_file_name`, or
2209   `get_dependency_pack_files_list` to answer questions about the pack itself.
2210
2211Important notes:
2212- Always call `list_open_packs` if you are unsure which pack key to use.
2213- If a file fails to decode, check `is_schema_loaded`; if false, call `update_schemas` first.
2214- When done, optionally call `close_pack` to free resources.
2215",
2216        )]
2217    }
2218
2219    #[prompt(name = "edit_db_table", description = "Guide for reading, modifying, and saving a DB table inside a pack.")]
2220    pub async fn edit_db_table(&self) -> Vec<PromptMessage> {
2221        vec![PromptMessage::new_text(
2222            PromptMessageRole::User,
2223            "\
2224You are an assistant helping the user edit a DB table inside a Total War PackFile.
2225
2226Workflow:
2227
22281. **Open the pack** – `open_packfiles` → note the `pack_key`.
22292. **Set the game** – `set_game_selected` with `rebuild_dependencies: true`.
22303. **Decode the table** – `decode_packed_file` with the DB path
2231   (e.g. `\"db/unit_stats_land_tables/my_table\"`) and `source: \"PackFile\"`.
2232   The response is an `RFileDecoded` JSON containing the table data and definition.
22334. **Modify rows** – Edit the decoded JSON: add, remove, or change rows/cells.
2234   Each row is typically a list of `DecodedData` values matching the table's
2235   fields processed list (retrievable via the `FieldsProcessed` message).
22365. **Save back** – Call `save_packed_file_from_view` with the pack key, the same path,
2237   and the modified `RFileDecoded` JSON as the `data` parameter.
22386. **Save the pack** – Call `save_packfile` (or `save_pack_as` for a new path).
2239
2240Tips:
2241- Use `get_table_definition_from_dependency_pack_file` to see the table's definition, but
2242  always run it through `fields_processed` before using its field list/count — the
2243  definition's raw `fields` do NOT match row shape (e.g. a colour column is split into
2244  separate r/g/b fields there); rows must match `fields_processed` exactly or saving
2245  will fail with a field-count or type error.
2246- Use `get_reference_data_from_definition` to discover valid values for referenced columns.
2247- After saving, you can run `diagnostics_check` to validate the pack.
2248",
2249        )]
2250    }
2251
2252    #[prompt(name = "create_new_mod", description = "Step-by-step guide for creating a new mod PackFile from scratch.")]
2253    pub async fn create_new_mod(&self) -> Vec<PromptMessage> {
2254        vec![PromptMessage::new_text(
2255            PromptMessageRole::User,
2256            "\
2257You are an assistant helping the user create a new Total War mod from scratch.
2258
2259Workflow:
2260
22611. **Set the game** – `set_game_selected` with the target game key and
2262   `rebuild_dependencies: true`.
2263
22642. **Create the pack** – `new_pack` returns a new empty pack and its pack key.
2265
22663. **Set pack type** – `set_pack_file_type` to `\"Mod\"` (the standard type for mods).
2267
22684. **Add DB tables** – For each table you need:
2269   a. Call `new_packed_file` with the pack key, the path (e.g. `\"db/land_units_tables/my_mod\"`),
2270      and the `new_file` JSON set to `\"DB\"` with the table name.
2271   b. Decode, edit, and save as described in the `edit_db_table` workflow.
2272
22735. **Add Loc files** – For localisation:
2274   a. `new_packed_file` with path `\"text/db/my_mod.loc\"` and `new_file` set to `\"Loc\"`.
2275   b. Decode, add key/value rows, and save.
2276
22776. **Add other files** – Use `add_packed_files` to import assets from disk (images, models, etc.).
2278
22797. **Save the pack** – `save_pack_as` to write the final `.pack` file to disk.
2280
2281Optional steps:
2282- `initialize_my_mod_folder` to set up a mod development folder with IDE support.
2283- `optimize_pack_file` to strip unchanged rows that match vanilla data.
2284- `diagnostics_check` to validate everything before release.
2285",
2286        )]
2287    }
2288
2289    #[prompt(name = "search_and_replace", description = "Find and replace values across all files in a pack.")]
2290    pub async fn search_and_replace(&self) -> Vec<PromptMessage> {
2291        vec![PromptMessage::new_text(
2292            PromptMessageRole::User,
2293            "\
2294You are an assistant helping the user search for and replace data across a PackFile.
2295
2296Workflow:
2297
22981. **Open the pack** and **set the game** (see `open_and_inspect_pack` prompt).
2299
23002. **Run a global search** – Call `global_search` with the pack key and a `GlobalSearch`
2301   JSON object. The search object specifies the pattern, whether to use regex, which file
2302   types to include (DB, Loc, Text), and the replacement string.
2303
23043. **Review matches** – The response contains all matches grouped by file.
2305   Present them to the user for review.
2306
23074. **Replace selectively** – Call `global_search_replace_matches` with the same search
2308   object and a `Vec<MatchHolder>` containing only the matches the user approved.
2309
23105. **Or replace all** – If the user confirms a blanket replace, call
2311   `global_search_replace_all` with the search object.
2312
23136. **Save** – `save_packfile` to persist changes.
2314
2315Related tools:
2316- `search_references` – Find all rows that reference a specific value across tables.
2317- `go_to_definition` – Jump to where a referenced key is defined.
2318- `go_to_loc` – Find the loc entry for a given key.
2319",
2320        )]
2321    }
2322
2323    #[prompt(name = "manage_dependencies", description = "Set up and work with game dependencies and vanilla data.")]
2324    pub async fn manage_dependencies(&self) -> Vec<PromptMessage> {
2325        vec![PromptMessage::new_text(
2326            PromptMessageRole::User,
2327            "\
2328You are an assistant helping the user work with dependency data (vanilla game files).
2329
2330Workflow:
2331
23321. **Set the game** – `set_game_selected` with `rebuild_dependencies: true`.
2333
23342. **Check dependency database** – `is_there_a_dependency_database` with `true` to verify
2335   that game data (including Assembly Kit data) is loaded.
2336   If it returns false, call `generate_dependencies_cache` first.
2337
23383. **Browse vanilla tables** – `get_table_list_from_dependency_pack_file` returns all
2339   DB table names from the vanilla game files.
2340
23414. **Read vanilla data** – `get_tables_from_dependencies` with a table name to get
2342   all rows from vanilla for that table.
2343
23445. **Get definitions** – `get_table_definition_from_dependency_pack_file` to get the
2345   schema definition for any table.
2346
23476. **Import from vanilla** – `import_dependencies_to_open_pack_file` to copy specific
2348   files from vanilla into your mod pack.
2349
23507. **Open CA packs** – `load_all_ca_pack_files` opens all vanilla packs as one merged
2351   read-only pack for full browsing.
2352
23538. **Cross-source lookups** – `get_rfiles_from_all_sources` retrieves files by path
2354   from PackFile, GameFiles, and ParentFiles simultaneously.
2355
2356Tips:
2357- Use `get_packed_files_names_starting_with_path_from_all_sources` to discover files
2358  under a given path prefix across all sources.
2359- `set_dependency_pack_files_list` lets you mark other mods as dependencies of your pack.
2360- The `definition` bundled in each file from `get_tables_from_dependencies` (and from
2361  `get_table_definition_from_dependency_pack_file`) lists RAW on-disk fields, which can
2362  have a different length/order than the actual decoded rows (e.g. colour columns are
2363  split into separate r/g/b fields there). Run it through `fields_processed` before
2364  matching it up against row cells or reusing it to build new rows.
2365",
2366        )]
2367    }
2368
2369    #[prompt(name = "run_diagnostics", description = "Validate a pack and fix common issues.")]
2370    pub async fn run_diagnostics(&self) -> Vec<PromptMessage> {
2371        vec![PromptMessage::new_text(
2372            PromptMessageRole::User,
2373            "\
2374You are an assistant helping the user validate a Total War mod PackFile.
2375
2376Workflow:
2377
23781. **Open the pack** and **set the game** with `rebuild_dependencies: true`.
2379
23802. **Generate dependencies** – If dependencies have not been generated yet,
2381   call `generate_dependencies` to build the dependency data needed for diagnostics.
2382
23833. **Run full diagnostics** – `diagnostics_check` with an empty `ignored` list
2384   and `check_ak_only_refs: false` (or `true` to include Assembly Kit references).
2385   The response contains all warnings and errors grouped by category.
2386
23874. **Review results** – Present the diagnostic results to the user, grouped by severity.
2388   Common issues include:
2389   - Invalid references (a column references a key that does not exist)
2390   - Duplicate keys
2391   - Empty loc entries
2392   - Outdated table versions
2393
23945. **Fix issues** – For each issue:
2395   - Decode the affected file with `decode_packed_file`.
2396   - Apply the fix (correct a reference, remove a duplicate row, etc.).
2397   - Save with `save_packed_file_from_view`.
2398
23996. **Ignore false positives** – Use `add_line_to_pack_ignored_diagnostics` to suppress
2400   specific diagnostic lines that are intentional.
2401
24027. **Re-check** – After fixes, call `diagnostics_check` again to confirm all issues
2403   are resolved.
2404
24058. **Optimize** – Optionally run `optimize_pack_file` to remove rows that are identical
2406   to vanilla, reducing pack size.
2407",
2408        )]
2409    }
2410
2411    #[prompt(name = "schema_operations", description = "Work with table schemas: inspect, update, and patch definitions.")]
2412    pub async fn schema_operations(&self) -> Vec<PromptMessage> {
2413        vec![PromptMessage::new_text(
2414            PromptMessageRole::User,
2415            "\
2416You are an assistant helping the user manage RPFM table schemas.
2417
2418Workflow:
2419
24201. **Check schema status** – `is_schema_loaded` to verify a schema is loaded.
2421   If not, call `update_schemas` to download the latest from the repository.
2422
24232. **Get the full schema** – `get_schema` returns the entire schema object.
2424
24253. **Inspect a table definition** – `definitions_by_table_name` with a table name
2426   returns all known versions. Use `definition_by_table_name_and_version` for a
2427   specific version.
2428
24294. **See processed fields** – `fields_processed` takes a Definition JSON and returns
2430   fields with bitwise expansion, enum conversions, and colour-group merging applied.
2431   This is required, not just cosmetic: `definitions_by_table_name` and
2432   `definition_by_table_name_and_version` return the raw on-disk field list (e.g. a
2433   colour column split into separate r/g/b fields), which has a different length/order
2434   than actual row data. Always call `fields_processed` before using a definition's
2435   field list/count to build or validate rows for saving.
2436
24375. **Find referencing columns** – `referencing_columns_for_definition` shows which
2438   other tables reference a given table's columns.
2439
24406. **Patch a definition** – To customise column metadata (descriptions, references,
2441   default values) without modifying the upstream schema:
2442   a. Build a `HashMap<String, DefinitionPatch>` with your changes.
2443   b. Call `save_local_schema_patch` to persist it locally.
2444   c. Use `remove_local_schema_patches_for_table` or
2445      `remove_local_schema_patches_for_table_and_field` to undo patches.
2446
24477. **Import patches** – `import_schema_patch` applies a patch from another source.
2448
24498. **Update from Assembly Kit** – `update_current_schema_from_asskit` merges
2450   definition data from the game's Assembly Kit into the loaded schema.
2451
24529. **Save the schema** – `save_schema` writes the current in-memory schema to disk.
2453",
2454        )]
2455    }
2456
2457    #[prompt(name = "file_operations", description = "Add, remove, rename, extract, and move files within packs.")]
2458    pub async fn file_operations(&self) -> Vec<PromptMessage> {
2459        vec![PromptMessage::new_text(
2460            PromptMessageRole::User,
2461            "\
2462You are an assistant helping the user manage files inside a Total War PackFile.
2463
2464Common operations:
2465
2466**Add files from disk:**
2467- `add_packed_files` – Import files from the filesystem into the pack. Provide source
2468  filesystem paths and destination `ContainerPath` entries as JSON.
2469
2470**Add files from another pack:**
2471- `add_packed_files_from_pack_file` – Copy files between two open packs.
2472
2473**Create new files:**
2474- `new_packed_file` – Create a blank DB table, Loc file, or other file type inside the pack.
2475
2476**Delete files:**
2477- `delete_packed_files` – Remove files by their `ContainerPath` list.
2478
2479**Rename / move files:**
2480- `rename_packed_files` – Pass a list of `(old_path, new_path)` tuples.
2481
2482**Copy / Cut / Paste / Duplicate:**
2483- `copy_packed_files` – Copy files to the internal clipboard for later pasting.
2484- `cut_packed_files` – Cut files to the internal clipboard (removed from source on paste).
2485- `paste_packed_files` – Paste clipboard contents into a pack at the given folder path.
2486- `duplicate_packed_files` – Clone files in-place with a numeric suffix.
2487
2488**Extract to disk:**
2489- `extract_packed_files` – Export files from the pack to a folder on disk.
2490  Set `export_as_tsv: true` to export tables as TSV files.
2491
2492**AnimPack operations:**
2493- `add_packed_files_from_pack_file_to_animpack` – Add files to an AnimPack.
2494- `add_packed_files_from_animpack` – Extract files from an AnimPack.
2495- `delete_from_animpack` – Remove files from an AnimPack.
2496
2497**File info:**
2498- `get_packed_files_info` / `get_rfile_info` – Get metadata about files.
2499- `folder_exists` / `packed_file_exists` – Check if a path exists.
2500- `get_packed_file_raw_data` – Get the raw binary content of a file.
2501
2502**Merge tables:**
2503- `merge_files` – Combine multiple compatible tables into one.
2504
2505**External editing:**
2506- `open_packed_file_in_external_program` – Open a file in the system's default editor.
2507- `save_packed_file_from_external_view` – Re-import after external editing.
2508
2509Always call `save_packfile` or `save_pack_as` when done to persist changes.
2510",
2511        )]
2512    }
2513
2514    #[prompt(name = "troubleshooting", description = "Diagnose and fix common issues with RPFM and PackFiles.")]
2515    pub async fn troubleshooting(&self) -> Vec<PromptMessage> {
2516        vec![PromptMessage::new_text(
2517            PromptMessageRole::User,
2518            "\
2519You are an assistant helping the user troubleshoot common RPFM and PackFile issues.
2520
2521## Common Issues and Solutions
2522
2523### 1. Schema not loaded
2524**Symptom**: Files fail to decode, or `decode_packed_file` returns raw data.
2525**Solution**:
2526- Call `is_schema_loaded()` – if false, call `update_schemas()`.
2527- Make sure `set_game_selected` was called with `rebuild_dependencies: true`.
2528
2529### 2. Dependencies not available
2530**Symptom**: References show as invalid, diagnostics report missing keys.
2531**Solution**:
2532- Call `is_there_a_dependency_database(true)` – if false, call `generate_dependencies_cache()`.
2533- Ensure the game path is configured correctly in settings.
2534
2535### 3. Pack won't save
2536**Symptom**: `save_packfile` returns an error.
2537**Solution**:
2538- Check if the file is read-only or locked by another process.
2539- Try `save_pack_as` to a different path.
2540- As a last resort, use `clean_and_save_pack_as` to recover from corruption.
2541
2542### 4. Table version mismatch
2543**Symptom**: Table data looks wrong or has missing columns after a game update.
2544**Solution**:
2545- Call `update_schemas()` to get the latest table definitions.
2546- Use `update_table` to migrate the table to the current version.
2547- Check `get_table_definition_from_dependency_pack_file` for the expected schema.
2548
2549### 5. Wrong game selected
2550**Symptom**: Tables decode with wrong columns or fail to decode, dependencies are for a different game.
2551**Solution**:
2552- Call `get_game_selected()` to verify the current game.
2553- Call `set_game_selected` with the correct game key and `rebuild_dependencies: true`.
2554
2555### 6. Diagnostics show many reference errors
2556**Symptom**: `diagnostics_check` reports hundreds of invalid references.
2557**Solution**:
2558- Ensure dependencies are loaded (`is_there_a_dependency_database(true)`).
2559- Check if the pack depends on other mods via `get_dependency_pack_files_list`.
2560- Some references are Assembly Kit only; re-run with `check_ak_only_refs: true`.
2561- Use `add_line_to_pack_ignored_diagnostics` for intentional deviations.
2562
2563### Diagnostic Tools
2564- `diagnostics_check` – Full pack validation.
2565- `get_game_selected` – Verify game context.
2566- `is_schema_loaded` – Check schema status.
2567- `is_there_a_dependency_database` – Check dependency database status.
2568- `list_open_packs` – Verify which packs are open.
2569- `config_path` / `schemas_path` – Verify RPFM paths.
2570",
2571        )]
2572    }
2573
2574    #[prompt(name = "tsv_workflow", description = "Import and export tables as TSV files for batch editing in spreadsheets.")]
2575    pub async fn tsv_workflow(&self) -> Vec<PromptMessage> {
2576        vec![PromptMessage::new_text(
2577            PromptMessageRole::User,
2578            "\
2579You are an assistant helping the user work with TSV (Tab-Separated Values) files for batch editing \
2580Total War mod data in spreadsheets.
2581
2582## Export Workflow (Pack → TSV → Spreadsheet)
2583
25841. **Open the pack** and **set the game** with `rebuild_dependencies: true`.
2585
25862. **Export a single table as TSV**:
2587   Call `export_tsv` with:
2588   - `pack_key`: the pack key
2589   - `tsv_path`: destination path on disk (e.g. `/home/user/my_table.tsv`)
2590   - `table_path`: the internal path (e.g. `db/land_units_tables/my_mod`)
2591
25923. **Export all tables as TSV**:
2593   Call `extract_packed_files` with `export_as_tsv: true`.
2594   This exports all tables in the pack as TSV files to the destination folder.
2595
25964. **Edit in a spreadsheet**: Open the TSV file in LibreOffice Calc, Excel, or Google Sheets.
2597   - Keep the header rows intact (they contain schema metadata).
2598   - Tab-separated values — do not change the delimiter.
2599
2600## Import Workflow (Spreadsheet → TSV → Pack)
2601
26021. **Save the spreadsheet as TSV** (tab-delimited, UTF-8 encoding).
2603
26042. **Import the TSV back**:
2605   Call `import_tsv` with:
2606   - `pack_key`: the target pack key
2607   - `tsv_path`: path to the TSV file on disk
2608   - `table_path`: the internal path where the table should go
2609
26103. **Verify**: Call `decode_packed_file` to confirm the data imported correctly.
2611
26124. **Save the pack**: Call `save_packfile` to persist changes.
2613
2614## Tips
2615- TSV files include metadata headers that RPFM uses for schema matching.
2616  Do not delete or modify these header rows.
2617- Use `get_table_definition_from_dependency_pack_file` to understand column types
2618  before editing.
2619- After import, run `diagnostics_check` to validate references.
2620",
2621        )]
2622    }
2623
2624    #[prompt(name = "translation_workflow", description = "Work with localisation and translation data in PackFiles.")]
2625    pub async fn translation_workflow(&self) -> Vec<PromptMessage> {
2626        vec![PromptMessage::new_text(
2627            PromptMessageRole::User,
2628            "\
2629You are an assistant helping the user work with localisation (translation) data in Total War mods.
2630
2631## Understanding Loc Files
2632
2633Loc files contain key-value pairs for in-game text. Each entry has:
2634- A **key** (unique identifier referenced by DB tables)
2635- A **value** (the displayed text in the game)
2636
2637## Viewing Existing Translations
2638
26391. **Open the pack** and **set the game**.
2640
26412. **Decode a loc file**:
2642   Call `decode_packed_file` with the loc file path (e.g. `text/db/my_mod.loc`)
2643   and `source: \"PackFile\"`.
2644
26453. **Get translation overview**:
2646   Call `get_pack_translation` with the pack key and a language code
2647   (e.g. `\"en\"`, `\"fr\"`, `\"de\"`, `\"es\"`, `\"it\"`, `\"zh\"`, `\"ru\"`, etc.).
2648
2649## Creating New Translations
2650
26511. **Create a new loc file**:
2652   Call `new_packed_file` with path `\"text/db/my_mod.loc\"` and
2653   `new_file = {\"Loc\": \"my_mod\"}`.
2654
26552. **Decode it**: `decode_packed_file` to get the empty structure.
2656
26573. **Add entries**: Modify the decoded JSON to add key-value rows.
2658   Each row is typically `[\"key_string\", \"Displayed text in game\"]`.
2659
26604. **Save back**: `save_packed_file_from_view` with the modified data.
2661
2662## Generating Missing Loc Data
2663
2664Call `generate_missing_loc_data` with the pack key to auto-generate
2665loc entries for DB fields that reference loc keys but don't have entries yet.
2666
2667## Finding Loc Keys
2668
2669- Use `go_to_loc` with a loc key to find its source loc file.
2670- Use `get_source_data_from_loc_key` to find where a loc key is referenced.
2671- Use `global_search` with `search_on.loc: true` to search across all loc files.
2672
2673## Tips
2674- Loc keys follow naming conventions like `<table>_<loc_column_name>_<keys_concatenated>`.
2675- Use `search_references` to find all DB columns that reference a specific loc key.
2676- After adding translations, run `diagnostics_check` to verify all references.
2677",
2678        )]
2679    }
2680}