@thelord/mcp-arr 1.2.3 → 1.4.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) {
@@ -1500,362 +1929,923 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1500
1929
  }],
1501
1930
  };
1502
1931
  }
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);
1932
+ case "sonarr_update_series": {
1933
+ if (!clients.sonarr)
1934
+ throw new Error("Sonarr not configured");
1935
+ const { seriesId, ...updates } = args;
1936
+ const series = await clients.sonarr.updateSeries(seriesId, updates);
1937
+ return {
1938
+ content: [{
1939
+ type: "text",
1940
+ text: JSON.stringify({
1941
+ success: true,
1942
+ message: `Series '${series.title}' updated`,
1943
+ seriesId: series.id,
1944
+ updates: Object.keys(updates),
1945
+ }, null, 2),
1946
+ }],
1947
+ };
1948
+ }
1949
+ case "sonarr_delete_series": {
1950
+ if (!clients.sonarr)
1951
+ throw new Error("Sonarr not configured");
1952
+ const { seriesId, deleteFiles = false, addImportListExclusion = false } = args;
1953
+ await clients.sonarr.deleteSeries(seriesId, deleteFiles, addImportListExclusion);
1954
+ return {
1955
+ content: [{
1956
+ type: "text",
1957
+ text: JSON.stringify({
1958
+ success: true,
1959
+ message: `Series deleted (files: ${deleteFiles}, exclude: ${addImportListExclusion})`,
1960
+ }, null, 2),
1961
+ }],
1962
+ };
1963
+ }
1964
+ case "sonarr_update_episode": {
1965
+ if (!clients.sonarr)
1966
+ throw new Error("Sonarr not configured");
1967
+ const { episodeId, monitored } = args;
1968
+ const episode = await clients.sonarr.updateEpisode(episodeId, { monitored });
1969
+ return {
1970
+ content: [{
1971
+ type: "text",
1972
+ text: JSON.stringify({
1973
+ success: true,
1974
+ message: `Episode ${episodeId} ${monitored ? 'monitored' : 'unmonitored'}`,
1975
+ episodeId: episode.id,
1976
+ monitored: episode.monitored,
1977
+ }, null, 2),
1978
+ }],
1979
+ };
1980
+ }
1981
+ case "sonarr_delete_episode_file": {
1982
+ if (!clients.sonarr)
1983
+ throw new Error("Sonarr not configured");
1984
+ const { episodeFileId } = args;
1985
+ await clients.sonarr.deleteEpisodeFile(episodeFileId);
1986
+ return {
1987
+ content: [{
1988
+ type: "text",
1989
+ text: JSON.stringify({
1990
+ success: true,
1991
+ message: `Episode file ${episodeFileId} deleted`,
1992
+ }, null, 2),
1993
+ }],
1994
+ };
1995
+ }
1996
+ case "sonarr_refresh_series": {
1997
+ if (!clients.sonarr)
1998
+ throw new Error("Sonarr not configured");
1999
+ const { seriesId } = args;
2000
+ const result = await clients.sonarr.refreshSeries(seriesId);
2001
+ return {
2002
+ content: [{
2003
+ type: "text",
2004
+ text: JSON.stringify({
2005
+ success: true,
2006
+ message: `Series ${seriesId} metadata refresh triggered`,
2007
+ commandId: result.id,
2008
+ }, null, 2),
2009
+ }],
2010
+ };
2011
+ }
2012
+ case "sonarr_rescan_series": {
2013
+ if (!clients.sonarr)
2014
+ throw new Error("Sonarr not configured");
2015
+ const { seriesId } = args;
2016
+ const result = await clients.sonarr.rescanSeries(seriesId);
2017
+ return {
2018
+ content: [{
2019
+ type: "text",
2020
+ text: JSON.stringify({
2021
+ success: true,
2022
+ message: `Series ${seriesId} disk rescan triggered`,
2023
+ commandId: result.id,
2024
+ }, null, 2),
2025
+ }],
2026
+ };
2027
+ }
2028
+ case "sonarr_rename_series": {
2029
+ if (!clients.sonarr)
2030
+ throw new Error("Sonarr not configured");
2031
+ const { seriesId } = args;
2032
+ const result = await clients.sonarr.renameSeries(seriesId);
2033
+ return {
2034
+ content: [{
2035
+ type: "text",
2036
+ text: JSON.stringify({
2037
+ success: true,
2038
+ message: `Series ${seriesId} files will be renamed`,
2039
+ commandId: result.id,
2040
+ }, null, 2),
2041
+ }],
2042
+ };
2043
+ }
2044
+ case "sonarr_rename_episodes": {
2045
+ if (!clients.sonarr)
2046
+ throw new Error("Sonarr not configured");
2047
+ const { seriesId } = args;
2048
+ const result = await clients.sonarr.renameEpisodes(seriesId);
2049
+ return {
2050
+ content: [{
2051
+ type: "text",
2052
+ text: JSON.stringify({
2053
+ success: true,
2054
+ message: `All episodes for series ${seriesId} will be renamed`,
2055
+ commandId: result.id,
2056
+ }, null, 2),
2057
+ }],
2058
+ };
2059
+ }
2060
+ case "sonarr_get_releases": {
2061
+ if (!clients.sonarr)
2062
+ throw new Error("Sonarr not configured");
2063
+ const { seriesId, page = 1, pageSize = 10 } = args;
2064
+ const releases = await clients.sonarr.getReleases(seriesId, page, pageSize);
2065
+ return {
2066
+ content: [{
2067
+ type: "text",
2068
+ text: JSON.stringify({
2069
+ totalRecords: releases.length,
2070
+ page,
2071
+ pageSize,
2072
+ releases: releases.map((r) => ({
2073
+ guid: r.guid,
2074
+ title: r.title,
2075
+ indexer: r.indexer,
2076
+ quality: r.quality,
2077
+ size: formatBytes(r.size),
2078
+ age: r.age,
2079
+ ageHours: r.ageHours,
2080
+ rejected: r.rejected,
2081
+ rejectionReason: r.rejectionReason,
2082
+ publishedDate: r.publishedDate,
2083
+ })),
2084
+ }, null, 2),
2085
+ }],
2086
+ };
2087
+ }
2088
+ case "sonarr_delete_release": {
2089
+ if (!clients.sonarr)
2090
+ throw new Error("Sonarr not configured");
2091
+ const { guid } = args;
2092
+ await clients.sonarr.deleteRelease(guid);
2093
+ return {
2094
+ content: [{
2095
+ type: "text",
2096
+ text: JSON.stringify({
2097
+ success: true,
2098
+ message: `Release ${guid} deleted from history`,
2099
+ }, null, 2),
2100
+ }],
2101
+ };
2102
+ }
2103
+ case "sonarr_create_release_profile": {
2104
+ if (!clients.sonarr)
2105
+ throw new Error("Sonarr not configured");
2106
+ const profile = await clients.sonarr.createReleaseProfile(args);
2107
+ return {
2108
+ content: [{
2109
+ type: "text",
2110
+ text: JSON.stringify({
2111
+ success: true,
2112
+ message: `Release profile '${profile.name}' created`,
2113
+ profileId: profile.id,
2114
+ profile,
2115
+ }, null, 2),
2116
+ }],
2117
+ };
2118
+ }
2119
+ case "sonarr_update_release_profile": {
2120
+ if (!clients.sonarr)
2121
+ throw new Error("Sonarr not configured");
2122
+ const { profileId, ...updates } = args;
2123
+ const profile = await clients.sonarr.updateReleaseProfile(profileId, updates);
2124
+ return {
2125
+ content: [{
2126
+ type: "text",
2127
+ text: JSON.stringify({
2128
+ success: true,
2129
+ message: `Release profile ${profileId} updated`,
2130
+ profileId: profile.id,
2131
+ profile,
2132
+ }, null, 2),
2133
+ }],
2134
+ };
2135
+ }
2136
+ case "sonarr_delete_release_profile": {
2137
+ if (!clients.sonarr)
2138
+ throw new Error("Sonarr not configured");
2139
+ const { profileId } = args;
2140
+ await clients.sonarr.deleteReleaseProfile(profileId);
2141
+ return {
2142
+ content: [{
2143
+ type: "text",
2144
+ text: JSON.stringify({
2145
+ success: true,
2146
+ message: `Release profile ${profileId} deleted`,
2147
+ }, null, 2),
2148
+ }],
2149
+ };
2150
+ }
2151
+ case "sonarr_create_tag": {
2152
+ if (!clients.sonarr)
2153
+ throw new Error("Sonarr not configured");
2154
+ const { label } = args;
2155
+ const tag = await clients.sonarr.createTag(label);
2156
+ return {
2157
+ content: [{
2158
+ type: "text",
2159
+ text: JSON.stringify({
2160
+ success: true,
2161
+ message: `Tag '${label}' created`,
2162
+ tagId: tag.id,
2163
+ tag,
2164
+ }, null, 2),
2165
+ }],
2166
+ };
2167
+ }
2168
+ case "sonarr_update_tag": {
2169
+ if (!clients.sonarr)
2170
+ throw new Error("Sonarr not configured");
2171
+ const { tagId, label } = args;
2172
+ const tag = await clients.sonarr.updateTag(tagId, label);
2173
+ return {
2174
+ content: [{
2175
+ type: "text",
2176
+ text: JSON.stringify({
2177
+ success: true,
2178
+ message: `Tag ${tagId} updated to '${label}'`,
2179
+ tagId: tag.id,
2180
+ tag,
2181
+ }, null, 2),
2182
+ }],
2183
+ };
2184
+ }
2185
+ case "sonarr_delete_tag": {
2186
+ if (!clients.sonarr)
2187
+ throw new Error("Sonarr not configured");
2188
+ const { tagId } = args;
2189
+ await clients.sonarr.deleteTag(tagId);
2190
+ return {
2191
+ content: [{
2192
+ type: "text",
2193
+ text: JSON.stringify({
2194
+ success: true,
2195
+ message: `Tag ${tagId} deleted`,
2196
+ }, null, 2),
2197
+ }],
2198
+ };
2199
+ }
2200
+ case "sonarr_get_history": {
2201
+ if (!clients.sonarr)
2202
+ throw new Error("Sonarr not configured");
2203
+ const { page = 1, pageSize = 20, seriesId } = args;
2204
+ if (seriesId) {
2205
+ const history = await clients.sonarr.getSeriesHistory(seriesId);
2206
+ return {
2207
+ content: [{
2208
+ type: "text",
2209
+ text: JSON.stringify({
2210
+ seriesId,
2211
+ count: history.length,
2212
+ records: history.map((h) => ({
2213
+ id: h.id,
2214
+ date: h.date,
2215
+ eventType: h.eventType,
2216
+ sourceTitle: h.sourceTitle,
2217
+ quality: h.quality?.quality?.name,
2218
+ episodeId: h.episodeId,
2219
+ })),
2220
+ }, null, 2),
2221
+ }],
2222
+ };
2223
+ }
2224
+ const history = await clients.sonarr.getHistory(page, pageSize);
2225
+ return {
2226
+ content: [{
2227
+ type: "text",
2228
+ text: JSON.stringify({
2229
+ page,
2230
+ pageSize,
2231
+ totalRecords: history.totalRecords,
2232
+ records: history.records.map((h) => ({
2233
+ id: h.id,
2234
+ seriesId: h.seriesId,
2235
+ episodeId: h.episodeId,
2236
+ date: h.date,
2237
+ eventType: h.eventType,
2238
+ sourceTitle: h.sourceTitle,
2239
+ quality: h.quality?.quality?.name,
2240
+ })),
2241
+ }, null, 2),
2242
+ }],
2243
+ };
2244
+ }
2245
+ case "sonarr_get_wanted": {
2246
+ if (!clients.sonarr)
2247
+ throw new Error("Sonarr not configured");
2248
+ const { page = 1, pageSize = 20 } = args;
2249
+ const wanted = await clients.sonarr.getWanted(page, pageSize);
2250
+ return {
2251
+ content: [{
2252
+ type: "text",
2253
+ text: JSON.stringify({
2254
+ page,
2255
+ pageSize,
2256
+ totalRecords: wanted.totalRecords,
2257
+ episodes: wanted.records.map((e) => ({
2258
+ id: e.id,
2259
+ seriesId: e.seriesId,
2260
+ seasonNumber: e.seasonNumber,
2261
+ episodeNumber: e.episodeNumber,
2262
+ title: e.title,
2263
+ airDate: e.airDate,
2264
+ monitored: e.monitored,
2265
+ })),
2266
+ }, null, 2),
2267
+ }],
2268
+ };
2269
+ }
2270
+ case "sonarr_get_cutoff_unmet": {
2271
+ if (!clients.sonarr)
2272
+ throw new Error("Sonarr not configured");
2273
+ const { page = 1, pageSize = 20 } = args;
2274
+ const cutoff = await clients.sonarr.getCutoffUnmet(page, pageSize);
2275
+ return {
2276
+ content: [{
2277
+ type: "text",
2278
+ text: JSON.stringify({
2279
+ page,
2280
+ pageSize,
2281
+ totalRecords: cutoff.totalRecords,
2282
+ episodes: cutoff.records.map((e) => ({
2283
+ id: e.id,
2284
+ seriesId: e.seriesId,
2285
+ seasonNumber: e.seasonNumber,
2286
+ episodeNumber: e.episodeNumber,
2287
+ title: e.title,
2288
+ currentQuality: e.episodeFile?.quality?.quality?.name,
2289
+ })),
2290
+ }, null, 2),
2291
+ }],
2292
+ };
2293
+ }
2294
+ case "sonarr_get_blocklist": {
2295
+ if (!clients.sonarr)
2296
+ throw new Error("Sonarr not configured");
2297
+ const { page = 1, pageSize = 20 } = args;
2298
+ const blocklist = await clients.sonarr.getBlocklist(page, pageSize);
2299
+ return {
2300
+ content: [{
2301
+ type: "text",
2302
+ text: JSON.stringify({
2303
+ page,
2304
+ pageSize,
2305
+ totalRecords: blocklist.totalRecords,
2306
+ records: blocklist.records.map((b) => ({
2307
+ id: b.id,
2308
+ seriesId: b.seriesId,
2309
+ sourceTitle: b.sourceTitle,
2310
+ date: b.date,
2311
+ protocol: b.protocol,
2312
+ indexer: b.indexer,
2313
+ message: b.message,
2314
+ })),
2315
+ }, null, 2),
2316
+ }],
2317
+ };
2318
+ }
2319
+ case "sonarr_delete_blocklist_item": {
2320
+ if (!clients.sonarr)
2321
+ throw new Error("Sonarr not configured");
2322
+ const { blocklistId } = args;
2323
+ await clients.sonarr.deleteBlocklistItem(blocklistId);
2324
+ return {
2325
+ content: [{
2326
+ type: "text",
2327
+ text: JSON.stringify({
2328
+ success: true,
2329
+ message: `Blocklist item ${blocklistId} deleted`,
2330
+ }, null, 2),
2331
+ }],
2332
+ };
2333
+ }
2334
+ case "sonarr_search_all_missing": {
2335
+ if (!clients.sonarr)
2336
+ throw new Error("Sonarr not configured");
2337
+ const result = await clients.sonarr.searchAllMissing();
2338
+ return {
2339
+ content: [{
2340
+ type: "text",
2341
+ text: JSON.stringify({
2342
+ success: true,
2343
+ message: 'Search triggered for all missing episodes',
2344
+ commandId: result.id,
2345
+ }, null, 2),
2346
+ }],
2347
+ };
2348
+ }
2349
+ // Radarr handlers
2350
+ case "radarr_get_movies": {
2351
+ if (!clients.radarr)
2352
+ throw new Error("Radarr not configured");
2353
+ const movies = await clients.radarr.getMovies();
2354
+ return {
2355
+ content: [{
2356
+ type: "text",
2357
+ text: JSON.stringify({
2358
+ count: movies.length,
2359
+ movies: movies.map(m => ({
2360
+ id: m.id,
2361
+ title: m.title,
2362
+ year: m.year,
2363
+ status: m.status,
2364
+ hasFile: m.hasFile,
2365
+ sizeOnDisk: formatBytes(m.sizeOnDisk),
2366
+ monitored: m.monitored,
2367
+ studio: m.studio,
2368
+ })),
2369
+ }, null, 2),
2370
+ }],
2371
+ };
2372
+ }
2373
+ case "radarr_search": {
2374
+ if (!clients.radarr)
2375
+ throw new Error("Radarr not configured");
2376
+ const term = args.term;
2377
+ const results = await clients.radarr.searchMovies(term);
2378
+ return {
2379
+ content: [{
2380
+ type: "text",
2381
+ text: JSON.stringify({
2382
+ count: results.length,
2383
+ results: results.slice(0, 10).map(r => ({
2384
+ title: r.title,
2385
+ year: r.year,
2386
+ tmdbId: r.tmdbId,
2387
+ imdbId: r.imdbId,
2388
+ overview: r.overview?.substring(0, 200) + (r.overview && r.overview.length > 200 ? '...' : ''),
2389
+ })),
2390
+ }, null, 2),
2391
+ }],
2392
+ };
2393
+ }
2394
+ case "radarr_get_queue": {
2395
+ if (!clients.radarr)
2396
+ throw new Error("Radarr not configured");
2397
+ const queue = await clients.radarr.getQueue();
2398
+ return {
2399
+ content: [{
2400
+ type: "text",
2401
+ text: JSON.stringify({
2402
+ totalRecords: queue.totalRecords,
2403
+ items: queue.records.map(q => ({
2404
+ title: q.title,
2405
+ status: q.status,
2406
+ progress: ((1 - q.sizeleft / q.size) * 100).toFixed(1) + '%',
2407
+ timeLeft: q.timeleft,
2408
+ downloadClient: q.downloadClient,
2409
+ })),
2410
+ }, null, 2),
2411
+ }],
2412
+ };
2413
+ }
2414
+ case "radarr_get_calendar": {
2415
+ if (!clients.radarr)
2416
+ throw new Error("Radarr not configured");
2417
+ const days = args?.days || 30;
2418
+ const start = new Date().toISOString().split('T')[0];
2419
+ const end = new Date(Date.now() + days * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
2420
+ const calendar = await clients.radarr.getCalendar(start, end);
2421
+ return {
2422
+ content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
2423
+ };
2424
+ }
2425
+ case "radarr_search_movie": {
2426
+ if (!clients.radarr)
2427
+ throw new Error("Radarr not configured");
2428
+ const movieId = args.movieId;
2429
+ const result = await clients.radarr.searchMovie(movieId);
1508
2430
  return {
1509
2431
  content: [{
1510
2432
  type: "text",
1511
2433
  text: JSON.stringify({
1512
2434
  success: true,
1513
- message: `Series '${series.title}' updated`,
1514
- seriesId: series.id,
1515
- updates: Object.keys(updates),
2435
+ message: `Search triggered for movie`,
2436
+ commandId: result.id,
1516
2437
  }, null, 2),
1517
2438
  }],
1518
2439
  };
1519
2440
  }
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);
2441
+ case "radarr_add_movie": {
2442
+ if (!clients.radarr)
2443
+ throw new Error("Radarr not configured");
2444
+ const { tmdbId, title, qualityProfileId, rootFolderPath, monitored, minimumAvailability, tags } = args;
2445
+ const movie = await clients.radarr.addMovie({
2446
+ tmdbId,
2447
+ title,
2448
+ qualityProfileId,
2449
+ rootFolderPath,
2450
+ monitored,
2451
+ minimumAvailability: minimumAvailability || 'released',
2452
+ tags,
2453
+ });
1525
2454
  return {
1526
2455
  content: [{
1527
2456
  type: "text",
1528
2457
  text: JSON.stringify({
1529
2458
  success: true,
1530
- message: `Series deleted (files: ${deleteFiles}, exclude: ${addImportListExclusion})`,
2459
+ message: `Movie '${movie.title}' added to Radarr`,
2460
+ movieId: movie.id,
2461
+ movie: {
2462
+ id: movie.id,
2463
+ title: movie.title,
2464
+ path: movie.path,
2465
+ monitored: movie.monitored,
2466
+ },
1531
2467
  }, null, 2),
1532
2468
  }],
1533
2469
  };
1534
2470
  }
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 });
2471
+ case "radarr_update_movie": {
2472
+ if (!clients.radarr)
2473
+ throw new Error("Radarr not configured");
2474
+ const { movieId, ...updates } = args;
2475
+ const movie = await clients.radarr.updateMovie(movieId, updates);
1540
2476
  return {
1541
2477
  content: [{
1542
2478
  type: "text",
1543
2479
  text: JSON.stringify({
1544
2480
  success: true,
1545
- message: `Episode ${episodeId} ${monitored ? 'monitored' : 'unmonitored'}`,
1546
- episodeId: episode.id,
1547
- monitored: episode.monitored,
2481
+ message: `Movie '${movie.title}' updated`,
2482
+ movieId: movie.id,
2483
+ updates: Object.keys(updates),
1548
2484
  }, null, 2),
1549
2485
  }],
1550
2486
  };
1551
2487
  }
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);
2488
+ case "radarr_delete_movie": {
2489
+ if (!clients.radarr)
2490
+ throw new Error("Radarr not configured");
2491
+ const { movieId, deleteFiles = false, addImportExclusion = false } = args;
2492
+ await clients.radarr.deleteMovie(movieId, deleteFiles, addImportExclusion);
1557
2493
  return {
1558
2494
  content: [{
1559
2495
  type: "text",
1560
2496
  text: JSON.stringify({
1561
2497
  success: true,
1562
- message: `Episode file ${episodeFileId} deleted`,
2498
+ message: `Movie deleted (files: ${deleteFiles}, exclude: ${addImportExclusion})`,
1563
2499
  }, null, 2),
1564
2500
  }],
1565
2501
  };
1566
2502
  }
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);
2503
+ case "radarr_get_history": {
2504
+ if (!clients.radarr)
2505
+ throw new Error("Radarr not configured");
2506
+ const { page = 1, pageSize = 20, movieId } = args;
2507
+ if (movieId) {
2508
+ const history = await clients.radarr.getMovieHistory(movieId);
2509
+ return {
2510
+ content: [{
2511
+ type: "text",
2512
+ text: JSON.stringify({
2513
+ movieId,
2514
+ count: history.length,
2515
+ records: history.map((h) => ({
2516
+ id: h.id,
2517
+ date: h.date,
2518
+ eventType: h.eventType,
2519
+ sourceTitle: h.sourceTitle,
2520
+ quality: h.quality?.quality?.name,
2521
+ downloadId: h.downloadId,
2522
+ })),
2523
+ }, null, 2),
2524
+ }],
2525
+ };
2526
+ }
2527
+ const history = await clients.radarr.getHistory(page, pageSize);
1572
2528
  return {
1573
2529
  content: [{
1574
2530
  type: "text",
1575
2531
  text: JSON.stringify({
1576
- success: true,
1577
- message: `Series ${seriesId} metadata refresh triggered`,
1578
- commandId: result.id,
2532
+ page,
2533
+ pageSize,
2534
+ totalRecords: history.totalRecords,
2535
+ records: history.records.map((h) => ({
2536
+ id: h.id,
2537
+ movieId: h.movieId,
2538
+ date: h.date,
2539
+ eventType: h.eventType,
2540
+ sourceTitle: h.sourceTitle,
2541
+ quality: h.quality?.quality?.name,
2542
+ })),
1579
2543
  }, null, 2),
1580
2544
  }],
1581
2545
  };
1582
2546
  }
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);
2547
+ case "radarr_refresh_movie": {
2548
+ if (!clients.radarr)
2549
+ throw new Error("Radarr not configured");
2550
+ const { movieId } = args;
2551
+ const result = await clients.radarr.refreshMovie(movieId);
1588
2552
  return {
1589
2553
  content: [{
1590
2554
  type: "text",
1591
2555
  text: JSON.stringify({
1592
2556
  success: true,
1593
- message: `Series ${seriesId} disk rescan triggered`,
2557
+ message: `Movie ${movieId} metadata refresh triggered`,
1594
2558
  commandId: result.id,
1595
2559
  }, null, 2),
1596
2560
  }],
1597
2561
  };
1598
2562
  }
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);
2563
+ case "radarr_rescan_movie": {
2564
+ if (!clients.radarr)
2565
+ throw new Error("Radarr not configured");
2566
+ const { movieId } = args;
2567
+ const result = await clients.radarr.rescanMovie(movieId);
1604
2568
  return {
1605
2569
  content: [{
1606
2570
  type: "text",
1607
2571
  text: JSON.stringify({
1608
2572
  success: true,
1609
- message: `Series ${seriesId} files will be renamed`,
2573
+ message: `Movie ${movieId} disk rescan triggered`,
1610
2574
  commandId: result.id,
1611
2575
  }, null, 2),
1612
2576
  }],
1613
2577
  };
1614
2578
  }
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);
2579
+ case "radarr_rename_movie": {
2580
+ if (!clients.radarr)
2581
+ throw new Error("Radarr not configured");
2582
+ const { movieId } = args;
2583
+ const result = await clients.radarr.renameMovie(movieId);
1620
2584
  return {
1621
2585
  content: [{
1622
2586
  type: "text",
1623
2587
  text: JSON.stringify({
1624
2588
  success: true,
1625
- message: `All episodes for series ${seriesId} will be renamed`,
2589
+ message: `Movie ${movieId} files will be renamed`,
1626
2590
  commandId: result.id,
1627
2591
  }, null, 2),
1628
2592
  }],
1629
2593
  };
1630
2594
  }
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);
2595
+ case "radarr_get_collections": {
2596
+ if (!clients.radarr)
2597
+ throw new Error("Radarr not configured");
2598
+ const collections = await clients.radarr.getCollections();
1636
2599
  return {
1637
2600
  content: [{
1638
2601
  type: "text",
1639
2602
  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,
2603
+ count: collections.length,
2604
+ collections: collections.map((c) => ({
2605
+ id: c.id,
2606
+ title: c.title,
2607
+ tmdbId: c.tmdbId,
2608
+ monitored: c.monitored,
2609
+ movieCount: c.movies?.length || 0,
2610
+ qualityProfileId: c.qualityProfileId,
2611
+ rootFolderPath: c.rootFolderPath,
1654
2612
  })),
1655
2613
  }, null, 2),
1656
2614
  }],
