@thelord/mcp-arr 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -572,6 +572,99 @@ if (clients.sonarr) {
572
572
  },
573
573
  required: ["tagId"],
574
574
  },
575
+ }, {
576
+ name: "sonarr_get_history",
577
+ description: "Get Sonarr download/import history",
578
+ inputSchema: {
579
+ type: "object",
580
+ properties: {
581
+ page: {
582
+ type: "number",
583
+ description: "Page number (default: 1)",
584
+ },
585
+ pageSize: {
586
+ type: "number",
587
+ description: "Items per page (default: 20)",
588
+ },
589
+ seriesId: {
590
+ type: "number",
591
+ description: "Optional: filter to specific series ID",
592
+ },
593
+ },
594
+ required: [],
595
+ },
596
+ }, {
597
+ name: "sonarr_get_wanted",
598
+ description: "Get list of missing/wanted episodes",
599
+ inputSchema: {
600
+ type: "object",
601
+ properties: {
602
+ page: {
603
+ type: "number",
604
+ description: "Page number (default: 1)",
605
+ },
606
+ pageSize: {
607
+ type: "number",
608
+ description: "Items per page (default: 20)",
609
+ },
610
+ },
611
+ required: [],
612
+ },
613
+ }, {
614
+ name: "sonarr_get_cutoff_unmet",
615
+ description: "Get episodes with quality below cutoff that could be upgraded",
616
+ inputSchema: {
617
+ type: "object",
618
+ properties: {
619
+ page: {
620
+ type: "number",
621
+ description: "Page number (default: 1)",
622
+ },
623
+ pageSize: {
624
+ type: "number",
625
+ description: "Items per page (default: 20)",
626
+ },
627
+ },
628
+ required: [],
629
+ },
630
+ }, {
631
+ name: "sonarr_get_blocklist",
632
+ description: "Get blocklisted releases (releases that failed or were manually blocklisted)",
633
+ inputSchema: {
634
+ type: "object",
635
+ properties: {
636
+ page: {
637
+ type: "number",
638
+ description: "Page number (default: 1)",
639
+ },
640
+ pageSize: {
641
+ type: "number",
642
+ description: "Items per page (default: 20)",
643
+ },
644
+ },
645
+ required: [],
646
+ },
647
+ }, {
648
+ name: "sonarr_delete_blocklist_item",
649
+ description: "Remove an item from the blocklist",
650
+ inputSchema: {
651
+ type: "object",
652
+ properties: {
653
+ blocklistId: {
654
+ type: "number",
655
+ description: "Blocklist item ID to delete",
656
+ },
657
+ },
658
+ required: ["blocklistId"],
659
+ },
660
+ }, {
661
+ name: "sonarr_search_all_missing",
662
+ description: "Trigger a search for all missing episodes across all series",
663
+ inputSchema: {
664
+ type: "object",
665
+ properties: {},
666
+ required: [],
667
+ },
575
668
  });
576
669
  }
577
670
  // Radarr tools
@@ -619,190 +712,526 @@ if (clients.radarr) {
619
712
  required: [],
620
713
  },
