@mkterswingman/5mghost-yonder 0.0.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.
- package/README.md +19 -0
- package/dist/auth/oauthFlow.d.ts +6 -0
- package/dist/auth/oauthFlow.js +151 -0
- package/dist/auth/tokenManager.d.ts +9 -0
- package/dist/auth/tokenManager.js +100 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +92 -0
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +24 -0
- package/dist/cli/setup.d.ts +1 -0
- package/dist/cli/setup.js +318 -0
- package/dist/cli/setupCookies.d.ts +34 -0
- package/dist/cli/setupCookies.js +196 -0
- package/dist/server.d.ts +21 -0
- package/dist/server.js +79 -0
- package/dist/tools/remote.d.ts +4 -0
- package/dist/tools/remote.js +230 -0
- package/dist/tools/subtitles.d.ts +4 -0
- package/dist/tools/subtitles.js +579 -0
- package/dist/utils/config.d.ts +23 -0
- package/dist/utils/config.js +47 -0
- package/dist/utils/cookieRefresh.d.ts +18 -0
- package/dist/utils/cookieRefresh.js +70 -0
- package/dist/utils/cookies.d.ts +18 -0
- package/dist/utils/cookies.js +78 -0
- package/dist/utils/launcher.d.ts +12 -0
- package/dist/utils/launcher.js +82 -0
- package/dist/utils/mcpRegistration.d.ts +7 -0
- package/dist/utils/mcpRegistration.js +23 -0
- package/dist/utils/videoInput.d.ts +5 -0
- package/dist/utils/videoInput.js +55 -0
- package/dist/utils/ytdlp.d.ts +7 -0
- package/dist/utils/ytdlp.js +52 -0
- package/dist/utils/ytdlpPath.d.ts +26 -0
- package/dist/utils/ytdlpPath.js +78 -0
- package/package.json +43 -0
- package/scripts/download-ytdlp.mjs +138 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function toolErr(code, message) {
|
|
3
|
+
const payload = { status: "failed", error: { code, message } };
|
|
4
|
+
return {
|
|
5
|
+
structuredContent: payload,
|
|
6
|
+
isError: true,
|
|
7
|
+
content: [{ type: "text", text: JSON.stringify(payload) }],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function createRemoteTool(server, def, tokenManager, apiUrl) {
|
|
11
|
+
server.registerTool(def.name, {
|
|
12
|
+
description: def.description,
|
|
13
|
+
inputSchema: def.schema,
|
|
14
|
+
}, async (params) => {
|
|
15
|
+
const token = await tokenManager.getValidToken();
|
|
16
|
+
if (!token) {
|
|
17
|
+
return toolErr("AUTH_EXPIRED", "请重新运行 npx @mkterswingman/5mghost-yonder setup");
|
|
18
|
+
}
|
|
19
|
+
let res;
|
|
20
|
+
try {
|
|
21
|
+
const method = def.method ?? "POST";
|
|
22
|
+
const url = new URL(`${apiUrl}/${def.remotePath}`);
|
|
23
|
+
const headers = {
|
|
24
|
+
Authorization: `Bearer ${token}`,
|
|
25
|
+
};
|
|
26
|
+
const fetchOptions = { method, headers };
|
|
27
|
+
if (method === "GET") {
|
|
28
|
+
// For GET requests, pass params as query string
|
|
29
|
+
for (const [key, value] of Object.entries(params)) {
|
|
30
|
+
if (value !== undefined && value !== null) {
|
|
31
|
+
url.searchParams.set(key, String(value));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
headers["Content-Type"] = "application/json";
|
|
37
|
+
fetchOptions.body = JSON.stringify(params);
|
|
38
|
+
}
|
|
39
|
+
res = await fetch(url.toString(), fetchOptions);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return toolErr("REMOTE_ERROR", `Network error: ${err instanceof Error ? err.message : String(err)}`);
|
|
43
|
+
}
|
|
44
|
+
if (res.status === 401) {
|
|
45
|
+
return toolErr("AUTH_EXPIRED", "Token 已过期");
|
|
46
|
+
}
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const text = await res.text().catch(() => "");
|
|
49
|
+
return toolErr("REMOTE_ERROR", `服务端错误 ${res.status}: ${text.slice(0, 200)}`);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const body = (await res.json());
|
|
53
|
+
// If the remote already returns MCP-shaped content, pass through
|
|
54
|
+
if (body.content && Array.isArray(body.content)) {
|
|
55
|
+
return body;
|
|
56
|
+
}
|
|
57
|
+
// Otherwise wrap it
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: JSON.stringify(body),
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return toolErr("REMOTE_ERROR", "服务端返回了非 JSON 内容");
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
const REMOTE_TOOLS = [
|
|
73
|
+
{
|
|
74
|
+
name: "search_videos",
|
|
75
|
+
description: "Search YouTube videos by keyword. Supports up to 300 results.",
|
|
76
|
+
schema: {
|
|
77
|
+
query: z.string().min(1),
|
|
78
|
+
max_results: z.number().int().min(1).max(300).optional(),
|
|
79
|
+
order: z
|
|
80
|
+
.enum([
|
|
81
|
+
"date",
|
|
82
|
+
"rating",
|
|
83
|
+
"relevance",
|
|
84
|
+
"title",
|
|
85
|
+
"videoCount",
|
|
86
|
+
"viewCount",
|
|
87
|
+
])
|
|
88
|
+
.optional(),
|
|
89
|
+
},
|
|
90
|
+
remotePath: "api/search",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "get_video_stats",
|
|
94
|
+
description: "Get YouTube video stats in one request. Accepts up to 1000 video IDs/URLs.",
|
|
95
|
+
schema: {
|
|
96
|
+
videos: z.array(z.string().min(1)).min(1).max(1000),
|
|
97
|
+
},
|
|
98
|
+
remotePath: "api/video-stats/bulk",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "start_video_stats_job",
|
|
102
|
+
description: "Start an async YouTube video stats job. Accepts up to 1000 items.",
|
|
103
|
+
schema: {
|
|
104
|
+
videos: z.array(z.string().min(1)).min(1).max(1000),
|
|
105
|
+
},
|
|
106
|
+
remotePath: "api/video-stats/job/start",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "poll_video_stats_job",
|
|
110
|
+
description: "Poll job progress and get partial/final results for a job_id.",
|
|
111
|
+
schema: {
|
|
112
|
+
job_id: z.string().min(1),
|
|
113
|
+
},
|
|
114
|
+
remotePath: "api/video-stats/job/poll",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "resume_video_stats_job",
|
|
118
|
+
description: "Resume a previously partial job with a resume_token.",
|
|
119
|
+
schema: {
|
|
120
|
+
resume_token: z.string().min(1),
|
|
121
|
+
},
|
|
122
|
+
remotePath: "api/video-stats/job/resume",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "get_trending",
|
|
126
|
+
description: "Get official YouTube mostPopular videos. Supports region/category filters.",
|
|
127
|
+
schema: {
|
|
128
|
+
region_code: z
|
|
129
|
+
.string()
|
|
130
|
+
.regex(/^[A-Za-z]{2}$/)
|
|
131
|
+
.optional(),
|
|
132
|
+
category_id: z.string().min(1).max(64).optional(),
|
|
133
|
+
max_results: z.number().int().min(1).max(300).optional(),
|
|
134
|
+
},
|
|
135
|
+
remotePath: "api/trending",
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "get_channel_stats",
|
|
139
|
+
description: "Get YouTube channel statistics for up to 50 channel inputs.",
|
|
140
|
+
schema: {
|
|
141
|
+
channels: z.array(z.string().min(1)).min(1).max(50).optional(),
|
|
142
|
+
channel_ids: z.array(z.string().min(1)).min(1).max(50).optional(),
|
|
143
|
+
},
|
|
144
|
+
remotePath: "api/channel/stats",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "list_channel_uploads",
|
|
148
|
+
description: "List videos uploaded by a YouTube channel. Supports up to 1000 results.",
|
|
149
|
+
schema: {
|
|
150
|
+
channel: z.string().min(1).optional(),
|
|
151
|
+
channel_id: z.string().min(1).optional(),
|
|
152
|
+
max_results: z.number().int().min(1).max(1000).optional(),
|
|
153
|
+
},
|
|
154
|
+
remotePath: "api/channel/uploads",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: "get_comments",
|
|
158
|
+
description: "Get comments for one YouTube video. max_comments up to 1000.",
|
|
159
|
+
schema: {
|
|
160
|
+
video: z.string().min(1),
|
|
161
|
+
max_comments: z.number().int().min(1).max(1000).optional(),
|
|
162
|
+
order: z.enum(["time", "relevance"]).optional(),
|
|
163
|
+
include_replies_preview: z.boolean().optional(),
|
|
164
|
+
replies_preview_per_comment: z.number().int().min(1).max(5).optional(),
|
|
165
|
+
replies_preview_parent_limit: z.number().int().min(1).max(25).optional(),
|
|
166
|
+
},
|
|
167
|
+
remotePath: "api/comments/sync",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "start_comments_job",
|
|
171
|
+
description: "Start a comments export job for one YouTube video.",
|
|
172
|
+
schema: {
|
|
173
|
+
video: z.string().min(1),
|
|
174
|
+
max_comments: z.number().int().min(1).max(1000).optional(),
|
|
175
|
+
order: z.enum(["time", "relevance"]).optional(),
|
|
176
|
+
},
|
|
177
|
+
remotePath: "api/comments/start",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "poll_comments_job",
|
|
181
|
+
description: "Check comments export job progress by job_id.",
|
|
182
|
+
schema: {
|
|
183
|
+
job_id: z.string().min(1),
|
|
184
|
+
},
|
|
185
|
+
remotePath: "api/comments/poll",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "resume_comments_job",
|
|
189
|
+
description: "Resume an unfinished comments export job.",
|
|
190
|
+
schema: {
|
|
191
|
+
resume_token: z.string().min(1),
|
|
192
|
+
},
|
|
193
|
+
remotePath: "api/comments/resume",
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "get_comment_replies",
|
|
197
|
+
description: "Get 2nd-level replies for one top-level comment.",
|
|
198
|
+
schema: {
|
|
199
|
+
parent_comment_id: z.string().min(1),
|
|
200
|
+
max_results: z.number().int().min(1).max(1000).optional(),
|
|
201
|
+
page_token: z.string().min(1).optional(),
|
|
202
|
+
},
|
|
203
|
+
remotePath: "api/comments/replies",
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: "get_quota_usage",
|
|
207
|
+
description: "Get YouTube API quota usage for today or a specific date.",
|
|
208
|
+
schema: {
|
|
209
|
+
date: z
|
|
210
|
+
.string()
|
|
211
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
212
|
+
.optional(),
|
|
213
|
+
},
|
|
214
|
+
remotePath: "api/quota",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
name: "get_patch_notes",
|
|
218
|
+
description: "Read server patch notes for latest MCP/API updates.",
|
|
219
|
+
schema: {
|
|
220
|
+
max_chars: z.number().int().min(500).max(50_000).optional(),
|
|
221
|
+
},
|
|
222
|
+
remotePath: "api/patch-notes",
|
|
223
|
+
method: "GET",
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
export function registerRemoteTools(server, config, tokenManager) {
|
|
227
|
+
for (const def of REMOTE_TOOLS) {
|
|
228
|
+
createRemoteTool(server, def, tokenManager, config.api_url);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { YtMcpConfig } from "../utils/config.js";
|
|
3
|
+
import type { TokenManager } from "../auth/tokenManager.js";
|
|
4
|
+
export declare function registerSubtitleTools(server: McpServer, config: YtMcpConfig, tokenManager: TokenManager): void;
|