@mkterswingman/5mghost-yonder 0.0.1 → 0.0.3

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 (67) hide show
  1. package/README.md +44 -3
  2. package/dist/auth/sharedAuth.d.ts +10 -0
  3. package/dist/auth/sharedAuth.js +24 -0
  4. package/dist/auth/tokenManager.d.ts +10 -1
  5. package/dist/auth/tokenManager.js +14 -22
  6. package/dist/cli/check.d.ts +4 -0
  7. package/dist/cli/check.js +90 -0
  8. package/dist/cli/index.d.ts +15 -1
  9. package/dist/cli/index.js +76 -27
  10. package/dist/cli/runtime.d.ts +9 -0
  11. package/dist/cli/runtime.js +35 -0
  12. package/dist/cli/serve.js +3 -1
  13. package/dist/cli/setup.js +60 -61
  14. package/dist/cli/setupCookies.js +2 -2
  15. package/dist/cli/smoke.d.ts +27 -0
  16. package/dist/cli/smoke.js +108 -0
  17. package/dist/cli/uninstall.d.ts +1 -0
  18. package/dist/cli/uninstall.js +67 -0
  19. package/dist/download/downloader.d.ts +64 -0
  20. package/dist/download/downloader.js +264 -0
  21. package/dist/download/jobManager.d.ts +21 -0
  22. package/dist/download/jobManager.js +198 -0
  23. package/dist/download/types.d.ts +43 -0
  24. package/dist/download/types.js +1 -0
  25. package/dist/runtime/ffmpegRuntime.d.ts +13 -0
  26. package/dist/runtime/ffmpegRuntime.js +51 -0
  27. package/dist/runtime/installers.d.ts +12 -0
  28. package/dist/runtime/installers.js +45 -0
  29. package/dist/runtime/manifest.d.ts +18 -0
  30. package/dist/runtime/manifest.js +43 -0
  31. package/dist/runtime/playwrightRuntime.d.ts +13 -0
  32. package/dist/runtime/playwrightRuntime.js +37 -0
  33. package/dist/runtime/systemDeps.d.ts +3 -0
  34. package/dist/runtime/systemDeps.js +30 -0
  35. package/dist/runtime/ytdlpRuntime.d.ts +14 -0
  36. package/dist/runtime/ytdlpRuntime.js +58 -0
  37. package/dist/server.d.ts +3 -1
  38. package/dist/server.js +4 -1
  39. package/dist/tools/downloads.d.ts +11 -0
  40. package/dist/tools/downloads.js +220 -0
  41. package/dist/tools/subtitles.d.ts +25 -0
  42. package/dist/tools/subtitles.js +135 -47
  43. package/dist/utils/config.d.ts +28 -0
  44. package/dist/utils/config.js +40 -11
  45. package/dist/utils/ffmpeg.d.ts +5 -0
  46. package/dist/utils/ffmpeg.js +16 -0
  47. package/dist/utils/ffmpegPath.d.ts +8 -0
  48. package/dist/utils/ffmpegPath.js +21 -0
  49. package/dist/utils/formatters.d.ts +4 -0
  50. package/dist/utils/formatters.js +42 -0
  51. package/dist/utils/mediaPaths.d.ts +7 -0
  52. package/dist/utils/mediaPaths.js +10 -0
  53. package/dist/utils/openClaw.d.ts +17 -0
  54. package/dist/utils/openClaw.js +79 -0
  55. package/dist/utils/videoInput.js +3 -0
  56. package/dist/utils/videoMetadata.d.ts +11 -0
  57. package/dist/utils/videoMetadata.js +1 -0
  58. package/dist/utils/ytdlp.d.ts +17 -1
  59. package/dist/utils/ytdlp.js +89 -2
  60. package/dist/utils/ytdlpPath.d.ts +9 -2
  61. package/dist/utils/ytdlpPath.js +19 -20
  62. package/dist/utils/ytdlpProgress.d.ts +13 -0
  63. package/dist/utils/ytdlpProgress.js +77 -0
  64. package/package.json +5 -3
  65. package/scripts/download-ytdlp.mjs +1 -1
  66. package/scripts/install.ps1 +9 -0
  67. package/scripts/install.sh +15 -0
