ExportTools.cs 4.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. using System.ComponentModel;
  2. using Microsoft.Extensions.DependencyInjection;
  3. using ModelContextProtocol.Server;
  4. using RackPeek.Domain.Graph;
  5. using RackPeek.Domain.Graph.Serialisers;
  6. using RackPeek.Domain.Graph.UseCases;
  7. using RackPeek.Domain.UseCases.Ansible;
  8. using RackPeek.Domain.UseCases.Hosts;
  9. using RackPeek.Domain.UseCases.SSH;
  10. namespace RackPeek.Mcp.Tools;
  11. public enum TopologyView {
  12. Physical,
  13. Logical
  14. }
  15. [McpServerToolType]
  16. public sealed class ExportTools(IServiceProvider services) {
  17. [McpServerTool(Name = "export_ansible_inventory", UseStructuredContent = true, ReadOnly = true, Idempotent = true, OpenWorld = false)]
  18. [Description("Renders the inventory as an Ansible inventory file. A resource needs an 'ansible_host', 'ip' or " +
  19. "'hostname' label to be included, and hosts are emitted under the groups built from groupByTags / " +
  20. "groupByLabelKeys — pass at least one of those or the inventory comes back empty.")]
  21. public Task<InventoryResult?> ExportAnsibleInventory(
  22. [Description("Output format: Ini or Yaml.")] InventoryFormat format = InventoryFormat.Ini,
  23. [Description("Create a group per listed tag.")] string[]? groupByTags = null,
  24. [Description("Create groups from these label keys, e.g. 'env' groups hosts into env_prod, env_dev.")]
  25. string[]? groupByLabelKeys = null) {
  26. return ToolErrors.RunAsync(() =>
  27. services.GetRequiredService<AnsibleInventoryGeneratorUseCase>().ExecuteAsync(new InventoryOptions {
  28. Format = format,
  29. GroupByTags = groupByTags ?? [],
  30. GroupByLabelKeys = groupByLabelKeys ?? []
  31. }));
  32. }
  33. [McpServerTool(Name = "export_ssh_config", UseStructuredContent = true, ReadOnly = true, Idempotent = true, OpenWorld = false)]
  34. [Description("Renders the inventory as an OpenSSH client config (Host blocks).")]
  35. public Task<SshExportResult?> ExportSshConfig(
  36. [Description("Only include resources carrying at least one of these tags.")]
  37. string[]? includeTags = null,
  38. [Description("Default SSH user for every host.")] string? defaultUser = null,
  39. [Description("Default SSH port for every host.")] int defaultPort = 22,
  40. [Description("Default IdentityFile path for every host.")] string? defaultIdentityFile = null) {
  41. return ToolErrors.RunAsync(() =>
  42. services.GetRequiredService<SshConfigExportUseCase>().ExecuteAsync(new SshExportOptions {
  43. IncludeTags = includeTags ?? [],
  44. DefaultUser = defaultUser,
  45. DefaultPort = defaultPort,
  46. DefaultIdentityFile = defaultIdentityFile
  47. }));
  48. }
  49. [McpServerTool(Name = "export_hosts_file", UseStructuredContent = true, ReadOnly = true, Idempotent = true, OpenWorld = false)]
  50. [Description("Renders the inventory as /etc/hosts entries.")]
  51. public Task<HostsExportResult?> ExportHostsFile(
  52. [Description("Only include resources carrying at least one of these tags.")]
  53. string[]? includeTags = null,
  54. [Description("Domain suffix appended to every host name, e.g. 'home.local'.")]
  55. string? domainSuffix = null,
  56. [Description("Include the localhost entries at the top.")]
  57. bool includeLocalhostDefaults = true) {
  58. return ToolErrors.RunAsync(() =>
  59. services.GetRequiredService<HostsFileExportUseCase>().ExecuteAsync(new HostsExportOptions {
  60. IncludeTags = includeTags ?? [],
  61. DomainSuffix = domainSuffix,
  62. IncludeLocalhostDefaults = includeLocalhostDefaults
  63. }));
  64. }
  65. [McpServerTool(Name = "export_topology_mermaid", ReadOnly = true, Idempotent = true, OpenWorld = false)]
  66. [Description("Renders the infrastructure as a Mermaid diagram: Physical shows hardware and its port connections, Logical shows host cards with their services grouped by subnet.")]
  67. public Task<string> ExportTopologyMermaid(
  68. [Description("Physical or Logical.")] TopologyView view = TopologyView.Physical) {
  69. return ToolErrors.RunAsync(async () => {
  70. Graph graph = view == TopologyView.Physical
  71. ? await services.GetRequiredService<BuildPhysicalTopologyUseCase>().ExecuteAsync()
  72. : await services.GetRequiredService<BuildLogicalGraphUseCase>().ExecuteAsync();
  73. return new MermaidSerialiser().Serialise(graph);
  74. });
  75. }
  76. }