@clawbow/synology-mcp-audiostation 0.1.1

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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,375 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import axios from 'axios';
6
+ // Configuration
7
+ const SYNOLOGY_URL = process.env.SYNOLOGY_URL || '';
8
+ const SYNOLOGY_USERNAME = process.env.SYNOLOGY_USERNAME || '';
9
+ const SYNOLOGY_PASSWORD = process.env.SYNOLOGY_PASSWORD || '';
10
+ if (!SYNOLOGY_URL || !SYNOLOGY_USERNAME || !SYNOLOGY_PASSWORD) {
11
+ console.error('Erreur: Variables d\'environnement requises:\n' +
12
+ ' SYNOLOGY_URL (ex: https://nas.example.com:5001)\n' +
13
+ ' SYNOLOGY_USERNAME\n' +
14
+ ' SYNOLOGY_PASSWORD');
15
+ process.exit(1);
16
+ }
17
+ // Client Synology Audio Station
18
+ class AudioStationClient {
19
+ baseUrl;
20
+ username;
21
+ password;
22
+ sid = null;
23
+ constructor(config) {
24
+ this.baseUrl = config.url.replace(/\/$/, '');
25
+ this.username = config.username;
26
+ this.password = config.password;
27
+ }
28
+ async login() {
29
+ const url = `${this.baseUrl}/webapi/auth.cgi`;
30
+ const response = await axios.get(url, {
31
+ params: {
32
+ api: 'SYNO.API.Auth',
33
+ version: 3,
34
+ method: 'login',
35
+ account: this.username,
36
+ passwd: this.password,
37
+ session: 'audiostation',
38
+ format: 'cookie',
39
+ },
40
+ });
41
+ if (response.data.success) {
42
+ this.sid = response.data.data.sid;
43
+ }
44
+ else {
45
+ throw new Error(`Login failed: ${response.data.error?.code || 'Unknown'}`);
46
+ }
47
+ }
48
+ async ensureAuth() {
49
+ if (!this.sid) {
50
+ await this.login();
51
+ }
52
+ }
53
+ async apiCall(api, version, method, params = {}) {
54
+ await this.ensureAuth();
55
+ const url = `${this.baseUrl}/webapi/entry.cgi`;
56
+ const response = await axios.get(url, {
57
+ params: {
58
+ api,
59
+ version,
60
+ method,
61
+ _sid: this.sid,
62
+ ...params,
63
+ },
64
+ });
65
+ return response.data;
66
+ }
67
+ async listartists(args) {
68
+ const params = {};
69
+ if (args.limit)
70
+ params.limit = args.limit;
71
+ return await this.apiCall('SYNO.AudioStation.Artist', 1, 'list', params);
72
+ }
73
+ async listalbums(args) {
74
+ const params = {};
75
+ if (args.artist)
76
+ params.artist = args.artist;
77
+ if (args.limit)
78
+ params.limit = args.limit;
79
+ return await this.apiCall('SYNO.AudioStation.Album', 1, 'list', params);
80
+ }
81
+ async listsongs(args) {
82
+ const params = {};
83
+ if (args.album)
84
+ params.album = args.album;
85
+ if (args.limit)
86
+ params.limit = args.limit;
87
+ return await this.apiCall('SYNO.AudioStation.Song', 1, 'list', params);
88
+ }
89
+ async listplaylists(args = {}) {
90
+ return await this.apiCall('SYNO.AudioStation.Playlist', 1, 'list');
91
+ }
92
+ async createplaylist(args) {
93
+ const params = { name: args.name };
94
+ if (args.songs)
95
+ params.songs = args.songs.join(',');
96
+ return await this.apiCall('SYNO.AudioStation.Playlist', 1, 'create', params);
97
+ }
98
+ async updateplaylist(args) {
99
+ const params = { id: args.id };
100
+ if (args.name)
101
+ params.name = args.name;
102
+ if (args.songs)
103
+ params.songs = args.songs.join(',');
104
+ return await this.apiCall('SYNO.AudioStation.Playlist', 1, 'update', params);
105
+ }
106
+ async deleteplaylist(args) {
107
+ return await this.apiCall('SYNO.AudioStation.Playlist', 1, 'delete', { id: args.id });
108
+ }
109
+ async addtoplaylist(args) {
110
+ return await this.apiCall('SYNO.AudioStation.Playlist', 1, 'add_song', { playlist_id: args.playlist_id, songs: args.songs.join(',') });
111
+ }
112
+ async removefromplaylist(args) {
113
+ return await this.apiCall('SYNO.AudioStation.Playlist', 1, 'delete_song', { playlist_id: args.playlist_id, songs: args.songs.join(',') });
114
+ }
115
+ async searchmusic(args) {
116
+ const params = { keyword: args.query };
117
+ if (args.type)
118
+ params.type = args.type;
119
+ return await this.apiCall('SYNO.AudioStation.Search', 1, 'list', params);
120
+ }
121
+ async customaudiocall(args) {
122
+ return await this.apiCall(args.endpoint, 1, args.method.toLowerCase(), args.body || {});
123
+ }
124
+ }
125
+ // Définition des outils MCP
126
+ const TOOLS = [
127
+ {
128
+ name: 'list_artists',
129
+ description: 'List artists',
130
+ inputSchema: {
131
+ "type": "object",
132
+ "properties": {
133
+ "limit": {
134
+ "type": "number",
135
+ "description": "Max results"
136
+ }
137
+ }
138
+ },
139
+ },
140
+ {
141
+ name: 'list_albums',
142
+ description: 'List albums',
143
+ inputSchema: {
144
+ "type": "object",
145
+ "properties": {
146
+ "artist": {
147
+ "type": "string",
148
+ "description": "Filter by artist"
149
+ },
150
+ "limit": {
151
+ "type": "number",
152
+ "description": "Max results"
153
+ }
154
+ }
155
+ },
156
+ },
157
+ {
158
+ name: 'list_songs',
159
+ description: 'List songs',
160
+ inputSchema: {
161
+ "type": "object",
162
+ "properties": {
163
+ "album": {
164
+ "type": "string",
165
+ "description": "Filter by album"
166
+ },
167
+ "limit": {
168
+ "type": "number",
169
+ "description": "Max results"
170
+ }
171
+ }
172
+ },
173
+ },
174
+ {
175
+ name: 'list_playlists',
176
+ description: 'List playlists',
177
+ inputSchema: {
178
+ "type": "object",
179
+ "properties": {}
180
+ },
181
+ },
182
+ {
183
+ name: 'create_playlist',
184
+ description: 'Create playlist',
185
+ inputSchema: {
186
+ "type": "object",
187
+ "properties": {
188
+ "name": {
189
+ "type": "string",
190
+ "description": "Playlist name"
191
+ },
192
+ "songs": {
193
+ "type": "string",
194
+ "description": "Song IDs"
195
+ }
196
+ },
197
+ "required": [
198
+ "name"
199
+ ]
200
+ },
201
+ },
202
+ {
203
+ name: 'update_playlist',
204
+ description: 'Update playlist',
205
+ inputSchema: {
206
+ "type": "object",
207
+ "properties": {
208
+ "id": {
209
+ "type": "string",
210
+ "description": "Playlist ID"
211
+ },
212
+ "name": {
213
+ "type": "string",
214
+ "description": "New name"
215
+ },
216
+ "songs": {
217
+ "type": "string",
218
+ "description": "Song IDs"
219
+ }
220
+ },
221
+ "required": [
222
+ "id"
223
+ ]
224
+ },
225
+ },
226
+ {
227
+ name: 'delete_playlist',
228
+ description: 'Delete playlist',
229
+ inputSchema: {
230
+ "type": "object",
231
+ "properties": {
232
+ "id": {
233
+ "type": "string",
234
+ "description": "Playlist ID"
235
+ }
236
+ },
237
+ "required": [
238
+ "id"
239
+ ]
240
+ },
241
+ },
242
+ {
243
+ name: 'add_to_playlist',
244
+ description: 'Add songs to playlist',
245
+ inputSchema: {
246
+ "type": "object",
247
+ "properties": {
248
+ "playlist_id": {
249
+ "type": "string",
250
+ "description": "Playlist ID"
251
+ },
252
+ "songs": {
253
+ "type": "string",
254
+ "description": "Song IDs to add"
255
+ }
256
+ },
257
+ "required": [
258
+ "playlist_id",
259
+ "songs"
260
+ ]
261
+ },
262
+ },
263
+ {
264
+ name: 'remove_from_playlist',
265
+ description: 'Remove songs from playlist',
266
+ inputSchema: {
267
+ "type": "object",
268
+ "properties": {
269
+ "playlist_id": {
270
+ "type": "string",
271
+ "description": "Playlist ID"
272
+ },
273
+ "songs": {
274
+ "type": "string",
275
+ "description": "Song IDs to remove"
276
+ }
277
+ },
278
+ "required": [
279
+ "playlist_id",
280
+ "songs"
281
+ ]
282
+ },
283
+ },
284
+ {
285
+ name: 'search_music',
286
+ description: 'Search music',
287
+ inputSchema: {
288
+ "type": "object",
289
+ "properties": {
290
+ "query": {
291
+ "type": "string",
292
+ "description": "Search query"
293
+ },
294
+ "type": {
295
+ "type": "string",
296
+ "description": "Search type (artist/album/song)"
297
+ }
298
+ },
299
+ "required": [
300
+ "query"
301
+ ]
302
+ },
303
+ },
304
+ {
305
+ name: 'custom_audio_call',
306
+ description: 'Custom API call',
307
+ inputSchema: {
308
+ "type": "object",
309
+ "properties": {
310
+ "method": {
311
+ "type": "string",
312
+ "description": "HTTP method"
313
+ },
314
+ "endpoint": {
315
+ "type": "string",
316
+ "description": "API endpoint"
317
+ },
318
+ "body": {
319
+ "type": "object",
320
+ "description": "Request body"
321
+ }
322
+ },
323
+ "required": [
324
+ "method",
325
+ "endpoint"
326
+ ]
327
+ },
328
+ },
329
+ ];
330
+ // Initialisation
331
+ const client = new AudioStationClient({
332
+ url: SYNOLOGY_URL,
333
+ username: SYNOLOGY_USERNAME,
334
+ password: SYNOLOGY_PASSWORD,
335
+ });
336
+ const server = new Server({ name: 'synology-mcp-audiostation', version: '0.1.0' }, { capabilities: { tools: {} } });
337
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
338
+ return { tools: TOOLS };
339
+ });
340
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
341
+ const { name, arguments: args } = request.params;
342
+ try {
343
+ let result;
344
+ switch (name) {
345
+ case 'list_artists': return await client.listartists(args);
346
+ case 'list_albums': return await client.listalbums(args);
347
+ case 'list_songs': return await client.listsongs(args);
348
+ case 'list_playlists': return await client.listplaylists(args);
349
+ case 'create_playlist': return await client.createplaylist(args);
350
+ case 'update_playlist': return await client.updateplaylist(args);
351
+ case 'delete_playlist': return await client.deleteplaylist(args);
352
+ case 'add_to_playlist': return await client.addtoplaylist(args);
353
+ case 'remove_from_playlist': return await client.removefromplaylist(args);
354
+ case 'search_music': return await client.searchmusic(args);
355
+ case 'custom_audio_call': return await client.customaudiocall(args);
356
+ default:
357
+ throw new Error(`Unknown tool: ${name}`);
358
+ }
359
+ return {
360
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
361
+ };
362
+ }
363
+ catch (error) {
364
+ return {
365
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
366
+ isError: true,
367
+ };
368
+ }
369
+ });
370
+ async function main() {
371
+ const transport = new StdioServerTransport();
372
+ await server.connect(transport);
373
+ console.error('Synology Audio Station MCP server running on stdio');
374
+ }
375
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@clawbow/synology-mcp-audiostation",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for Synology Audio Station - Audio Station management via Model Context Protocol",
5
+ "type": "module",
6
+ "bin": {
7
+ "synology-mcp-audiostation": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "dev": "tsc --watch",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "synology",
20
+ "audiostation",
21
+ "dsm",
22
+ "model-context-protocol"
23
+ ],
24
+ "author": "Bob (@clawbow)",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.0.0",
28
+ "axios": "^1.6.0"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "typescript": "^5.3.0"
33
+ },
34
+ "engines": {
35
+ "node": ">=18.0.0"
36
+ }
37
+ }