1657
2615
  };
1658
2616
  }
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);
2617
+ case "radarr_update_collection": {
2618
+ if (!clients.radarr)
2619
+ throw new Error("Radarr not configured");
2620
+ const { collectionId, ...updates } = args;
2621
+ const collection = await clients.radarr.updateCollection(collectionId, updates);
1664
2622
  return {
1665
2623
  content: [{
1666
2624
  type: "text",
1667
2625
  text: JSON.stringify({
1668
2626
  success: true,
1669
- message: `Release ${guid} deleted from history`,
2627
+ message: `Collection '${collection.title}' updated`,
2628
+ collectionId: collection.id,
2629
+ updates: Object.keys(updates),
1670
2630
  }, null, 2),
1671
2631
  }],
1672
2632
  };
1673
2633
  }
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);
2634
+ case "radarr_get_movie_files": {
2635
+ if (!clients.radarr)
2636
+ throw new Error("Radarr not configured");
2637
+ const { movieId } = args;
2638
+ const files = await clients.radarr.getMovieFiles(movieId);
1678
2639
  return {
1679
2640
  content: [{
1680
2641
  type: "text",
1681
2642
  text: JSON.stringify({
1682
- success: true,
1683
- message: `Release profile '${profile.name}' created`,
1684
- profileId: profile.id,
1685
- profile,
2643
+ movieId,
2644
+ count: files.length,
2645
+ files: files.map((f) => ({
2646
+ id: f.id,
2647
+ relativePath: f.relativePath,
2648
+ size: formatBytes(f.size),
2649
+ quality: f.quality?.quality?.name,
2650
+ dateAdded: f.dateAdded,
2651
+ mediaInfo: f.mediaInfo ? {
2652
+ resolution: f.mediaInfo.resolution,
2653
+ videoCodec: f.mediaInfo.videoCodec,
2654
+ audioCodec: f.mediaInfo.audioCodec,
2655
+ audioChannels: f.mediaInfo.audioChannels,
2656
+ runTime: f.mediaInfo.runTime,
2657
+ } : null,
2658
+ })),
1686
2659
  }, null, 2),
1687
2660
  }],
1688
2661
  };
1689
2662
  }
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);
2663
+ case "radarr_delete_movie_file": {
2664
+ if (!clients.radarr)
2665
+ throw new Error("Radarr not configured");
2666
+ const { movieFileId } = args;
2667
+ await clients.radarr.deleteMovieFile(movieFileId);
1695
2668
  return {
1696
2669
  content: [{
1697
2670
  type: "text",
1698
2671
  text: JSON.stringify({
1699
2672
  success: true,
1700
- message: `Release profile ${profileId} updated`,
1701
- profileId: profile.id,
1702
- profile,
2673
+ message: `Movie file ${movieFileId} deleted`,
1703
2674
  }, null, 2),
1704
2675
  }],
1705
2676
  };
1706
2677
  }
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);
2678
+ case "radarr_get_blocklist": {
2679
+ if (!clients.radarr)
2680
+ throw new Error("Radarr not configured");
2681
+ const { page = 1, pageSize = 20 } = args;
2682
+ const blocklist = await clients.radarr.getBlocklist(page, pageSize);
1712
2683
  return {
1713
2684
  content: [{
1714
2685
  type: "text",
1715
2686
  text: JSON.stringify({
1716
- success: true,
1717
- message: `Release profile ${profileId} deleted`,
2687
+ page,
2688
+ pageSize,
2689
+ totalRecords: blocklist.totalRecords,
2690
+ records: blocklist.records.map((b) => ({
2691
+ id: b.id,
2692
+ movieId: b.movieId,
2693
+ sourceTitle: b.sourceTitle,
2694
+ date: b.date,
2695
+ protocol: b.protocol,
2696
+ indexer: b.indexer,
2697
+ message: b.message,
2698
+ })),
1718
2699
  }, null, 2),
1719
2700
  }],
1720
2701
  };
1721
2702
  }
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);
2703
+ case "radarr_delete_blocklist_item": {
2704
+ if (!clients.radarr)
2705
+ throw new Error("Radarr not configured");
2706
+ const { blocklistId } = args;
2707
+ await clients.radarr.deleteBlocklistItem(blocklistId);
1727
2708
  return {
1728
2709
  content: [{
1729
2710
  type: "text",
1730
2711
  text: JSON.stringify({
1731
2712
  success: true,
1732
- message: `Tag '${label}' created`,
1733
- tagId: tag.id,
1734
- tag,
2713
+ message: `Blocklist item ${blocklistId} deleted`,
1735
2714
  }, null, 2),
1736
2715
  }],
1737
2716
  };
1738
2717
  }
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);
2718
+ case "radarr_get_extra_files": {
2719
+ if (!clients.radarr)
2720
+ throw new Error("Radarr not configured");
2721
+ const { movieId } = args;
2722
+ const files = await clients.radarr.getExtraFiles(movieId);
1744
2723
  return {
1745
2724
  content: [{
1746
2725
  type: "text",
1747
2726
  text: JSON.stringify({
1748
- success: true,
1749
- message: `Tag ${tagId} updated to '${label}'`,
1750
- tagId: tag.id,
1751
- tag,
2727
+ movieId,
2728
+ count: files.length,
2729
+ files: files.map((f) => ({
2730
+ id: f.id,
2731
+ relativePath: f.relativePath,
2732
+ extension: f.extension,
2733
+ type: f.type,
2734
+ })),
1752
2735
  }, null, 2),
1753
2736
  }],
1754
2737
  };
1755
2738
  }
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);
2739
+ case "radarr_get_credits": {
2740
+ if (!clients.radarr)
2741
+ throw new Error("Radarr not configured");
2742
+ const { movieId } = args;
2743
+ const credits = await clients.radarr.getCredits(movieId);
1761
2744
  return {
1762
2745
  content: [{
1763
2746
  type: "text",
1764
2747
  text: JSON.stringify({
1765
- success: true,
1766
- message: `Tag ${tagId} deleted`,
2748
+ movieId,
2749
+ count: credits.length,
2750
+ cast: credits.filter((c) => c.type === 'cast').slice(0, 20).map((c) => ({
2751
+ name: c.personName,
2752
+ character: c.character,
2753
+ order: c.order,
2754
+ })),
2755
+ crew: credits.filter((c) => c.type === 'crew').slice(0, 10).map((c) => ({
2756
+ name: c.personName,
2757
+ job: c.job,
2758
+ department: c.department,
2759
+ })),
1767
2760
  }, null, 2),
1768
2761
  }],
1769
2762
  };
1770
2763
  }