package/README.md CHANGED
@@ -5,15 +5,56 @@ Internal MCP server package for 5mghost workflows.
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- npx @mkterswingman/5mghost-yonder setup
8
+ curl -fsSL https://mkterswingman.com/install/yt-mcp.sh | bash
9
9
  ```
10
10
 
11
- The setup command configures auth, optional YouTube cookies, and prints MCP config for supported AI clients.
11
+ ```powershell
12
+ powershell -ExecutionPolicy Bypass -Command "irm https://mkterswingman.com/install/yt-mcp.ps1 | iex"
13
+ ```
14
+
15
+ ```
16
+
17
+ The bootstrap installer:
18
+
19
+ - installs the npm package
20
+ - installs required runtimes (`playwright`, `yt-dlp`, `ffmpeg`)
21
+ - runs `yt-mcp setup`
22
+ - runs `yt-mcp smoke` to verify MCP startup and authenticated remote access
23
+ - asks whether to configure YouTube cookies now; if you confirm, it runs `yt-mcp setup-cookies` and a subtitle smoke check immediately
24
+
25
+ If you are working inside the repo instead of using the hosted installer:
26
+
27
+ ```bash
28
+ bash scripts/install.sh
29
+ ```
30
+
31
+ ```powershell
32
+ powershell -ExecutionPolicy Bypass -File .\scripts\install.ps1
33
+ ```
34
+
35
+ `yt-mcp` stores first-party shared auth at `~/.mkterswingman/auth.json`, so logging in here also covers other first-party local MCPs on the same machine. YouTube cookies remain local to `~/.yt-mcp/`.
36
+
37
+ Client registration notes:
38
+
39
+ - `setup` auto-registers Claude/Codex/Gemini/OpenCode when their CLI supports `mcp add`
40
+ - OpenClaw is registered by writing `mcporter.json` directly, which avoids Windows path escaping issues
41
+ - pasted PAT tokens are stored in `~/.mkterswingman/auth.json`; client `env.YT_MCP_TOKEN` is optional after setup
42
+
43
+ Media download runtime expectations:
44
+
45
+ - `start_download_job` and `poll_download_job` are job-based local tools
46
+ - `ffmpeg` is required for video download modes
47
+ - each job handles up to 5 YouTube videos
48
+ - downloads are written to `~/Downloads/yt-mcp/YYYY-MM-DD_<video_id>`
12
49
 
13
50
  ## Commands
14
51
 
15
52
  - `setup` — first-time setup
16
53
  - `serve` — start the stdio MCP server
54
+ - `smoke` — run the installer smoke checks (`search_videos`, plus `validate_cookies` with `--subtitles`)
55
+ - `runtime` — install, update, or check required runtimes
56
+ - `check` — inspect shared auth, runtime status, and YouTube cookies
17
57
  - `setup-cookies` — refresh YouTube cookies
18
- - `update` — update to the latest npm version
58
+ - `uninstall` — remove MCP registrations and local `~/.yt-mcp` config
59
+ - `update` — update the main package and required runtimes
19
60
  - `version` — print the installed version
@@ -0,0 +1,10 @@
1
+ export interface SharedAuthData {
2
+ type: "jwt" | "pat";
3
+ access_token?: string;
4
+ refresh_token?: string;
5
+ expires_at?: number;
6
+ client_id?: string;
7
+ pat?: string;
8
+ }
9
+ export declare function readSharedAuth(authPath: string): SharedAuthData | null;
10
+ export declare function writeSharedAuth(authPath: string, data: SharedAuthData): void;
@@ -0,0 +1,24 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ export function readSharedAuth(authPath) {
4
+ if (!existsSync(authPath))
5
+ return null;
6
+ try {
7
+ return JSON.parse(readFileSync(authPath, "utf8"));
8
+ }
9
+ catch {
10
+ return null;
11
+ }
12
+ }
13
+ export function writeSharedAuth(authPath, data) {
14
+ mkdirSync(dirname(authPath), { recursive: true });
15
+ const tempPath = `${authPath}.tmp`;
16
+ writeFileSync(tempPath, JSON.stringify(data, null, 2), { encoding: "utf8", mode: 0o600 });
17
+ renameSync(tempPath, authPath);
18
+ try {
19
+ chmodSync(authPath, 0o600);
20
+ }
21
+ catch {
22
+ // Why: chmod is best-effort on Windows and should not block auth persistence.
23
+ }
24
+ }
@@ -1,9 +1,18 @@
1
+ interface TokenManagerOptions {
2
+ authPath?: string;
3
+ env?: NodeJS.ProcessEnv;
4
+ fetchImpl?: typeof fetch;
5
+ }
1
6
  export declare class TokenManager {
2
7
  private authUrl;
3
- constructor(authUrl: string);
8
+ private authPath;
9
+ private env;
10
+ private fetchImpl;
11
+ constructor(authUrl: string, options?: TokenManagerOptions);
4
12
  getValidToken(): Promise<string | null>;
5
13
  saveTokens(accessToken: string, refreshToken: string, expiresIn: number, clientId?: string): Promise<void>;
6
14
  savePAT(pat: string): Promise<void>;
7
15
  private readAuth;
8
16
  private refreshTokens;
9
17
  }
