mpv-mcp-server 1.0.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.
Files changed (53) hide show
  1. package/README.md +123 -0
  2. package/dist/config.d.ts +12 -0
  3. package/dist/config.js +30 -0
  4. package/dist/config.js.map +1 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +52 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/services/media-scanner.d.ts +19 -0
  9. package/dist/services/media-scanner.js +61 -0
  10. package/dist/services/media-scanner.js.map +1 -0
  11. package/dist/services/mpv-ipc.d.ts +23 -0
  12. package/dist/services/mpv-ipc.js +112 -0
  13. package/dist/services/mpv-ipc.js.map +1 -0
  14. package/dist/services/mpv-process.d.ts +9 -0
  15. package/dist/services/mpv-process.js +73 -0
  16. package/dist/services/mpv-process.js.map +1 -0
  17. package/dist/tools/browse.d.ts +3 -0
  18. package/dist/tools/browse.js +86 -0
  19. package/dist/tools/browse.js.map +1 -0
  20. package/dist/tools/navigation.d.ts +3 -0
  21. package/dist/tools/navigation.js +62 -0
  22. package/dist/tools/navigation.js.map +1 -0
  23. package/dist/tools/play.d.ts +4 -0
  24. package/dist/tools/play.js +82 -0
  25. package/dist/tools/play.js.map +1 -0
  26. package/dist/tools/playback.d.ts +3 -0
  27. package/dist/tools/playback.js +69 -0
  28. package/dist/tools/playback.js.map +1 -0
  29. package/dist/tools/playlist.d.ts +4 -0
  30. package/dist/tools/playlist.js +174 -0
  31. package/dist/tools/playlist.js.map +1 -0
  32. package/dist/tools/seek.d.ts +3 -0
  33. package/dist/tools/seek.js +57 -0
  34. package/dist/tools/seek.js.map +1 -0
  35. package/dist/tools/status.d.ts +3 -0
  36. package/dist/tools/status.js +58 -0
  37. package/dist/tools/status.js.map +1 -0
  38. package/dist/tools/volume.d.ts +3 -0
  39. package/dist/tools/volume.js +43 -0
  40. package/dist/tools/volume.js.map +1 -0
  41. package/dist/tools/youtube.d.ts +4 -0
  42. package/dist/tools/youtube.js +148 -0
  43. package/dist/tools/youtube.js.map +1 -0
  44. package/dist/utils/format.d.ts +7 -0
  45. package/dist/utils/format.js +34 -0
  46. package/dist/utils/format.js.map +1 -0
  47. package/dist/utils/fuzzy-match.d.ts +16 -0
  48. package/dist/utils/fuzzy-match.js +38 -0
  49. package/dist/utils/fuzzy-match.js.map +1 -0
  50. package/dist/utils/path.d.ts +2 -0
  51. package/dist/utils/path.js +5 -0
  52. package/dist/utils/path.js.map +1 -0
  53. package/package.json +47 -0