621
714
  }, {
622
- name: "radarr_search_movie",
623
- description: "Trigger a search to download a movie that's already in your library",
715
+ name: "radarr_add_movie",
716
+ description: "Add a new movie to Radarr",
624
717
  inputSchema: {
625
718
  type: "object",
626
719
  properties: {
627
- movieId: {
720
+ tmdbId: {
628
721
  type: "number",
629
- description: "Movie ID to search for",
722
+ description: "TMDB ID for the movie (get from radarr_search)",
723
+ },
724
+ title: {
725
+ type: "string",
726
+ description: "Movie title",
727
+ },
728
+ qualityProfileId: {
729
+ type: "number",
730
+ description: "Quality profile ID (get from radarr_get_quality_profiles)",
731
+ },
732
+ rootFolderPath: {
733
+ type: "string",
734
+ description: "Root folder path (get from radarr_get_root_folders)",
735
+ },
736
+ monitored: {
737
+ type: "boolean",
738
+ description: "Monitor movie (default: true)",
739
+ },
740
+ minimumAvailability: {
741
+ type: "string",
742
+ description: "When to consider movie available: announced, inCinemas, released (default: released)",
743
+ },
744
+ tags: {
745
+ type: "array",
746
+ items: { type: "number" },
747
+ description: "Tag IDs to apply",
630
748
  },
631
749
  },
632
- required: ["movieId"],
750
+ required: ["tmdbId", "qualityProfileId", "rootFolderPath"],
633
751
  },
634
- });
635
- }
636
- // Lidarr tools
637
- if (clients.lidarr) {
638
- TOOLS.push({
639
- name: "lidarr_get_artists",
640
- description: "Get all artists in Lidarr library",
752
+ }, {
753
+ name: "radarr_update_movie",
754
+ description: "Update an existing movie configuration (monitored, tags, quality profile, etc.)",
641
755
  inputSchema: {
642
756
  type: "object",
643
- properties: {},
644
- required: [],
757
+ properties: {
758
+ movieId: {
759
+ type: "number",
760
+ description: "Movie ID to update",
761
+ },
762
+ monitored: {
763
+ type: "boolean",
764
+ description: "Monitor movie",
765
+ },
766
+ qualityProfileId: {
767
+ type: "number",
768
+ description: "Quality profile ID",
769
+ },
770
+ minimumAvailability: {
771
+ type: "string",
772
+ description: "Minimum availability: announced, inCinemas, released",
773
+ },
774
+ tags: {
775
+ type: "array",
776
+ items: { type: "number" },
777
+ description: "Tag IDs to apply",
778
+ },
779
+ },
780
+ required: ["movieId"],
645
781
  },
646
782
  }, {
647
- name: "lidarr_search",
648
- description: "Search for artists to add to Lidarr",
783
+ name: "radarr_delete_movie",
784
+ description: "Delete a movie from Radarr library. Optionally delete files and add to exclusion list.",
649
785
  inputSchema: {
650
786
  type: "object",
651
787
  properties: {
652
- term: {
653
- type: "string",
654
- description: "Search term (artist name)",
788
+ movieId: {
789
+ type: "number",
790
+ description: "Movie ID to delete",
791
+ },
792
+ deleteFiles: {
793
+ type: "boolean",
794
+ description: "Delete all files for this movie (default: false)",
795
+ },
796
+ addImportExclusion: {
797
+ type: "boolean",
798
+ description: "Add movie to import exclusion list (default: false)",
655
799
  },
656
800
  },
657
- required: ["term"],
801
+ required: ["movieId"],
658
802
  },
659
803
  }, {
660
- name: "lidarr_get_queue",
661
- description: "Get Lidarr download queue",
804
+ name: "radarr_search_movie",
805
+ description: "Trigger a search to download a movie that's already in your library",
662
806
  inputSchema: {
663
807
  type: "object",
664
- properties: {},
665
- required: [],
808
+ properties: {
809
+ movieId: {
810
+ type: "number",
811
+ description: "Movie ID to search for",
812
+ },
813
+ },
814
+ required: ["movieId"],
666
815
  },
667
816
  }, {
668
- name: "lidarr_get_albums",
669
- description: "Get albums for an artist in Lidarr. Shows which albums are available and which are missing.",
817
+ name: "radarr_get_history",
818
+ description: "Get Radarr download/import history",
670
819
  inputSchema: {
671
820
  type: "object",
672
821
  properties: {
673
- artistId: {
822
+ page: {
674
823
  type: "number",
675
- description: "Artist ID to get albums for",
824
+ description: "Page number (default: 1)",
825
+ },
826
+ pageSize: {
827
+ type: "number",
828
+ description: "Items per page (default: 20)",
829
+ },
830
+ movieId: {
831
+ type: "number",
832
+ description: "Optional: filter to specific movie ID",
676
833
  },
677
834
  },
678
- required: ["artistId"],
835
+ required: [],
679
836
  },
680
837
  }, {
681
- name: "lidarr_search_album",
682
- description: "Trigger a search for a specific album to download",
838
+ name: "radarr_refresh_movie",
839
+ description: "Refresh movie metadata from TMDB",
683
840
  inputSchema: {
684
841
  type: "object",
685
842
  properties: {
686
- albumId: {
843
+ movieId: {
687
844
  type: "number",
688
- description: "Album ID to search for",
845
+ description: "Movie ID to refresh",
689
846
  },
690
847
  },
691
- required: ["albumId"],
848
+ required: ["movieId"],
692
849
  },
693
850
  }, {
694
- name: "lidarr_search_missing",
695
- description: "Trigger a search for all missing albums for an artist",
851
+ name: "radarr_rescan_movie",
852
+ description: "Rescan movie disk for new/changed files",
696
853
  inputSchema: {
697
854
  type: "object",
698
855
  properties: {
699
- artistId: {
856
+ movieId: {
700
857
  type: "number",
701
- description: "Artist ID to search missing albums for",
858
+ description: "Movie ID to rescan",
702
859
  },
703
860
  },
704
- required: ["artistId"],
861
+ required: ["movieId"],
705
862
  },
706
863
  }, {
707
- name: "lidarr_get_calendar",
708
- description: "Get upcoming album releases from Lidarr",
864
+ name: "radarr_rename_movie",
865
+ description: "Rename movie files according to Radarr naming conventions",
709
866
  inputSchema: {
710
867
  type: "object",
711
868
  properties: {
712
- days: {
869
+ movieId: {
713
870
  type: "number",
714
- description: "Number of days to look ahead (default: 30)",
871
+ description: "Movie ID to rename",
715
872
  },
716
873
  },
717
- required: [],
874
+ required: ["movieId"],
718
875
  },
719
- });
720
- }
721
- // Readarr tools
722
- if (clients.readarr) {
723
- TOOLS.push({
724
- name: "readarr_get_authors",
725
- description: "Get all authors in Readarr library",
876
+ }, {
877
+ name: "radarr_get_collections",
878
+ description: "Get all movie collections in Radarr",
726
879
  inputSchema: {
727
880
  type: "object",
728
881
  properties: {},
729
882
  required: [],
730
883
  },
731
884
  }, {
732
- name: "readarr_search",
733
- description: "Search for authors to add to Readarr",
885
+ name: "radarr_update_collection",
886
+ description: "Update a collection (monitored status, quality profile, root folder)",
734
887
  inputSchema: {
735
888
  type: "object",
736
889
  properties: {
737
- term: {
890
+ collectionId: {
891
+ type: "number",
892
+ description: "Collection ID to update",
893
+ },
894
+ monitored: {
895
+ type: "boolean",
896
+ description: "Monitor collection (automatically add new movies)",
897
+ },
898
+ qualityProfileId: {
899
+ type: "number",
900
+ description: "Quality profile ID for new movies",
901
+ },
902
+ rootFolderPath: {
738
903
  type: "string",
739
- description: "Search term (author name)",
904
+ description: "Root folder for new movies",
905
+ },
906
+ searchOnAdd: {
907
+ type: "boolean",
908
+ description: "Search for movies when added from collection",
740
909
  },
741
910
  },
742
- required: ["term"],
911
+ required: ["collectionId"],
743
912
  },
744
913
  }, {
745
- name: "readarr_get_queue",
746
- description: "Get Readarr download queue",
914
+ name: "radarr_get_movie_files",
915
+ description: "Get movie file details including quality, size, and media info",
747
916
  inputSchema: {
748
917
  type: "object",
749
- properties: {},
750
- required: [],
918
+ properties: {
919
+ movieId: {
920
+ type: "number",
921
+ description: "Movie ID to get files for",
922
+ },
923
+ },
924
+ required: ["movieId"],
751
925
  },
752
926
  }, {
753
- name: "readarr_get_books",
754
- description: "Get books for an author in Readarr. Shows which books are available and which are missing.",
927
+ name: "radarr_delete_movie_file",
928
+ description: "Delete a movie file. Useful for removing bad quality files to force re-download.",
755
929
  inputSchema: {
756
930
  type: "object",
757
931
  properties: {
758
- authorId: {
932
+ movieFileId: {
759
933
  type: "number",
760
- description: "Author ID to get books for",
934
+ description: "Movie file ID to delete",
761
935
  },
762
936
  },
763
- required: ["authorId"],
937
+ required: ["movieFileId"],
764
938
  },
765
939
  }, {
766
- name: "readarr_search_book",
767
- description: "Trigger a search for a specific book to download",
940
+ name: "radarr_get_blocklist",
941
+ description: "Get blocklisted releases (releases that failed or were manually blocklisted)",
768
942
  inputSchema: {
769
943
  type: "object",
770
944
  properties: {
771
- bookIds: {
772
- type: "array",
773
- items: { type: "number" },
774
- description: "Book ID(s) to search for",
945
+ page: {
946
+ type: "number",
947
+ description: "Page number (default: 1)",
948
+ },
949
+ pageSize: {
950
+ type: "number",
951
+ description: "Items per page (default: 20)",
775
952
  },
776
953
  },
777
- required: ["bookIds"],
954
+ required: [],
778
955
  },
779
956
  }, {
780
- name: "readarr_search_missing",
781
- description: "Trigger a search for all missing books for an author",
957
+ name: "radarr_delete_blocklist_item",
958
+ description: "Remove an item from the blocklist",
782
959
  inputSchema: {
783
960
  type: "object",
784
961
  properties: {
785
- authorId: {
962
+ blocklistId: {
786
963
  type: "number",
787
- description: "Author ID to search missing books for",
964
+ description: "Blocklist item ID to delete",
788
965
  },
789
966
  },
790
- required: ["authorId"],
967
+ required: ["blocklistId"],
791
968
  },
792
969
  }, {
793
- name: "readarr_get_calendar",
794
- description: "Get upcoming book releases from Readarr",
970
+ name: "radarr_get_extra_files",
971
+ description: "Get extra files for a movie (subtitles, nfo files, etc.)",
795
972
  inputSchema: {
796
973
  type: "object",
797
974
  properties: {
798
- days: {
975
+ movieId: {
799
976
  type: "number",
800
- description: "Number of days to look ahead (default: 30)",
977
+ description: "Movie ID to get extra files for",
801
978
  },
802
979
  },
803
- required: [],
980
+ required: ["movieId"],
804
981
  },
805
- });
982
+ }, {
983
+ name: "radarr_get_credits",
984
+ description: "Get cast and crew credits for a movie",
985
+ inputSchema: {
986
+ type: "object",
987
+ properties: {
988
+ movieId: {
989
+ type: "number",
990
+ description: "Movie ID to get credits for",
991
+ },
992
+ },
993
+ required: ["movieId"],
994
+ },
995
+ }, {
996
+ name: "radarr_get_wanted",
997
+ description: "Get list of missing/wanted movies",
998
+ inputSchema: {
999
+ type: "object",
1000
+ properties: {
1001
+ page: {
1002
+ type: "number",
1003
+ description: "Page number (default: 1)",
1004
+ },
1005
+ pageSize: {
1006
+ type: "number",
1007
+ description: "Items per page (default: 20)",
1008
+ },
1009
+ },
1010
+ required: [],
1011
+ },
1012
+ }, {
1013
+ name: "radarr_search_missing",
1014
+ description: "Trigger a search for all missing/wanted movies",
1015
+ inputSchema: {
1016
+ type: "object",
1017
+ properties: {},
1018
+ required: [],
1019
+ },
1020
+ }, {
1021
+ name: "radarr_create_tag",
1022
+ description: "Create a new tag",
1023
+ inputSchema: {
1024
+ type: "object",
1025
+ properties: {
1026
+ label: {
1027
+ type: "string",
1028
+ description: "Tag label/name",
1029
+ },
1030
+ },
1031
+ required: ["label"],
1032
+ },
1033
+ }, {
1034
+ name: "radarr_update_tag",
1035
+ description: "Update a tag label",
1036
+ inputSchema: {
1037
+ type: "object",
1038
+ properties: {
1039
+ tagId: {
1040
+ type: "number",
1041
+ description: "Tag ID to update",
1042
+ },
1043
+ label: {
1044
+ type: "string",
1045
+ description: "New tag label",
1046
+ },
1047
+ },
1048
+ required: ["tagId", "label"],
1049
+ },
1050
+ }, {
1051
+ name: "radarr_delete_tag",
1052
+ description: "Delete a tag",
1053
+ inputSchema: {
1054
+ type: "object",
1055
+ properties: {
1056
+ tagId: {
1057
+ type: "number",
1058
+ description: "Tag ID to delete",
1059
+ },
1060
+ },
1061
+ required: ["tagId"],
1062
+ },
1063
+ });
1064
+ }
1065
+ // Lidarr tools
1066
+ if (clients.lidarr) {
1067
+ TOOLS.push({
1068
+ name: "lidarr_get_artists",
1069
+ description: "Get all artists in Lidarr library",
1070
+ inputSchema: {
1071
+ type: "object",
1072
+ properties: {},
1073
+ required: [],
1074
+ },
1075
+ }, {
1076
+ name: "lidarr_search",
1077
+ description: "Search for artists to add to Lidarr",
1078
+ inputSchema: {
1079
+ type: "object",
1080
+ properties: {
1081
+ term: {
1082
+ type: "string",
1083
+ description: "Search term (artist name)",
1084
+ },
1085
+ },
1086
+ required: ["term"],
1087
+ },
1088
+ }, {
1089
+ name: "lidarr_get_queue",
1090
+ description: "Get Lidarr download queue",
1091
+ inputSchema: {
1092
+ type: "object",
1093
+ properties: {},
1094
+ required: [],
1095
+ },
1096
+ }, {
1097
+ name: "lidarr_get_albums",
1098
+ description: "Get albums for an artist in Lidarr. Shows which albums are available and which are missing.",
1099
+ inputSchema: {
1100
+ type: "object",
1101
+ properties: {
1102
+ artistId: {
1103
+ type: "number",
1104
+ description: "Artist ID to get albums for",
1105
+ },
1106
+ },
1107
+ required: ["artistId"],
1108
+ },
1109
+ }, {
1110
+ name: "lidarr_search_album",
1111
+ description: "Trigger a search for a specific album to download",
1112
+ inputSchema: {
1113
+ type: "object",
1114
+ properties: {
1115
+ albumId: {
1116
+ type: "number",
1117
+ description: "Album ID to search for",
1118
+ },
1119
+ },
1120
+ required: ["albumId"],
1121
+ },
1122
+ }, {
1123
+ name: "lidarr_search_missing",
1124
+ description: "Trigger a search for all missing albums for an artist",
1125
+ inputSchema: {
1126
+ type: "object",
1127
+ properties: {
1128
+ artistId: {
1129
+ type: "number",
1130
+ description: "Artist ID to search missing albums for",
1131
+ },
1132
+ },
1133
+ required: ["artistId"],
1134
+ },
1135
+ }, {
1136
+ name: "lidarr_get_calendar",
1137
+ description: "Get upcoming album releases from Lidarr",
1138
+ inputSchema: {
1139
+ type: "object",
1140
+ properties: {
1141
+ days: {
1142
+ type: "number",
1143
+ description: "Number of days to look ahead (default: 30)",
1144
+ },
1145
+ },
1146
+ required: [],
1147
+ },
1148
+ });
1149
+ }
1150
+ // Readarr tools
1151
+ if (clients.readarr) {
1152
+ TOOLS.push({
1153
+ name: "readarr_get_authors",
1154
+ description: "Get all authors in Readarr library",
1155
+ inputSchema: {
1156
+ type: "object",
1157
+ properties: {},
1158
+ required: [],
1159
+ },
1160
+ }, {
1161
+ name: "readarr_search",
1162
+ description: "Search for authors to add to Readarr",
1163
+ inputSchema: {
1164
+ type: "object",
1165
+ properties: {
1166
+ term: {
1167
+ type: "string",
1168
+ description: "Search term (author name)",
1169
+ },
1170
+ },
1171
+ required: ["term"],
1172
+ },
1173
+ }, {
1174
+ name: "readarr_get_queue",
1175
+ description: "Get Readarr download queue",
1176
+ inputSchema: {
1177
+ type: "object",
1178
+ properties: {},
1179
+ required: [],
1180
+ },
1181
+ }, {
1182
+ name: "readarr_get_books",
1183
+ description: "Get books for an author in Readarr. Shows which books are available and which are missing.",
1184
+ inputSchema: {
1185
+ type: "object",
1186
+ properties: {
1187
+ authorId: {
1188
+ type: "number",
1189
+ description: "Author ID to get books for",
1190
+ },
1191
+ },
1192
+ required: ["authorId"],
1193
+ },
1194
+ }, {
1195
+ name: "readarr_search_book",
1196
+ description: "Trigger a search for a specific book to download",
1197
+ inputSchema: {
1198
+ type: "object",
1199
+ properties: {
1200
+ bookIds: {
1201
+ type: "array",
1202
+ items: { type: "number" },
1203
+ description: "Book ID(s) to search for",
1204
+ },
1205
+ },
1206
+ required: ["bookIds"],
1207
+ },
1208
+ }, {
1209
+ name: "readarr_search_missing",
1210
+ description: "Trigger a search for all missing books for an author",
1211
+ inputSchema: {
1212
+ type: "object",
1213
+ properties: {
1214
+ authorId: {
1215
+ type: "number",
1216
+ description: "Author ID to search missing books for",
1217
+ },
1218
+ },
1219
+ required: ["authorId"],
1220
+ },
1221
+ }, {
1222
+ name: "readarr_get_calendar",
1223
+ description: "Get upcoming book releases from Readarr",
1224
+ inputSchema: {
1225
+ type: "object",
1226
+ properties: {
1227
+ days: {
1228
+ type: "number",
1229
+ description: "Number of days to look ahead (default: 30)",
1230
+ },
1231
+ },
1232
+ required: [],
1233
+ },
1234
+ });
806
1235
  }
807
1236
  // Prowlarr tools
808
1237
  if (clients.prowlarr) {
@@ -843,20 +1272,274 @@ if (clients.prowlarr) {
843
1272
  properties: {},
844
1273
  required: [],
845
1274
  },
846
- });
847
- }
848
- // Lingarr tools - imported from separate module to reduce merge conflicts
849
- if (clients.lingarr) {
850
- TOOLS.push(...getLingarrTools());
851
- }
852
- // Bazarr tools - imported from separate module
853
- if (clients.bazarr) {
854
- TOOLS.push(...bazarrTools);
855
- }
856
- // Cross-service search tool
857
- TOOLS.push({
858
- name: "arr_search_all",
859
- description: "Search across all configured *arr services for any media",
1275
+ }, {
1276
+ name: "prowlarr_get_indexer",
1277
+ description: "Get a specific indexer by ID",
1278
+ inputSchema: {
1279
+ type: "object",
1280
+ properties: {
1281
+ indexerId: {
1282
+ type: "number",
1283
+ description: "Indexer ID",
1284
+ },
1285
+ },
1286
+ required: ["indexerId"],
1287
+ },
1288
+ }, {
1289
+ name: "prowlarr_update_indexer",
1290
+ description: "Update an indexer (enable/disable, priority, tags)",
1291
+ inputSchema: {
1292
+ type: "object",
1293
+ properties: {
1294
+ indexerId: {
1295
+ type: "number",
1296
+ description: "Indexer ID to update",
1297
+ },
1298
+ enable: {
1299
+ type: "boolean",
1300
+ description: "Enable or disable the indexer",
1301
+ },
1302
+ priority: {
1303
+ type: "number",
1304
+ description: "Indexer priority (1-50, lower is higher priority)",
1305
+ },
1306
+ tags: {
1307
+ type: "array",
1308
+ items: { type: "number" },
1309
+ description: "Tag IDs to apply",
1310
+ },
1311
+ },
1312
+ required: ["indexerId"],
1313
+ },
1314
+ }, {
1315
+ name: "prowlarr_delete_indexer",
1316
+ description: "Delete an indexer from Prowlarr",
1317
+ inputSchema: {
1318
+ type: "object",
1319
+ properties: {
1320
+ indexerId: {
1321
+ type: "number",
1322
+ description: "Indexer ID to delete",
1323
+ },
1324
+ },
1325
+ required: ["indexerId"],
1326
+ },
1327
+ }, {
1328
+ name: "prowlarr_test_indexer",
1329
+ description: "Test a specific indexer connection",
1330
+ inputSchema: {
1331
+ type: "object",
1332
+ properties: {
1333
+ indexerId: {
1334
+ type: "number",
1335
+ description: "Indexer ID to test",
1336
+ },
1337
+ },
1338
+ required: ["indexerId"],
1339
+ },
1340
+ }, {
1341
+ name: "prowlarr_get_applications",
1342
+ description: "Get all applications (Sonarr, Radarr, etc. connections)",
1343
+ inputSchema: {
1344
+ type: "object",
1345
+ properties: {},
1346
+ required: [],
1347
+ },
1348
+ }, {
1349
+ name: "prowlarr_get_application",
1350
+ description: "Get a specific application by ID",
1351
+ inputSchema: {
1352
+ type: "object",
1353
+ properties: {
1354
+ applicationId: {
1355
+ type: "number",
1356
+ description: "Application ID",
1357
+ },
1358
+ },
1359
+ required: ["applicationId"],
1360
+ },
1361
+ }, {
1362
+ name: "prowlarr_update_application",
1363
+ description: "Update an application connection",
1364
+ inputSchema: {
1365
+ type: "object",
1366
+ properties: {
1367
+ applicationId: {
1368
+ type: "number",
1369
+ description: "Application ID to update",
1370
+ },
1371
+ syncLevel: {
1372
+ type: "string",
1373
+ description: "Sync level: disabled, addOnly, fullSync",
1374
+ },
1375
+ tags: {
1376
+ type: "array",
1377
+ items: { type: "number" },
1378
+ description: "Tag IDs to apply",
1379
+ },
1380
+ },
1381
+ required: ["applicationId"],
1382
+ },
1383
+ }, {
1384
+ name: "prowlarr_delete_application",
1385
+ description: "Delete an application from Prowlarr",
1386
+ inputSchema: {
1387
+ type: "object",
1388
+ properties: {
1389
+ applicationId: {
1390
+ type: "number",
1391
+ description: "Application ID to delete",
1392
+ },
1393
+ },
1394
+ required: ["applicationId"],
1395
+ },
1396
+ }, {
1397
+ name: "prowlarr_test_application",
1398
+ description: "Test an application connection",
1399
+ inputSchema: {
1400
+ type: "object",
1401
+ properties: {
1402
+ applicationId: {
1403
+ type: "number",
1404
+ description: "Application ID to test",
1405
+ },
1406
+ },
1407
+ required: ["applicationId"],
1408
+ },
1409
+ }, {
1410
+ name: "prowlarr_sync_applications",
1411
+ description: "Sync indexers to all applications (push indexer config to Sonarr/Radarr/etc.)",
1412
+ inputSchema: {
1413
+ type: "object",
1414
+ properties: {},
1415
+ required: [],
1416
+ },
1417
+ }, {
1418
+ name: "prowlarr_get_app_profiles",
1419
+ description: "Get app sync profiles (control which indexers sync to which apps)",
1420
+ inputSchema: {
1421
+ type: "object",
1422
+ properties: {},
1423
+ required: [],
1424
+ },
1425
+ }, {
1426
+ name: "prowlarr_get_history",
1427
+ description: "Get Prowlarr history (searches, grabs, failures)",
1428
+ inputSchema: {
1429
+ type: "object",
1430
+ properties: {
1431
+ page: {
1432
+ type: "number",
1433
+ description: "Page number (default: 1)",
1434
+ },
1435
+ pageSize: {
1436
+ type: "number",
1437
+ description: "Items per page (default: 20)",
1438
+ },
1439
+ indexerId: {
1440
+ type: "number",
1441
+ description: "Filter by indexer ID",
1442
+ },
1443
+ },
1444
+ required: [],
1445
+ },
1446
+ }, {
1447
+ name: "prowlarr_get_tags",
1448
+ description: "Get all tags defined in Prowlarr",
1449
+ inputSchema: {
1450
+ type: "object",
1451
+ properties: {},
1452
+ required: [],
1453
+ },
1454
+ }, {
1455
+ name: "prowlarr_create_tag",
1456
+ description: "Create a new tag in Prowlarr",
1457
+ inputSchema: {
1458
+ type: "object",
1459
+ properties: {
1460
+ label: {
1461
+ type: "string",
1462
+ description: "Tag label/name",
1463
+ },
1464
+ },
1465
+ required: ["label"],
1466
+ },
1467
+ }, {
1468
+ name: "prowlarr_update_tag",
1469
+ description: "Update a tag label in Prowlarr",
1470
+ inputSchema: {
1471
+ type: "object",
1472
+ properties: {
1473
+ tagId: {
1474
+ type: "number",
1475
+ description: "Tag ID to update",
1476
+ },
1477
+ label: {
1478
+ type: "string",
1479
+ description: "New tag label",
1480
+ },
1481
+ },
1482
+ required: ["tagId", "label"],
1483
+ },
1484
+ }, {
1485
+ name: "prowlarr_delete_tag",
1486
+ description: "Delete a tag from Prowlarr",
1487
+ inputSchema: {
1488
+ type: "object",
1489
+ properties: {
1490
+ tagId: {
1491
+ type: "number",
1492
+ description: "Tag ID to delete",
1493
+ },
1494
+ },
1495
+ required: ["tagId"],
1496
+ },
1497
+ }, {
1498
+ name: "prowlarr_get_indexer_schemas",
1499
+ description: "Get available indexer schemas (templates for adding new indexers)",
1500
+ inputSchema: {
1501
+ type: "object",
1502
+ properties: {},
1503
+ required: [],
1504
+ },
1505
+ }, {
1506
+ name: "prowlarr_get_notifications",
1507
+ description: "Get configured notification connections",
1508
+ inputSchema: {
1509
+ type: "object",
1510
+ properties: {},
1511
+ required: [],
1512
+ },
1513
+ }, {
1514
+ name: "prowlarr_rss_sync",
1515
+ description: "Trigger RSS sync for all indexers",
1516
+ inputSchema: {
1517
+ type: "object",
1518
+ properties: {},
1519
+ required: [],
1520
+ },
1521
+ }, {
1522
+ name: "prowlarr_get_download_clients",
1523
+ description: "Get configured download clients",
1524
+ inputSchema: {
1525
+ type: "object",
1526
+ properties: {},
1527
+ required: [],
1528
+ },
1529
+ });
1530
+ }
1531
+ // Lingarr tools - imported from separate module to reduce merge conflicts
1532
+ if (clients.lingarr) {
1533
+ TOOLS.push(...getLingarrTools());
1534
+ }
1535
+ // Bazarr tools - imported from separate module
1536
+ if (clients.bazarr) {
1537
+ TOOLS.push(...bazarrTools);
1538
+ }
1539
+ // Cross-service search tool
1540
+ TOOLS.push({
1541
+ name: "arr_search_all",
1542
+ description: "Search across all configured *arr services for any media",
860
1543
  inputSchema: {
861
1544
  type: "object",
862
1545
  properties: {
@@ -1182,193 +1865,770 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1182
1865
  content: [{
1183
1866
  type: "text",
1184
1867
  text: JSON.stringify({
1185
- count: downloadClients.length,
1186
- clients: downloadClients.map((c) => ({
1187
- id: c.id,
1188
- name: c.name,
1189
- implementation: c.implementationName,
1190
- protocol: c.protocol,
1191
- enabled: c.enable,
1192
- priority: c.priority,
1193
- removeCompletedDownloads: c.removeCompletedDownloads,
1194
- removeFailedDownloads: c.removeFailedDownloads,
1195
- tags: c.tags,
1868
+ count: downloadClients.length,
1869
+ clients: downloadClients.map((c) => ({
1870
+ id: c.id,
1871
+ name: c.name,
1872
+ implementation: c.implementationName,
1873
+ protocol: c.protocol,
1874
+ enabled: c.enable,
1875
+ priority: c.priority,
1876
+ removeCompletedDownloads: c.removeCompletedDownloads,
1877
+ removeFailedDownloads: c.removeFailedDownloads,
1878
+ tags: c.tags,
1879
+ })),
1880
+ }, null, 2),
1881
+ }],
1882
+ };
1883
+ }
1884
+ // Naming config
1885
+ case "sonarr_get_naming":
1886
+ case "radarr_get_naming":
1887
+ case "lidarr_get_naming":
1888
+ case "readarr_get_naming": {
1889
+ const serviceName = name.split('_')[0];
1890
+ const client = clients[serviceName];
1891
+ if (!client)
1892
+ throw new Error(`${serviceName} not configured`);
1893
+ const naming = await client.getNamingConfig();
1894
+ return {
1895
+ content: [{
1896
+ type: "text",
1897
+ text: JSON.stringify(naming, null, 2),
1898
+ }],
1899
+ };
1900
+ }
1901
+ // Tags
1902
+ case "sonarr_get_tags":
1903
+ case "radarr_get_tags":
1904
+ case "lidarr_get_tags":
1905
+ case "readarr_get_tags": {
1906
+ const serviceName = name.split('_')[0];
1907
+ const client = clients[serviceName];
1908
+ if (!client)
1909
+ throw new Error(`${serviceName} not configured`);
1910
+ const tags = await client.getTags();
1911
+ return {
1912
+ content: [{
1913
+ type: "text",
1914
+ text: JSON.stringify({
1915
+ count: tags.length,
1916
+ tags: tags.map((t) => ({ id: t.id, label: t.label })),
1917
+ }, null, 2),
1918
+ }],
1919
+ };
1920
+ }
1921
+ // Comprehensive setup review
1922
+ case "sonarr_review_setup":
1923
+ case "radarr_review_setup":
1924
+ case "lidarr_review_setup":
1925
+ case "readarr_review_setup": {
1926
+ const serviceName = name.split('_')[0];
1927
+ const client = clients[serviceName];
1928
+ if (!client)
1929
+ throw new Error(`${serviceName} not configured`);
1930
+ // Gather all configuration data
1931
+ const [status, health, qualityProfiles, qualityDefinitions, downloadClients, naming, mediaManagement, rootFolders, tags, indexers] = await Promise.all([
1932
+ client.getStatus(),
1933
+ client.getHealth(),
1934
+ client.getQualityProfiles(),
1935
+ client.getQualityDefinitions(),
1936
+ client.getDownloadClients(),
1937
+ client.getNamingConfig(),
1938
+ client.getMediaManagement(),
1939
+ client.getRootFoldersDetailed(),
1940
+ client.getTags(),
1941
+ client.getIndexers(),
1942
+ ]);
1943
+ // For Lidarr/Readarr, also get metadata profiles
1944
+ let metadataProfiles = null;
1945
+ if (serviceName === 'lidarr' && clients.lidarr) {
1946
+ metadataProfiles = await clients.lidarr.getMetadataProfiles();
1947
+ }
1948
+ else if (serviceName === 'readarr' && clients.readarr) {
1949
+ metadataProfiles = await clients.readarr.getMetadataProfiles();
1950
+ }
1951
+ const review = {
1952
+ service: serviceName,
1953
+ version: status.version,
1954
+ appName: status.appName,
1955
+ platform: {
1956
+ os: status.osName,
1957
+ isDocker: status.isDocker,
1958
+ },
1959
+ health: {
1960
+ issueCount: health.length,
1961
+ issues: health,
1962
+ },
1963
+ storage: {
1964
+ rootFolders: rootFolders.map((f) => ({
1965
+ path: f.path,
1966
+ accessible: f.accessible,
1967
+ freeSpace: formatBytes(f.freeSpace),
1968
+ freeSpaceBytes: f.freeSpace,
1969
+ unmappedFolderCount: f.unmappedFolders?.length || 0,
1970
+ })),
1971
+ },
1972
+ qualityProfiles: qualityProfiles.map((p) => ({
1973
+ id: p.id,
1974
+ name: p.name,
1975
+ upgradeAllowed: p.upgradeAllowed,
1976
+ cutoff: p.cutoff,
1977
+ allowedQualities: p.items
1978
+ .filter((i) => i.allowed)
1979
+ .map((i) => i.quality?.name || i.name || (i.items?.map((q) => q.quality.name).join(', ')))
1980
+ .filter(Boolean),
1981
+ customFormatsWithScores: p.formatItems?.filter((f) => f.score !== 0).length || 0,
1982
+ minFormatScore: p.minFormatScore,
1983
+ })),
1984
+ qualityDefinitions: qualityDefinitions.map((d) => ({
1985
+ quality: d.quality.name,
1986
+ minSize: d.minSize + ' MB/min',
1987
+ maxSize: d.maxSize === 0 ? 'unlimited' : d.maxSize + ' MB/min',
1988
+ preferredSize: d.preferredSize + ' MB/min',
1989
+ })),
1990
+ downloadClients: downloadClients.map((c) => ({
1991
+ name: c.name,
1992
+ type: c.implementationName,
1993
+ protocol: c.protocol,
1994
+ enabled: c.enable,
1995
+ priority: c.priority,
1996
+ })),
1997
+ indexers: indexers.map((i) => ({
1998
+ name: i.name,
1999
+ protocol: i.protocol,
2000
+ enableRss: i.enableRss,
2001
+ enableAutomaticSearch: i.enableAutomaticSearch,
2002
+ enableInteractiveSearch: i.enableInteractiveSearch,
2003
+ priority: i.priority,
2004
+ })),
2005
+ naming: naming,
2006
+ mediaManagement: {
2007
+ recycleBin: mediaManagement.recycleBin || 'not set',
2008
+ recycleBinCleanupDays: mediaManagement.recycleBinCleanupDays,
2009
+ downloadPropersAndRepacks: mediaManagement.downloadPropersAndRepacks,
2010
+ deleteEmptyFolders: mediaManagement.deleteEmptyFolders,
2011
+ copyUsingHardlinks: mediaManagement.copyUsingHardlinks,
2012
+ importExtraFiles: mediaManagement.importExtraFiles,
2013
+ extraFileExtensions: mediaManagement.extraFileExtensions,
2014
+ },
2015
+ tags: tags.map((t) => t.label),
2016
+ ...(metadataProfiles && { metadataProfiles }),
2017
+ };
2018
+ return {
2019
+ content: [{
2020
+ type: "text",
2021
+ text: JSON.stringify(review, null, 2),
2022
+ }],
2023
+ };
2024
+ }
2025
+ // Sonarr handlers
2026
+ case "sonarr_get_series": {
2027
+ if (!clients.sonarr)
2028
+ throw new Error("Sonarr not configured");
2029
+ const series = await clients.sonarr.getSeries();
2030
+ return {
2031
+ content: [{
2032
+ type: "text",
2033
+ text: JSON.stringify({
2034
+ count: series.length,
2035
+ series: series.map(s => ({
2036
+ id: s.id,
2037
+ title: s.title,
2038
+ year: s.year,
2039
+ status: s.status,
2040
+ network: s.network,
2041
+ seasons: s.statistics?.seasonCount,
2042
+ episodes: s.statistics?.episodeFileCount + '/' + s.statistics?.totalEpisodeCount,
2043
+ sizeOnDisk: formatBytes(s.statistics?.sizeOnDisk || 0),
2044
+ monitored: s.monitored,
2045
+ })),
2046
+ }, null, 2),
2047
+ }],
2048
+ };
2049
+ }
2050
+ case "sonarr_search": {
2051
+ if (!clients.sonarr)
2052
+ throw new Error("Sonarr not configured");
2053
+ const term = args.term;
2054
+ const results = await clients.sonarr.searchSeries(term);
2055
+ return {
2056
+ content: [{
2057
+ type: "text",
2058
+ text: JSON.stringify({
2059
+ count: results.length,
2060
+ results: results.slice(0, 10).map(r => ({
2061
+ title: r.title,
2062
+ year: r.year,
2063
+ tvdbId: r.tvdbId,
2064
+ overview: r.overview?.substring(0, 200) + (r.overview && r.overview.length > 200 ? '...' : ''),
2065
+ })),
2066
+ }, null, 2),
2067
+ }],
2068
+ };
2069
+ }
2070
+ case "sonarr_get_queue": {
2071
+ if (!clients.sonarr)
2072
+ throw new Error("Sonarr not configured");
2073
+ const queue = await clients.sonarr.getQueue();
2074
+ return {
2075
+ content: [{
2076
+ type: "text",
2077
+ text: JSON.stringify({
2078
+ totalRecords: queue.totalRecords,
2079
+ items: queue.records.map(q => ({
2080
+ title: q.title,
2081
+ status: q.status,
2082
+ progress: ((1 - q.sizeleft / q.size) * 100).toFixed(1) + '%',
2083
+ timeLeft: q.timeleft,
2084
+ downloadClient: q.downloadClient,
2085
+ })),
2086
+ }, null, 2),
2087
+ }],
2088
+ };
2089
+ }
2090
+ case "sonarr_get_calendar": {
2091
+ if (!clients.sonarr)
2092
+ throw new Error("Sonarr not configured");
2093
+ const days = args?.days || 7;
2094
+ const start = new Date().toISOString().split('T')[0];
2095
+ const end = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
2096
+ const calendar = await clients.sonarr.getCalendar(start, end);
2097
+ return {
2098
+ content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
2099
+ };
2100
+ }
2101
+ case "sonarr_get_episodes": {
2102
+ if (!clients.sonarr)
2103
+ throw new Error("Sonarr not configured");
2104
+ const { seriesId, seasonNumber } = args;
2105
+ const episodes = await clients.sonarr.getEpisodes(seriesId, seasonNumber);
2106
+ return {
2107
+ content: [{
2108
+ type: "text",
2109
+ text: JSON.stringify({
2110
+ count: episodes.length,
2111
+ episodes: episodes.map(e => ({
2112
+ id: e.id,
2113
+ seasonNumber: e.seasonNumber,
2114
+ episodeNumber: e.episodeNumber,
2115
+ title: e.title,
2116
+ airDate: e.airDate,
2117
+ hasFile: e.hasFile,
2118
+ monitored: e.monitored,
2119
+ })),
2120
+ }, null, 2),
2121
+ }],
2122
+ };
2123
+ }
2124
+ case "sonarr_search_missing": {
2125
+ if (!clients.sonarr)
2126
+ throw new Error("Sonarr not configured");
2127
+ const seriesId = args.seriesId;
2128
+ const result = await clients.sonarr.searchMissing(seriesId);
2129
+ return {
2130
+ content: [{
2131
+ type: "text",
2132
+ text: JSON.stringify({
2133
+ success: true,
2134
+ message: `Search triggered for missing episodes`,
2135
+ commandId: result.id,
2136
+ }, null, 2),
2137
+ }],
2138
+ };
2139
+ }
2140
+ case "sonarr_search_episode": {
2141
+ if (!clients.sonarr)
2142
+ throw new Error("Sonarr not configured");
2143
+ const episodeIds = args.episodeIds;
2144
+ const result = await clients.sonarr.searchEpisode(episodeIds);
2145
+ return {
2146
+ content: [{
2147
+ type: "text",
2148
+ text: JSON.stringify({
2149
+ success: true,
2150
+ message: `Search triggered for ${episodeIds.length} episode(s)`,
2151
+ commandId: result.id,
2152
+ }, null, 2),
2153
+ }],
2154
+ };
2155
+ }
2156
+ case "sonarr_add_series": {
2157
+ if (!clients.sonarr)
2158
+ throw new Error("Sonarr not configured");
2159
+ const { tvdbId, title, qualityProfileId, rootFolderPath, seasonFolder, monitored, tags } = args;
2160
+ const series = await clients.sonarr.addSeries({
2161
+ tvdbId,
2162
+ title,
2163
+ qualityProfileId,
2164
+ rootFolderPath,
2165
+ seasonFolder,
2166
+ monitored,
2167
+ tags,
2168
+ });
2169
+ return {
2170
+ content: [{
2171
+ type: "text",
2172
+ text: JSON.stringify({
2173
+ success: true,
2174
+ message: `Series '${title}' added to Sonarr`,
2175
+ seriesId: series.id,
2176
+ series: {
2177
+ id: series.id,
2178
+ title: series.title,
2179
+ path: series.path,
2180
+ monitored: series.monitored,
2181
+ },
2182
+ }, null, 2),
2183
+ }],
2184
+ };
2185
+ }
2186
+ case "sonarr_update_series": {
2187
+ if (!clients.sonarr)
2188
+ throw new Error("Sonarr not configured");
2189
+ const { seriesId, ...updates } = args;
2190
+ const series = await clients.sonarr.updateSeries(seriesId, updates);
2191
+ return {
2192
+ content: [{
2193
+ type: "text",
2194
+ text: JSON.stringify({
2195
+ success: true,
2196
+ message: `Series '${series.title}' updated`,
2197
+ seriesId: series.id,
2198
+ updates: Object.keys(updates),
2199
+ }, null, 2),
2200
+ }],
2201
+ };
2202
+ }
2203
+ case "sonarr_delete_series": {
2204
+ if (!clients.sonarr)
2205
+ throw new Error("Sonarr not configured");
2206
+ const { seriesId, deleteFiles = false, addImportListExclusion = false } = args;
2207
+ await clients.sonarr.deleteSeries(seriesId, deleteFiles, addImportListExclusion);
2208
+ return {
2209
+ content: [{
2210
+ type: "text",
2211
+ text: JSON.stringify({
2212
+ success: true,
2213
+ message: `Series deleted (files: ${deleteFiles}, exclude: ${addImportListExclusion})`,
2214
+ }, null, 2),
2215
+ }],
2216
+ };
2217
+ }
2218
+ case "sonarr_update_episode": {
2219
+ if (!clients.sonarr)
2220
+ throw new Error("Sonarr not configured");
2221
+ const { episodeId, monitored } = args;
2222
+ const episode = await clients.sonarr.updateEpisode(episodeId, { monitored });
2223
+ return {
2224
+ content: [{
2225
+ type: "text",
2226
+ text: JSON.stringify({
2227
+ success: true,
2228
+ message: `Episode ${episodeId} ${monitored ? 'monitored' : 'unmonitored'}`,
2229
+ episodeId: episode.id,
2230
+ monitored: episode.monitored,
2231
+ }, null, 2),
2232
+ }],
2233
+ };
2234
+ }
2235
+ case "sonarr_delete_episode_file": {
2236
+ if (!clients.sonarr)
2237
+ throw new Error("Sonarr not configured");
2238
+ const { episodeFileId } = args;
2239
+ await clients.sonarr.deleteEpisodeFile(episodeFileId);
2240
+ return {
2241
+ content: [{
2242
+ type: "text",
2243
+ text: JSON.stringify({
2244
+ success: true,
2245
+ message: `Episode file ${episodeFileId} deleted`,
2246
+ }, null, 2),
2247
+ }],
2248
+ };
2249
+ }
2250
+ case "sonarr_refresh_series": {
2251
+ if (!clients.sonarr)
2252
+ throw new Error("Sonarr not configured");
2253
+ const { seriesId } = args;
2254
+ const result = await clients.sonarr.refreshSeries(seriesId);
2255
+ return {
2256
+ content: [{
2257
+ type: "text",
2258
+ text: JSON.stringify({
2259
+ success: true,
2260
+ message: `Series ${seriesId} metadata refresh triggered`,
2261
+ commandId: result.id,
2262
+ }, null, 2),
2263
+ }],
2264
+ };
2265
+ }
2266
+ case "sonarr_rescan_series": {
2267
+ if (!clients.sonarr)
2268
+ throw new Error("Sonarr not configured");
2269
+ const { seriesId } = args;
2270
+ const result = await clients.sonarr.rescanSeries(seriesId);
2271
+ return {
2272
+ content: [{
2273
+ type: "text",
2274
+ text: JSON.stringify({
2275
+ success: true,
2276
+ message: `Series ${seriesId} disk rescan triggered`,
2277
+ commandId: result.id,
2278
+ }, null, 2),
2279
+ }],
2280
+ };
2281
+ }
2282
+ case "sonarr_rename_series": {
2283
+ if (!clients.sonarr)
2284
+ throw new Error("Sonarr not configured");
2285
+ const { seriesId } = args;
2286
+ const result = await clients.sonarr.renameSeries(seriesId);
2287
+ return {
2288
+ content: [{
2289
+ type: "text",
2290
+ text: JSON.stringify({
2291
+ success: true,
2292
+ message: `Series ${seriesId} files will be renamed`,
2293
+ commandId: result.id,
2294
+ }, null, 2),
2295
+ }],
2296
+ };
2297
+ }
2298
+ case "sonarr_rename_episodes": {
2299
+ if (!clients.sonarr)
2300
+ throw new Error("Sonarr not configured");
2301
+ const { seriesId } = args;
2302
+ const result = await clients.sonarr.renameEpisodes(seriesId);
2303
+ return {
2304
+ content: [{
2305
+ type: "text",
2306
+ text: JSON.stringify({
2307
+ success: true,
2308
+ message: `All episodes for series ${seriesId} will be renamed`,
2309
+ commandId: result.id,
2310
+ }, null, 2),
2311
+ }],
2312
+ };
2313
+ }
2314
+ case "sonarr_get_releases": {
2315
+ if (!clients.sonarr)
2316
+ throw new Error("Sonarr not configured");
2317
+ const { seriesId, page = 1, pageSize = 10 } = args;
2318
+ const releases = await clients.sonarr.getReleases(seriesId, page, pageSize);
2319
+ return {
2320
+ content: [{
2321
+ type: "text",
2322
+ text: JSON.stringify({
2323
+ totalRecords: releases.length,
2324
+ page,
2325
+ pageSize,
2326
+ releases: releases.map((r) => ({
2327
+ guid: r.guid,
2328
+ title: r.title,
2329
+ indexer: r.indexer,
2330
+ quality: r.quality,
2331
+ size: formatBytes(r.size),
2332
+ age: r.age,
2333
+ ageHours: r.ageHours,
2334
+ rejected: r.rejected,
2335
+ rejectionReason: r.rejectionReason,
2336
+ publishedDate: r.publishedDate,
2337
+ })),
2338
+ }, null, 2),
2339
+ }],
2340
+ };
2341
+ }
2342
+ case "sonarr_delete_release": {
2343
+ if (!clients.sonarr)
2344
+ throw new Error("Sonarr not configured");
2345
+ const { guid } = args;
2346
+ await clients.sonarr.deleteRelease(guid);
2347
+ return {
2348
+ content: [{
2349
+ type: "text",
2350
+ text: JSON.stringify({
2351
+ success: true,
2352
+ message: `Release ${guid} deleted from history`,
2353
+ }, null, 2),
2354
+ }],
2355
+ };
2356
+ }
2357
+ case "sonarr_create_release_profile": {
2358
+ if (!clients.sonarr)
2359
+ throw new Error("Sonarr not configured");
2360
+ const profile = await clients.sonarr.createReleaseProfile(args);
2361
+ return {
2362
+ content: [{
2363
+ type: "text",
2364
+ text: JSON.stringify({
2365
+ success: true,
2366
+ message: `Release profile '${profile.name}' created`,
2367
+ profileId: profile.id,
2368
+ profile,
2369
+ }, null, 2),
2370
+ }],
2371
+ };
2372
+ }
2373
+ case "sonarr_update_release_profile": {
2374
+ if (!clients.sonarr)
2375
+ throw new Error("Sonarr not configured");
2376
+ const { profileId, ...updates } = args;
2377
+ const profile = await clients.sonarr.updateReleaseProfile(profileId, updates);
2378
+ return {
2379
+ content: [{
2380
+ type: "text",
2381
+ text: JSON.stringify({
2382
+ success: true,
2383
+ message: `Release profile ${profileId} updated`,
2384
+ profileId: profile.id,
2385
+ profile,
2386
+ }, null, 2),
2387
+ }],
2388
+ };
2389
+ }
2390
+ case "sonarr_delete_release_profile": {
2391
+ if (!clients.sonarr)
2392
+ throw new Error("Sonarr not configured");
2393
+ const { profileId } = args;
2394
+ await clients.sonarr.deleteReleaseProfile(profileId);
2395
+ return {
2396
+ content: [{
2397
+ type: "text",
2398
+ text: JSON.stringify({
2399
+ success: true,
2400
+ message: `Release profile ${profileId} deleted`,
2401
+ }, null, 2),
2402
+ }],
2403
+ };
2404
+ }
2405
+ case "sonarr_create_tag": {
2406
+ if (!clients.sonarr)
2407
+ throw new Error("Sonarr not configured");
2408
+ const { label } = args;
2409
+ const tag = await clients.sonarr.createTag(label);
2410
+ return {
2411
+ content: [{
2412
+ type: "text",
2413
+ text: JSON.stringify({
2414
+ success: true,
2415
+ message: `Tag '${label}' created`,
2416
+ tagId: tag.id,
2417
+ tag,
2418
+ }, null, 2),
2419
+ }],
2420
+ };
2421
+ }
2422
+ case "sonarr_update_tag": {
2423
+ if (!clients.sonarr)
2424
+ throw new Error("Sonarr not configured");
2425
+ const { tagId, label } = args;
2426
+ const tag = await clients.sonarr.updateTag(tagId, label);
2427
+ return {
2428
+ content: [{
2429
+ type: "text",
2430
+ text: JSON.stringify({
2431
+ success: true,
2432
+ message: `Tag ${tagId} updated to '${label}'`,
2433
+ tagId: tag.id,
2434
+ tag,
2435
+ }, null, 2),
2436
+ }],
2437
+ };
2438
+ }
2439
+ case "sonarr_delete_tag": {
2440
+ if (!clients.sonarr)
2441
+ throw new Error("Sonarr not configured");
2442
+ const { tagId } = args;
2443
+ await clients.sonarr.deleteTag(tagId);
2444
+ return {
2445
+ content: [{
2446
+ type: "text",
2447
+ text: JSON.stringify({
2448
+ success: true,
2449
+ message: `Tag ${tagId} deleted`,
2450
+ }, null, 2),
2451
+ }],
2452
+ };
2453
+ }
2454
+ case "sonarr_get_history": {
2455
+ if (!clients.sonarr)
2456
+ throw new Error("Sonarr not configured");
2457
+ const { page = 1, pageSize = 20, seriesId } = args;
2458
+ if (seriesId) {
2459
+ const history = await clients.sonarr.getSeriesHistory(seriesId);
2460
+ return {
2461
+ content: [{
2462
+ type: "text",
2463
+ text: JSON.stringify({
2464
+ seriesId,
2465
+ count: history.length,
2466
+ records: history.map((h) => ({
2467
+ id: h.id,
2468
+ date: h.date,
2469
+ eventType: h.eventType,
2470
+ sourceTitle: h.sourceTitle,
2471
+ quality: h.quality?.quality?.name,
2472
+ episodeId: h.episodeId,
2473
+ })),
2474
+ }, null, 2),
2475
+ }],
2476
+ };
2477
+ }
2478
+ const history = await clients.sonarr.getHistory(page, pageSize);
2479
+ return {
2480
+ content: [{
2481
+ type: "text",
2482
+ text: JSON.stringify({
2483
+ page,
2484
+ pageSize,
2485
+ totalRecords: history.totalRecords,
2486
+ records: history.records.map((h) => ({
2487
+ id: h.id,
2488
+ seriesId: h.seriesId,
2489
+ episodeId: h.episodeId,
2490
+ date: h.date,
2491
+ eventType: h.eventType,
2492
+ sourceTitle: h.sourceTitle,
2493
+ quality: h.quality?.quality?.name,
2494
+ })),
2495
+ }, null, 2),
2496
+ }],
2497
+ };
2498
+ }
2499
+ case "sonarr_get_wanted": {
2500
+ if (!clients.sonarr)
2501
+ throw new Error("Sonarr not configured");
2502
+ const { page = 1, pageSize = 20 } = args;
2503
+ const wanted = await clients.sonarr.getWanted(page, pageSize);
2504
+ return {
2505
+ content: [{
2506
+ type: "text",
2507
+ text: JSON.stringify({
2508
+ page,
2509
+ pageSize,
2510
+ totalRecords: wanted.totalRecords,
2511
+ episodes: wanted.records.map((e) => ({
2512
+ id: e.id,
2513
+ seriesId: e.seriesId,
2514
+ seasonNumber: e.seasonNumber,
2515
+ episodeNumber: e.episodeNumber,
2516
+ title: e.title,
2517
+ airDate: e.airDate,
2518
+ monitored: e.monitored,
2519
+ })),
2520
+ }, null, 2),
2521
+ }],
2522
+ };
2523
+ }
2524
+ case "sonarr_get_cutoff_unmet": {
2525
+ if (!clients.sonarr)
2526
+ throw new Error("Sonarr not configured");
2527
+ const { page = 1, pageSize = 20 } = args;
2528
+ const cutoff = await clients.sonarr.getCutoffUnmet(page, pageSize);
2529
+ return {
2530
+ content: [{
2531
+ type: "text",
2532
+ text: JSON.stringify({
2533
+ page,
2534
+ pageSize,
2535
+ totalRecords: cutoff.totalRecords,
2536
+ episodes: cutoff.records.map((e) => ({
2537
+ id: e.id,
2538
+ seriesId: e.seriesId,
2539
+ seasonNumber: e.seasonNumber,
2540
+ episodeNumber: e.episodeNumber,
2541
+ title: e.title,
2542
+ currentQuality: e.episodeFile?.quality?.quality?.name,
1196
2543
  })),
1197
2544
  }, null, 2),
1198
2545
  }],
1199
2546
  };
1200
2547
  }
1201
- // Naming config
1202
- case "sonarr_get_naming":
1203
- case "radarr_get_naming":
1204
- case "lidarr_get_naming":
1205
- case "readarr_get_naming": {
1206
- const serviceName = name.split('_')[0];
1207
- const client = clients[serviceName];
1208
- if (!client)
1209
- throw new Error(`${serviceName} not configured`);
1210
- const naming = await client.getNamingConfig();
2548
+ case "sonarr_get_blocklist": {
2549
+ if (!clients.sonarr)
2550
+ throw new Error("Sonarr not configured");
2551
+ const { page = 1, pageSize = 20 } = args;
2552
+ const blocklist = await clients.sonarr.getBlocklist(page, pageSize);
1211
2553
  return {
1212
2554
  content: [{
1213
2555
  type: "text",
1214
- text: JSON.stringify(naming, null, 2),
2556
+ text: JSON.stringify({
2557
+ page,
2558
+ pageSize,
2559
+ totalRecords: blocklist.totalRecords,
2560
+ records: blocklist.records.map((b) => ({
2561
+ id: b.id,
2562
+ seriesId: b.seriesId,
2563
+ sourceTitle: b.sourceTitle,
2564
+ date: b.date,
2565
+ protocol: b.protocol,
2566
+ indexer: b.indexer,
2567
+ message: b.message,
2568
+ })),
2569
+ }, null, 2),
1215
2570
  }],
1216
2571
  };
1217
2572
  }
1218
- // Tags
1219
- case "sonarr_get_tags":
1220
- case "radarr_get_tags":
1221
- case "lidarr_get_tags":
1222
- case "readarr_get_tags": {
1223
- const serviceName = name.split('_')[0];
1224
- const client = clients[serviceName];
1225
- if (!client)
1226
- throw new Error(`${serviceName} not configured`);
1227
- const tags = await client.getTags();
2573
+ case "sonarr_delete_blocklist_item": {
2574
+ if (!clients.sonarr)
2575
+ throw new Error("Sonarr not configured");
2576
+ const { blocklistId } = args;
2577
+ await clients.sonarr.deleteBlocklistItem(blocklistId);
1228
2578
  return {
1229
2579
  content: [{
1230
2580
  type: "text",
1231
2581
  text: JSON.stringify({
1232
- count: tags.length,
1233
- tags: tags.map((t) => ({ id: t.id, label: t.label })),
2582
+ success: true,
2583
+ message: `Blocklist item ${blocklistId} deleted`,
1234
2584
  }, null, 2),
1235
2585
  }],
1236
2586
  };
1237
2587
  }
1238
- // Comprehensive setup review
1239
- case "sonarr_review_setup":
1240
- case "radarr_review_setup":
1241
- case "lidarr_review_setup":
1242
- case "readarr_review_setup": {
1243
- const serviceName = name.split('_')[0];
1244
- const client = clients[serviceName];
1245
- if (!client)
1246
- throw new Error(`${serviceName} not configured`);
1247
- // Gather all configuration data
1248
- const [status, health, qualityProfiles, qualityDefinitions, downloadClients, naming, mediaManagement, rootFolders, tags, indexers] = await Promise.all([
1249
- client.getStatus(),
1250
- client.getHealth(),
1251
- client.getQualityProfiles(),
1252
- client.getQualityDefinitions(),
1253
- client.getDownloadClients(),
1254
- client.getNamingConfig(),
1255
- client.getMediaManagement(),
1256
- client.getRootFoldersDetailed(),
1257
- client.getTags(),
1258
- client.getIndexers(),
1259
- ]);
1260
- // For Lidarr/Readarr, also get metadata profiles
1261
- let metadataProfiles = null;
1262
- if (serviceName === 'lidarr' && clients.lidarr) {
1263
- metadataProfiles = await clients.lidarr.getMetadataProfiles();
1264
- }
1265
- else if (serviceName === 'readarr' && clients.readarr) {
1266
- metadataProfiles = await clients.readarr.getMetadataProfiles();
1267
- }
1268
- const review = {
1269
- service: serviceName,
1270
- version: status.version,
1271
- appName: status.appName,
1272
- platform: {
1273
- os: status.osName,
1274
- isDocker: status.isDocker,
1275
- },
1276
- health: {
1277
- issueCount: health.length,
1278
- issues: health,
1279
- },
1280
- storage: {
1281
- rootFolders: rootFolders.map((f) => ({
1282
- path: f.path,
1283
- accessible: f.accessible,
1284
- freeSpace: formatBytes(f.freeSpace),
1285
- freeSpaceBytes: f.freeSpace,
1286
- unmappedFolderCount: f.unmappedFolders?.length || 0,
1287
- })),
1288
- },
1289
- qualityProfiles: qualityProfiles.map((p) => ({
1290
- id: p.id,
1291
- name: p.name,
1292
- upgradeAllowed: p.upgradeAllowed,
1293
- cutoff: p.cutoff,
1294
- allowedQualities: p.items
1295
- .filter((i) => i.allowed)
1296
- .map((i) => i.quality?.name || i.name || (i.items?.map((q) => q.quality.name).join(', ')))
1297
- .filter(Boolean),
1298
- customFormatsWithScores: p.formatItems?.filter((f) => f.score !== 0).length || 0,
1299
- minFormatScore: p.minFormatScore,
1300
- })),
1301
- qualityDefinitions: qualityDefinitions.map((d) => ({
1302
- quality: d.quality.name,
1303
- minSize: d.minSize + ' MB/min',
1304
- maxSize: d.maxSize === 0 ? 'unlimited' : d.maxSize + ' MB/min',
1305
- preferredSize: d.preferredSize + ' MB/min',
1306
- })),
1307
- downloadClients: downloadClients.map((c) => ({
1308
- name: c.name,
1309
- type: c.implementationName,
1310
- protocol: c.protocol,
1311
- enabled: c.enable,
1312
- priority: c.priority,
1313
- })),
1314
- indexers: indexers.map((i) => ({
1315
- name: i.name,
1316
- protocol: i.protocol,
1317
- enableRss: i.enableRss,
1318
- enableAutomaticSearch: i.enableAutomaticSearch,
1319
- enableInteractiveSearch: i.enableInteractiveSearch,
1320
- priority: i.priority,
1321
- })),
1322
- naming: naming,
1323
- mediaManagement: {
1324
- recycleBin: mediaManagement.recycleBin || 'not set',
1325
- recycleBinCleanupDays: mediaManagement.recycleBinCleanupDays,
1326
- downloadPropersAndRepacks: mediaManagement.downloadPropersAndRepacks,
1327
- deleteEmptyFolders: mediaManagement.deleteEmptyFolders,
1328
- copyUsingHardlinks: mediaManagement.copyUsingHardlinks,
1329
- importExtraFiles: mediaManagement.importExtraFiles,
1330
- extraFileExtensions: mediaManagement.extraFileExtensions,
1331
- },
1332
- tags: tags.map((t) => t.label),
1333
- ...(metadataProfiles && { metadataProfiles }),
1334
- };
2588
+ case "sonarr_search_all_missing": {
2589
+ if (!clients.sonarr)
2590
+ throw new Error("Sonarr not configured");
2591
+ const result = await clients.sonarr.searchAllMissing();
1335
2592
  return {
1336
2593
  content: [{
1337
2594
  type: "text",
1338
- text: JSON.stringify(review, null, 2),
2595
+ text: JSON.stringify({
2596
+ success: true,
2597
+ message: 'Search triggered for all missing episodes',
2598
+ commandId: result.id,
2599
+ }, null, 2),
1339
2600
  }],
1340
2601
  };
1341
2602
  }
1342
- // Sonarr handlers
1343
- case "sonarr_get_series": {
1344
- if (!clients.sonarr)
1345
- throw new Error("Sonarr not configured");
1346
- const series = await clients.sonarr.getSeries();
2603
+ // Radarr handlers
2604
+ case "radarr_get_movies": {
2605
+ if (!clients.radarr)
2606
+ throw new Error("Radarr not configured");
2607
+ const movies = await clients.radarr.getMovies();
1347
2608
  return {
1348
2609
  content: [{
1349
2610
  type: "text",
1350
2611
  text: JSON.stringify({
1351
- count: series.length,
1352
- series: series.map(s => ({
1353
- id: s.id,
1354
- title: s.title,
1355
- year: s.year,
1356
- status: s.status,
1357
- network: s.network,
1358
- seasons: s.statistics?.seasonCount,
1359
- episodes: s.statistics?.episodeFileCount + '/' + s.statistics?.totalEpisodeCount,
1360
- sizeOnDisk: formatBytes(s.statistics?.sizeOnDisk || 0),
1361
- monitored: s.monitored,
2612
+ count: movies.length,
2613
+ movies: movies.map(m => ({
2614
+ id: m.id,
2615
+ title: m.title,
2616
+ year: m.year,
2617
+ status: m.status,
2618
+ hasFile: m.hasFile,
2619
+ sizeOnDisk: formatBytes(m.sizeOnDisk),
2620
+ monitored: m.monitored,
2621
+ studio: m.studio,
1362
2622
  })),
1363
2623
  }, null, 2),
1364
2624
  }],
1365
2625
  };
1366
2626
  }
1367
- case "sonarr_search": {
1368
- if (!clients.sonarr)
1369
- throw new Error("Sonarr not configured");
2627
+ case "radarr_search": {
2628
+ if (!clients.radarr)
2629
+ throw new Error("Radarr not configured");
1370
2630
  const term = args.term;
1371
- const results = await clients.sonarr.searchSeries(term);
2631
+ const results = await clients.radarr.searchMovies(term);
1372
2632
  return {
1373
2633
  content: [{
1374
2634
  type: "text",
@@ -1377,17 +2637,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1377
2637
  results: results.slice(0, 10).map(r => ({
1378
2638
  title: r.title,
1379
2639
  year: r.year,
1380
- tvdbId: r.tvdbId,
2640
+ tmdbId: r.tmdbId,
2641
+ imdbId: r.imdbId,
1381
2642
  overview: r.overview?.substring(0, 200) + (r.overview && r.overview.length > 200 ? '...' : ''),
1382
2643
  })),
1383
2644
  }, null, 2),
1384
2645
  }],
1385
2646
  };
1386
2647
  }
1387
- case "sonarr_get_queue": {
1388
- if (!clients.sonarr)
1389
- throw new Error("Sonarr not configured");
1390
- const queue = await clients.sonarr.getQueue();
2648
+ case "radarr_get_queue": {
2649
+ if (!clients.radarr)
2650
+ throw new Error("Radarr not configured");
2651
+ const queue = await clients.radarr.getQueue();
1391
2652
  return {
1392
2653
  content: [{
1393
2654
  type: "text",
@@ -1404,83 +2665,44 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1404
2665
  }],
1405
2666
  };
1406
2667
  }
1407
- case "sonarr_get_calendar": {
1408
- if (!clients.sonarr)
1409
- throw new Error("Sonarr not configured");
1410
- const days = args?.days || 7;
2668
+ case "radarr_get_calendar": {
2669
+ if (!clients.radarr)
2670
+ throw new Error("Radarr not configured");
2671
+ const days = args?.days || 30;
1411
2672
  const start = new Date().toISOString().split('T')[0];
1412
2673
  const end = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
1413
- const calendar = await clients.sonarr.getCalendar(start, end);
1414
- return {
1415
- content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
1416
- };
1417
- }
1418
- case "sonarr_get_episodes": {
1419
- if (!clients.sonarr)
1420
- throw new Error("Sonarr not configured");
1421
- const { seriesId, seasonNumber } = args;
1422
- const episodes = await clients.sonarr.getEpisodes(seriesId, seasonNumber);
1423
- return {
1424
- content: [{
1425
- type: "text",
1426
- text: JSON.stringify({
1427
- count: episodes.length,
1428
- episodes: episodes.map(e => ({
1429
- id: e.id,
1430
- seasonNumber: e.seasonNumber,
1431
- episodeNumber: e.episodeNumber,
1432
- title: e.title,
1433
- airDate: e.airDate,
1434
- hasFile: e.hasFile,
1435
- monitored: e.monitored,
1436
- })),
1437
- }, null, 2),
1438
- }],
1439
- };
1440
- }
1441
- case "sonarr_search_missing": {
1442
- if (!clients.sonarr)
1443
- throw new Error("Sonarr not configured");
1444
- const seriesId = args.seriesId;
1445
- const result = await clients.sonarr.searchMissing(seriesId);
2674
+ const calendar = await clients.radarr.getCalendar(start, end);
1446
2675
  return {
1447
- content: [{
1448
- type: "text",
1449
- text: JSON.stringify({
1450
- success: true,
1451
- message: `Search triggered for missing episodes`,
1452
- commandId: result.id,
1453
- }, null, 2),
1454
- }],
2676
+ content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
1455
2677
  };
1456
2678
  }
1457
- case "sonarr_search_episode": {
1458
- if (!clients.sonarr)
1459
- throw new Error("Sonarr not configured");
1460
- const episodeIds = args.episodeIds;
1461
- const result = await clients.sonarr.searchEpisode(episodeIds);
2679
+ case "radarr_search_movie": {
2680
+ if (!clients.radarr)
2681
+ throw new Error("Radarr not configured");
2682
+ const movieId = args.movieId;
2683
+ const result = await clients.radarr.searchMovie(movieId);
1462
2684
  return {
1463
2685
  content: [{
1464
2686
  type: "text",
1465
2687
  text: JSON.stringify({
1466
2688
  success: true,
1467
- message: `Search triggered for ${episodeIds.length} episode(s)`,
2689
+ message: `Search triggered for movie`,
1468
2690
  commandId: result.id,
1469
2691
  }, null, 2),
1470
2692
  }],
1471
2693
  };
1472
2694
  }
1473
- case "sonarr_add_series": {
1474
- if (!clients.sonarr)
1475
- throw new Error("Sonarr not configured");
1476
- const { tvdbId, title, qualityProfileId, rootFolderPath, seasonFolder, monitored, tags } = args;
1477
- const series = await clients.sonarr.addSeries({
1478
- tvdbId,
2695
+ case "radarr_add_movie": {
2696
+ if (!clients.radarr)
2697
+ throw new Error("Radarr not configured");
2698
+ const { tmdbId, title, qualityProfileId, rootFolderPath, monitored, minimumAvailability, tags } = args;
2699
+ const movie = await clients.radarr.addMovie({
2700
+ tmdbId,
1479
2701
  title,
1480
2702
  qualityProfileId,
1481
2703
  rootFolderPath,
1482
- seasonFolder,
1483
2704
  monitored,
2705
+ minimumAvailability: minimumAvailability || 'released',
1484
2706
  tags,
1485
2707
  });
1486
2708
  return {
@@ -1488,374 +2710,396 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1488
2710
  type: "text",
1489
2711
  text: JSON.stringify({
1490
2712
  success: true,
1491
- message: `Series '${title}' added to Sonarr`,
1492
- seriesId: series.id,
1493
- series: {
1494
- id: series.id,
1495
- title: series.title,
1496
- path: series.path,
1497
- monitored: series.monitored,
2713
+ message: `Movie '${movie.title}' added to Radarr`,
2714
+ movieId: movie.id,
2715
+ movie: {
2716
+ id: movie.id,
2717
+ title: movie.title,
2718
+ path: movie.path,
2719
+ monitored: movie.monitored,
1498
2720
  },
1499
2721
  }, null, 2),
1500
2722
  }],
1501
2723
  };
1502
2724
  }
1503
- case "sonarr_update_series": {
1504
- if (!clients.sonarr)
1505
- throw new Error("Sonarr not configured");
1506
- const { seriesId, ...updates } = args;
1507
- const series = await clients.sonarr.updateSeries(seriesId, updates);
2725
+ case "radarr_update_movie": {
2726
+ if (!clients.radarr)
2727
+ throw new Error("Radarr not configured");
2728
+ const { movieId, ...updates } = args;
2729
+ const movie = await clients.radarr.updateMovie(movieId, updates);
1508
2730
  return {
1509
2731
  content: [{
1510
2732
  type: "text",
1511
2733
  text: JSON.stringify({
1512
2734
  success: true,
1513
- message: `Series '${series.title}' updated`,
1514
- seriesId: series.id,
2735
+ message: `Movie '${movie.title}' updated`,
2736
+ movieId: movie.id,
1515
2737
  updates: Object.keys(updates),
1516
2738
  }, null, 2),
1517
2739
  }],
1518
2740
  };
1519
2741
  }
1520
- case "sonarr_delete_series": {
1521
- if (!clients.sonarr)
1522
- throw new Error("Sonarr not configured");
1523
- const { seriesId, deleteFiles = false, addImportListExclusion = false } = args;
1524
- await clients.sonarr.deleteSeries(seriesId, deleteFiles, addImportListExclusion);
1525
- return {
1526
- content: [{
1527
- type: "text",
1528
- text: JSON.stringify({
1529
- success: true,
1530
- message: `Series deleted (files: ${deleteFiles}, exclude: ${addImportListExclusion})`,
1531
- }, null, 2),
1532
- }],
1533
- };
1534
- }
1535
- case "sonarr_update_episode": {
1536
- if (!clients.sonarr)
1537
- throw new Error("Sonarr not configured");
1538
- const { episodeId, monitored } = args;
1539
- const episode = await clients.sonarr.updateEpisode(episodeId, { monitored });
1540
- return {
1541
- content: [{
1542
- type: "text",
1543
- text: JSON.stringify({
1544
- success: true,
1545
- message: `Episode ${episodeId} ${monitored ? 'monitored' : 'unmonitored'}`,
1546
- episodeId: episode.id,
1547
- monitored: episode.monitored,
1548
- }, null, 2),
1549
- }],
1550
- };
1551
- }
1552
- case "sonarr_delete_episode_file": {
1553
- if (!clients.sonarr)
1554
- throw new Error("Sonarr not configured");
1555
- const { episodeFileId } = args;
1556
- await clients.sonarr.deleteEpisodeFile(episodeFileId);
2742
+ case "radarr_delete_movie": {
2743
+ if (!clients.radarr)
2744
+ throw new Error("Radarr not configured");
2745
+ const { movieId, deleteFiles = false, addImportExclusion = false } = args;
2746
+ await clients.radarr.deleteMovie(movieId, deleteFiles, addImportExclusion);
1557
2747
  return {
1558
2748
  content: [{
1559
2749
  type: "text",
1560
2750
  text: JSON.stringify({
1561
2751
  success: true,
1562
- message: `Episode file ${episodeFileId} deleted`,
2752
+ message: `Movie deleted (files: ${deleteFiles}, exclude: ${addImportExclusion})`,
1563
2753
  }, null, 2),
1564
2754
  }],
1565
2755
  };
1566
2756
  }
1567
- case "sonarr_refresh_series": {
1568
- if (!clients.sonarr)
1569
- throw new Error("Sonarr not configured");
1570
- const { seriesId } = args;
1571
- const result = await clients.sonarr.refreshSeries(seriesId);
2757
+ case "radarr_get_history": {
2758
+ if (!clients.radarr)
2759
+ throw new Error("Radarr not configured");
2760
+ const { page = 1, pageSize = 20, movieId } = args;
2761
+ if (movieId) {
2762
+ const history = await clients.radarr.getMovieHistory(movieId);
2763
+ return {
2764
+ content: [{
2765
+ type: "text",
2766
+ text: JSON.stringify({
2767
+ movieId,
2768
+ count: history.length,
2769
+ records: history.map((h) => ({
2770
+ id: h.id,
2771
+ date: h.date,
2772
+ eventType: h.eventType,
2773
+ sourceTitle: h.sourceTitle,
2774
+ quality: h.quality?.quality?.name,
2775
+ downloadId: h.downloadId,
2776
+ })),
2777
+ }, null, 2),
2778
+ }],
2779
+ };
2780
+ }
2781
+ const history = await clients.radarr.getHistory(page, pageSize);
1572
2782
  return {
1573
2783
  content: [{
1574
2784
  type: "text",
1575
2785
  text: JSON.stringify({
1576
- success: true,
1577
- message: `Series ${seriesId} metadata refresh triggered`,
1578
- commandId: result.id,
2786
+ page,
2787
+ pageSize,
2788
+ totalRecords: history.totalRecords,
2789
+ records: history.records.map((h) => ({
2790
+ id: h.id,
2791
+ movieId: h.movieId,
2792
+ date: h.date,
2793
+ eventType: h.eventType,
2794
+ sourceTitle: h.sourceTitle,
2795
+ quality: h.quality?.quality?.name,
2796
+ })),
1579
2797
  }, null, 2),
1580
2798
  }],
1581
2799
  };
1582
2800
  }
1583
- case "sonarr_rescan_series": {
1584
- if (!clients.sonarr)
1585
- throw new Error("Sonarr not configured");
1586
- const { seriesId } = args;
1587
- const result = await clients.sonarr.rescanSeries(seriesId);
2801
+ case "radarr_refresh_movie": {
2802
+ if (!clients.radarr)
2803
+ throw new Error("Radarr not configured");
2804
+ const { movieId } = args;
2805
+ const result = await clients.radarr.refreshMovie(movieId);
1588
2806
  return {
1589
2807
  content: [{
1590
2808
  type: "text",
1591
2809
  text: JSON.stringify({
1592
2810
  success: true,
1593
- message: `Series ${seriesId} disk rescan triggered`,
2811
+ message: `Movie ${movieId} metadata refresh triggered`,
1594
2812
  commandId: result.id,
1595
2813
  }, null, 2),
1596
2814
  }],
1597
2815
  };
1598
2816
  }
1599
- case "sonarr_rename_series": {
1600
- if (!clients.sonarr)
1601
- throw new Error("Sonarr not configured");
1602
- const { seriesId } = args;
1603
- const result = await clients.sonarr.renameSeries(seriesId);
2817
+ case "radarr_rescan_movie": {
2818
+ if (!clients.radarr)
2819
+ throw new Error("Radarr not configured");
2820
+ const { movieId } = args;
2821
+ const result = await clients.radarr.rescanMovie(movieId);
1604
2822
  return {
1605
2823
  content: [{
1606
2824
  type: "text",
1607
2825
  text: JSON.stringify({
1608
2826
  success: true,
1609
- message: `Series ${seriesId} files will be renamed`,
2827
+ message: `Movie ${movieId} disk rescan triggered`,
1610
2828
  commandId: result.id,
1611
2829
  }, null, 2),
1612
2830
  }],
1613
2831
  };
1614
2832
  }
1615
- case "sonarr_rename_episodes": {
1616
- if (!clients.sonarr)
1617
- throw new Error("Sonarr not configured");
1618
- const { seriesId } = args;
1619
- const result = await clients.sonarr.renameEpisodes(seriesId);
2833
+ case "radarr_rename_movie": {
2834
+ if (!clients.radarr)
2835
+ throw new Error("Radarr not configured");
2836
+ const { movieId } = args;
2837
+ const result = await clients.radarr.renameMovie(movieId);
1620
2838
  return {
1621
2839
  content: [{
1622
2840
  type: "text",
1623
2841
  text: JSON.stringify({
1624
2842
  success: true,
1625
- message: `All episodes for series ${seriesId} will be renamed`,
2843
+ message: `Movie ${movieId} files will be renamed`,
1626
2844
  commandId: result.id,
1627
2845
  }, null, 2),
1628
2846
  }],
1629
2847
  };
1630
2848
  }
1631
- case "sonarr_get_releases": {
1632
- if (!clients.sonarr)
1633
- throw new Error("Sonarr not configured");
1634
- const { seriesId, page = 1, pageSize = 10 } = args;
1635
- const releases = await clients.sonarr.getReleases(seriesId, page, pageSize);
2849
+ case "radarr_get_collections": {
2850
+ if (!clients.radarr)
2851
+ throw new Error("Radarr not configured");
2852
+ const collections = await clients.radarr.getCollections();
1636
2853
  return {
1637
2854
  content: [{
1638
2855
  type: "text",
1639
2856
  text: JSON.stringify({
1640
- totalRecords: releases.length,
1641
- page,
1642
- pageSize,
1643
- releases: releases.map((r) => ({
1644
- guid: r.guid,
1645
- title: r.title,
1646
- indexer: r.indexer,
1647
- quality: r.quality,
1648
- size: formatBytes(r.size),
1649
- age: r.age,
1650
- ageHours: r.ageHours,
1651
- rejected: r.rejected,
1652
- rejectionReason: r.rejectionReason,
1653
- publishedDate: r.publishedDate,
2857
+ count: collections.length,
2858
+ collections: collections.map((c) => ({
2859
+ id: c.id,
2860
+ title: c.title,
2861
+ tmdbId: c.tmdbId,
2862
+ monitored: c.monitored,
2863
+ movieCount: c.movies?.length || 0,
2864
+ qualityProfileId: c.qualityProfileId,
2865
+ rootFolderPath: c.rootFolderPath,
1654
2866
  })),
1655
2867
  }, null, 2),
1656
2868
  }],
1657
2869
  };
1658
2870
  }
1659
- case "sonarr_delete_release": {
1660
- if (!clients.sonarr)
1661
- throw new Error("Sonarr not configured");
1662
- const { guid } = args;
1663
- await clients.sonarr.deleteRelease(guid);
2871
+ case "radarr_update_collection": {
2872
+ if (!clients.radarr)
2873
+ throw new Error("Radarr not configured");
2874
+ const { collectionId, ...updates } = args;
2875
+ const collection = await clients.radarr.updateCollection(collectionId, updates);
1664
2876
  return {
1665
2877
  content: [{
1666
2878
  type: "text",
1667
2879
  text: JSON.stringify({
1668
2880
  success: true,
1669
- message: `Release ${guid} deleted from history`,
2881
+ message: `Collection '${collection.title}' updated`,
2882
+ collectionId: collection.id,
2883
+ updates: Object.keys(updates),
1670
2884
  }, null, 2),
1671
2885
  }],
1672
2886
  };
1673
2887
  }
1674
- case "sonarr_create_release_profile": {
1675
- if (!clients.sonarr)
1676
- throw new Error("Sonarr not configured");
1677
- const profile = await clients.sonarr.createReleaseProfile(args);
2888
+ case "radarr_get_movie_files": {
2889
+ if (!clients.radarr)
2890
+ throw new Error("Radarr not configured");
2891
+ const { movieId } = args;
2892
+ const files = await clients.radarr.getMovieFiles(movieId);
1678
2893
  return {
1679
2894
  content: [{
1680
2895
  type: "text",
1681
2896
  text: JSON.stringify({
1682
- success: true,
1683
- message: `Release profile '${profile.name}' created`,
1684
- profileId: profile.id,
1685
- profile,
2897
+ movieId,
2898
+ count: files.length,
2899
+ files: files.map((f) => ({
2900
+ id: f.id,
2901
+ relativePath: f.relativePath,
2902
+ size: formatBytes(f.size),
2903
+ quality: f.quality?.quality?.name,
2904
+ dateAdded: f.dateAdded,
2905
+ mediaInfo: f.mediaInfo ? {
2906
+ resolution: f.mediaInfo.resolution,
2907
+ videoCodec: f.mediaInfo.videoCodec,
2908
+ audioCodec: f.mediaInfo.audioCodec,
2909
+ audioChannels: f.mediaInfo.audioChannels,
2910
+ runTime: f.mediaInfo.runTime,
2911
+ } : null,
2912
+ })),
1686
2913
  }, null, 2),
1687
2914
  }],
1688
2915
  };
1689
2916
  }
1690
- case "sonarr_update_release_profile": {
1691
- if (!clients.sonarr)
1692
- throw new Error("Sonarr not configured");
1693
- const { profileId, ...updates } = args;
1694
- const profile = await clients.sonarr.updateReleaseProfile(profileId, updates);
2917
+ case "radarr_delete_movie_file": {
2918
+ if (!clients.radarr)
2919
+ throw new Error("Radarr not configured");
2920
+ const { movieFileId } = args;
2921
+ await clients.radarr.deleteMovieFile(movieFileId);
1695
2922
  return {
1696
2923
  content: [{
1697
2924
  type: "text",
1698
2925
  text: JSON.stringify({
1699
2926
  success: true,
1700
- message: `Release profile ${profileId} updated`,
1701
- profileId: profile.id,
1702
- profile,
2927
+ message: `Movie file ${movieFileId} deleted`,
1703
2928
  }, null, 2),
1704
2929
  }],
1705
2930
  };
1706
2931
  }
1707
- case "sonarr_delete_release_profile": {
1708
- if (!clients.sonarr)
1709
- throw new Error("Sonarr not configured");
1710
- const { profileId } = args;
1711
- await clients.sonarr.deleteReleaseProfile(profileId);
2932
+ case "radarr_get_blocklist": {
2933
+ if (!clients.radarr)
2934
+ throw new Error("Radarr not configured");
2935
+ const { page = 1, pageSize = 20 } = args;
2936
+ const blocklist = await clients.radarr.getBlocklist(page, pageSize);
1712
2937
  return {
1713
2938
  content: [{
1714
2939
  type: "text",
1715
2940
  text: JSON.stringify({
1716
- success: true,
1717
- message: `Release profile ${profileId} deleted`,
2941
+ page,
2942
+ pageSize,
2943
+ totalRecords: blocklist.totalRecords,
2944
+ records: blocklist.records.map((b) => ({
2945
+ id: b.id,
2946
+ movieId: b.movieId,
2947
+ sourceTitle: b.sourceTitle,
2948
+ date: b.date,
2949
+ protocol: b.protocol,
2950
+ indexer: b.indexer,
2951
+ message: b.message,
2952
+ })),
1718
2953
  }, null, 2),
1719
2954
  }],
1720
2955
  };
1721
2956
  }
1722
- case "sonarr_create_tag": {
1723
- if (!clients.sonarr)
1724
- throw new Error("Sonarr not configured");
1725
- const { label } = args;
1726
- const tag = await clients.sonarr.createTag(label);
2957
+ case "radarr_delete_blocklist_item": {
2958
+ if (!clients.radarr)
2959
+ throw new Error("Radarr not configured");
2960
+ const { blocklistId } = args;
2961
+ await clients.radarr.deleteBlocklistItem(blocklistId);
1727
2962
  return {
1728
2963
  content: [{
1729
2964
  type: "text",
1730
2965
  text: JSON.stringify({
1731
2966
  success: true,
1732
- message: `Tag '${label}' created`,
1733
- tagId: tag.id,
1734
- tag,
2967
+ message: `Blocklist item ${blocklistId} deleted`,
1735
2968
  }, null, 2),
1736
2969
  }],
1737
2970
  };
1738
2971
  }
1739
- case "sonarr_update_tag": {
1740
- if (!clients.sonarr)
1741
- throw new Error("Sonarr not configured");
1742
- const { tagId, label } = args;
1743
- const tag = await clients.sonarr.updateTag(tagId, label);
2972
+ case "radarr_get_extra_files": {
2973
+ if (!clients.radarr)
2974
+ throw new Error("Radarr not configured");
2975
+ const { movieId } = args;
2976
+ const files = await clients.radarr.getExtraFiles(movieId);
1744
2977
  return {
1745
2978
  content: [{
1746
2979
  type: "text",
1747
2980
  text: JSON.stringify({
1748
- success: true,
1749
- message: `Tag ${tagId} updated to '${label}'`,
1750
- tagId: tag.id,
1751
- tag,
2981
+ movieId,
2982
+ count: files.length,
2983
+ files: files.map((f) => ({
2984
+ id: f.id,
2985
+ relativePath: f.relativePath,
2986
+ extension: f.extension,
2987
+ type: f.type,
2988
+ })),
1752
2989
  }, null, 2),
1753
2990
  }],
1754
2991
  };
1755
2992
  }
1756
- case "sonarr_delete_tag": {
1757
- if (!clients.sonarr)
1758
- throw new Error("Sonarr not configured");
1759
- const { tagId } = args;
1760
- await clients.sonarr.deleteTag(tagId);
2993
+ case "radarr_get_credits": {
2994
+ if (!clients.radarr)
2995
+ throw new Error("Radarr not configured");
2996
+ const { movieId } = args;
2997
+ const credits = await clients.radarr.getCredits(movieId);
1761
2998
  return {
1762
2999
  content: [{
1763
3000
  type: "text",
1764
3001
  text: JSON.stringify({
1765
- success: true,
1766
- message: `Tag ${tagId} deleted`,
3002
+ movieId,
3003
+ count: credits.length,
3004
+ cast: credits.filter((c) => c.type === 'cast').slice(0, 20).map((c) => ({
3005
+ name: c.personName,
3006
+ character: c.character,
3007
+ order: c.order,
3008
+ })),
3009
+ crew: credits.filter((c) => c.type === 'crew').slice(0, 10).map((c) => ({
3010
+ name: c.personName,
3011
+ job: c.job,
3012
+ department: c.department,
3013
+ })),
1767
3014
  }, null, 2),
1768
3015
  }],
1769
3016
  };
1770
3017
  }
1771
- // Radarr handlers
1772
- case "radarr_get_movies": {
3018
+ case "radarr_get_wanted": {
1773
3019
  if (!clients.radarr)
1774
3020
  throw new Error("Radarr not configured");
1775
- const movies = await clients.radarr.getMovies();
3021
+ const { page = 1, pageSize = 20 } = args;
3022
+ const wanted = await clients.radarr.getWanted(page, pageSize);
1776
3023
  return {
1777
3024
  content: [{
1778
3025
  type: "text",
1779
3026
  text: JSON.stringify({
1780
- count: movies.length,
1781
- movies: movies.map(m => ({
3027
+ page,
3028
+ pageSize,
3029
+ totalRecords: wanted.totalRecords,
3030
+ movies: wanted.records.map((m) => ({
1782
3031
  id: m.id,
1783
3032
  title: m.title,
1784
3033
  year: m.year,
1785
3034
  status: m.status,
1786
- hasFile: m.hasFile,
1787
- sizeOnDisk: formatBytes(m.sizeOnDisk),
1788
3035
  monitored: m.monitored,
1789
- studio: m.studio,
3036
+ isAvailable: m.isAvailable,
3037
+ minimumAvailability: m.minimumAvailability,
1790
3038
  })),
1791
3039
  }, null, 2),
1792
3040
  }],
1793
3041
  };
1794
3042
  }
1795
- case "radarr_search": {
3043
+ case "radarr_search_missing": {
1796
3044
  if (!clients.radarr)
1797
3045
  throw new Error("Radarr not configured");
1798
- const term = args.term;
1799
- const results = await clients.radarr.searchMovies(term);
3046
+ const result = await clients.radarr.searchMissing();
1800
3047
  return {
1801
3048
  content: [{
1802
- type: "text",
1803
- text: JSON.stringify({
1804
- count: results.length,
1805
- results: results.slice(0, 10).map(r => ({
1806
- title: r.title,
1807
- year: r.year,
1808
- tmdbId: r.tmdbId,
1809
- imdbId: r.imdbId,
1810
- overview: r.overview?.substring(0, 200) + (r.overview && r.overview.length > 200 ? '...' : ''),
1811
- })),
3049
+ type: "text",
3050
+ text: JSON.stringify({
3051
+ success: true,
3052
+ message: 'Search triggered for all missing movies',
3053
+ commandId: result.id,
1812
3054
  }, null, 2),
1813
3055
  }],
1814
3056
  };
1815
3057
  }
1816
- case "radarr_get_queue": {
3058
+ case "radarr_create_tag": {
1817
3059
  if (!clients.radarr)
1818
3060
  throw new Error("Radarr not configured");
1819
- const queue = await clients.radarr.getQueue();
3061
+ const { label } = args;
3062
+ const tag = await clients.radarr.createTag(label);
1820
3063
  return {
1821
3064
  content: [{
1822
3065
  type: "text",
1823
3066
  text: JSON.stringify({
1824
- totalRecords: queue.totalRecords,
1825
- items: queue.records.map(q => ({
1826
- title: q.title,
1827
- status: q.status,
1828
- progress: ((1 - q.sizeleft / q.size) * 100).toFixed(1) + '%',
1829
- timeLeft: q.timeleft,
1830
- downloadClient: q.downloadClient,
1831
- })),
3067
+ success: true,
3068
+ message: `Tag '${label}' created`,
3069
+ tagId: tag.id,
3070
+ tag,
1832
3071
  }, null, 2),
1833
3072
  }],
1834
3073
  };
1835
3074
  }
1836
- case "radarr_get_calendar": {
3075
+ case "radarr_update_tag": {
1837
3076
  if (!clients.radarr)
1838
3077
  throw new Error("Radarr not configured");
1839
- const days = args?.days || 30;
1840
- const start = new Date().toISOString().split('T')[0];
1841
- const end = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
1842
- const calendar = await clients.radarr.getCalendar(start, end);
3078
+ const { tagId, label } = args;
3079
+ const tag = await clients.radarr.updateTag(tagId, label);
1843
3080
  return {
1844
- content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
3081
+ content: [{
3082
+ type: "text",
3083
+ text: JSON.stringify({
3084
+ success: true,
3085
+ message: `Tag ${tagId} updated to '${label}'`,
3086
+ tagId: tag.id,
3087
+ tag,
3088
+ }, null, 2),
3089
+ }],
1845
3090
  };
1846
3091
  }
1847
- case "radarr_search_movie": {
3092
+ case "radarr_delete_tag": {
1848
3093
  if (!clients.radarr)
1849
3094
  throw new Error("Radarr not configured");
1850
- const movieId = args.movieId;
1851
- const result = await clients.radarr.searchMovie(movieId);
3095
+ const { tagId } = args;
3096
+ await clients.radarr.deleteTag(tagId);
1852
3097
  return {
1853
3098
  content: [{
1854
3099
  type: "text",
1855
3100
  text: JSON.stringify({
1856
3101
  success: true,
1857
- message: `Search triggered for movie`,
1858
- commandId: result.id,
3102
+ message: `Tag ${tagId} deleted`,
1859
3103
  }, null, 2),
1860
3104
  }],
1861
3105
  };
@@ -2147,7 +3391,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2147
3391
  case "prowlarr_get_indexers": {
2148
3392
  if (!clients.prowlarr)
2149
3393
  throw new Error("Prowlarr not configured");
2150
- const indexers = await clients.prowlarr.getIndexers();
3394
+ const indexers = await clients.prowlarr.getProwlarrIndexers();
2151
3395
  return {
2152
3396
  content: [{
2153
3397
  type: "text",
@@ -2157,9 +3401,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2157
3401
  id: i.id,
2158
3402
  name: i.name,
2159
3403
  protocol: i.protocol,
2160
- enableRss: i.enableRss,
2161
- enableAutomaticSearch: i.enableAutomaticSearch,
2162
- enableInteractiveSearch: i.enableInteractiveSearch,
3404
+ enable: i.enable,
3405
+ supportsRss: i.supportsRss,
3406
+ supportsSearch: i.supportsSearch,
2163
3407
  priority: i.priority,
2164
3408
  })),
2165
3409
  }, null, 2),
@@ -2179,7 +3423,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2179
3423
  if (!clients.prowlarr)
2180
3424
  throw new Error("Prowlarr not configured");
2181
3425
  const results = await clients.prowlarr.testAllIndexers();
2182
- const indexers = await clients.prowlarr.getIndexers();
3426
+ const indexers = await clients.prowlarr.getProwlarrIndexers();
2183
3427
  const indexerMap = new Map(indexers.map(i => [i.id, i.name]));
2184
3428
  return {
2185
3429
  content: [{
@@ -2225,6 +3469,350 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2225
3469
  }],
2226
3470
  };
2227
3471
  }
3472
+ case "prowlarr_get_indexer": {
3473
+ if (!clients.prowlarr)
3474
+ throw new Error("Prowlarr not configured");
3475
+ const { indexerId } = args;
3476
+ const indexer = await clients.prowlarr.getIndexerById(indexerId);
3477
+ return {
3478
+ content: [{
3479
+ type: "text",
3480
+ text: JSON.stringify(indexer, null, 2),
3481
+ }],
3482
+ };
3483
+ }
3484
+ case "prowlarr_update_indexer": {
3485
+ if (!clients.prowlarr)
3486
+ throw new Error("Prowlarr not configured");
3487
+ const { indexerId, enable, priority, tags } = args;
3488
+ const updates = {};
3489
+ if (enable !== undefined)
3490
+ updates.enable = enable;
3491
+ if (priority !== undefined)
3492
+ updates.priority = priority;
3493
+ if (tags !== undefined)
3494
+ updates.tags = tags;
3495
+ const updated = await clients.prowlarr.updateIndexer(indexerId, updates);
3496
+ return {
3497
+ content: [{
3498
+ type: "text",
3499
+ text: JSON.stringify({
3500
+ success: true,
3501
+ indexer: {
3502
+ id: updated.id,
3503
+ name: updated.name,
3504
+ enable: updated.enable,
3505
+ priority: updated.priority,
3506
+ tags: updated.tags,
3507
+ },
3508
+ }, null, 2),
3509
+ }],
3510
+ };
3511
+ }
3512
+ case "prowlarr_delete_indexer": {
3513
+ if (!clients.prowlarr)
3514
+ throw new Error("Prowlarr not configured");
3515
+ const { indexerId } = args;
3516
+ await clients.prowlarr.deleteIndexer(indexerId);
3517
+ return {
3518
+ content: [{
3519
+ type: "text",
3520
+ text: JSON.stringify({ success: true, message: `Indexer ${indexerId} deleted` }, null, 2),
3521
+ }],
3522
+ };
3523
+ }
3524
+ case "prowlarr_test_indexer": {
3525
+ if (!clients.prowlarr)
3526
+ throw new Error("Prowlarr not configured");
3527
+ const { indexerId } = args;
3528
+ const result = await clients.prowlarr.testIndexer(indexerId);
3529
+ const indexer = await clients.prowlarr.getIndexerById(indexerId);
3530
+ return {
3531
+ content: [{
3532
+ type: "text",
3533
+ text: JSON.stringify({
3534
+ id: indexerId,
3535
+ name: indexer.name,
3536
+ isValid: result.isValid,
3537
+ errors: result.validationFailures.map(f => f.errorMessage),
3538
+ }, null, 2),
3539
+ }],
3540
+ };
3541
+ }
3542
+ case "prowlarr_get_applications": {
3543
+ if (!clients.prowlarr)
3544
+ throw new Error("Prowlarr not configured");
3545
+ const apps = await clients.prowlarr.getApplications();
3546
+ return {
3547
+ content: [{
3548
+ type: "text",
3549
+ text: JSON.stringify({
3550
+ count: apps.length,
3551
+ applications: apps.map(a => ({
3552
+ id: a.id,
3553
+ name: a.name,
3554
+ implementation: a.implementation,
3555
+ syncLevel: a.syncLevel,
3556
+ tags: a.tags,
3557
+ })),
3558
+ }, null, 2),
3559
+ }],
3560
+ };
3561
+ }
3562
+ case "prowlarr_get_application": {
3563
+ if (!clients.prowlarr)
3564
+ throw new Error("Prowlarr not configured");
3565
+ const { applicationId } = args;
3566
+ const app = await clients.prowlarr.getApplicationById(applicationId);
3567
+ return {
3568
+ content: [{
3569
+ type: "text",
3570
+ text: JSON.stringify(app, null, 2),
3571
+ }],
3572
+ };
3573
+ }
3574
+ case "prowlarr_update_application": {
3575
+ if (!clients.prowlarr)
3576
+ throw new Error("Prowlarr not configured");
3577
+ const { applicationId, syncLevel, tags } = args;
3578
+ const updates = {};
3579
+ if (syncLevel !== undefined)
3580
+ updates.syncLevel = syncLevel;
3581
+ if (tags !== undefined)
3582
+ updates.tags = tags;
3583
+ const updated = await clients.prowlarr.updateApplication(applicationId, updates);
3584
+ return {
3585
+ content: [{
3586
+ type: "text",
3587
+ text: JSON.stringify({
3588
+ success: true,
3589
+ application: {
3590
+ id: updated.id,
3591
+ name: updated.name,
3592
+ syncLevel: updated.syncLevel,
3593
+ tags: updated.tags,
3594
+ },
3595
+ }, null, 2),
3596
+ }],
3597
+ };
3598
+ }
3599
+ case "prowlarr_delete_application": {
3600
+ if (!clients.prowlarr)
3601
+ throw new Error("Prowlarr not configured");
3602
+ const { applicationId } = args;
3603
+ await clients.prowlarr.deleteApplication(applicationId);
3604
+ return {
3605
+ content: [{
3606
+ type: "text",
3607
+ text: JSON.stringify({ success: true, message: `Application ${applicationId} deleted` }, null, 2),
3608
+ }],
3609
+ };
3610
+ }
3611
+ case "prowlarr_test_application": {
3612
+ if (!clients.prowlarr)
3613
+ throw new Error("Prowlarr not configured");
3614
+ const { applicationId } = args;
3615
+ const result = await clients.prowlarr.testApplication(applicationId);
3616
+ const app = await clients.prowlarr.getApplicationById(applicationId);
3617
+ return {
3618
+ content: [{
3619
+ type: "text",
3620
+ text: JSON.stringify({
3621
+ id: applicationId,
3622
+ name: app.name,
3623
+ isValid: result.isValid,
3624
+ errors: result.validationFailures.map(f => f.errorMessage),
3625
+ }, null, 2),
3626
+ }],
3627
+ };
3628
+ }
3629
+ case "prowlarr_sync_applications": {
3630
+ if (!clients.prowlarr)
3631
+ throw new Error("Prowlarr not configured");
3632
+ const result = await clients.prowlarr.syncApplications();
3633
+ return {
3634
+ content: [{
3635
+ type: "text",
3636
+ text: JSON.stringify({
3637
+ success: true,
3638
+ message: "Application indexer sync triggered",
3639
+ commandId: result.id,
3640
+ }, null, 2),
3641
+ }],
3642
+ };
3643
+ }
3644
+ case "prowlarr_get_app_profiles": {
3645
+ if (!clients.prowlarr)
3646
+ throw new Error("Prowlarr not configured");
3647
+ const profiles = await clients.prowlarr.getAppProfiles();
3648
+ return {
3649
+ content: [{
3650
+ type: "text",
3651
+ text: JSON.stringify({
3652
+ count: profiles.length,
3653
+ profiles: profiles.map(p => ({
3654
+ id: p.id,
3655
+ name: p.name,
3656
+ enableRss: p.enableRss,
3657
+ enableAutomaticSearch: p.enableAutomaticSearch,
3658
+ enableInteractiveSearch: p.enableInteractiveSearch,
3659
+ minimumSeeders: p.minimumSeeders,
3660
+ })),
3661
+ }, null, 2),
3662
+ }],
3663
+ };
3664
+ }
3665
+ case "prowlarr_get_history": {
3666
+ if (!clients.prowlarr)
3667
+ throw new Error("Prowlarr not configured");
3668
+ const { page, pageSize, indexerId } = args;
3669
+ let history;
3670
+ if (indexerId) {
3671
+ const records = await clients.prowlarr.getIndexerHistory(indexerId);
3672
+ history = { records, totalRecords: records.length };
3673
+ }
3674
+ else {
3675
+ history = await clients.prowlarr.getHistory(page || 1, pageSize || 20);
3676
+ }
3677
+ return {
3678
+ content: [{
3679
+ type: "text",
3680
+ text: JSON.stringify({
3681
+ totalRecords: history.totalRecords,
3682
+ records: history.records.map((r) => ({
3683
+ id: r.id,
3684
+ indexerId: r.indexerId,
3685
+ eventType: r.eventType,
3686
+ successful: r.successful,
3687
+ date: r.date,
3688
+ query: r.data?.query,
3689
+ })),
3690
+ }, null, 2),
3691
+ }],
3692
+ };
3693
+ }
3694
+ case "prowlarr_get_tags": {
3695
+ if (!clients.prowlarr)
3696
+ throw new Error("Prowlarr not configured");
3697
+ const tags = await clients.prowlarr.getTags();
3698
+ return {
3699
+ content: [{
3700
+ type: "text",
3701
+ text: JSON.stringify({ count: tags.length, tags }, null, 2),
3702
+ }],
3703
+ };
3704
+ }
3705
+ case "prowlarr_create_tag": {
3706
+ if (!clients.prowlarr)
3707
+ throw new Error("Prowlarr not configured");
3708
+ const { label } = args;
3709
+ const tag = await clients.prowlarr.createTag(label);
3710
+ return {
3711
+ content: [{
3712
+ type: "text",
3713
+ text: JSON.stringify({ success: true, tag }, null, 2),
3714
+ }],
3715
+ };
3716
+ }
3717
+ case "prowlarr_update_tag": {
3718
+ if (!clients.prowlarr)
3719
+ throw new Error("Prowlarr not configured");
3720
+ const { tagId, label } = args;
3721
+ const tag = await clients.prowlarr.updateTag(tagId, label);
3722
+ return {
3723
+ content: [{
3724
+ type: "text",
3725
+ text: JSON.stringify({ success: true, tag }, null, 2),
3726
+ }],
3727
+ };
3728
+ }
3729
+ case "prowlarr_delete_tag": {
3730
+ if (!clients.prowlarr)
3731
+ throw new Error("Prowlarr not configured");
3732
+ const { tagId } = args;
3733
+ await clients.prowlarr.deleteTag(tagId);
3734
+ return {
3735
+ content: [{
3736
+ type: "text",
3737
+ text: JSON.stringify({ success: true, message: `Tag ${tagId} deleted` }, null, 2),
3738
+ }],
3739
+ };
3740
+ }
3741
+ case "prowlarr_get_indexer_schemas": {
3742
+ if (!clients.prowlarr)
3743
+ throw new Error("Prowlarr not configured");
3744
+ const schemas = await clients.prowlarr.getIndexerSchemas();
3745
+ return {
3746
+ content: [{
3747
+ type: "text",
3748
+ text: JSON.stringify({
3749
+ count: schemas.length,
3750
+ schemas: schemas.map(s => ({
3751
+ implementation: s.implementation,
3752
+ name: s.name,
3753
+ protocol: s.protocol,
3754
+ privacy: s.privacy,
3755
+ })),
3756
+ }, null, 2),
3757
+ }],
3758
+ };
3759
+ }
3760
+ case "prowlarr_get_notifications": {
3761
+ if (!clients.prowlarr)
3762
+ throw new Error("Prowlarr not configured");
3763
+ const notifications = await clients.prowlarr.getNotifications();
3764
+ return {
3765
+ content: [{
3766
+ type: "text",
3767
+ text: JSON.stringify({
3768
+ count: notifications.length,
3769
+ notifications: notifications.map(n => ({
3770
+ id: n.id,
3771
+ name: n.name,
3772
+ implementation: n.implementation,
3773
+ onHealthIssue: n.onHealthIssue,
3774
+ onApplicationUpdate: n.onApplicationUpdate,
3775
+ })),
3776
+ }, null, 2),
3777
+ }],
3778
+ };
3779
+ }
3780
+ case "prowlarr_rss_sync": {
3781
+ if (!clients.prowlarr)
3782
+ throw new Error("Prowlarr not configured");
3783
+ const result = await clients.prowlarr.rssSync();
3784
+ return {
3785
+ content: [{
3786
+ type: "text",
3787
+ text: JSON.stringify({
3788
+ success: true,
3789
+ message: "RSS sync triggered for all indexers",
3790
+ commandId: result.id,
3791
+ }, null, 2),
3792
+ }],
3793
+ };
3794
+ }
3795
+ case "prowlarr_get_download_clients": {
3796
+ if (!clients.prowlarr)
3797
+ throw new Error("Prowlarr not configured");
3798
+ const downloadClients = await clients.prowlarr.getDownloadClients();
3799
+ return {
3800
+ content: [{
3801
+ type: "text",
3802
+ text: JSON.stringify({
3803
+ count: downloadClients.length,
3804
+ clients: downloadClients.map(c => ({
3805
+ id: c.id,
3806
+ name: c.name,
3807
+ implementation: c.implementation,
3808
+ protocol: c.protocol,
3809
+ enable: c.enable,
3810
+ priority: c.priority,
3811
+ })),
3812
+ }, null, 2),
3813
+ }],
3814
+ };
3815
+ }
2228
3816
  // Cross-service search
2229
3817
  case "arr_search_all": {
2230
3818
  const term = args.term;