audiobookshelf-mcp 0.1.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/LICENSE +21 -0
- package/README.md +255 -0
- package/dist/api.d.ts +38 -0
- package/dist/api.js +122 -0
- package/dist/api.js.map +1 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/confirm.d.ts +36 -0
- package/dist/confirm.js +72 -0
- package/dist/confirm.js.map +1 -0
- package/dist/filters.d.ts +32 -0
- package/dist/filters.js +81 -0
- package/dist/filters.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/result.d.ts +18 -0
- package/dist/result.js +71 -0
- package/dist/result.js.map +1 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.js +36 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +43 -0
- package/dist/server.js.map +1 -0
- package/dist/shape.d.ts +72 -0
- package/dist/shape.js +397 -0
- package/dist/shape.js.map +1 -0
- package/dist/tools/collections.d.ts +5 -0
- package/dist/tools/collections.js +183 -0
- package/dist/tools/collections.js.map +1 -0
- package/dist/tools/items.d.ts +3 -0
- package/dist/tools/items.js +117 -0
- package/dist/tools/items.js.map +1 -0
- package/dist/tools/libraries.d.ts +3 -0
- package/dist/tools/libraries.js +337 -0
- package/dist/tools/libraries.js.map +1 -0
- package/dist/tools/me.d.ts +3 -0
- package/dist/tools/me.js +141 -0
- package/dist/tools/me.js.map +1 -0
- package/dist/tools/playlists.d.ts +5 -0
- package/dist/tools/playlists.js +200 -0
- package/dist/tools/playlists.js.map +1 -0
- package/dist/tools/progress.d.ts +5 -0
- package/dist/tools/progress.js +161 -0
- package/dist/tools/progress.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type AudiobookshelfApi } from '../api.js';
|
|
3
|
+
import { type ConfirmationStore } from '../confirm.js';
|
|
4
|
+
export declare function registerPlaylistReadTools(server: McpServer, api: AudiobookshelfApi): void;
|
|
5
|
+
export declare function registerPlaylistWriteTools(server: McpServer, api: AudiobookshelfApi, confirmations: ConfirmationStore): void;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { assertPathSegment } from '../api.js';
|
|
3
|
+
import { confirmationPrompt, setResourceKey, } from '../confirm.js';
|
|
4
|
+
import { errorResult, run, textResult, untrustedJsonResult, } from '../result.js';
|
|
5
|
+
import { confirmTokenParam, detailParam } from '../schema.js';
|
|
6
|
+
import { compactPlaylist, listFrom } from '../shape.js';
|
|
7
|
+
const playlistIdParam = z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1)
|
|
10
|
+
.describe('Playlist id, as returned by list_playlists');
|
|
11
|
+
/**
|
|
12
|
+
* Audiobookshelf keeps playlists homogeneous: either every entry carries an
|
|
13
|
+
* episode_id (podcast playlist) or none does (book playlist). A mixed list is
|
|
14
|
+
* rejected with HTTP 400.
|
|
15
|
+
*/
|
|
16
|
+
const playlistItemsParam = z
|
|
17
|
+
.array(z.object({
|
|
18
|
+
library_item_id: z.string().min(1).describe('Library item id'),
|
|
19
|
+
episode_id: z
|
|
20
|
+
.string()
|
|
21
|
+
.min(1)
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('Podcast episode id — required for podcast playlists, must be omitted ' +
|
|
24
|
+
'for book playlists'),
|
|
25
|
+
}))
|
|
26
|
+
.max(500);
|
|
27
|
+
function toApiItems(items) {
|
|
28
|
+
return items.map((item) => ({
|
|
29
|
+
libraryItemId: assertPathSegment(item.library_item_id, 'library_item_id'),
|
|
30
|
+
...(item.episode_id !== undefined
|
|
31
|
+
? { episodeId: assertPathSegment(item.episode_id, 'episode_id') }
|
|
32
|
+
: {}),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
export function registerPlaylistReadTools(server, api) {
|
|
36
|
+
server.registerTool('list_playlists', {
|
|
37
|
+
title: 'List playlists',
|
|
38
|
+
description: 'Lists the playlists of the API key’s user. Without library_id it returns ' +
|
|
39
|
+
'the playlists of every accessible library. Playlists are private per ' +
|
|
40
|
+
'user and can hold books or podcast episodes; collections are shared ' +
|
|
41
|
+
'server-wide and hold books only.',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
library_id: z
|
|
44
|
+
.string()
|
|
45
|
+
.min(1)
|
|
46
|
+
.optional()
|
|
47
|
+
.describe('Restrict the result to this library'),
|
|
48
|
+
detail: detailParam,
|
|
49
|
+
},
|
|
50
|
+
annotations: { readOnlyHint: true },
|
|
51
|
+
}, async ({ library_id, detail }) => run(async () => {
|
|
52
|
+
const path = library_id === undefined
|
|
53
|
+
? '/api/playlists'
|
|
54
|
+
: `/api/libraries/${assertPathSegment(library_id, 'library_id')}/playlists`;
|
|
55
|
+
const playlists = listFrom(await api.get(path), 'playlists');
|
|
56
|
+
return untrustedJsonResult({
|
|
57
|
+
numPlaylists: playlists.length,
|
|
58
|
+
playlists: detail === 'full' ? playlists : playlists.map(compactPlaylist),
|
|
59
|
+
});
|
|
60
|
+
}));
|
|
61
|
+
server.registerTool('get_playlist', {
|
|
62
|
+
title: 'Get playlist',
|
|
63
|
+
description: 'Fetches one playlist with its entries, in order.',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
playlist_id: playlistIdParam,
|
|
66
|
+
detail: detailParam,
|
|
67
|
+
},
|
|
68
|
+
annotations: { readOnlyHint: true },
|
|
69
|
+
}, async ({ playlist_id, detail }) => run(async () => {
|
|
70
|
+
const data = await api.get(`/api/playlists/${assertPathSegment(playlist_id, 'playlist_id')}`);
|
|
71
|
+
return untrustedJsonResult(detail === 'full' ? data : compactPlaylist(data));
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
export function registerPlaylistWriteTools(server, api, confirmations) {
|
|
75
|
+
server.registerTool('create_playlist', {
|
|
76
|
+
title: 'Create playlist',
|
|
77
|
+
description: 'Creates a playlist for the API key’s user. Unlike a collection it may ' +
|
|
78
|
+
'start out empty and it may hold podcast episodes.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
library_id: z
|
|
81
|
+
.string()
|
|
82
|
+
.min(1)
|
|
83
|
+
.describe('Library the playlist belongs to'),
|
|
84
|
+
name: z.string().min(1).max(255).describe('Playlist name'),
|
|
85
|
+
description: z
|
|
86
|
+
.string()
|
|
87
|
+
.max(4000)
|
|
88
|
+
.optional()
|
|
89
|
+
.describe('Optional description'),
|
|
90
|
+
items: playlistItemsParam
|
|
91
|
+
.optional()
|
|
92
|
+
.describe('Initial entries, optional'),
|
|
93
|
+
},
|
|
94
|
+
}, async ({ library_id, name, description, items }) => run(async () => {
|
|
95
|
+
const created = await api.post('/api/playlists', {
|
|
96
|
+
libraryId: assertPathSegment(library_id, 'library_id'),
|
|
97
|
+
name,
|
|
98
|
+
...(description !== undefined ? { description } : {}),
|
|
99
|
+
items: items === undefined ? [] : toApiItems(items),
|
|
100
|
+
});
|
|
101
|
+
return untrustedJsonResult(compactPlaylist(created));
|
|
102
|
+
}));
|
|
103
|
+
server.registerTool('update_playlist', {
|
|
104
|
+
title: 'Update playlist',
|
|
105
|
+
description: 'Renames a playlist, changes its description or reorders its entries. ' +
|
|
106
|
+
'items replaces the order completely, so it has to contain every entry ' +
|
|
107
|
+
'that should stay — use add_items_to_playlist and ' +
|
|
108
|
+
'remove_items_from_playlist to change membership. The library of a ' +
|
|
109
|
+
'playlist cannot be changed.',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
playlist_id: playlistIdParam,
|
|
112
|
+
name: z.string().min(1).max(255).optional().describe('New name'),
|
|
113
|
+
description: z
|
|
114
|
+
.string()
|
|
115
|
+
.max(4000)
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('New description'),
|
|
118
|
+
items: playlistItemsParam
|
|
119
|
+
.optional()
|
|
120
|
+
.describe('Complete, newly ordered list of entries'),
|
|
121
|
+
},
|
|
122
|
+
}, async ({ playlist_id, name, description, items }) => run(async () => {
|
|
123
|
+
if (name === undefined &&
|
|
124
|
+
description === undefined &&
|
|
125
|
+
items === undefined) {
|
|
126
|
+
return errorResult('Nothing to update: pass name, description or items.');
|
|
127
|
+
}
|
|
128
|
+
const updated = await api.patch(`/api/playlists/${assertPathSegment(playlist_id, 'playlist_id')}`, {
|
|
129
|
+
...(name !== undefined ? { name } : {}),
|
|
130
|
+
...(description !== undefined ? { description } : {}),
|
|
131
|
+
...(items !== undefined ? { items: toApiItems(items) } : {}),
|
|
132
|
+
});
|
|
133
|
+
return untrustedJsonResult(compactPlaylist(updated));
|
|
134
|
+
}));
|
|
135
|
+
server.registerTool('add_items_to_playlist', {
|
|
136
|
+
title: 'Add items to playlist',
|
|
137
|
+
description: 'Appends books or podcast episodes to a playlist. All entries must come ' +
|
|
138
|
+
'from the playlist’s library and match its kind — a podcast playlist ' +
|
|
139
|
+
'needs an episode_id on every entry, a book playlist on none.',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
playlist_id: playlistIdParam,
|
|
142
|
+
items: playlistItemsParam.min(1),
|
|
143
|
+
},
|
|
144
|
+
}, async ({ playlist_id, items }) => run(async () => {
|
|
145
|
+
const updated = await api.post(`/api/playlists/${assertPathSegment(playlist_id, 'playlist_id')}/batch/add`, { items: toApiItems(items) });
|
|
146
|
+
return untrustedJsonResult(compactPlaylist(updated));
|
|
147
|
+
}));
|
|
148
|
+
server.registerTool('remove_items_from_playlist', {
|
|
149
|
+
title: 'Remove items from playlist',
|
|
150
|
+
description: 'Removes entries from a playlist. The media itself is untouched and the ' +
|
|
151
|
+
'entries can be added back with add_items_to_playlist. Note that ' +
|
|
152
|
+
'Audiobookshelf deletes a playlist automatically once its last entry is ' +
|
|
153
|
+
'removed.',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
playlist_id: playlistIdParam,
|
|
156
|
+
items: playlistItemsParam.min(1),
|
|
157
|
+
},
|
|
158
|
+
annotations: { destructiveHint: true },
|
|
159
|
+
}, async ({ playlist_id, items }) => run(async () => {
|
|
160
|
+
const updated = await api.post(`/api/playlists/${assertPathSegment(playlist_id, 'playlist_id')}/batch/remove`, { items: toApiItems(items) });
|
|
161
|
+
const shaped = compactPlaylist(updated);
|
|
162
|
+
// The API answers with the playlist as it was, so an emptied playlist is
|
|
163
|
+
// only recognizable by its item count — say so instead of returning
|
|
164
|
+
// something that looks like an ordinary update.
|
|
165
|
+
return untrustedJsonResult(shaped.numItems === 0
|
|
166
|
+
? {
|
|
167
|
+
...shaped,
|
|
168
|
+
note: 'The playlist has no entries left and was therefore deleted by ' +
|
|
169
|
+
'Audiobookshelf. It cannot be restored — recreate it with ' +
|
|
170
|
+
'create_playlist.',
|
|
171
|
+
}
|
|
172
|
+
: shaped);
|
|
173
|
+
}));
|
|
174
|
+
server.registerTool('delete_playlist', {
|
|
175
|
+
title: 'Delete playlist',
|
|
176
|
+
description: 'Deletes a playlist. The media stays in the library. Two-step: the first ' +
|
|
177
|
+
'call returns a confirmation token, the second call with that token ' +
|
|
178
|
+
'performs the deletion.',
|
|
179
|
+
inputSchema: {
|
|
180
|
+
playlist_id: playlistIdParam,
|
|
181
|
+
confirm_token: confirmTokenParam,
|
|
182
|
+
},
|
|
183
|
+
annotations: { destructiveHint: true },
|
|
184
|
+
}, async ({ playlist_id, confirm_token }) => run(async () => {
|
|
185
|
+
const safeId = assertPathSegment(playlist_id, 'playlist_id');
|
|
186
|
+
const resource = setResourceKey('delete_playlist', [safeId]);
|
|
187
|
+
if (!confirmations.consume(resource, confirm_token)) {
|
|
188
|
+
if (confirm_token !== undefined) {
|
|
189
|
+
return errorResult('The confirmation token is invalid, expired or was issued for a ' +
|
|
190
|
+
'different playlist. Call delete_playlist without a token to get ' +
|
|
191
|
+
'a new one.');
|
|
192
|
+
}
|
|
193
|
+
const token = confirmations.issue(resource);
|
|
194
|
+
return textResult(confirmationPrompt(`delete playlist ${safeId}`, token, confirmations.ttlMinutes));
|
|
195
|
+
}
|
|
196
|
+
await api.delete(`/api/playlists/${safeId}`);
|
|
197
|
+
return textResult(`Playlist ${safeId} deleted.`);
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=playlists.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playlists.js","sourceRoot":"","sources":["../../src/tools/playlists.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { type AudiobookshelfApi } from '../api.js';
|
|
3
|
+
import { type ConfirmationStore } from '../confirm.js';
|
|
4
|
+
export declare function registerProgressWriteTools(server: McpServer, api: AudiobookshelfApi, confirmations: ConfirmationStore): void;
|
|
5
|
+
export declare function registerBookmarkWriteTools(server: McpServer, api: AudiobookshelfApi): void;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { assertPathSegment } from '../api.js';
|
|
3
|
+
import { confirmationPrompt, setResourceKey, } from '../confirm.js';
|
|
4
|
+
import { errorResult, run, textResult } from '../result.js';
|
|
5
|
+
import { confirmTokenParam, libraryItemIdParam } from '../schema.js';
|
|
6
|
+
const episodeIdParam = z
|
|
7
|
+
.string()
|
|
8
|
+
.min(1)
|
|
9
|
+
.optional()
|
|
10
|
+
.describe('Podcast episode id — required to address a podcast episode, omitted for books');
|
|
11
|
+
export function registerProgressWriteTools(server, api, confirmations) {
|
|
12
|
+
server.registerTool('set_media_progress', {
|
|
13
|
+
title: 'Set media progress',
|
|
14
|
+
description: 'Creates or updates the listening progress of the API key’s user for one ' +
|
|
15
|
+
'book or podcast episode. Set is_finished=true to mark it as finished, ' +
|
|
16
|
+
'is_finished=false to reopen it (which resets the position to 0), or ' +
|
|
17
|
+
'current_time to jump to a position in seconds. Audiobookshelf also marks ' +
|
|
18
|
+
'an item finished on its own once less than ten seconds remain.',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
library_item_id: libraryItemIdParam,
|
|
21
|
+
episode_id: episodeIdParam,
|
|
22
|
+
current_time: z
|
|
23
|
+
.number()
|
|
24
|
+
.min(0)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe('New playback position in seconds'),
|
|
27
|
+
progress: z
|
|
28
|
+
.number()
|
|
29
|
+
.min(0)
|
|
30
|
+
.max(1)
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Progress as a fraction between 0 and 1. Only used when is_finished ' +
|
|
33
|
+
'is not given.'),
|
|
34
|
+
is_finished: z
|
|
35
|
+
.boolean()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('Mark the item as finished (true) or unfinished (false)'),
|
|
38
|
+
hide_from_continue_listening: z
|
|
39
|
+
.boolean()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Hide the item from the "Continue Listening" shelf without changing ' +
|
|
42
|
+
'its position'),
|
|
43
|
+
},
|
|
44
|
+
}, async ({ library_item_id, episode_id, current_time, progress, is_finished, hide_from_continue_listening, }) => run(async () => {
|
|
45
|
+
if (current_time === undefined &&
|
|
46
|
+
progress === undefined &&
|
|
47
|
+
is_finished === undefined &&
|
|
48
|
+
hide_from_continue_listening === undefined) {
|
|
49
|
+
return errorResult('Nothing to update: pass current_time, progress, is_finished or ' +
|
|
50
|
+
'hide_from_continue_listening.');
|
|
51
|
+
}
|
|
52
|
+
const safeItemId = assertPathSegment(library_item_id, 'library_item_id');
|
|
53
|
+
const path = `/api/me/progress/${safeItemId}` +
|
|
54
|
+
(episode_id === undefined
|
|
55
|
+
? ''
|
|
56
|
+
: `/${assertPathSegment(episode_id, 'episode_id')}`);
|
|
57
|
+
// Only the fields below are forwarded. The endpoint applies its payload
|
|
58
|
+
// to the progress record wholesale, so passing arbitrary keys through
|
|
59
|
+
// would let a caller write columns this tool never intended to touch.
|
|
60
|
+
await api.patch(path, {
|
|
61
|
+
...(current_time !== undefined ? { currentTime: current_time } : {}),
|
|
62
|
+
...(progress !== undefined ? { progress } : {}),
|
|
63
|
+
...(is_finished !== undefined ? { isFinished: is_finished } : {}),
|
|
64
|
+
...(hide_from_continue_listening !== undefined
|
|
65
|
+
? { hideFromContinueListening: hide_from_continue_listening }
|
|
66
|
+
: {}),
|
|
67
|
+
});
|
|
68
|
+
// The endpoint answers with 200 and no body; read the result back so the
|
|
69
|
+
// model sees the state that actually got stored.
|
|
70
|
+
const updated = await api.get(path);
|
|
71
|
+
return textResult(`Progress updated.\n${JSON.stringify(updated, null, 2)}`);
|
|
72
|
+
}));
|
|
73
|
+
server.registerTool('delete_media_progress', {
|
|
74
|
+
title: 'Delete media progress',
|
|
75
|
+
description: 'Deletes a progress record of the API key’s user, which removes the ' +
|
|
76
|
+
'listening history for that item — position, finished state and dates. ' +
|
|
77
|
+
'Takes the media progress id (field "id" of get_media_progress), not the ' +
|
|
78
|
+
'library item id. Two-step: the first call returns a confirmation token, ' +
|
|
79
|
+
'the second call with that token performs the deletion.',
|
|
80
|
+
inputSchema: {
|
|
81
|
+
media_progress_id: z
|
|
82
|
+
.string()
|
|
83
|
+
.min(1)
|
|
84
|
+
.describe('Media progress id, from the "id" field of get_media_progress'),
|
|
85
|
+
confirm_token: confirmTokenParam,
|
|
86
|
+
},
|
|
87
|
+
annotations: { destructiveHint: true },
|
|
88
|
+
}, async ({ media_progress_id, confirm_token }) => run(async () => {
|
|
89
|
+
const safeId = assertPathSegment(media_progress_id, 'media_progress_id');
|
|
90
|
+
const resource = setResourceKey('delete_media_progress', [safeId]);
|
|
91
|
+
if (!confirmations.consume(resource, confirm_token)) {
|
|
92
|
+
if (confirm_token !== undefined) {
|
|
93
|
+
return errorResult('The confirmation token is invalid, expired or was issued for a ' +
|
|
94
|
+
'different progress record. Call delete_media_progress without a ' +
|
|
95
|
+
'token to get a new one.');
|
|
96
|
+
}
|
|
97
|
+
const token = confirmations.issue(resource);
|
|
98
|
+
return textResult(confirmationPrompt(`delete the listening history of media progress ${safeId}`, token, confirmations.ttlMinutes));
|
|
99
|
+
}
|
|
100
|
+
await api.delete(`/api/me/progress/${safeId}`);
|
|
101
|
+
return textResult(`Media progress ${safeId} deleted.`);
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
export function registerBookmarkWriteTools(server, api) {
|
|
105
|
+
server.registerTool('create_bookmark', {
|
|
106
|
+
title: 'Create bookmark',
|
|
107
|
+
description: 'Creates a bookmark at a position of a book for the API key’s user. The ' +
|
|
108
|
+
'position in seconds is the bookmark’s identity — a second bookmark at ' +
|
|
109
|
+
'the same second is rejected.',
|
|
110
|
+
inputSchema: {
|
|
111
|
+
library_item_id: libraryItemIdParam,
|
|
112
|
+
time: z
|
|
113
|
+
.number()
|
|
114
|
+
.min(0)
|
|
115
|
+
.describe('Position in seconds where the bookmark is placed'),
|
|
116
|
+
title: z.string().min(1).max(255).describe('Bookmark title'),
|
|
117
|
+
},
|
|
118
|
+
}, async ({ library_item_id, time, title }) => run(async () => {
|
|
119
|
+
const created = await api.post(`/api/me/item/${assertPathSegment(library_item_id, 'library_item_id')}/bookmark`, { time, title });
|
|
120
|
+
return textResult(`Bookmark created.\n${JSON.stringify(created, null, 2)}`);
|
|
121
|
+
}));
|
|
122
|
+
server.registerTool('update_bookmark', {
|
|
123
|
+
title: 'Update bookmark',
|
|
124
|
+
description: 'Renames the bookmark at a given position. The position itself cannot be ' +
|
|
125
|
+
'changed — delete the bookmark and create a new one for that.',
|
|
126
|
+
inputSchema: {
|
|
127
|
+
library_item_id: libraryItemIdParam,
|
|
128
|
+
time: z
|
|
129
|
+
.number()
|
|
130
|
+
.min(0)
|
|
131
|
+
.describe('Position in seconds identifying the bookmark'),
|
|
132
|
+
title: z.string().min(1).max(255).describe('New bookmark title'),
|
|
133
|
+
},
|
|
134
|
+
}, async ({ library_item_id, time, title }) => run(async () => {
|
|
135
|
+
const updated = await api.patch(`/api/me/item/${assertPathSegment(library_item_id, 'library_item_id')}/bookmark`, { time, title });
|
|
136
|
+
return textResult(`Bookmark updated.\n${JSON.stringify(updated, null, 2)}`);
|
|
137
|
+
}));
|
|
138
|
+
server.registerTool('delete_bookmark', {
|
|
139
|
+
title: 'Delete bookmark',
|
|
140
|
+
description: 'Deletes the bookmark at a given position. No confirmation token: a ' +
|
|
141
|
+
'bookmark is a position and a title, and create_bookmark restores it.',
|
|
142
|
+
inputSchema: {
|
|
143
|
+
library_item_id: libraryItemIdParam,
|
|
144
|
+
time: z
|
|
145
|
+
.number()
|
|
146
|
+
.min(0)
|
|
147
|
+
.describe('Position in seconds identifying the bookmark'),
|
|
148
|
+
},
|
|
149
|
+
annotations: { destructiveHint: true },
|
|
150
|
+
}, async ({ library_item_id, time }) => run(async () => {
|
|
151
|
+
const safeId = assertPathSegment(library_item_id, 'library_item_id');
|
|
152
|
+
// The position goes into the path, so it must be a finite number and
|
|
153
|
+
// never a string that could carry path segments.
|
|
154
|
+
if (!Number.isFinite(time)) {
|
|
155
|
+
throw new Error('invalid time: must be a finite number of seconds');
|
|
156
|
+
}
|
|
157
|
+
await api.delete(`/api/me/item/${safeId}/bookmark/${time}`);
|
|
158
|
+
return textResult(`Bookmark at ${time} seconds of item ${safeId} deleted.`);
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../../src/tools/progress.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "audiobookshelf-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"mcpName": "io.github.ni-c/audiobookshelf-mcp",
|
|
5
|
+
"description": "MCP server for Audiobookshelf, the self-hosted audiobook and podcast server",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"audiobookshelf",
|
|
10
|
+
"audiobook",
|
|
11
|
+
"podcast",
|
|
12
|
+
"selfhosted"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Willi Thiel",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/ni-c/audiobookshelf-mcp.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/ni-c/audiobookshelf-mcp/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/ni-c/audiobookshelf-mcp#readme",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"bin": {
|
|
26
|
+
"audiobookshelf-mcp": "dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "dist/index.js",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=22"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc",
|
|
37
|
+
"watch": "tsc --watch",
|
|
38
|
+
"lint": "eslint . && prettier --check .",
|
|
39
|
+
"format": "prettier --write .",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
42
|
+
"prepublishOnly": "npm run lint && npm test && npm run build",
|
|
43
|
+
"docs:tools:check": "node scripts/gen-tools-doc.mjs --check",
|
|
44
|
+
"docs:tools": "node scripts/gen-tools-doc.mjs"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
48
|
+
"undici": "^8.10.0",
|
|
49
|
+
"zod": "^4.4.3"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
53
|
+
"@types/node": "^26.2.0",
|
|
54
|
+
"@vitest/coverage-v8": "4.1.10",
|
|
55
|
+
"eslint": "^10.8.1",
|
|
56
|
+
"prettier": "^3.9.6",
|
|
57
|
+
"typescript": "^6.0.3",
|
|
58
|
+
"typescript-eslint": "^8.67.0",
|
|
59
|
+
"vitest": "^4.1.10"
|
|
60
|
+
}
|
|
61
|
+
}
|