@@ -0,0 +1,62 @@
1
+ export function registerNavigationTools(server, ipc) {
2
+ server.registerTool("mpv_next", {
3
+ title: "Next Track",
4
+ description: "Skip to the next track in the playlist.",
5
+ inputSchema: {},
6
+ annotations: {
7
+ readOnlyHint: false,
8
+ destructiveHint: false,
9
+ idempotentHint: false,
10
+ openWorldHint: false,
11
+ },
12
+ }, async () => {
13
+ try {
14
+ await ipc.command(["playlist-next"]);
15
+ await delay(300);
16
+ const title = await ipc.getProperty("media-title").catch(() => "Unknown");
17
+ return {
18
+ content: [{ type: "text", text: `Now playing: ${title}` }],
19
+ };
20
+ }
21
+ catch {
22
+ return {
23
+ content: [
24
+ { type: "text", text: "Already at the end of the playlist." },
25
+ ],
26
+ isError: true,
27
+ };
28
+ }
29
+ });
30
+ server.registerTool("mpv_prev", {
31
+ title: "Previous Track",
32
+ description: "Go back to the previous track in the playlist.",
33
+ inputSchema: {},
34
+ annotations: {
35
+ readOnlyHint: false,
36
+ destructiveHint: false,
37
+ idempotentHint: false,
38
+ openWorldHint: false,
39
+ },
40
+ }, async () => {
41
+ try {
42
+ await ipc.command(["playlist-prev"]);
43
+ await delay(300);
44
+ const title = await ipc.getProperty("media-title").catch(() => "Unknown");
45
+ return {
46
+ content: [{ type: "text", text: `Now playing: ${title}` }],
47
+ };
48
+ }
49
+ catch {
50
+ return {
51
+ content: [
52
+ { type: "text", text: "Already at the beginning of the playlist." },
53
+ ],
54
+ isError: true,
55
+ };
56
+ }
57
+ });
58
+ }
59
+ function delay(ms) {
60
+ return new Promise((r) => setTimeout(r, ms));
61
+ }
62
+ //# sourceMappingURL=navigation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/tools/navigation.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,GAAW;IACpE,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,yCAAyC;QACtD,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;YACrC,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YACjB,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1E,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gBAAgB,KAAK,EAAE,EAAE,CAAC;aACpE,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,qCAAqC,EAAE;iBACvE;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,gDAAgD;QAC7D,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;YACrC,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;YACjB,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,aAAa,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YAC1E,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,gBAAgB,KAAK,EAAE,EAAE,CAAC;aACpE,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,2CAA2C,EAAE;iBAC7E;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ import type { MediaScanner } from "../services/media-scanner.js";
4
+ export declare function registerPlayTool(server: McpServer, ipc: MpvIPC, scanner: MediaScanner): void;
@@ -0,0 +1,82 @@
1
+ import { existsSync } from "node:fs";
2
+ import { isAbsolute } from "node:path";
3
+ import { z } from "zod";
4
+ import { toMpvPath } from "../utils/path.js";
5
+ export function registerPlayTool(server, ipc, scanner) {
6
+ server.registerTool("mpv_play", {
7
+ title: "Play Music/Video",
8
+ description: "Play a file by path or search term. Searches your Music and Videos folders when given a name like \"sultans of swing\".",
9
+ inputSchema: {
10
+ query: z
11
+ .string()
12
+ .min(1)
13
+ .describe("File path or search term to find and play"),
14
+ },
15
+ annotations: {
16
+ readOnlyHint: false,
17
+ destructiveHint: false,
18
+ idempotentHint: false,
19
+ openWorldHint: false,
20
+ },
21
+ }, async ({ query }) => {
22
+ try {
23
+ // Check if it's an absolute path
24
+ if (isAbsolute(query) || /^[A-Za-z]:/.test(query)) {
25
+ if (!existsSync(query)) {
26
+ return {
27
+ content: [{ type: "text", text: `File not found: ${query}` }],
28
+ isError: true,
29
+ };
30
+ }
31
+ await ipc.command(["loadfile", toMpvPath(query), "replace"]);
32
+ await delay(500);
33
+ const title = await ipc.getProperty("media-title").catch(() => query);
34
+ return {
35
+ content: [{ type: "text", text: `Now playing: ${title}` }],
36
+ };
37
+ }
38
+ // Search for the file
39
+ const results = await scanner.search(query, 5);
40
+ if (results.length === 0) {
41
+ return {
42
+ content: [
43
+ {
44
+ type: "text",
45
+ text: `No files matching "${query}" found in your music library.`,
46
+ },
47
+ ],
48
+ isError: true,
49
+ };
50
+ }
51
+ // Play the best match
52
+ const best = results[0];
53
+ await ipc.command(["loadfile", toMpvPath(best.path), "replace"]);
54
+ await delay(500);
55
+ const title = await ipc.getProperty("media-title").catch(() => best.name);
56
+ const lines = [`Now playing: ${title}`];
57
+ if (results.length > 1) {
58
+ lines.push("");
59
+ lines.push("Also found:");
60
+ for (const r of results.slice(1)) {
61
+ lines.push(` - ${r.name}${r.ext}`);
62
+ }
63
+ }
64
+ return { content: [{ type: "text", text: lines.join("\n") }] };
65
+ }
66
+ catch (err) {
67
+ return {
68
+ content: [
69
+ {
70
+ type: "text",
71
+ text: `Error: ${err instanceof Error ? err.message : err}`,
72
+ },
73
+ ],
74
+ isError: true,
75
+ };
76
+ }
77
+ });
78
+ }
79
+ function delay(ms) {
80
+ return new Promise((r) => setTimeout(r, ms));
81
+ }
82
+ //# sourceMappingURL=play.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"play.js","sourceRoot":"","sources":["../../src/tools/play.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ export declare function registerPlaybackTools(server: McpServer, ipc: MpvIPC): void;
@@ -0,0 +1,69 @@
1
+ export function registerPlaybackTools(server, ipc) {
2
+ server.registerTool("mpv_pause", {
3
+ title: "Pause Playback",
4
+ description: "Pause the currently playing track.",
5
+ inputSchema: {},
6
+ annotations: {
7
+ readOnlyHint: false,
8
+ destructiveHint: false,
9
+ idempotentHint: true,
10
+ openWorldHint: false,
11
+ },
12
+ }, async () => {
13
+ try {
14
+ await ipc.setProperty("pause", true);
15
+ return { content: [{ type: "text", text: "Playback paused." }] };
16
+ }
17
+ catch {
18
+ return {
19
+ content: [{ type: "text", text: "Nothing is playing." }],
20
+ isError: true,
21
+ };
22
+ }
23
+ });
24
+ server.registerTool("mpv_resume", {
25
+ title: "Resume Playback",
26
+ description: "Resume playback if paused.",
27
+ inputSchema: {},
28
+ annotations: {
29
+ readOnlyHint: false,
30
+ destructiveHint: false,
31
+ idempotentHint: true,
32
+ openWorldHint: false,
33
+ },
34
+ }, async () => {
35
+ try {
36
+ await ipc.setProperty("pause", false);
37
+ return { content: [{ type: "text", text: "Playback resumed." }] };
38
+ }
39
+ catch {
40
+ return {
41
+ content: [{ type: "text", text: "Nothing is playing." }],
42
+ isError: true,
43
+ };
44
+ }
45
+ });
46
+ server.registerTool("mpv_stop", {
47
+ title: "Stop Playback",
48
+ description: "Stop playback and clear the current file. The playlist is preserved.",
49
+ inputSchema: {},
50
+ annotations: {
51
+ readOnlyHint: false,
52
+ destructiveHint: true,
53
+ idempotentHint: true,
54
+ openWorldHint: false,
55
+ },
56
+ }, async () => {
57
+ try {
58
+ await ipc.command(["stop"]);
59
+ return { content: [{ type: "text", text: "Playback stopped." }] };
60
+ }
61
+ catch {
62
+ return {
63
+ content: [{ type: "text", text: "mpv is not running." }],
64
+ isError: true,
65
+ };
66
+ }
67
+ });
68
+ }
69
+ //# sourceMappingURL=playback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playback.js","sourceRoot":"","sources":["../../src/tools/playback.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,GAAW;IAClE,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE,oCAAoC;QACjD,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACrC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;QAC5E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;gBACjE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,iBAAiB;QACxB,WAAW,EAAE,4BAA4B;QACzC,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,WAAW,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YACtC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,EAAE,CAAC;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;gBACjE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,sEAAsE;QACnF,WAAW,EAAE,EAAE;QACf,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,IAAI;YACrB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,IAAI,EAAE;QACT,IAAI,CAAC;YACH,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC,EAAE,CAAC;QAC7E,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;gBACjE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ import type { MediaScanner } from "../services/media-scanner.js";
4
+ export declare function registerPlaylistTools(server: McpServer, ipc: MpvIPC, scanner: MediaScanner): void;
@@ -0,0 +1,174 @@
1
+ import { existsSync } from "node:fs";
2
+ import { isAbsolute } from "node:path";
3
+ import { z } from "zod";
4
+ import { toMpvPath } from "../utils/path.js";
5
+ export function registerPlaylistTools(server, ipc, scanner) {
6
+ server.registerTool("mpv_playlist", {
7
+ title: "Show Playlist",
8
+ description: "Show the current playlist with track numbers and the active track marked.",
9
+ inputSchema: {},
10
+ annotations: {
11
+ readOnlyHint: true,
12
+ destructiveHint: false,
13
+ idempotentHint: true,
14
+ openWorldHint: false,
15
+ },
16
+ }, async () => {
17
+ try {
18
+ const playlist = (await ipc.getProperty("playlist"));
19
+ const pos = (await ipc.getProperty("playlist-pos").catch(() => -1));
20
+ if (!playlist || playlist.length === 0) {
21
+ return { content: [{ type: "text", text: "Playlist is empty." }] };
22
+ }
23
+ const lines = [`Playlist (${playlist.length} tracks):`];
24
+ for (let i = 0; i < playlist.length; i++) {
25
+ const entry = playlist[i];
26
+ // Extract just the filename from the path
27
+ const name = entry.filename.split(/[/\\]/).pop() ?? entry.filename;
28
+ const marker = i === pos ? ">" : " ";
29
+ lines.push(`${marker} ${i + 1}. ${name}`);
30
+ }
31
+ return { content: [{ type: "text", text: lines.join("\n") }] };
32
+ }
33
+ catch {
34
+ return {
35
+ content: [{ type: "text", text: "mpv is not running." }],
36
+ isError: true,
37
+ };
38
+ }
39
+ });
40
+ server.registerTool("mpv_add", {
41
+ title: "Add to Playlist",
42
+ description: "Add a file to the playlist by path or search term. Use mode \"append-play\" to start playing it immediately.",
43
+ inputSchema: {
44
+ query: z.string().min(1).describe("File path or search term"),
45
+ mode: z
46
+ .enum(["append", "append-play"])
47
+ .default("append")
48
+ .optional()
49
+ .describe('"append" adds to end, "append-play" adds and starts playing it'),
50
+ },
51
+ annotations: {
52
+ readOnlyHint: false,
53
+ destructiveHint: false,
54
+ idempotentHint: false,
55
+ openWorldHint: false,
56
+ },
57
+ }, async ({ query, mode }) => {
58
+ try {
59
+ const appendMode = mode ?? "append";
60
+ let filePath;
61
+ let displayName;
62
+ if (isAbsolute(query) || /^[A-Za-z]:/.test(query)) {
63
+ if (!existsSync(query)) {
64
+ return {
65
+ content: [{ type: "text", text: `File not found: ${query}` }],
66
+ isError: true,
67
+ };
68
+ }
69
+ filePath = query;
70
+ displayName = query.split(/[/\\]/).pop() ?? query;
71
+ }
72
+ else {
73
+ const results = await scanner.search(query, 1);
74
+ if (results.length === 0) {
75
+ return {
76
+ content: [
77
+ { type: "text", text: `No files matching "${query}" found.` },
78
+ ],
79
+ isError: true,
80
+ };
81
+ }
82
+ filePath = results[0].path;
83
+ displayName = results[0].name + results[0].ext;
84
+ }
85
+ await ipc.command(["loadfile", toMpvPath(filePath), appendMode]);
86
+ const verb = appendMode === "append-play" ? "Added and playing" : "Added to playlist";
87
+ return {
88
+ content: [{ type: "text", text: `${verb}: ${displayName}` }],
89
+ };
90
+ }
91
+ catch (err) {
92
+ return {
93
+ content: [
94
+ {
95
+ type: "text",
96
+ text: `Error: ${err instanceof Error ? err.message : err}`,
97
+ },
98
+ ],
99
+ isError: true,
100
+ };
101
+ }
102
+ });
103
+ server.registerTool("mpv_load_playlist", {
104
+ title: "Load Playlist File",
105
+ description: "Load a playlist file (.txt, .m3u). Omit path to list available playlists.",
106
+ inputSchema: {
107
+ path: z
108
+ .string()
109
+ .optional()
110
+ .describe("Path to playlist file. Omit to list available playlists."),
111
+ mode: z
112
+ .enum(["replace", "append"])
113
+ .default("replace")
114
+ .optional()
115
+ .describe('"replace" clears current playlist, "append" adds to it'),
116
+ },
117
+ annotations: {
118
+ readOnlyHint: false,
119
+ destructiveHint: false,
120
+ idempotentHint: false,
121
+ openWorldHint: false,
122
+ },
123
+ }, async ({ path, mode }) => {
124
+ try {
125
+ // List available playlists if no path given
126
+ if (!path) {
127
+ const playlists = await scanner.findPlaylists();
128
+ if (playlists.length === 0) {
129
+ return {
130
+ content: [{ type: "text", text: "No playlist files found." }],
131
+ };
132
+ }
133
+ const lines = ["Available playlists:"];
134
+ for (const pl of playlists) {
135
+ lines.push(` - ${pl.name}${pl.ext} (${pl.path})`);
136
+ }
137
+ return { content: [{ type: "text", text: lines.join("\n") }] };
138
+ }
139
+ if (!existsSync(path)) {
140
+ return {
141
+ content: [{ type: "text", text: `Playlist file not found: ${path}` }],
142
+ isError: true,
143
+ };
144
+ }
145
+ const loadMode = mode ?? "replace";
146
+ await ipc.command(["loadlist", toMpvPath(path), loadMode]);
147
+ await delay(500);
148
+ const count = await ipc.getProperty("playlist-count").catch(() => "?");
149
+ return {
150
+ content: [
151
+ {
152
+ type: "text",
153
+ text: `Loaded playlist: ${count} tracks.`,
154
+ },
155
+ ],
156
+ };
157
+ }
158
+ catch (err) {
159
+ return {
160
+ content: [
161
+ {
162
+ type: "text",
163
+ text: `Error: ${err instanceof Error ? err.message : err}`,
164
+ },
165
+ ],
166
+ isError: true,
167
+ };
168
+ }
169
+ });
170
+ }
171
+ function delay(ms) {
172
+ return new Promise((r) => setTimeout(r, ms));
173
+ }
174
+ //# sourceMappingURL=playlist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"playlist.js","sourceRoot":"","sources":["../../src/tools/playlist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ export declare function registerSeekTool(server: McpServer, ipc: MpvIPC): void;
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ import { formatDuration, parseSeekTarget } from "../utils/format.js";
3
+ export function registerSeekTool(server, ipc) {
4
+ server.registerTool("mpv_seek", {
5
+ title: "Seek",
6
+ description: 'Seek to a position. Accepts seconds ("90"), timestamp ("1:30"), or relative ("+10", "-30").',
7
+ inputSchema: {
8
+ target: z
9
+ .string()
10
+ .min(1)
11
+ .describe('Seek target: seconds ("90"), timestamp ("1:30"), or relative ("+10", "-30")'),
12
+ },
13
+ annotations: {
14
+ readOnlyHint: false,
15
+ destructiveHint: false,
16
+ idempotentHint: false,
17
+ openWorldHint: false,
18
+ },
19
+ }, async ({ target }) => {
20
+ try {
21
+ const { seconds, mode } = parseSeekTarget(target);
22
+ if (isNaN(seconds)) {
23
+ return {
24
+ content: [{ type: "text", text: `Invalid seek target: "${target}"` }],
25
+ isError: true,
26
+ };
27
+ }
28
+ await ipc.command([
29
+ "seek",
30
+ seconds,
31
+ mode === "absolute" ? "absolute" : "relative",
32
+ ]);
33
+ const pos = (await ipc.getProperty("time-pos").catch(() => null));
34
+ const dur = (await ipc.getProperty("duration").catch(() => null));
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: `Seeked to ${formatDuration(pos)} / ${formatDuration(dur)}`,
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ catch (err) {
45
+ return {
46
+ content: [
47
+ {
48
+ type: "text",
49
+ text: `Seek failed: ${err instanceof Error ? err.message : err}`,
50
+ },
51
+ ],
52
+ isError: true,
53
+ };
54
+ }
55
+ });
56
+ }
57
+ //# sourceMappingURL=seek.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seek.js","sourceRoot":"","sources":["../../src/tools/seek.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErE,MAAM,UAAU,gBAAgB,CAAC,MAAiB,EAAE,GAAW;IAC7D,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;QACE,KAAK,EAAE,MAAM;QACb,WAAW,EACT,6FAA6F;QAC/F,WAAW,EAAE;YACX,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,QAAQ,CACP,6EAA6E,CAC9E;SACJ;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,KAAK;YACrB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;QACnB,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;YAClD,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACnB,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,yBAAyB,MAAM,GAAG,EAAE,CAAC;oBAC9E,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;YAED,MAAM,GAAG,CAAC,OAAO,CAAC;gBAChB,MAAM;gBACN,OAAO;gBACP,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU;aAC9C,CAAC,CAAC;YAEH,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAkB,CAAC;YACnF,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAkB,CAAC;YAEnF,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,aAAa,cAAc,CAAC,GAAG,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC,EAAE;qBAClE;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAe;wBACrB,IAAI,EAAE,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,EAAE;qBACjE;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ export declare function registerStatusTool(server: McpServer, ipc: MpvIPC): void;
@@ -0,0 +1,58 @@
1
+ import { formatDuration } from "../utils/format.js";
2
+ export function registerStatusTool(server, ipc) {
3
+ server.registerTool("mpv_status", {
4
+ title: "mpv Status",
5
+ description: "Get current mpv playback status: track title, position, duration, volume, and pause state.",
6
+ inputSchema: {},
7
+ annotations: {
8
+ readOnlyHint: true,
9
+ destructiveHint: false,
10
+ idempotentHint: true,
11
+ openWorldHint: false,
12
+ },
13
+ }, async () => {
14
+ try {
15
+ const idle = await ipc.getProperty("idle-active");
16
+ if (idle) {
17
+ return { content: [{ type: "text", text: "mpv is idle — nothing playing." }] };
18
+ }
19
+ const [title, pos, duration, volume, paused, plPos, plCount, filename] = await Promise.all([
20
+ ipc.getProperty("media-title").catch(() => null),
21
+ ipc.getProperty("time-pos").catch(() => null),
22
+ ipc.getProperty("duration").catch(() => null),
23
+ ipc.getProperty("volume").catch(() => null),
24
+ ipc.getProperty("pause").catch(() => null),
25
+ ipc.getProperty("playlist-pos-1").catch(() => null),
26
+ ipc.getProperty("playlist-count").catch(() => null),
27
+ ipc.getProperty("filename").catch(() => null),
28
+ ]);
29
+ const displayTitle = title ?? filename ?? "Unknown";
30
+ const status = paused ? "Paused" : "Playing";
31
+ const position = `${formatDuration(pos)} / ${formatDuration(duration)}`;
32
+ const vol = volume != null ? `${Math.round(volume)}%` : "N/A";
33
+ const playlist = plPos != null && plCount != null
34
+ ? `Track ${plPos} of ${plCount}`
35
+ : "";
36
+ const lines = [
37
+ `Now Playing: ${displayTitle}`,
38
+ `Status: ${status}`,
39
+ `Position: ${position}`,
40
+ `Volume: ${vol}`,
41
+ ];
42
+ if (playlist)
43
+ lines.push(`Playlist: ${playlist}`);
44
+ return { content: [{ type: "text", text: lines.join("\n") }] };
45
+ }
46
+ catch {
47
+ return {
48
+ content: [
49
+ {
50
+ type: "text",
51
+ text: "mpv is not running. Use mpv_play to start playback.",
52
+ },
53
+ ],
54
+ };
55
+ }
56
+ });
57
+ }
58
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ export declare function registerVolumeTool(server: McpServer, ipc: MpvIPC): void;
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ export function registerVolumeTool(server, ipc) {
3
+ server.registerTool("mpv_volume", {
4
+ title: "Get/Set Volume",
5
+ description: "Get or set the playback volume. Provide a level (0-150) to set, or omit to get current volume.",
6
+ inputSchema: {
7
+ level: z
8
+ .number()
9
+ .min(0)
10
+ .max(150)
11
+ .optional()
12
+ .describe("Volume level (0-150). Omit to get current volume."),
13
+ },
14
+ annotations: {
15
+ readOnlyHint: false,
16
+ destructiveHint: false,
17
+ idempotentHint: true,
18
+ openWorldHint: false,
19
+ },
20
+ }, async ({ level }) => {
21
+ try {
22
+ if (level != null) {
23
+ await ipc.setProperty("volume", level);
24
+ return {
25
+ content: [{ type: "text", text: `Volume set to ${level}%` }],
26
+ };
27
+ }
28
+ const vol = (await ipc.getProperty("volume"));
29
+ return {
30
+ content: [
31
+ { type: "text", text: `Current volume: ${Math.round(vol)}%` },
32
+ ],
33
+ };
34
+ }
35
+ catch {
36
+ return {
37
+ content: [{ type: "text", text: "mpv is not running." }],
38
+ isError: true,
39
+ };
40
+ }
41
+ });
42
+ }
43
+ //# sourceMappingURL=volume.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volume.js","sourceRoot":"","sources":["../../src/tools/volume.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,UAAU,kBAAkB,CAAC,MAAiB,EAAE,GAAW;IAC/D,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,gGAAgG;QAClG,WAAW,EAAE;YACX,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,GAAG,CAAC,CAAC,CAAC;iBACN,GAAG,CAAC,GAAG,CAAC;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,mDAAmD,CAAC;SACjE;QACD,WAAW,EAAE;YACX,YAAY,EAAE,KAAK;YACnB,eAAe,EAAE,KAAK;YACtB,cAAc,EAAE,IAAI;YACpB,aAAa,EAAE,KAAK;SACrB;KACF,EACD,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE;QAClB,IAAI,CAAC;YACH,IAAI,KAAK,IAAI,IAAI,EAAE,CAAC;gBAClB,MAAM,GAAG,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBACvC,OAAO;oBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,iBAAiB,KAAK,GAAG,EAAE,CAAC;iBACtE,CAAC;YACJ,CAAC;YAED,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAW,CAAC;YACxD,OAAO;gBACL,OAAO,EAAE;oBACP,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,mBAAmB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;iBACvE;aACF,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,qBAAqB,EAAE,CAAC;gBACjE,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { MpvIPC } from "../services/mpv-ipc.js";
3
+ import type { MediaScanner } from "../services/media-scanner.js";
4
+ export declare function registerYoutubeTools(server: McpServer, ipc: MpvIPC, scanner: MediaScanner): void;