plex-mcp 0.0.2 → 0.0.4
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/TODO.md +252 -0
- package/index.js +304 -109
- package/package.json +6 -4
package/TODO.md
ADDED
@@ -0,0 +1,252 @@
|
|
1
|
+
# Plex MCP Server - TODO List
|
2
|
+
|
3
|
+
## Unimplemented Plex API Features
|
4
|
+
|
5
|
+
### Critical Missing APIs (High Priority)
|
6
|
+
|
7
|
+
#### Session Management
|
8
|
+
- [ ] **get_active_sessions** - List current "Now Playing" sessions
|
9
|
+
- Endpoint: `/status/sessions`
|
10
|
+
- Returns: Active playback sessions with user, client, media info
|
11
|
+
- [ ] **get_transcode_sessions** - List active transcoding operations
|
12
|
+
- Endpoint: `/transcode/sessions`
|
13
|
+
- Returns: Transcoding progress, quality settings, resource usage
|
14
|
+
- [ ] **terminate_session** - Stop/kill active playback sessions
|
15
|
+
- Endpoint: `/status/sessions/terminate`
|
16
|
+
- Action: Force stop sessions by session ID
|
17
|
+
|
18
|
+
#### Playback Control
|
19
|
+
- [ ] **control_playback** - Control playback (play/pause/stop/seek)
|
20
|
+
- Endpoint: `/player/playback/{command}`
|
21
|
+
- Commands: play, pause, stop, stepForward, stepBack, seekTo
|
22
|
+
- [ ] **start_playback** - Initiate playback on specific clients
|
23
|
+
- Endpoint: `/player/playback/playMedia`
|
24
|
+
- Parameters: media key, client ID, resume offset
|
25
|
+
- [ ] **remote_control** - Advanced remote control operations
|
26
|
+
- Endpoint: `/player/navigation/{command}`
|
27
|
+
- Commands: moveUp, moveDown, select, back, home
|
28
|
+
|
29
|
+
#### Client & Device Management
|
30
|
+
- [ ] **get_clients** - List available Plex clients/players
|
31
|
+
- Endpoint: `/clients`
|
32
|
+
- Returns: Client names, IDs, capabilities, online status
|
33
|
+
- [ ] **get_devices** - List all registered devices
|
34
|
+
- Endpoint: `/devices`
|
35
|
+
- Returns: Device info, last seen, platform details
|
36
|
+
- [ ] **get_servers** - List available Plex servers
|
37
|
+
- Endpoint: `/servers`
|
38
|
+
- Returns: Server list for multi-server setups
|
39
|
+
|
40
|
+
### Server Administration (Medium Priority)
|
41
|
+
|
42
|
+
#### Server Info & Management
|
43
|
+
- [ ] **get_server_info** - Server capabilities and status
|
44
|
+
- Endpoint: `/`
|
45
|
+
- Returns: Version, capabilities, transcoder info, platform
|
46
|
+
- [ ] **get_server_preferences** - Server configuration settings
|
47
|
+
- Endpoint: `/:/prefs`
|
48
|
+
- Returns: All server preferences and settings
|
49
|
+
- [ ] **scan_library** - Trigger library content scan
|
50
|
+
- Endpoint: `/library/sections/{id}/refresh`
|
51
|
+
- Action: Force library scan for new content
|
52
|
+
- [ ] **refresh_metadata** - Force metadata refresh for items
|
53
|
+
- Endpoint: `/library/metadata/{id}/refresh`
|
54
|
+
- Action: Re-download metadata, artwork, etc.
|
55
|
+
|
56
|
+
#### User Management
|
57
|
+
- [ ] **get_users** - List server users and accounts
|
58
|
+
- Endpoint: `/accounts`
|
59
|
+
- Returns: User list, permissions, sharing status
|
60
|
+
- [ ] **get_user_activity** - User-specific activity logs
|
61
|
+
- Endpoint: `/status/sessions/history/all?accountID={id}`
|
62
|
+
- Returns: Per-user watch history and statistics
|
63
|
+
|
64
|
+
### Content Discovery & Recommendations (Medium Priority)
|
65
|
+
|
66
|
+
#### Advanced Discovery
|
67
|
+
- [ ] **get_content_hubs** - Plex's recommendation engine
|
68
|
+
- Endpoint: `/hubs`
|
69
|
+
- Returns: Curated content recommendations, trending
|
70
|
+
- [ ] **get_discover_content** - Discover new content across libraries
|
71
|
+
- Endpoint: `/library/sections/all?discover=1`
|
72
|
+
- Returns: Cross-library content discovery
|
73
|
+
- [ ] **get_trending** - Trending content on Plex platform
|
74
|
+
- Endpoint: `/hubs/trending`
|
75
|
+
- Returns: Popular content across Plex network
|
76
|
+
|
77
|
+
#### Metadata Enhancement
|
78
|
+
- [ ] **get_genres** - Available genres across libraries
|
79
|
+
- Endpoint: `/library/sections/{id}/genre`
|
80
|
+
- Returns: Genre list with item counts
|
81
|
+
- [ ] **get_years** - Available years across libraries
|
82
|
+
- Endpoint: `/library/sections/{id}/year`
|
83
|
+
- Returns: Year list with item counts
|
84
|
+
- [ ] **get_studios** - Available studios/networks
|
85
|
+
- Endpoint: `/library/sections/{id}/studio`
|
86
|
+
- Returns: Studio/network list with item counts
|
87
|
+
- [ ] **get_directors** - Available directors/actors
|
88
|
+
- Endpoint: `/library/sections/{id}/director`
|
89
|
+
- Returns: People list with filmography counts
|
90
|
+
|
91
|
+
### Media Management (Medium Priority)
|
92
|
+
|
93
|
+
#### Watch Status Management
|
94
|
+
- [ ] **mark_watched** - Mark items as watched
|
95
|
+
- Endpoint: `/:/scrobble?key={id}&identifier=com.plexapp.plugins.library`
|
96
|
+
- Action: Set watch status, update play count
|
97
|
+
- [ ] **mark_unwatched** - Mark items as unwatched
|
98
|
+
- Endpoint: `/:/unscrobble?key={id}&identifier=com.plexapp.plugins.library`
|
99
|
+
- Action: Remove watch status, reset play count
|
100
|
+
- [ ] **set_rating** - Rate content (stars/thumbs)
|
101
|
+
- Endpoint: `/:/rate?key={id}&rating={rating}&identifier=com.plexapp.plugins.library`
|
102
|
+
- Action: Set user rating for content
|
103
|
+
|
104
|
+
#### Library Maintenance
|
105
|
+
- [ ] **optimize_library** - Database optimization operations
|
106
|
+
- Endpoint: `/library/optimize`
|
107
|
+
- Action: Clean up database, optimize indexes
|
108
|
+
- [ ] **empty_trash** - Empty library trash/deleted items
|
109
|
+
- Endpoint: `/library/sections/{id}/emptyTrash`
|
110
|
+
- Action: Permanently delete trashed items
|
111
|
+
- [ ] **update_library** - Update library metadata
|
112
|
+
- Endpoint: `/library/sections/{id}/update`
|
113
|
+
- Action: Update library without full scan
|
114
|
+
|
115
|
+
### Advanced Features (Low Priority)
|
116
|
+
|
117
|
+
#### Transcoding Management
|
118
|
+
- [ ] **get_transcoding_settings** - Transcoding preferences
|
119
|
+
- Endpoint: `/:/prefs?group=transcoder`
|
120
|
+
- Returns: Quality settings, codec preferences
|
121
|
+
- [ ] **optimize_content** - Content optimization for devices
|
122
|
+
- Endpoint: `/library/metadata/{id}/optimize`
|
123
|
+
- Action: Pre-transcode content for specific devices
|
124
|
+
|
125
|
+
#### Sync & Download
|
126
|
+
- [ ] **get_sync_items** - Sync queue and downloaded items
|
127
|
+
- Endpoint: `/sync/items`
|
128
|
+
- Returns: Sync status, download queue
|
129
|
+
- [ ] **download_media** - Download content for offline viewing
|
130
|
+
- Endpoint: `/sync/items`
|
131
|
+
- Action: Queue content for download/sync
|
132
|
+
|
133
|
+
#### Webhooks & Events
|
134
|
+
- [ ] **get_webhooks** - List configured webhooks
|
135
|
+
- Endpoint: `/:/webhooks`
|
136
|
+
- Returns: Webhook URLs and trigger events
|
137
|
+
- [ ] **listen_events** - Real-time event streaming
|
138
|
+
- Endpoint: `/:/events` (WebSocket/SSE)
|
139
|
+
- Returns: Live server events, playback updates
|
140
|
+
|
141
|
+
## Missing Test Coverage
|
142
|
+
|
143
|
+
### Unit Tests
|
144
|
+
- [ ] **Session management handlers** - Tests for new session control APIs
|
145
|
+
- [ ] **Playback control handlers** - Tests for media control functionality
|
146
|
+
- [ ] **Client management handlers** - Tests for device/client discovery
|
147
|
+
- [ ] **Server admin handlers** - Tests for administrative functions
|
148
|
+
- [ ] **User management handlers** - Tests for multi-user functionality
|
149
|
+
- [ ] **Content discovery handlers** - Tests for recommendation features
|
150
|
+
- [ ] **Watch status handlers** - Tests for marking watched/unwatched
|
151
|
+
- [ ] **Library management handlers** - Tests for scan/refresh operations
|
152
|
+
|
153
|
+
### Integration Tests
|
154
|
+
- [ ] **Multi-library operations** - Cross-library search and discovery
|
155
|
+
- [ ] **Playlist management with new content** - Advanced playlist operations
|
156
|
+
- [ ] **User permission scenarios** - Multi-user access control
|
157
|
+
- [ ] **Server configuration changes** - Settings modification testing
|
158
|
+
- [ ] **Transcoding workflow** - End-to-end transcoding scenarios
|
159
|
+
- [ ] **Sync/download workflows** - Offline content management
|
160
|
+
|
161
|
+
### Mock Data Enhancement
|
162
|
+
- [ ] **Session data mocks** - Active session responses
|
163
|
+
- [ ] **Client data mocks** - Available client/device responses
|
164
|
+
- [ ] **Server info mocks** - Server capabilities and status
|
165
|
+
- [ ] **User data mocks** - Multi-user account responses
|
166
|
+
- [ ] **Transcoding mocks** - Transcoding session responses
|
167
|
+
- [ ] **Webhook mocks** - Event and webhook responses
|
168
|
+
|
169
|
+
## E2E Tests to Add
|
170
|
+
|
171
|
+
### Real Server Integration
|
172
|
+
- [ ] **Live session monitoring** - Connect to real server, monitor active sessions
|
173
|
+
- Test with actual playback sessions
|
174
|
+
- Verify session data accuracy
|
175
|
+
- Test session termination
|
176
|
+
- [ ] **Live playback control** - Control actual Plex clients
|
177
|
+
- Test play/pause/stop commands
|
178
|
+
- Test seek functionality
|
179
|
+
- Test client discovery and selection
|
180
|
+
- [ ] **Live library management** - Real library operations
|
181
|
+
- Test library scanning
|
182
|
+
- Test metadata refresh
|
183
|
+
- Test library optimization
|
184
|
+
- [ ] **Live user scenarios** - Multi-user testing
|
185
|
+
- Test shared library access
|
186
|
+
- Test user-specific history
|
187
|
+
- Test permission boundaries
|
188
|
+
|
189
|
+
### Cross-Platform Testing
|
190
|
+
- [ ] **Multiple client types** - Test with various Plex clients
|
191
|
+
- Desktop app, mobile app, web player
|
192
|
+
- Smart TV apps, streaming devices
|
193
|
+
- Verify control compatibility
|
194
|
+
- [ ] **Multiple server versions** - Test against different Plex server versions
|
195
|
+
- Latest stable, previous versions
|
196
|
+
- PlexPass vs free features
|
197
|
+
- API compatibility testing
|
198
|
+
|
199
|
+
### Network Scenarios
|
200
|
+
- [ ] **SSL/TLS configurations** - Various security setups
|
201
|
+
- Self-signed certificates
|
202
|
+
- Valid SSL certificates
|
203
|
+
- Mixed HTTP/HTTPS environments
|
204
|
+
- [ ] **Remote access testing** - Plex relay and direct connections
|
205
|
+
- Remote server access via Plex.tv
|
206
|
+
- Direct IP connections
|
207
|
+
- VPN/tunnel scenarios
|
208
|
+
- [ ] **Performance testing** - Large library scenarios
|
209
|
+
- Libraries with 10k+ items
|
210
|
+
- Multiple concurrent operations
|
211
|
+
- Memory and CPU usage monitoring
|
212
|
+
|
213
|
+
## Code Quality & Architecture
|
214
|
+
|
215
|
+
### Error Handling Improvements
|
216
|
+
- [ ] **Granular error types** - Specific error classes for different failure modes
|
217
|
+
- [ ] **Retry mechanisms** - Automatic retry for transient failures
|
218
|
+
- [ ] **Circuit breaker pattern** - Fail-fast for consistently failing operations
|
219
|
+
- [ ] **Rate limiting** - Respect Plex server rate limits
|
220
|
+
|
221
|
+
### Performance Optimizations
|
222
|
+
- [ ] **Response caching** - Cache frequently accessed data
|
223
|
+
- [ ] **Batch operations** - Combine multiple API calls when possible
|
224
|
+
- [ ] **Streaming responses** - Handle large datasets efficiently
|
225
|
+
- [ ] **Connection pooling** - Reuse HTTP connections
|
226
|
+
|
227
|
+
### Documentation
|
228
|
+
- [ ] **API reference docs** - Complete documentation for all tools
|
229
|
+
- [ ] **Usage examples** - Real-world scenarios and code examples
|
230
|
+
- [ ] **Troubleshooting guide** - Common issues and solutions
|
231
|
+
- [ ] **Performance tuning** - Optimization recommendations
|
232
|
+
|
233
|
+
### Security Enhancements
|
234
|
+
- [ ] **Token validation** - Verify Plex token format and permissions
|
235
|
+
- [ ] **SSL certificate validation** - Proper certificate handling
|
236
|
+
- [ ] **Input sanitization** - Validate all user inputs
|
237
|
+
- [ ] **Audit logging** - Log all administrative operations
|
238
|
+
|
239
|
+
## Development Infrastructure
|
240
|
+
|
241
|
+
### CI/CD Improvements
|
242
|
+
- [ ] **Automated testing** - Run full test suite on every commit
|
243
|
+
- [ ] **Code coverage tracking** - Monitor and improve test coverage
|
244
|
+
- [ ] **Performance benchmarks** - Track performance regressions
|
245
|
+
- [ ] **Security scanning** - Automated vulnerability detection
|
246
|
+
|
247
|
+
### Development Tools
|
248
|
+
- [ ] **Mock Plex server** - Local development server for testing
|
249
|
+
- [ ] **Test data generator** - Generate realistic test datasets
|
250
|
+
- [ ] **Performance profiler** - Identify bottlenecks and optimize
|
251
|
+
- [ ] **Documentation generator** - Auto-generate API docs from code
|
252
|
+
|
package/index.js
CHANGED
@@ -25,6 +25,14 @@ class PlexMCPServer {
|
|
25
25
|
this.setupToolHandlers();
|
26
26
|
}
|
27
27
|
|
28
|
+
getHttpsAgent() {
|
29
|
+
const verifySSL = process.env.PLEX_VERIFY_SSL !== 'false';
|
30
|
+
return new (require('https').Agent)({
|
31
|
+
rejectUnauthorized: verifySSL,
|
32
|
+
minVersion: 'TLSv1.2'
|
33
|
+
});
|
34
|
+
}
|
35
|
+
|
28
36
|
setupToolHandlers() {
|
29
37
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
30
38
|
return {
|
@@ -380,6 +388,25 @@ class PlexMCPServer {
|
|
380
388
|
required: [],
|
381
389
|
},
|
382
390
|
},
|
391
|
+
{
|
392
|
+
name: "browse_playlist",
|
393
|
+
description: "Browse and view the contents of a specific playlist with full track metadata",
|
394
|
+
inputSchema: {
|
395
|
+
type: "object",
|
396
|
+
properties: {
|
397
|
+
playlist_id: {
|
398
|
+
type: "string",
|
399
|
+
description: "The ID of the playlist to browse",
|
400
|
+
},
|
401
|
+
limit: {
|
402
|
+
type: "number",
|
403
|
+
description: "Maximum number of items to return (default: 50)",
|
404
|
+
default: 50,
|
405
|
+
},
|
406
|
+
},
|
407
|
+
required: ["playlist_id"],
|
408
|
+
},
|
409
|
+
},
|
383
410
|
{
|
384
411
|
name: "create_playlist",
|
385
412
|
description: "Create a new playlist on the Plex server. Note: Non-smart playlists require an initial item (item_key parameter) to be created successfully.",
|
@@ -611,6 +638,8 @@ class PlexMCPServer {
|
|
611
638
|
return await this.handleOnDeck(request.params.arguments);
|
612
639
|
case "list_playlists":
|
613
640
|
return await this.handleListPlaylists(request.params.arguments);
|
641
|
+
case "browse_playlist":
|
642
|
+
return await this.handleBrowsePlaylist(request.params.arguments);
|
614
643
|
case "create_playlist":
|
615
644
|
return await this.handleCreatePlaylist(request.params.arguments);
|
616
645
|
case "add_to_playlist":
|
@@ -690,10 +719,7 @@ class PlexMCPServer {
|
|
690
719
|
|
691
720
|
const response = await axios.get(searchUrl, {
|
692
721
|
params,
|
693
|
-
httpsAgent:
|
694
|
-
rejectUnauthorized: false,
|
695
|
-
minVersion: 'TLSv1.2'
|
696
|
-
})
|
722
|
+
httpsAgent: this.getHttpsAgent()
|
697
723
|
});
|
698
724
|
|
699
725
|
let results = this.parseSearchResults(response.data);
|
@@ -786,6 +812,12 @@ class PlexMCPServer {
|
|
786
812
|
contentRating: item.contentRating,
|
787
813
|
Media: item.Media,
|
788
814
|
key: item.key,
|
815
|
+
ratingKey: item.ratingKey, // Critical: the unique identifier for playlist operations
|
816
|
+
// Additional hierarchical info for music tracks
|
817
|
+
parentTitle: item.parentTitle, // Album name
|
818
|
+
grandparentTitle: item.grandparentTitle, // Artist name
|
819
|
+
parentRatingKey: item.parentRatingKey, // Album ID
|
820
|
+
grandparentRatingKey: item.grandparentRatingKey, // Artist ID
|
789
821
|
// Additional metadata for basic filters
|
790
822
|
studio: item.studio,
|
791
823
|
genres: item.Genre ? item.Genre.map(g => g.tag) : [],
|
@@ -807,8 +839,24 @@ class PlexMCPServer {
|
|
807
839
|
formatted += ` - ${item.type}`;
|
808
840
|
}
|
809
841
|
|
842
|
+
// Add artist/album info for music tracks
|
843
|
+
if (item.grandparentTitle && item.parentTitle) {
|
844
|
+
formatted += `\n Artist: ${item.grandparentTitle} | Album: ${item.parentTitle}`;
|
845
|
+
} else if (item.parentTitle) {
|
846
|
+
formatted += `\n Album/Show: ${item.parentTitle}`;
|
847
|
+
}
|
848
|
+
|
810
849
|
if (item.rating) {
|
811
|
-
formatted +=
|
850
|
+
formatted += `\n Rating: ${item.rating}`;
|
851
|
+
}
|
852
|
+
|
853
|
+
if (item.duration) {
|
854
|
+
formatted += `\n Duration: ${this.formatDuration(item.duration)}`;
|
855
|
+
}
|
856
|
+
|
857
|
+
// CRITICAL: Show the ratingKey for playlist operations
|
858
|
+
if (item.ratingKey) {
|
859
|
+
formatted += `\n **ID: ${item.ratingKey}** (use this for playlists)`;
|
812
860
|
}
|
813
861
|
|
814
862
|
if (item.summary) {
|
@@ -819,6 +867,21 @@ class PlexMCPServer {
|
|
819
867
|
}).join('\n\n');
|
820
868
|
}
|
821
869
|
|
870
|
+
formatDuration(milliseconds) {
|
871
|
+
if (!milliseconds || milliseconds === 0) return 'Unknown';
|
872
|
+
|
873
|
+
const seconds = Math.floor(milliseconds / 1000);
|
874
|
+
const minutes = Math.floor(seconds / 60);
|
875
|
+
const hours = Math.floor(minutes / 60);
|
876
|
+
|
877
|
+
if (hours > 0) {
|
878
|
+
const remainingMinutes = minutes % 60;
|
879
|
+
return `${hours}h ${remainingMinutes}m`;
|
880
|
+
} else {
|
881
|
+
return `${minutes}m`;
|
882
|
+
}
|
883
|
+
}
|
884
|
+
|
822
885
|
async handleBrowseLibraries(args) {
|
823
886
|
try {
|
824
887
|
const plexUrl = process.env.PLEX_URL || 'http://localhost:32400';
|
@@ -835,10 +898,7 @@ class PlexMCPServer {
|
|
835
898
|
|
836
899
|
const response = await axios.get(librariesUrl, {
|
837
900
|
params,
|
838
|
-
httpsAgent:
|
839
|
-
rejectUnauthorized: false,
|
840
|
-
minVersion: 'TLSv1.2'
|
841
|
-
})
|
901
|
+
httpsAgent: this.getHttpsAgent()
|
842
902
|
});
|
843
903
|
|
844
904
|
const libraries = this.parseLibraries(response.data);
|
@@ -965,10 +1025,7 @@ class PlexMCPServer {
|
|
965
1025
|
|
966
1026
|
const response = await axios.get(libraryUrl, {
|
967
1027
|
params,
|
968
|
-
httpsAgent:
|
969
|
-
rejectUnauthorized: false,
|
970
|
-
minVersion: 'TLSv1.2'
|
971
|
-
})
|
1028
|
+
httpsAgent: this.getHttpsAgent()
|
972
1029
|
});
|
973
1030
|
|
974
1031
|
let results = this.parseLibraryContent(response.data);
|
@@ -1090,10 +1147,7 @@ class PlexMCPServer {
|
|
1090
1147
|
|
1091
1148
|
const response = await axios.get(recentUrl, {
|
1092
1149
|
params,
|
1093
|
-
httpsAgent:
|
1094
|
-
rejectUnauthorized: false,
|
1095
|
-
minVersion: 'TLSv1.2'
|
1096
|
-
})
|
1150
|
+
httpsAgent: this.getHttpsAgent()
|
1097
1151
|
});
|
1098
1152
|
|
1099
1153
|
const results = this.parseLibraryContent(response.data);
|
@@ -1191,10 +1245,7 @@ class PlexMCPServer {
|
|
1191
1245
|
|
1192
1246
|
const response = await axios.get(historyUrl, {
|
1193
1247
|
params,
|
1194
|
-
httpsAgent:
|
1195
|
-
rejectUnauthorized: false,
|
1196
|
-
minVersion: 'TLSv1.2'
|
1197
|
-
})
|
1248
|
+
httpsAgent: this.getHttpsAgent()
|
1198
1249
|
});
|
1199
1250
|
|
1200
1251
|
const results = this.parseWatchHistory(response.data);
|
@@ -1314,10 +1365,7 @@ class PlexMCPServer {
|
|
1314
1365
|
|
1315
1366
|
const response = await axios.get(onDeckUrl, {
|
1316
1367
|
params,
|
1317
|
-
httpsAgent:
|
1318
|
-
rejectUnauthorized: false,
|
1319
|
-
minVersion: 'TLSv1.2'
|
1320
|
-
})
|
1368
|
+
httpsAgent: this.getHttpsAgent()
|
1321
1369
|
});
|
1322
1370
|
|
1323
1371
|
const results = this.parseOnDeck(response.data);
|
@@ -1423,10 +1471,7 @@ class PlexMCPServer {
|
|
1423
1471
|
|
1424
1472
|
const response = await axios.get(playlistsUrl, {
|
1425
1473
|
params,
|
1426
|
-
httpsAgent:
|
1427
|
-
rejectUnauthorized: false,
|
1428
|
-
minVersion: 'TLSv1.2'
|
1429
|
-
})
|
1474
|
+
httpsAgent: this.getHttpsAgent()
|
1430
1475
|
});
|
1431
1476
|
|
1432
1477
|
const playlists = this.parsePlaylists(response.data);
|
@@ -1456,6 +1501,120 @@ class PlexMCPServer {
|
|
1456
1501
|
}
|
1457
1502
|
}
|
1458
1503
|
|
1504
|
+
async handleBrowsePlaylist(args) {
|
1505
|
+
const { playlist_id, limit = 50 } = args;
|
1506
|
+
|
1507
|
+
try {
|
1508
|
+
const plexUrl = process.env.PLEX_URL || 'http://localhost:32400';
|
1509
|
+
const plexToken = process.env.PLEX_TOKEN;
|
1510
|
+
|
1511
|
+
if (!plexToken) {
|
1512
|
+
throw new Error('PLEX_TOKEN environment variable is required');
|
1513
|
+
}
|
1514
|
+
|
1515
|
+
// First get playlist info
|
1516
|
+
const playlistUrl = `${plexUrl}/playlists/${playlist_id}`;
|
1517
|
+
const response = await axios.get(playlistUrl, {
|
1518
|
+
params: {
|
1519
|
+
'X-Plex-Token': plexToken
|
1520
|
+
},
|
1521
|
+
httpsAgent: this.getHttpsAgent()
|
1522
|
+
});
|
1523
|
+
|
1524
|
+
const playlistData = response.data.MediaContainer;
|
1525
|
+
if (!playlistData || !playlistData.Metadata || playlistData.Metadata.length === 0) {
|
1526
|
+
return {
|
1527
|
+
content: [
|
1528
|
+
{
|
1529
|
+
type: "text",
|
1530
|
+
text: `Playlist with ID ${playlist_id} not found or is empty`,
|
1531
|
+
},
|
1532
|
+
],
|
1533
|
+
};
|
1534
|
+
}
|
1535
|
+
|
1536
|
+
const playlist = playlistData.Metadata[0];
|
1537
|
+
const items = playlistData.Metadata[0].Metadata || [];
|
1538
|
+
|
1539
|
+
// Limit results if specified
|
1540
|
+
const limitedItems = limit ? items.slice(0, limit) : items;
|
1541
|
+
|
1542
|
+
let resultText = `**${playlist.title}**`;
|
1543
|
+
if (playlist.smart) {
|
1544
|
+
resultText += ` (Smart Playlist)`;
|
1545
|
+
}
|
1546
|
+
resultText += `\n`;
|
1547
|
+
if (playlist.summary) {
|
1548
|
+
resultText += `${playlist.summary}\n`;
|
1549
|
+
}
|
1550
|
+
resultText += `Duration: ${this.formatDuration(playlist.duration || 0)}\n`;
|
1551
|
+
resultText += `Items: ${items.length}`;
|
1552
|
+
if (limit && items.length > limit) {
|
1553
|
+
resultText += ` (showing first ${limit})`;
|
1554
|
+
}
|
1555
|
+
resultText += `\n\n`;
|
1556
|
+
|
1557
|
+
if (limitedItems.length === 0) {
|
1558
|
+
resultText += `This playlist is empty.`;
|
1559
|
+
} else {
|
1560
|
+
resultText += limitedItems.map((item, index) => {
|
1561
|
+
let itemText = `${index + 1}. **${item.title}**`;
|
1562
|
+
|
1563
|
+
// Add artist/album info for music
|
1564
|
+
if (item.grandparentTitle && item.parentTitle) {
|
1565
|
+
itemText += `\n Artist: ${item.grandparentTitle}\n Album: ${item.parentTitle}`;
|
1566
|
+
} else if (item.parentTitle) {
|
1567
|
+
itemText += `\n Album/Show: ${item.parentTitle}`;
|
1568
|
+
}
|
1569
|
+
|
1570
|
+
// Add duration
|
1571
|
+
if (item.duration) {
|
1572
|
+
itemText += `\n Duration: ${this.formatDuration(item.duration)}`;
|
1573
|
+
}
|
1574
|
+
|
1575
|
+
// Add rating key for identification
|
1576
|
+
itemText += `\n ID: ${item.ratingKey}`;
|
1577
|
+
|
1578
|
+
// Add media type
|
1579
|
+
const mediaType = this.getMediaTypeFromItem(item);
|
1580
|
+
if (mediaType) {
|
1581
|
+
itemText += `\n Type: ${mediaType}`;
|
1582
|
+
}
|
1583
|
+
|
1584
|
+
return itemText;
|
1585
|
+
}).join('\n\n');
|
1586
|
+
}
|
1587
|
+
|
1588
|
+
return {
|
1589
|
+
content: [
|
1590
|
+
{
|
1591
|
+
type: "text",
|
1592
|
+
text: resultText,
|
1593
|
+
},
|
1594
|
+
],
|
1595
|
+
};
|
1596
|
+
} catch (error) {
|
1597
|
+
return {
|
1598
|
+
content: [
|
1599
|
+
{
|
1600
|
+
type: "text",
|
1601
|
+
text: `Error browsing playlist: ${error.message}`,
|
1602
|
+
},
|
1603
|
+
],
|
1604
|
+
isError: true,
|
1605
|
+
};
|
1606
|
+
}
|
1607
|
+
}
|
1608
|
+
|
1609
|
+
getMediaTypeFromItem(item) {
|
1610
|
+
if (item.type === 'track') return 'Music Track';
|
1611
|
+
if (item.type === 'episode') return 'TV Episode';
|
1612
|
+
if (item.type === 'movie') return 'Movie';
|
1613
|
+
if (item.type === 'artist') return 'Artist';
|
1614
|
+
if (item.type === 'album') return 'Album';
|
1615
|
+
return item.type || 'Unknown';
|
1616
|
+
}
|
1617
|
+
|
1459
1618
|
parsePlaylists(data) {
|
1460
1619
|
if (!data.MediaContainer || !data.MediaContainer.Metadata) {
|
1461
1620
|
return [];
|
@@ -1540,10 +1699,7 @@ class PlexMCPServer {
|
|
1540
1699
|
// First get server info to get machine identifier
|
1541
1700
|
const serverResponse = await axios.get(`${plexUrl}/`, {
|
1542
1701
|
headers: { 'X-Plex-Token': plexToken },
|
1543
|
-
httpsAgent:
|
1544
|
-
rejectUnauthorized: false,
|
1545
|
-
minVersion: 'TLSv1.2'
|
1546
|
-
})
|
1702
|
+
httpsAgent: this.getHttpsAgent()
|
1547
1703
|
});
|
1548
1704
|
|
1549
1705
|
const machineIdentifier = serverResponse.data?.MediaContainer?.machineIdentifier;
|
@@ -1576,20 +1732,22 @@ class PlexMCPServer {
|
|
1576
1732
|
headers: {
|
1577
1733
|
'Content-Length': '0'
|
1578
1734
|
},
|
1579
|
-
httpsAgent:
|
1580
|
-
rejectUnauthorized: false,
|
1581
|
-
minVersion: 'TLSv1.2'
|
1582
|
-
})
|
1735
|
+
httpsAgent: this.getHttpsAgent()
|
1583
1736
|
});
|
1584
1737
|
|
1585
1738
|
// Get the created playlist info from the response
|
1586
1739
|
const playlistData = response.data?.MediaContainer?.Metadata?.[0];
|
1587
1740
|
|
1588
|
-
let resultText =
|
1741
|
+
let resultText = `✅ Successfully created ${smart ? 'smart ' : ''}playlist: **${title}**`;
|
1589
1742
|
if (playlistData) {
|
1590
|
-
resultText += `\n Playlist ID: ${playlistData.ratingKey}`;
|
1743
|
+
resultText += `\n **Playlist ID: ${playlistData.ratingKey}** (use this ID for future operations)`;
|
1591
1744
|
resultText += `\n Type: ${type}`;
|
1592
1745
|
if (smart) resultText += `\n Smart Playlist: Yes`;
|
1746
|
+
if (item_key && !smart) {
|
1747
|
+
resultText += `\n Initial item added: ${item_key}`;
|
1748
|
+
}
|
1749
|
+
} else {
|
1750
|
+
resultText += `\n ⚠️ Playlist created but details not available - check your playlists`;
|
1593
1751
|
}
|
1594
1752
|
|
1595
1753
|
return {
|
@@ -1636,6 +1794,21 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
1636
1794
|
throw new Error('PLEX_TOKEN environment variable is required');
|
1637
1795
|
}
|
1638
1796
|
|
1797
|
+
// Get playlist info before adding items
|
1798
|
+
const playlistUrl = `${plexUrl}/playlists/${playlist_id}`;
|
1799
|
+
const beforeResponse = await axios.get(playlistUrl, {
|
1800
|
+
params: {
|
1801
|
+
'X-Plex-Token': plexToken
|
1802
|
+
},
|
1803
|
+
httpsAgent: this.getHttpsAgent()
|
1804
|
+
});
|
1805
|
+
|
1806
|
+
const beforeData = beforeResponse.data.MediaContainer;
|
1807
|
+
const beforeCount = (beforeData.Metadata && beforeData.Metadata[0] && beforeData.Metadata[0].Metadata)
|
1808
|
+
? beforeData.Metadata[0].Metadata.length : 0;
|
1809
|
+
const playlistTitle = beforeData.Metadata && beforeData.Metadata[0]
|
1810
|
+
? beforeData.Metadata[0].title : `Playlist ${playlist_id}`;
|
1811
|
+
|
1639
1812
|
const addUrl = `${plexUrl}/playlists/${playlist_id}/items`;
|
1640
1813
|
const params = {
|
1641
1814
|
'X-Plex-Token': plexToken,
|
@@ -1644,13 +1817,39 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
1644
1817
|
|
1645
1818
|
const response = await axios.put(addUrl, null, {
|
1646
1819
|
params,
|
1647
|
-
httpsAgent:
|
1648
|
-
|
1649
|
-
|
1650
|
-
|
1820
|
+
httpsAgent: this.getHttpsAgent()
|
1821
|
+
});
|
1822
|
+
|
1823
|
+
// Verify the addition by checking the playlist again
|
1824
|
+
const afterResponse = await axios.get(playlistUrl, {
|
1825
|
+
params: {
|
1826
|
+
'X-Plex-Token': plexToken
|
1827
|
+
},
|
1828
|
+
httpsAgent: this.getHttpsAgent()
|
1651
1829
|
});
|
1652
1830
|
|
1653
|
-
const
|
1831
|
+
const afterData = afterResponse.data.MediaContainer;
|
1832
|
+
const afterCount = (afterData.Metadata && afterData.Metadata[0] && afterData.Metadata[0].Metadata)
|
1833
|
+
? afterData.Metadata[0].Metadata.length : 0;
|
1834
|
+
|
1835
|
+
const actualAdded = afterCount - beforeCount;
|
1836
|
+
const attempted = item_keys.length;
|
1837
|
+
|
1838
|
+
let resultText = `Playlist "${playlistTitle}" update:\n`;
|
1839
|
+
resultText += `• Attempted to add: ${attempted} item(s)\n`;
|
1840
|
+
resultText += `• Actually added: ${actualAdded} item(s)\n`;
|
1841
|
+
resultText += `• Playlist size: ${beforeCount} → ${afterCount} items\n`;
|
1842
|
+
|
1843
|
+
if (actualAdded === attempted) {
|
1844
|
+
resultText += `✅ All items added successfully!`;
|
1845
|
+
} else if (actualAdded > 0) {
|
1846
|
+
resultText += `⚠️ Partial success: ${attempted - actualAdded} item(s) may have been duplicates or invalid`;
|
1847
|
+
} else {
|
1848
|
+
resultText += `❌ No items were added. This may indicate:\n`;
|
1849
|
+
resultText += ` - Invalid item IDs (use ratingKey from search results)\n`;
|
1850
|
+
resultText += ` - Items already exist in playlist\n`;
|
1851
|
+
resultText += ` - Permission issues`;
|
1852
|
+
}
|
1654
1853
|
|
1655
1854
|
return {
|
1656
1855
|
content: [
|
@@ -1684,6 +1883,21 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
1684
1883
|
throw new Error('PLEX_TOKEN environment variable is required');
|
1685
1884
|
}
|
1686
1885
|
|
1886
|
+
// Get playlist info before removing items
|
1887
|
+
const playlistUrl = `${plexUrl}/playlists/${playlist_id}`;
|
1888
|
+
const beforeResponse = await axios.get(playlistUrl, {
|
1889
|
+
params: {
|
1890
|
+
'X-Plex-Token': plexToken
|
1891
|
+
},
|
1892
|
+
httpsAgent: this.getHttpsAgent()
|
1893
|
+
});
|
1894
|
+
|
1895
|
+
const beforeData = beforeResponse.data.MediaContainer;
|
1896
|
+
const beforeCount = (beforeData.Metadata && beforeData.Metadata[0] && beforeData.Metadata[0].Metadata)
|
1897
|
+
? beforeData.Metadata[0].Metadata.length : 0;
|
1898
|
+
const playlistTitle = beforeData.Metadata && beforeData.Metadata[0]
|
1899
|
+
? beforeData.Metadata[0].title : `Playlist ${playlist_id}`;
|
1900
|
+
|
1687
1901
|
const removeUrl = `${plexUrl}/playlists/${playlist_id}/items`;
|
1688
1902
|
const params = {
|
1689
1903
|
'X-Plex-Token': plexToken,
|
@@ -1692,13 +1906,39 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
1692
1906
|
|
1693
1907
|
const response = await axios.delete(removeUrl, {
|
1694
1908
|
params,
|
1695
|
-
httpsAgent:
|
1696
|
-
|
1697
|
-
|
1698
|
-
|
1909
|
+
httpsAgent: this.getHttpsAgent()
|
1910
|
+
});
|
1911
|
+
|
1912
|
+
// Verify the removal by checking the playlist again
|
1913
|
+
const afterResponse = await axios.get(playlistUrl, {
|
1914
|
+
params: {
|
1915
|
+
'X-Plex-Token': plexToken
|
1916
|
+
},
|
1917
|
+
httpsAgent: this.getHttpsAgent()
|
1699
1918
|
});
|
1700
1919
|
|
1701
|
-
const
|
1920
|
+
const afterData = afterResponse.data.MediaContainer;
|
1921
|
+
const afterCount = (afterData.Metadata && afterData.Metadata[0] && afterData.Metadata[0].Metadata)
|
1922
|
+
? afterData.Metadata[0].Metadata.length : 0;
|
1923
|
+
|
1924
|
+
const actualRemoved = beforeCount - afterCount;
|
1925
|
+
const attempted = item_keys.length;
|
1926
|
+
|
1927
|
+
let resultText = `Playlist "${playlistTitle}" update:\n`;
|
1928
|
+
resultText += `• Attempted to remove: ${attempted} item(s)\n`;
|
1929
|
+
resultText += `• Actually removed: ${actualRemoved} item(s)\n`;
|
1930
|
+
resultText += `• Playlist size: ${beforeCount} → ${afterCount} items\n`;
|
1931
|
+
|
1932
|
+
if (actualRemoved === attempted) {
|
1933
|
+
resultText += `✅ All items removed successfully!`;
|
1934
|
+
} else if (actualRemoved > 0) {
|
1935
|
+
resultText += `⚠️ Partial success: ${attempted - actualRemoved} item(s) were not found in the playlist`;
|
1936
|
+
} else {
|
1937
|
+
resultText += `❌ No items were removed. This may indicate:\n`;
|
1938
|
+
resultText += ` - Invalid item IDs\n`;
|
1939
|
+
resultText += ` - Items not present in playlist\n`;
|
1940
|
+
resultText += ` - Permission issues`;
|
1941
|
+
}
|
1702
1942
|
|
1703
1943
|
return {
|
1704
1944
|
content: [
|
@@ -1739,10 +1979,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
1739
1979
|
|
1740
1980
|
const response = await axios.delete(deleteUrl, {
|
1741
1981
|
params,
|
1742
|
-
httpsAgent:
|
1743
|
-
rejectUnauthorized: false,
|
1744
|
-
minVersion: 'TLSv1.2'
|
1745
|
-
})
|
1982
|
+
httpsAgent: this.getHttpsAgent()
|
1746
1983
|
});
|
1747
1984
|
|
1748
1985
|
const resultText = `Successfully deleted playlist ${playlist_id}`;
|
@@ -1795,10 +2032,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
1795
2032
|
|
1796
2033
|
const response = await axios.get(itemUrl, {
|
1797
2034
|
params,
|
1798
|
-
httpsAgent:
|
1799
|
-
rejectUnauthorized: false,
|
1800
|
-
minVersion: 'TLSv1.2'
|
1801
|
-
})
|
2035
|
+
httpsAgent: this.getHttpsAgent()
|
1802
2036
|
});
|
1803
2037
|
|
1804
2038
|
const item = response.data?.MediaContainer?.Metadata?.[0];
|
@@ -2242,10 +2476,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
2242
2476
|
|
2243
2477
|
const response = await axios.get(collectionsUrl, {
|
2244
2478
|
params,
|
2245
|
-
httpsAgent:
|
2246
|
-
rejectUnauthorized: false,
|
2247
|
-
minVersion: 'TLSv1.2'
|
2248
|
-
})
|
2479
|
+
httpsAgent: this.getHttpsAgent()
|
2249
2480
|
});
|
2250
2481
|
|
2251
2482
|
const collections = this.parseCollections(response.data);
|
@@ -2296,10 +2527,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
2296
2527
|
|
2297
2528
|
const response = await axios.get(collectionUrl, {
|
2298
2529
|
params,
|
2299
|
-
httpsAgent:
|
2300
|
-
rejectUnauthorized: false,
|
2301
|
-
minVersion: 'TLSv1.2'
|
2302
|
-
})
|
2530
|
+
httpsAgent: this.getHttpsAgent()
|
2303
2531
|
});
|
2304
2532
|
|
2305
2533
|
const results = this.parseLibraryContent(response.data);
|
@@ -2399,10 +2627,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
2399
2627
|
|
2400
2628
|
const response = await axios.get(mediaUrl, {
|
2401
2629
|
params,
|
2402
|
-
httpsAgent:
|
2403
|
-
rejectUnauthorized: false,
|
2404
|
-
minVersion: 'TLSv1.2'
|
2405
|
-
})
|
2630
|
+
httpsAgent: this.getHttpsAgent()
|
2406
2631
|
});
|
2407
2632
|
|
2408
2633
|
const item = response.data?.MediaContainer?.Metadata?.[0];
|
@@ -2691,10 +2916,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
2691
2916
|
// Get library information first
|
2692
2917
|
const librariesResponse = await axios.get(`${plexUrl}/library/sections`, {
|
2693
2918
|
params: { 'X-Plex-Token': plexToken },
|
2694
|
-
httpsAgent:
|
2695
|
-
rejectUnauthorized: false,
|
2696
|
-
minVersion: 'TLSv1.2'
|
2697
|
-
})
|
2919
|
+
httpsAgent: this.getHttpsAgent()
|
2698
2920
|
});
|
2699
2921
|
|
2700
2922
|
const libraries = this.parseLibraries(librariesResponse.data);
|
@@ -2783,10 +3005,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
2783
3005
|
'X-Plex-Container-Start': offset,
|
2784
3006
|
'X-Plex-Container-Size': batchSize
|
2785
3007
|
},
|
2786
|
-
httpsAgent:
|
2787
|
-
rejectUnauthorized: false,
|
2788
|
-
minVersion: 'TLSv1.2'
|
2789
|
-
})
|
3008
|
+
httpsAgent: this.getHttpsAgent()
|
2790
3009
|
});
|
2791
3010
|
|
2792
3011
|
const content = this.parseLibraryContent(contentResponse.data);
|
@@ -2827,10 +3046,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
2827
3046
|
try {
|
2828
3047
|
const mediaResponse = await axios.get(`${plexUrl}${item.key}`, {
|
2829
3048
|
params: { 'X-Plex-Token': plexToken },
|
2830
|
-
httpsAgent:
|
2831
|
-
rejectUnauthorized: false,
|
2832
|
-
minVersion: 'TLSv1.2'
|
2833
|
-
})
|
3049
|
+
httpsAgent: this.getHttpsAgent()
|
2834
3050
|
});
|
2835
3051
|
|
2836
3052
|
const detailedItem = mediaResponse.data?.MediaContainer?.Metadata?.[0];
|
@@ -3059,10 +3275,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3059
3275
|
} else {
|
3060
3276
|
const librariesResponse = await axios.get(`${plexUrl}/library/sections`, {
|
3061
3277
|
params: { 'X-Plex-Token': plexToken },
|
3062
|
-
httpsAgent:
|
3063
|
-
rejectUnauthorized: false,
|
3064
|
-
minVersion: 'TLSv1.2'
|
3065
|
-
})
|
3278
|
+
httpsAgent: this.getHttpsAgent()
|
3066
3279
|
});
|
3067
3280
|
|
3068
3281
|
const allLibraries = this.parseLibraries(librariesResponse.data);
|
@@ -3165,10 +3378,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3165
3378
|
try {
|
3166
3379
|
const historyResponse = await axios.get(`${plexUrl}/status/sessions/history/all`, {
|
3167
3380
|
params: historyParams,
|
3168
|
-
httpsAgent:
|
3169
|
-
rejectUnauthorized: false,
|
3170
|
-
minVersion: 'TLSv1.2'
|
3171
|
-
})
|
3381
|
+
httpsAgent: this.getHttpsAgent()
|
3172
3382
|
});
|
3173
3383
|
|
3174
3384
|
const history = this.parseWatchHistory(historyResponse.data);
|
@@ -3249,10 +3459,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3249
3459
|
query: trackName,
|
3250
3460
|
type: 10 // Track type
|
3251
3461
|
},
|
3252
|
-
httpsAgent:
|
3253
|
-
rejectUnauthorized: false,
|
3254
|
-
minVersion: 'TLSv1.2'
|
3255
|
-
})
|
3462
|
+
httpsAgent: this.getHttpsAgent()
|
3256
3463
|
});
|
3257
3464
|
|
3258
3465
|
const tracks = this.parseSearchResults(searchResponse.data);
|
@@ -3260,10 +3467,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3260
3467
|
if (track.key) {
|
3261
3468
|
const trackDetailResponse = await axios.get(`${plexUrl}${track.key}`, {
|
3262
3469
|
params: { 'X-Plex-Token': plexToken },
|
3263
|
-
httpsAgent:
|
3264
|
-
rejectUnauthorized: false,
|
3265
|
-
minVersion: 'TLSv1.2'
|
3266
|
-
})
|
3470
|
+
httpsAgent: this.getHttpsAgent()
|
3267
3471
|
});
|
3268
3472
|
|
3269
3473
|
const trackDetail = trackDetailResponse.data?.MediaContainer?.Metadata?.[0];
|
@@ -3308,10 +3512,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3308
3512
|
'X-Plex-Container-Size': 10,
|
3309
3513
|
sort: 'addedAt:desc' // Recently added first
|
3310
3514
|
},
|
3311
|
-
httpsAgent:
|
3312
|
-
rejectUnauthorized: false,
|
3313
|
-
minVersion: 'TLSv1.2'
|
3314
|
-
})
|
3515
|
+
httpsAgent: this.getHttpsAgent()
|
3315
3516
|
});
|
3316
3517
|
|
3317
3518
|
const tracks = this.parseLibraryContent(genreSearchResponse.data);
|
@@ -3342,10 +3543,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3342
3543
|
query: artist,
|
3343
3544
|
type: 8 // Artist type
|
3344
3545
|
},
|
3345
|
-
httpsAgent:
|
3346
|
-
rejectUnauthorized: false,
|
3347
|
-
minVersion: 'TLSv1.2'
|
3348
|
-
})
|
3546
|
+
httpsAgent: this.getHttpsAgent()
|
3349
3547
|
});
|
3350
3548
|
|
3351
3549
|
const artists = this.parseSearchResults(artistSearchResponse.data);
|
@@ -3353,10 +3551,7 @@ You can try creating the playlist manually in Plex and then use other MCP tools
|
|
3353
3551
|
if (foundArtist.key) {
|
3354
3552
|
const artistDetailResponse = await axios.get(`${plexUrl}${foundArtist.key}`, {
|
3355
3553
|
params: { 'X-Plex-Token': plexToken },
|
3356
|
-
httpsAgent:
|
3357
|
-
rejectUnauthorized: false,
|
3358
|
-
minVersion: 'TLSv1.2'
|
3359
|
-
})
|
3554
|
+
httpsAgent: this.getHttpsAgent()
|
3360
3555
|
});
|
3361
3556
|
|
3362
3557
|
const artistTracks = this.parseLibraryContent(artistDetailResponse.data);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "plex-mcp",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.4",
|
4
4
|
"description": "A Model Context Protocol (MCP) server that enables Claude to query and manage Plex media libraries.",
|
5
5
|
"main": "index.js",
|
6
6
|
"bin": {
|
@@ -8,9 +8,11 @@
|
|
8
8
|
},
|
9
9
|
"scripts": {
|
10
10
|
"start": "node index.js",
|
11
|
-
"test": "jest",
|
12
|
-
"test:
|
13
|
-
"test:
|
11
|
+
"test": "jest --testPathIgnorePatterns=/tests/e2e/",
|
12
|
+
"test:e2e": "jest --testPathPattern=e2e",
|
13
|
+
"test:all": "jest",
|
14
|
+
"test:watch": "jest --watch --testPathIgnorePatterns=/tests/e2e/",
|
15
|
+
"test:coverage": "jest --coverage --testPathIgnorePatterns=/tests/e2e/",
|
14
16
|
"prepublishOnly": "npm test",
|
15
17
|
"version:patch": "npm version patch",
|
16
18
|
"version:minor": "npm version minor",
|