18
+ export {};
@@ -1,13 +1,19 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { PATHS, ensureConfigDir } from "../utils/config.js";
1
+ import { PATHS } from "../utils/config.js";
2
+ import { readSharedAuth, writeSharedAuth } from "./sharedAuth.js";
3
3
  export class TokenManager {
4
4
  authUrl;
5
- constructor(authUrl) {
5
+ authPath;
6
+ env;
7
+ fetchImpl;
8
+ constructor(authUrl, options = {}) {
6
9
  this.authUrl = authUrl;
10
+ this.authPath = options.authPath ?? PATHS.sharedAuthJson;
11
+ this.env = options.env ?? process.env;
12
+ this.fetchImpl = options.fetchImpl ?? fetch;
7
13
  }
8
14
  async getValidToken() {
9
15
  // Check env var PAT first
10
- const envPat = process.env.YT_MCP_TOKEN;
16
+ const envPat = this.env.YT_MCP_TOKEN;
11
17
  if (envPat)
12
18
  return envPat;
13
19
  const auth = this.readAuth();
@@ -38,7 +44,6 @@ export class TokenManager {
38
44
  return null;
39
45
  }
40
46
  async saveTokens(accessToken, refreshToken, expiresIn, clientId) {
41
- ensureConfigDir();
42
47
  const data = {
43
48
  type: "jwt",
44
49
  access_token: accessToken,
@@ -46,30 +51,17 @@ export class TokenManager {
46
51
  expires_at: Date.now() + expiresIn * 1000,
47
52
  client_id: clientId,
48
53
  };
49
- writeFileSync(PATHS.authJson, JSON.stringify(data, null, 2), {
50
- mode: 0o600,
51
- });
54
+ writeSharedAuth(this.authPath, data);
52
55
  }
53
56
  async savePAT(pat) {
54
- ensureConfigDir();
55
57
  const data = {
56
58
  type: "pat",
57
59
  pat,
58
60
  };
59
- writeFileSync(PATHS.authJson, JSON.stringify(data, null, 2), {
60
- mode: 0o600,
61
- });
61
+ writeSharedAuth(this.authPath, data);
62
62
  }
63
63
  readAuth() {
64
- if (!existsSync(PATHS.authJson))
65
- return null;
66
- try {
67
- const raw = readFileSync(PATHS.authJson, "utf8");
68
- return JSON.parse(raw);
69
- }
70
- catch {
71
- return null;
72
- }
64
+ return readSharedAuth(this.authPath);
73
65
  }
74
66
  async refreshTokens(refreshToken, clientId) {
75
67
  const url = `${this.authUrl}/oauth/token`;
@@ -80,7 +72,7 @@ export class TokenManager {
80
72
  if (clientId) {
81
73
  body.client_id = clientId;
82
74
  }
83
- const res = await fetch(url, {
75
+ const res = await this.fetchImpl(url, {
84
76
  method: "POST",
85
77
  headers: { "Content-Type": "application/json" },
86
78
  body: JSON.stringify(body),
@@ -0,0 +1,4 @@
1
+ /**
2
+ * `yt-mcp check` — verify token, cookies, yt-dlp, and remote API connectivity.
3
+ */
4
+ export declare function runCheck(): Promise<void>;
@@ -0,0 +1,90 @@
1
+ import { existsSync } from "node:fs";
2
+ import { PATHS, loadConfig } from "../utils/config.js";
3
+ import { TokenManager } from "../auth/tokenManager.js";
4
+ import { hasSIDCookies, areCookiesExpired } from "../utils/cookies.js";
5
+ import { getYtDlpVersion } from "../utils/ytdlpPath.js";
6
+ import { runRuntimeCommand } from "./runtime.js";
7
+ /**
8
+ * `yt-mcp check` — verify token, cookies, yt-dlp, and remote API connectivity.
9
+ */
10
+ export async function runCheck() {
11
+ const config = loadConfig();
12
+ const tm = new TokenManager(config.auth_url);
13
+ console.log("yt-mcp check\n");
14
+ // 1. Token
15
+ const token = await tm.getValidToken();
16
+ if (token) {
17
+ const masked = token.slice(0, 8) + "...";
18
+ const source = process.env.YT_MCP_TOKEN ? "env" : PATHS.sharedAuthJson;
19
+ console.log(`🔑 Shared auth: ✅ present (${masked}) [${source}]`);
20
+ // Server verification
21
+ try {
22
+ const res = await fetch(`${config.auth_url}/api/me`, {
23
+ headers: { Authorization: `Bearer ${token}` },
24
+ signal: AbortSignal.timeout(10_000),
25
+ });
26
+ if (res.ok) {
27
+ console.log("🔐 Server verification: ✅ active");
28
+ }
29
+ else {
30
+ console.log(`🔐 Server verification: ❌ ${res.status} ${res.statusText}`);
31
+ console.log(" Run `yt-mcp setup` to re-authenticate.");
32
+ }
33
+ }
34
+ catch (err) {
35
+ const msg = err instanceof Error ? err.message : String(err);
36
+ console.log(`🔐 Server verification: ⚠️ unreachable (${msg})`);
37
+ }
38
+ }
39
+ else {
40
+ console.log("🔑 Shared auth: ❌ not configured");
41
+ console.log(" Run `yt-mcp setup` or set YT_MCP_TOKEN env var");
42
+ }
43
+ console.log("");
44
+ console.log(await runRuntimeCommand("check"));
45
+ // 2. Cookies
46
+ if (existsSync(PATHS.cookiesTxt)) {
47
+ const hasSID = hasSIDCookies(PATHS.cookiesTxt);
48
+ const expired = areCookiesExpired(PATHS.cookiesTxt);
49
+ if (hasSID && !expired) {
50
+ console.log("🍪 Cookies: ✅ valid (SID present, not expired)");
51
+ }
52
+ else if (hasSID && expired) {
53
+ console.log("🍪 Cookies: ❌ expired");
54
+ console.log(" Run `yt-mcp setup-cookies` to refresh");
55
+ }
56
+ else {
57
+ console.log("🍪 Cookies: ❌ no YouTube session cookies found");
58
+ console.log(" Run `yt-mcp setup-cookies`");
59
+ }
60
+ }
61
+ else {
62
+ console.log("🍪 Cookies: ❌ not found");
63
+ console.log(" Run `yt-mcp setup-cookies`");
64
+ }
65
+ // 3. yt-dlp
66
+ const ytdlp = getYtDlpVersion();
67
+ if (ytdlp) {
68
+ console.log(`🎬 yt-dlp: ✅ ${ytdlp.version} (${ytdlp.source})`);
69
+ }
70
+ else {
71
+ console.log("🎬 yt-dlp: ❌ not found");
72
+ console.log(" Install: brew install yt-dlp (or pip install yt-dlp)");
73
+ }
74
+ // 4. Remote API
75
+ try {
76
+ const res = await fetch(`${config.api_url}/healthz`, {
77
+ signal: AbortSignal.timeout(10_000),
78
+ });
79
+ if (res.ok) {
80
+ console.log(`🌐 Remote API: ✅ reachable (${config.api_url})`);
81
+ }
82
+ else {
83
+ console.log(`🌐 Remote API: ⚠️ ${res.status} ${res.statusText}`);
84
+ }
85
+ }
86
+ catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ console.log(`🌐 Remote API: ❌ unreachable (${msg})`);
89
+ }
90
+ }
@@ -1,2 +1,16 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export declare function buildHelpText(version: string): string;
3
+ export interface UnifiedUpdateDeps {
4
+ getCurrentVersion(): string;
5
+ getLatestVersion(): Promise<string>;
6
+ installLatestPackage(): Promise<void>;
7
+ updateRuntime(): Promise<import("../runtime/installers.js").RuntimeSummary>;
8
+ }
9
+ export declare function runUnifiedUpdate(deps: UnifiedUpdateDeps): Promise<{
10
+ package: {
11
+ currentVersion: string;
12
+ latestVersion: string;
13
+ updated: boolean;
14
+ };
15
+ runtime: import("../runtime/installers.js").RuntimeSummary;
16
+ }>;
package/dist/cli/index.js CHANGED
@@ -14,6 +14,41 @@ function getVersion() {
14
14
  return "unknown";
15
15
  }
16
16
  }
17
+ export function buildHelpText(version) {
18
+ return `
19
+ yt-mcp v${version}
20
+
21
+ Usage: yt-mcp <command>
22
+
23
+ Commands:
24
+ setup Run first-time setup (OAuth + cookies + MCP registration)
25
+ serve Start the MCP server (stdio transport)
26
+ smoke Run installer smoke checks
27
+ setup-cookies Refresh YouTube cookies using browser login
28
+ runtime Manage required runtimes
29
+ check Check auth, runtime, cookies, and connectivity
30
+ uninstall Remove MCP registrations and local yt-mcp config
31
+ update Update the main package and required runtimes
32
+ version Show current version
33
+
34
+ Environment variables:
35
+ YT_MCP_TOKEN Personal Access Token (skips OAuth)
36
+ `;
37
+ }
38
+ export async function runUnifiedUpdate(deps) {
39
+ const currentVersion = deps.getCurrentVersion();
40
+ const latestVersion = await deps.getLatestVersion();
41
+ let updated = false;
42
+ if (latestVersion !== currentVersion) {
43
+ await deps.installLatestPackage();
44
+ updated = true;
45
+ }
46
+ const runtime = await deps.updateRuntime();
47
+ return {
48
+ package: { currentVersion, latestVersion, updated },
49
+ runtime,
50
+ };
51
+ }
17
52
  const command = process.argv[2];
18
53
  async function main() {
19
54
  switch (command) {
@@ -27,31 +62,59 @@ async function main() {
27
62
  await runServe();
28
63
  break;
29
64
  }
65
+ case "smoke": {
66
+ const { runSmoke } = await import("./smoke.js");
67
+ await runSmoke(process.argv.slice(3));
68
+ break;
69
+ }
30
70
  case "setup-cookies": {
31
71
  const { runSetupCookies } = await import("./setupCookies.js");
32
72
  await runSetupCookies();
33
73
  break;
34
74
  }
75
+ case "uninstall": {
76
+ const { runUninstall } = await import("./uninstall.js");
77
+ await runUninstall();
78
+ break;
79
+ }
80
+ case "runtime": {
81
+ const { runRuntimeCli } = await import("./runtime.js");
82
+ await runRuntimeCli(process.argv[3]);
83
+ break;
84
+ }
85
+ case "check": {
86
+ const { runCheck } = await import("./check.js");
87
+ await runCheck();
88
+ break;
89
+ }
35
90
  case "update": {
36
91
  const current = getVersion();
37
92
  console.log(`Current version: ${current}`);
38
93
  console.log("Checking for updates...\n");
39
94
  try {
40
- const latest = execSync("npm view @mkterswingman/5mghost-yonder version", {
41
- encoding: "utf8",
42
- stdio: ["ignore", "pipe", "ignore"],
43
- }).trim();
44
- if (latest === current) {
45
- console.log(`✅ Already on the latest version (${current})`);
95
+ const { updateAll } = await import("../runtime/installers.js");
96
+ const summary = await runUnifiedUpdate({
97
+ getCurrentVersion: getVersion,
98
+ getLatestVersion: async () => execSync("npm view @mkterswingman/5mghost-yonder version", {
99
+ encoding: "utf8",
100
+ stdio: ["ignore", "pipe", "ignore"],
101
+ }).trim(),
102
+ installLatestPackage: async () => {
103
+ execSync("npm install -g @mkterswingman/5mghost-yonder@latest", {
104
+ stdio: "inherit",
105
+ });
106
+ },
107
+ updateRuntime: updateAll,
108
+ });
109
+ if (summary.package.updated) {
110
+ console.log(`\n✅ Updated package to ${summary.package.latestVersion}`);
46
111
  }
47
112
  else {
48
- console.log(`New version available: ${latest}`);
49
- console.log("Updating...\n");
50
- execSync("npm install -g @mkterswingman/5mghost-yonder@latest", {
51
- stdio: "inherit",
52
- });
53
- console.log(`\n✅ Updated to ${latest}`);
113
+ console.log(`✅ Already on the latest version (${summary.package.currentVersion})`);
54
114
  }
115
+ console.log("");
116
+ const { formatRuntimeSummary } = await import("./runtime.js");
117
+ console.log(formatRuntimeSummary(summary.runtime));
55
118
  }
56
119
  catch (err) {
57
120
  const msg = err instanceof Error ? err.message : String(err);
@@ -68,21 +131,7 @@ async function main() {
68
131
  break;
69
132
  }
70
133
  default:
71
- console.log(`
72
- yt-mcp v${getVersion()}
73
-
74
- Usage: yt-mcp <command>
75
-
76
- Commands:
77
- setup Run first-time setup (OAuth + cookies + MCP registration)
78
- serve Start the MCP server (stdio transport)
79
- setup-cookies Refresh YouTube cookies using browser login
80
- update Update to the latest version
81
- version Show current version
82
-
83
- Environment variables:
84
- YT_MCP_TOKEN Personal Access Token (skips OAuth)
85
- `);
134
+ console.log(buildHelpText(getVersion()));
86
135
  process.exit(command ? 1 : 0);
87
136
  }
88
137
  }
@@ -0,0 +1,9 @@
1
+ import { type RuntimeSummary } from "../runtime/installers.js";
2
+ export interface RuntimeCommandDeps {
3
+ installAll(): Promise<RuntimeSummary>;
4
+ updateAll(): Promise<RuntimeSummary>;
5
+ checkAll(): Promise<RuntimeSummary>;
6
+ }
7
+ export declare function formatRuntimeSummary(summary: RuntimeSummary): string;
8
+ export declare function runRuntimeCommand(action: "install" | "update" | "check", deps?: RuntimeCommandDeps): Promise<string>;
9
+ export declare function runRuntimeCli(action?: string): Promise<void>;
@@ -0,0 +1,35 @@
1
+ import { checkAll, installAll, updateAll } from "../runtime/installers.js";
2
+ export function formatRuntimeSummary(summary) {
3
+ const lines = [`Runtime ${summary.action} completed`];
4
+ for (const component of summary.components) {
5
+ const base = `${component.name}: ${component.status} (${component.source}`;
6
+ if (component.version) {
7
+ lines.push(`${base}, ${component.version})`);
8
+ }
9
+ else if (component.message) {
10
+ lines.push(`${base}) — ${component.message}`);
11
+ }
12
+ else {
13
+ lines.push(`${base})`);
14
+ }
15
+ }
16
+ return lines.join("\n");
17
+ }
18
+ export async function runRuntimeCommand(action, deps = {
19
+ installAll,
20
+ updateAll,
21
+ checkAll,
22
+ }) {
23
+ const summary = action === "install"
24
+ ? await deps.installAll()
25
+ : action === "update"
26
+ ? await deps.updateAll()
27
+ : await deps.checkAll();
28
+ return formatRuntimeSummary(summary);
29
+ }
30
+ export async function runRuntimeCli(action = "check") {
31
+ const normalized = action === "install" || action === "update" || action === "check"
32
+ ? action
33
+ : "check";
34
+ console.log(await runRuntimeCommand(normalized));
35
+ }
package/dist/cli/serve.js CHANGED
@@ -1,16 +1,18 @@
1
1
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
2
  import { loadConfig } from "../utils/config.js";
3
3
  import { TokenManager } from "../auth/tokenManager.js";
4
+ import { DownloadJobManager } from "../download/jobManager.js";
4
5
  import { createServer } from "../server.js";
5
6
  export async function runServe() {
6
7
  const config = loadConfig();
7
8
  const tokenManager = new TokenManager(config.auth_url);
9
+ const downloadJobManager = new DownloadJobManager();
8
10
  // PAT mode via env var (don't persist — just keep in memory for this session)
9
11
  const pat = process.env.YT_MCP_TOKEN;
10
12
  if (pat) {
11
13
  await tokenManager.savePAT(pat);
12
14
  }
13
- const server = await createServer(config, tokenManager);
15
+ const server = await createServer(config, tokenManager, downloadJobManager);
14
16
  const transport = new StdioServerTransport();
15
17
  await server.connect(transport);
16
18
  process.on("SIGINT", async () => {