1771
- // Radarr handlers
1772
- case "radarr_get_movies": {
2764
+ case "radarr_get_wanted": {
1773
2765
  if (!clients.radarr)
1774
2766
  throw new Error("Radarr not configured");
1775
- const movies = await clients.radarr.getMovies();
2767
+ const { page = 1, pageSize = 20 } = args;
2768
+ const wanted = await clients.radarr.getWanted(page, pageSize);
1776
2769
  return {
1777
2770
  content: [{
1778
2771
  type: "text",
1779
2772
  text: JSON.stringify({
1780
- count: movies.length,
1781
- movies: movies.map(m => ({
2773
+ page,
2774
+ pageSize,
2775
+ totalRecords: wanted.totalRecords,
2776
+ movies: wanted.records.map((m) => ({
1782
2777
  id: m.id,
1783
2778
  title: m.title,
1784
2779
  year: m.year,
1785
2780
  status: m.status,
1786
- hasFile: m.hasFile,
1787
- sizeOnDisk: formatBytes(m.sizeOnDisk),
1788
2781
  monitored: m.monitored,
1789
- studio: m.studio,
2782
+ isAvailable: m.isAvailable,
2783
+ minimumAvailability: m.minimumAvailability,
1790
2784
  })),
1791
2785
  }, null, 2),
1792
2786
  }],
1793
2787
  };
1794
2788
  }
1795
- case "radarr_search": {
2789
+ case "radarr_search_missing": {
1796
2790
  if (!clients.radarr)
1797
2791
  throw new Error("Radarr not configured");
1798
- const term = args.term;
1799
- const results = await clients.radarr.searchMovies(term);
2792
+ const result = await clients.radarr.searchMissing();
1800
2793
  return {
1801
2794
  content: [{
1802
2795
  type: "text",
1803
2796
  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
- })),
2797
+ success: true,
2798
+ message: 'Search triggered for all missing movies',
2799
+ commandId: result.id,
1812
2800
  }, null, 2),
1813
2801
  }],
1814
2802
  };
1815
2803
  }
