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.
- package/README.md +123 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +30 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/services/media-scanner.d.ts +19 -0
- package/dist/services/media-scanner.js +61 -0
- package/dist/services/media-scanner.js.map +1 -0
- package/dist/services/mpv-ipc.d.ts +23 -0
- package/dist/services/mpv-ipc.js +112 -0
- package/dist/services/mpv-ipc.js.map +1 -0
- package/dist/services/mpv-process.d.ts +9 -0
- package/dist/services/mpv-process.js +73 -0
- package/dist/services/mpv-process.js.map +1 -0
- package/dist/tools/browse.d.ts +3 -0
- package/dist/tools/browse.js +86 -0
- package/dist/tools/browse.js.map +1 -0
- package/dist/tools/navigation.d.ts +3 -0
- package/dist/tools/navigation.js +62 -0
- package/dist/tools/navigation.js.map +1 -0
- package/dist/tools/play.d.ts +4 -0
- package/dist/tools/play.js +82 -0
- package/dist/tools/play.js.map +1 -0
- package/dist/tools/playback.d.ts +3 -0
- package/dist/tools/playback.js +69 -0
- package/dist/tools/playback.js.map +1 -0
- package/dist/tools/playlist.d.ts +4 -0
- package/dist/tools/playlist.js +174 -0
- package/dist/tools/playlist.js.map +1 -0
- package/dist/tools/seek.d.ts +3 -0
- package/dist/tools/seek.js +57 -0
- package/dist/tools/seek.js.map +1 -0
- package/dist/tools/status.d.ts +3 -0
- package/dist/tools/status.js +58 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/volume.d.ts +3 -0
- package/dist/tools/volume.js +43 -0
- package/dist/tools/volume.js.map +1 -0
- package/dist/tools/youtube.d.ts +4 -0
- package/dist/tools/youtube.js +148 -0
- package/dist/tools/youtube.js.map +1 -0
- package/dist/utils/format.d.ts +7 -0
- package/dist/utils/format.js +34 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/fuzzy-match.d.ts +16 -0
- package/dist/utils/fuzzy-match.js +38 -0
- package/dist/utils/fuzzy-match.js.map +1 -0
- package/dist/utils/path.d.ts +2 -0
- package/dist/utils/path.js +5 -0
- package/dist/utils/path.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { DOWNLOAD_DIR } from "../config.js";
|
|
6
|
+
export function registerYoutubeTools(server, ipc, scanner) {
|
|
7
|
+
server.registerTool("mpv_youtube", {
|
|
8
|
+
title: "Play from YouTube",
|
|
9
|
+
description: "Search YouTube and stream the result through mpv. Accepts a search query or a direct YouTube URL.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
query: z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.describe("YouTube search term or URL"),
|
|
15
|
+
results: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.min(1)
|
|
19
|
+
.max(10)
|
|
20
|
+
.default(1)
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Number of search results to queue (default 1, max 10)"),
|
|
23
|
+
},
|
|
24
|
+
annotations: {
|
|
25
|
+
readOnlyHint: false,
|
|
26
|
+
destructiveHint: false,
|
|
27
|
+
idempotentHint: false,
|
|
28
|
+
openWorldHint: true,
|
|
29
|
+
},
|
|
30
|
+
}, async ({ query, results }) => {
|
|
31
|
+
try {
|
|
32
|
+
const count = results ?? 1;
|
|
33
|
+
const isUrl = query.includes("youtube.com/") || query.includes("youtu.be/");
|
|
34
|
+
const target = isUrl ? query : `ytdl://ytsearch${count}:${query}`;
|
|
35
|
+
await ipc.command(["loadfile", target, "replace"]);
|
|
36
|
+
await delay(2000); // YouTube resolution takes longer than local files
|
|
37
|
+
const title = await ipc
|
|
38
|
+
.getProperty("media-title")
|
|
39
|
+
.catch(() => null);
|
|
40
|
+
if (title) {
|
|
41
|
+
const lines = [`Streaming from YouTube: ${title}`];
|
|
42
|
+
if (!isUrl && count > 1) {
|
|
43
|
+
lines.push(`(queued top ${count} results)`);
|
|
44
|
+
}
|
|
45
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: isUrl
|
|
52
|
+
? "Loading YouTube URL... (may take a moment to buffer)"
|
|
53
|
+
: `Searching YouTube for "${query}"... (playback will start shortly)`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `YouTube playback failed: ${err instanceof Error ? err.message : err}`,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
isError: true,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
server.registerTool("mpv_download", {
|
|
71
|
+
title: "Download from YouTube",
|
|
72
|
+
description: "Download audio or video from YouTube. Specify format: \"audio\" for MP3, \"video\" for full video.",
|
|
73
|
+
inputSchema: {
|
|
74
|
+
query: z
|
|
75
|
+
.string()
|
|
76
|
+
.min(1)
|
|
77
|
+
.describe("YouTube URL or search term"),
|
|
78
|
+
format: z
|
|
79
|
+
.enum(["audio", "video"])
|
|
80
|
+
.describe("\"audio\" saves MP3 only, \"video\" saves full video"),
|
|
81
|
+
},
|
|
82
|
+
annotations: {
|
|
83
|
+
readOnlyHint: false,
|
|
84
|
+
destructiveHint: false,
|
|
85
|
+
idempotentHint: false,
|
|
86
|
+
openWorldHint: true,
|
|
87
|
+
},
|
|
88
|
+
}, async ({ query, format }) => {
|
|
89
|
+
try {
|
|
90
|
+
const fmt = format;
|
|
91
|
+
const isUrl = query.includes("youtube.com/") || query.includes("youtu.be/");
|
|
92
|
+
const target = isUrl ? query : `ytsearch:${query}`;
|
|
93
|
+
if (!existsSync(DOWNLOAD_DIR)) {
|
|
94
|
+
mkdirSync(DOWNLOAD_DIR, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
const args = [];
|
|
97
|
+
if (fmt === "audio") {
|
|
98
|
+
args.push("-x", "--audio-format", "mp3", "--audio-quality", "0", "-o", join(DOWNLOAD_DIR, "%(artist,uploader)s - %(title)s.%(ext)s"));
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
args.push("-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best", "-o", join(DOWNLOAD_DIR, "%(artist,uploader)s - %(title)s.%(ext)s"));
|
|
102
|
+
}
|
|
103
|
+
args.push("--no-playlist", "--restrict-filenames", target);
|
|
104
|
+
// First, get the title so we can report what's downloading
|
|
105
|
+
const info = await runYtDlp(["--print", "title", "--no-playlist", target]);
|
|
106
|
+
const title = info.trim() || query;
|
|
107
|
+
const result = await runYtDlp(args);
|
|
108
|
+
// Invalidate the media scanner cache so the new file shows up
|
|
109
|
+
scanner.invalidateCache();
|
|
110
|
+
const typeLabel = fmt === "audio" ? "audio (MP3)" : "video";
|
|
111
|
+
return {
|
|
112
|
+
content: [
|
|
113
|
+
{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: `Downloaded ${typeLabel}: ${title}\nSaved to: ${DOWNLOAD_DIR}`,
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
return {
|
|
122
|
+
content: [
|
|
123
|
+
{
|
|
124
|
+
type: "text",
|
|
125
|
+
text: `Download failed: ${err instanceof Error ? err.message : err}`,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
isError: true,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function delay(ms) {
|
|
134
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
135
|
+
}
|
|
136
|
+
function runYtDlp(args) {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
execFile("yt-dlp", args, { timeout: 120_000 }, (err, stdout, stderr) => {
|
|
139
|
+
if (err) {
|
|
140
|
+
reject(new Error(stderr || err.message));
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
resolve(stdout);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=youtube.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"youtube.js","sourceRoot":"","sources":["../../src/tools/youtube.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Format seconds into MM:SS or H:MM:SS */
|
|
2
|
+
export declare function formatDuration(seconds: number | null | undefined): string;
|
|
3
|
+
/** Parse a seek target string into seconds. Supports "90", "1:30", "1:02:30", "+10", "-30" */
|
|
4
|
+
export declare function parseSeekTarget(target: string): {
|
|
5
|
+
seconds: number;
|
|
6
|
+
mode: "absolute" | "relative";
|
|
7
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Format seconds into MM:SS or H:MM:SS */
|
|
2
|
+
export function formatDuration(seconds) {
|
|
3
|
+
if (seconds == null || seconds < 0)
|
|
4
|
+
return "--:--";
|
|
5
|
+
const s = Math.floor(seconds);
|
|
6
|
+
const h = Math.floor(s / 3600);
|
|
7
|
+
const m = Math.floor((s % 3600) / 60);
|
|
8
|
+
const sec = s % 60;
|
|
9
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
10
|
+
return h > 0 ? `${h}:${pad(m)}:${pad(sec)}` : `${m}:${pad(sec)}`;
|
|
11
|
+
}
|
|
12
|
+
/** Parse a seek target string into seconds. Supports "90", "1:30", "1:02:30", "+10", "-30" */
|
|
13
|
+
export function parseSeekTarget(target) {
|
|
14
|
+
const trimmed = target.trim();
|
|
15
|
+
// Relative seek: "+10" or "-30"
|
|
16
|
+
if (trimmed.startsWith("+") || trimmed.startsWith("-")) {
|
|
17
|
+
return { seconds: parseFloat(trimmed), mode: "relative" };
|
|
18
|
+
}
|
|
19
|
+
// Timestamp: "1:30" or "1:02:30"
|
|
20
|
+
if (trimmed.includes(":")) {
|
|
21
|
+
const parts = trimmed.split(":").map(Number);
|
|
22
|
+
let total = 0;
|
|
23
|
+
if (parts.length === 3) {
|
|
24
|
+
total = parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
25
|
+
}
|
|
26
|
+
else if (parts.length === 2) {
|
|
27
|
+
total = parts[0] * 60 + parts[1];
|
|
28
|
+
}
|
|
29
|
+
return { seconds: total, mode: "absolute" };
|
|
30
|
+
}
|
|
31
|
+
// Plain number: absolute seconds
|
|
32
|
+
return { seconds: parseFloat(trimmed), mode: "absolute" };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,MAAM,UAAU,cAAc,CAAC,OAAkC;IAC/D,IAAI,OAAO,IAAI,IAAI,IAAI,OAAO,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC;IACnD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACtC,MAAM,GAAG,GAAG,CAAC,GAAG,EAAE,CAAC;IACnB,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;IAE9B,gCAAgC;IAChC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC5D,CAAC;IAED,iCAAiC;IACjC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACrD,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC9B,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC9C,CAAC;IAED,iCAAiC;IACjC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ScoredResult<T> {
|
|
2
|
+
item: T;
|
|
3
|
+
score: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Score how well `query` matches `text`.
|
|
7
|
+
* Returns 0 for no match, higher for better matches.
|
|
8
|
+
*
|
|
9
|
+
* Scoring:
|
|
10
|
+
* - Exact substring match: 100
|
|
11
|
+
* - All query words present: 60 + (10 * word count bonus)
|
|
12
|
+
* - Partial word matches: 10 per matched word
|
|
13
|
+
*/
|
|
14
|
+
export declare function fuzzyScore(query: string, text: string): number;
|
|
15
|
+
/** Search items by scoring a text field, return top N sorted by score descending */
|
|
16
|
+
export declare function fuzzySearch<T>(items: T[], query: string, getText: (item: T) => string, limit?: number): ScoredResult<T>[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score how well `query` matches `text`.
|
|
3
|
+
* Returns 0 for no match, higher for better matches.
|
|
4
|
+
*
|
|
5
|
+
* Scoring:
|
|
6
|
+
* - Exact substring match: 100
|
|
7
|
+
* - All query words present: 60 + (10 * word count bonus)
|
|
8
|
+
* - Partial word matches: 10 per matched word
|
|
9
|
+
*/
|
|
10
|
+
export function fuzzyScore(query, text) {
|
|
11
|
+
const q = query.toLowerCase().trim();
|
|
12
|
+
const t = text.toLowerCase();
|
|
13
|
+
if (!q)
|
|
14
|
+
return 0;
|
|
15
|
+
// Exact substring match is the strongest signal
|
|
16
|
+
if (t.includes(q))
|
|
17
|
+
return 100;
|
|
18
|
+
// Split query into words and check each
|
|
19
|
+
const words = q.split(/\s+/).filter(Boolean);
|
|
20
|
+
if (words.length === 0)
|
|
21
|
+
return 0;
|
|
22
|
+
const matched = words.filter((w) => t.includes(w));
|
|
23
|
+
if (matched.length === words.length) {
|
|
24
|
+
// All words present
|
|
25
|
+
return 60 + matched.length * 10;
|
|
26
|
+
}
|
|
27
|
+
// Partial matches
|
|
28
|
+
return matched.length * 10;
|
|
29
|
+
}
|
|
30
|
+
/** Search items by scoring a text field, return top N sorted by score descending */
|
|
31
|
+
export function fuzzySearch(items, query, getText, limit = 10) {
|
|
32
|
+
return items
|
|
33
|
+
.map((item) => ({ item, score: fuzzyScore(query, getText(item)) }))
|
|
34
|
+
.filter((r) => r.score > 0)
|
|
35
|
+
.sort((a, b) => b.score - a.score)
|
|
36
|
+
.slice(0, limit);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=fuzzy-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuzzy-match.js","sourceRoot":"","sources":["../../src/utils/fuzzy-match.ts"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,IAAY;IACpD,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IACrC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAE7B,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAEjB,gDAAgD;IAChD,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,GAAG,CAAC;IAE9B,wCAAwC;IACxC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IAEjC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnD,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,EAAE,CAAC;QACpC,oBAAoB;QACpB,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC;IAClC,CAAC;IAED,kBAAkB;IAClB,OAAO,OAAO,CAAC,MAAM,GAAG,EAAE,CAAC;AAC7B,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,WAAW,CACzB,KAAU,EACV,KAAa,EACb,OAA4B,EAC5B,KAAK,GAAG,EAAE;IAEV,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;SAClE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;SAC1B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../src/utils/path.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AAC/B,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mpv-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for controlling mpv media player — playback, playlists, YouTube streaming and downloading",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mpv-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"mpv",
|
|
16
|
+
"music",
|
|
17
|
+
"media",
|
|
18
|
+
"youtube",
|
|
19
|
+
"claude",
|
|
20
|
+
"model-context-protocol"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/arijit-gogoi/mpv-mcp-server.git"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/arijit-gogoi/mpv-mcp-server#readme",
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/arijit-gogoi/mpv-mcp-server/issues"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc",
|
|
36
|
+
"start": "node dist/index.js",
|
|
37
|
+
"dev": "tsc --watch"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
|
+
"zod": "^3.24.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"typescript": "^5.7.0"
|
|
46
|
+
}
|
|
47
|
+
}
|