1816
- case "radarr_get_queue": {
2804
+ case "radarr_create_tag": {
1817
2805
  if (!clients.radarr)
1818
2806
  throw new Error("Radarr not configured");
1819
- const queue = await clients.radarr.getQueue();
2807
+ const { label } = args;
2808
+ const tag = await clients.radarr.createTag(label);
1820
2809
  return {
1821
2810
  content: [{
1822
2811
  type: "text",
1823
2812
  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
- })),
2813
+ success: true,
2814
+ message: `Tag '${label}' created`,
2815
+ tagId: tag.id,
2816
+ tag,
1832
2817
  }, null, 2),
1833
2818
  }],
1834
2819
  };
1835
2820
  }
1836
- case "radarr_get_calendar": {
2821
+ case "radarr_update_tag": {
1837
2822
  if (!clients.radarr)
1838
2823
  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);
2824
+ const { tagId, label } = args;
2825
+ const tag = await clients.radarr.updateTag(tagId, label);
1843
2826
  return {
1844
- content: [{ type: "text", text: JSON.stringify(calendar, null, 2) }],
2827
+ content: [{
2828
+ type: "text",
2829
+ text: JSON.stringify({
2830
+ success: true,
2831
+ message: `Tag ${tagId} updated to '${label}'`,
2832
+ tagId: tag.id,
2833
+ tag,
2834
+ }, null, 2),
2835
+ }],
1845
2836
  };
1846
2837
  }
1847
- case "radarr_search_movie": {
2838
+ case "radarr_delete_tag": {
1848
2839
  if (!clients.radarr)
1849
2840
  throw new Error("Radarr not configured");
1850
- const movieId = args.movieId;
1851
- const result = await clients.radarr.searchMovie(movieId);
2841
+ const { tagId } = args;
2842
+ await clients.radarr.deleteTag(tagId);
1852
2843
  return {
1853
2844
  content: [{
1854
2845
  type: "text",
1855
2846
  text: JSON.stringify({
1856
2847
  success: true,
1857
- message: `Search triggered for movie`,
1858
- commandId: result.id,
2848
+ message: `Tag ${tagId} deleted`,
1859
2849
  }, null, 2),
1860
2850
  }],
1861
2851
  };