@liorium/youtube-omni-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.
Files changed (92) hide show
  1. package/.env.example +11 -0
  2. package/README.md +86 -0
  3. package/dist/src/config.d.ts +10 -0
  4. package/dist/src/config.js +15 -0
  5. package/dist/src/config.js.map +1 -0
  6. package/dist/src/core/envelope.d.ts +40 -0
  7. package/dist/src/core/envelope.js +31 -0
  8. package/dist/src/core/envelope.js.map +1 -0
  9. package/dist/src/core/errors.d.ts +44 -0
  10. package/dist/src/core/errors.js +25 -0
  11. package/dist/src/core/errors.js.map +1 -0
  12. package/dist/src/core/provider-registry.d.ts +6 -0
  13. package/dist/src/core/provider-registry.js +14 -0
  14. package/dist/src/core/provider-registry.js.map +1 -0
  15. package/dist/src/core/quota.d.ts +2 -0
  16. package/dist/src/core/quota.js +15 -0
  17. package/dist/src/core/quota.js.map +1 -0
  18. package/dist/src/core/redaction.d.ts +1 -0
  19. package/dist/src/core/redaction.js +23 -0
  20. package/dist/src/core/redaction.js.map +1 -0
  21. package/dist/src/core/risk.d.ts +9 -0
  22. package/dist/src/core/risk.js +31 -0
  23. package/dist/src/core/risk.js.map +1 -0
  24. package/dist/src/index.d.ts +2 -0
  25. package/dist/src/index.js +50 -0
  26. package/dist/src/index.js.map +1 -0
  27. package/dist/src/providers/mock-provider.d.ts +150 -0
  28. package/dist/src/providers/mock-provider.js +61 -0
  29. package/dist/src/providers/mock-provider.js.map +1 -0
  30. package/dist/src/providers/transcript-provider.d.ts +9 -0
  31. package/dist/src/providers/transcript-provider.js +9 -0
  32. package/dist/src/providers/transcript-provider.js.map +1 -0
  33. package/dist/src/providers/types.d.ts +85 -0
  34. package/dist/src/providers/types.js +2 -0
  35. package/dist/src/providers/types.js.map +1 -0
  36. package/dist/src/providers/youtube-data-api.d.ts +11 -0
  37. package/dist/src/providers/youtube-data-api.js +13 -0
  38. package/dist/src/providers/youtube-data-api.js.map +1 -0
  39. package/dist/src/schemas/channel.d.ts +19 -0
  40. package/dist/src/schemas/channel.js +11 -0
  41. package/dist/src/schemas/channel.js.map +1 -0
  42. package/dist/src/schemas/comments.d.ts +20 -0
  43. package/dist/src/schemas/comments.js +16 -0
  44. package/dist/src/schemas/comments.js.map +1 -0
  45. package/dist/src/schemas/common.d.ts +11 -0
  46. package/dist/src/schemas/common.js +8 -0
  47. package/dist/src/schemas/common.js.map +1 -0
  48. package/dist/src/schemas/discovery.d.ts +58 -0
  49. package/dist/src/schemas/discovery.js +23 -0
  50. package/dist/src/schemas/discovery.js.map +1 -0
  51. package/dist/src/schemas/index.d.ts +7 -0
  52. package/dist/src/schemas/index.js +8 -0
  53. package/dist/src/schemas/index.js.map +1 -0
  54. package/dist/src/schemas/planning.d.ts +9 -0
  55. package/dist/src/schemas/planning.js +11 -0
  56. package/dist/src/schemas/planning.js.map +1 -0
  57. package/dist/src/schemas/transcript.d.ts +18 -0
  58. package/dist/src/schemas/transcript.js +10 -0
  59. package/dist/src/schemas/transcript.js.map +1 -0
  60. package/dist/src/schemas/video.d.ts +14 -0
  61. package/dist/src/schemas/video.js +9 -0
  62. package/dist/src/schemas/video.js.map +1 -0
  63. package/dist/src/server.d.ts +78 -0
  64. package/dist/src/server.js +47 -0
  65. package/dist/src/server.js.map +1 -0
  66. package/dist/src/tools/channel.d.ts +8 -0
  67. package/dist/src/tools/channel.js +15 -0
  68. package/dist/src/tools/channel.js.map +1 -0
  69. package/dist/src/tools/comments.d.ts +15 -0
  70. package/dist/src/tools/comments.js +15 -0
  71. package/dist/src/tools/comments.js.map +1 -0
  72. package/dist/src/tools/discovery.d.ts +8 -0
  73. package/dist/src/tools/discovery.js +15 -0
  74. package/dist/src/tools/discovery.js.map +1 -0
  75. package/dist/src/tools/planning.d.ts +13 -0
  76. package/dist/src/tools/planning.js +9 -0
  77. package/dist/src/tools/planning.js.map +1 -0
  78. package/dist/src/tools/safety.d.ts +10 -0
  79. package/dist/src/tools/safety.js +10 -0
  80. package/dist/src/tools/safety.js.map +1 -0
  81. package/dist/src/tools/transcript.d.ts +17 -0
  82. package/dist/src/tools/transcript.js +21 -0
  83. package/dist/src/tools/transcript.js.map +1 -0
  84. package/dist/src/tools/video.d.ts +10 -0
  85. package/dist/src/tools/video.js +16 -0
  86. package/dist/src/tools/video.js.map +1 -0
  87. package/docs/architecture.md +11 -0
  88. package/docs/provider-capability-map.md +5 -0
  89. package/docs/safety-policy.md +5 -0
  90. package/docs/tool-schema.md +5 -0
  91. package/docs/v0-spec.md +5 -0
  92. package/package.json +51 -0
package/.env.example ADDED
@@ -0,0 +1,11 @@
1
+ # Optional for public YouTube Data API reads
2
+ YOUTUBE_API_KEY=
3
+
4
+ # Optional cache/outlier provider, disabled by default
5
+ YOUTUBE_OMNI_ENABLE_OUTLIER=false
6
+ YOUTUBE_OMNI_MONGO_URI=
7
+
8
+ # Safety defaults
9
+ YOUTUBE_OMNI_MAX_RESULTS=50
10
+ YOUTUBE_OMNI_ENABLE_WRITE_TOOLS=false
11
+ YOUTUBE_OMNI_ENABLE_DOWNLOAD_TOOLS=false
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # YouTube Omni MCP
2
+
3
+ `youtube-omni` is a full-scope YouTube Creator Intelligence + Operations + Analytics + Production MCP project.
4
+
5
+ Current implementation phase: **v0 read-only foundation**.
6
+
7
+ ## v0 boundary
8
+
9
+ Enabled in v0:
10
+
11
+ - public discovery/search
12
+ - video details/comparison
13
+ - channel statistics/top videos
14
+ - transcript segments/full text/structure analysis
15
+ - comments read/summarize
16
+ - research brief drafts
17
+ - safety/capability reporting
18
+
19
+ Locked for later phases, but part of final scope:
20
+
21
+ - upload/scheduling
22
+ - metadata/thumbnail/playlist writes
23
+ - comments/community writes
24
+ - YouTube Analytics/Reporting/revenue/retention
25
+ - yt-dlp download/clip/audio/frame extraction
26
+ - Shorts production
27
+ - YouTube Music private/library features
28
+ - governed automation
29
+
30
+ ## Safety defaults
31
+
32
+ - No write tools are exposed in v0.
33
+ - No download tools are exposed in v0.
34
+ - No Hermes MCP registration is performed by this repository setup.
35
+ - Secrets must be supplied via environment variables and are never committed.
36
+
37
+ ## Development
38
+
39
+ ```bash
40
+ npm install
41
+ npm test
42
+ npm run build
43
+ npm run validate:v0
44
+ npm run validate:secrets
45
+ npm run smoke:mcp
46
+ ```
47
+
48
+ ## Environment
49
+
50
+ See `.env.example`.
51
+
52
+ ## Use with npx / Hermes MCP
53
+
54
+ The package is intended to run as a stdio MCP server via `npx`:
55
+
56
+ ```bash
57
+ npx -y @liorium/youtube-omni-mcp
58
+ ```
59
+
60
+ Hermes config example:
61
+
62
+ ```yaml
63
+ mcp_servers:
64
+ youtube-omni:
65
+ command: "npx"
66
+ args:
67
+ - "-y"
68
+ - "@liorium/youtube-omni-mcp"
69
+ timeout: 300
70
+ connect_timeout: 60
71
+ env:
72
+ YOUTUBE_API_KEY: "..."
73
+ YOUTUBE_OMNI_ENABLE_WRITE_TOOLS: "false"
74
+ YOUTUBE_OMNI_ENABLE_DOWNLOAD_TOOLS: "false"
75
+ ```
76
+
77
+ For local development before publishing, use a packed tarball:
78
+
79
+ ```bash
80
+ npm pack
81
+ npx -y ./liorium-youtube-omni-mcp-0.1.0.tgz
82
+ ```
83
+
84
+ ## Research and specs
85
+
86
+ All research/specification documents are under `research/`. Implementation docs copied for convenience are under `docs/`.
@@ -0,0 +1,10 @@
1
+ export interface YoutubeOmniConfig {
2
+ youtubeApiKey?: string;
3
+ enableOutlier: boolean;
4
+ mongoUri?: string;
5
+ maxResults: number;
6
+ enableWriteTools: boolean;
7
+ enableDownloadTools: boolean;
8
+ }
9
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): YoutubeOmniConfig;
10
+ export declare function redactedConfig(config: YoutubeOmniConfig): Record<string, unknown>;
@@ -0,0 +1,15 @@
1
+ import { redactSecrets } from './core/redaction.js';
2
+ export function loadConfig(env = process.env) {
3
+ return {
4
+ youtubeApiKey: env.YOUTUBE_API_KEY,
5
+ enableOutlier: env.YOUTUBE_OMNI_ENABLE_OUTLIER === 'true',
6
+ mongoUri: env.YOUTUBE_OMNI_MONGO_URI,
7
+ maxResults: Number(env.YOUTUBE_OMNI_MAX_RESULTS ?? 50),
8
+ enableWriteTools: env.YOUTUBE_OMNI_ENABLE_WRITE_TOOLS === 'true',
9
+ enableDownloadTools: env.YOUTUBE_OMNI_ENABLE_DOWNLOAD_TOOLS === 'true'
10
+ };
11
+ }
12
+ export function redactedConfig(config) {
13
+ return JSON.parse(redactSecrets(JSON.stringify(config)));
14
+ }
15
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAWpD,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC7D,OAAO;QACL,aAAa,EAAE,GAAG,CAAC,eAAe;QAClC,aAAa,EAAE,GAAG,CAAC,2BAA2B,KAAK,MAAM;QACzD,QAAQ,EAAE,GAAG,CAAC,sBAAsB;QACpC,UAAU,EAAE,MAAM,CAAC,GAAG,CAAC,wBAAwB,IAAI,EAAE,CAAC;QACtD,gBAAgB,EAAE,GAAG,CAAC,+BAA+B,KAAK,MAAM;QAChE,mBAAmB,EAAE,GAAG,CAAC,kCAAkC,KAAK,MAAM;KACvE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAyB;IACtD,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { YoutubeOmniErrorCode } from './errors.js';
2
+ export type AuthTier = 'none' | 'api_key' | 'oauth_readonly' | 'oauth_write' | 'hosted' | 'experimental';
3
+ export interface ToolEnvelope<T> {
4
+ ok: boolean;
5
+ tool: string;
6
+ timestamp: string;
7
+ source: {
8
+ provider: string;
9
+ authTier: AuthTier;
10
+ quotaCost?: number;
11
+ cacheHit?: boolean;
12
+ };
13
+ data?: T;
14
+ warnings: string[];
15
+ nextActions?: string[];
16
+ error?: {
17
+ code: YoutubeOmniErrorCode;
18
+ message: string;
19
+ retryable: boolean;
20
+ };
21
+ }
22
+ export declare function makeEnvelope<T>(input: {
23
+ tool: string;
24
+ provider: string;
25
+ authTier: AuthTier;
26
+ data: T;
27
+ warnings?: string[];
28
+ nextActions?: string[];
29
+ quotaCost?: number;
30
+ cacheHit?: boolean;
31
+ }): ToolEnvelope<T>;
32
+ export declare function makeErrorEnvelope(input: {
33
+ tool: string;
34
+ provider: string;
35
+ authTier: AuthTier;
36
+ code: YoutubeOmniErrorCode;
37
+ message: string;
38
+ retryable: boolean;
39
+ warnings?: string[];
40
+ }): ToolEnvelope<never>;
@@ -0,0 +1,31 @@
1
+ export function makeEnvelope(input) {
2
+ return {
3
+ ok: true,
4
+ tool: input.tool,
5
+ timestamp: new Date().toISOString(),
6
+ source: {
7
+ provider: input.provider,
8
+ authTier: input.authTier,
9
+ quotaCost: input.quotaCost,
10
+ cacheHit: input.cacheHit
11
+ },
12
+ data: input.data,
13
+ warnings: input.warnings ?? [],
14
+ nextActions: input.nextActions ?? []
15
+ };
16
+ }
17
+ export function makeErrorEnvelope(input) {
18
+ return {
19
+ ok: false,
20
+ tool: input.tool,
21
+ timestamp: new Date().toISOString(),
22
+ source: { provider: input.provider, authTier: input.authTier },
23
+ warnings: input.warnings ?? [],
24
+ error: {
25
+ code: input.code,
26
+ message: input.message,
27
+ retryable: input.retryable
28
+ }
29
+ };
30
+ }
31
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../src/core/envelope.ts"],"names":[],"mappings":"AAwBA,MAAM,UAAU,YAAY,CAAI,KAS/B;IACC,OAAO;QACL,EAAE,EAAE,IAAI;QACR,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,MAAM,EAAE;YACN,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,QAAQ,EAAE,KAAK,CAAC,QAAQ;SACzB;QACD,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;QAC9B,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAQjC;IACC,OAAO;QACL,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;QAC9D,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,EAAE;QAC9B,KAAK,EAAE;YACL,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,44 @@
1
+ export declare const YOUTUBE_OMNI_ERRORS: {
2
+ readonly INVALID_ARGUMENT: {
3
+ readonly retryable: false;
4
+ };
5
+ readonly PROVIDER_NOT_CONFIGURED: {
6
+ readonly retryable: false;
7
+ };
8
+ readonly AUTH_REQUIRED: {
9
+ readonly retryable: false;
10
+ };
11
+ readonly QUOTA_EXCEEDED: {
12
+ readonly retryable: true;
13
+ };
14
+ readonly RATE_LIMITED: {
15
+ readonly retryable: true;
16
+ };
17
+ readonly VIDEO_NOT_FOUND: {
18
+ readonly retryable: false;
19
+ };
20
+ readonly CHANNEL_NOT_FOUND: {
21
+ readonly retryable: false;
22
+ };
23
+ readonly TRANSCRIPT_UNAVAILABLE: {
24
+ readonly retryable: false;
25
+ };
26
+ readonly COMMENTS_DISABLED: {
27
+ readonly retryable: false;
28
+ };
29
+ readonly UNSUPPORTED_REGION: {
30
+ readonly retryable: false;
31
+ };
32
+ readonly PROVIDER_ERROR: {
33
+ readonly retryable: true;
34
+ };
35
+ readonly SAFETY_BLOCKED: {
36
+ readonly retryable: false;
37
+ };
38
+ };
39
+ export type YoutubeOmniErrorCode = keyof typeof YOUTUBE_OMNI_ERRORS;
40
+ export declare class YoutubeOmniError extends Error {
41
+ readonly code: YoutubeOmniErrorCode;
42
+ readonly retryable: boolean;
43
+ constructor(code: YoutubeOmniErrorCode, message: string);
44
+ }
@@ -0,0 +1,25 @@
1
+ export const YOUTUBE_OMNI_ERRORS = {
2
+ INVALID_ARGUMENT: { retryable: false },
3
+ PROVIDER_NOT_CONFIGURED: { retryable: false },
4
+ AUTH_REQUIRED: { retryable: false },
5
+ QUOTA_EXCEEDED: { retryable: true },
6
+ RATE_LIMITED: { retryable: true },
7
+ VIDEO_NOT_FOUND: { retryable: false },
8
+ CHANNEL_NOT_FOUND: { retryable: false },
9
+ TRANSCRIPT_UNAVAILABLE: { retryable: false },
10
+ COMMENTS_DISABLED: { retryable: false },
11
+ UNSUPPORTED_REGION: { retryable: false },
12
+ PROVIDER_ERROR: { retryable: true },
13
+ SAFETY_BLOCKED: { retryable: false }
14
+ };
15
+ export class YoutubeOmniError extends Error {
16
+ code;
17
+ retryable;
18
+ constructor(code, message) {
19
+ super(`${code}: ${message}`);
20
+ this.name = 'YoutubeOmniError';
21
+ this.code = code;
22
+ this.retryable = YOUTUBE_OMNI_ERRORS[code].retryable;
23
+ }
24
+ }
25
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/core/errors.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IACtC,uBAAuB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IAC7C,aAAa,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IACnC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;IACnC,YAAY,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;IACjC,eAAe,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IACrC,iBAAiB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IACvC,sBAAsB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IAC5C,iBAAiB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IACvC,kBAAkB,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;IACxC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;IACnC,cAAc,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;CAC5B,CAAC;AAIX,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,CAAuB;IAC3B,SAAS,CAAU;IAE5B,YAAY,IAA0B,EAAE,OAAe;QACrD,KAAK,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC;IACvD,CAAC;CACF"}
@@ -0,0 +1,6 @@
1
+ import type { YoutubeOmniProvider } from '../providers/types.js';
2
+ export declare class ProviderRegistry {
3
+ readonly providers: YoutubeOmniProvider[];
4
+ constructor(providers: YoutubeOmniProvider[]);
5
+ requireCapability(capability: string): YoutubeOmniProvider;
6
+ }
@@ -0,0 +1,14 @@
1
+ import { YoutubeOmniError } from './errors.js';
2
+ export class ProviderRegistry {
3
+ providers;
4
+ constructor(providers) {
5
+ this.providers = providers;
6
+ }
7
+ requireCapability(capability) {
8
+ const provider = this.providers.find((candidate) => candidate.isConfigured() && candidate.capabilities.includes(capability));
9
+ if (!provider)
10
+ throw new YoutubeOmniError('PROVIDER_NOT_CONFIGURED', `No provider configured for capability: ${capability}`);
11
+ return provider;
12
+ }
13
+ }
14
+ //# sourceMappingURL=provider-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-registry.js","sourceRoot":"","sources":["../../../src/core/provider-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,OAAO,gBAAgB;IAClB,SAAS,CAAwB;IAE1C,YAAY,SAAgC;QAC1C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,iBAAiB,CAAC,UAAkB;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;QAC7H,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,gBAAgB,CAAC,yBAAyB,EAAE,0CAA0C,UAAU,EAAE,CAAC,CAAC;QAC7H,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export declare function estimateQuotaCost(toolName: string): number;
2
+ export declare function enforceMaxResults(value: number, hardCap?: number): number;
@@ -0,0 +1,15 @@
1
+ export function estimateQuotaCost(toolName) {
2
+ if (toolName.includes('search'))
3
+ return 100;
4
+ if (toolName.includes('get_video_categories'))
5
+ return 1;
6
+ if (toolName.includes('comments'))
7
+ return 1;
8
+ if (toolName.includes('transcript'))
9
+ return 0;
10
+ return 1;
11
+ }
12
+ export function enforceMaxResults(value, hardCap = 50) {
13
+ return Math.min(Math.max(1, value), hardCap);
14
+ }
15
+ //# sourceMappingURL=quota.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.js","sourceRoot":"","sources":["../../../src/core/quota.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,GAAG,CAAC;IAC5C,IAAI,QAAQ,CAAC,QAAQ,CAAC,sBAAsB,CAAC;QAAE,OAAO,CAAC,CAAC;IACxD,IAAI,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC;QAAE,OAAO,CAAC,CAAC;IAC5C,IAAI,QAAQ,CAAC,QAAQ,CAAC,YAAY,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9C,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,OAAO,GAAG,EAAE;IAC3D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;AAC/C,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function redactSecrets(value: string): string;
@@ -0,0 +1,23 @@
1
+ const SECRET_PATTERNS = [
2
+ /AIza[0-9A-Za-z_\-]+/g,
3
+ /(Authorization:\s*Bearer\s+)[^\s;]+/gi,
4
+ /(Bearer\s+)[A-Za-z0-9._\-]+/g,
5
+ /(token\s*=\s*)[^\s;]+/gi,
6
+ /(key\s*=\s*)[^\s;]+/gi,
7
+ /(api[_-]?key\s*=\s*)[^\s;]+/gi,
8
+ /(password\s*=\s*)[^\s;]+/gi,
9
+ /(secret\s*=\s*)[^\s;]+/gi,
10
+ /(Cookie:\s*)[^\n]+/gi,
11
+ /([A-Z0-9_]*TOKEN[A-Z0-9_]*\s*=\s*)[^\s;]+/gi,
12
+ /(YOUTUBE_API_KEY\s*=\s*)[^\s;]+/gi
13
+ ];
14
+ export function redactSecrets(value) {
15
+ return SECRET_PATTERNS.reduce((acc, pattern) => {
16
+ return acc.replace(pattern, (match, prefix) => {
17
+ if (typeof prefix === 'string' && match.startsWith(prefix))
18
+ return `${prefix}[REDACTED]`;
19
+ return '[REDACTED]';
20
+ });
21
+ }, value);
22
+ }
23
+ //# sourceMappingURL=redaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../../src/core/redaction.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAa;IAChC,sBAAsB;IACtB,uCAAuC;IACvC,8BAA8B;IAC9B,yBAAyB;IACzB,uBAAuB;IACvB,+BAA+B;IAC/B,4BAA4B;IAC5B,0BAA0B;IAC1B,sBAAsB;IACtB,6CAA6C;IAC7C,mCAAmC;CACpC,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE;QAC7C,OAAO,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;YAC5C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,OAAO,GAAG,MAAM,YAAY,CAAC;YACzF,OAAO,YAAY,CAAC;QACtB,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,KAAK,CAAC,CAAC;AACZ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare const V0_LOCKED_PATTERNS: readonly ["upload", "update_video", "delete", "set_thumbnail", "reply_to_comment", "moderate", "publish", "download_video", "clip_segment", "revenue"];
2
+ export interface ActionRisk {
3
+ riskTier: 0 | 1 | 2 | 3 | 4 | 5;
4
+ authTier: 'none' | 'api_key' | 'oauth_readonly' | 'oauth_write' | 'hosted' | 'experimental';
5
+ allowedInV0: boolean;
6
+ requiredControls: string[];
7
+ }
8
+ export declare function isAllowedInV0(action: string): boolean;
9
+ export declare function classifyActionRisk(action: string): ActionRisk;
@@ -0,0 +1,31 @@
1
+ export const V0_LOCKED_PATTERNS = [
2
+ 'upload',
3
+ 'update_video',
4
+ 'delete',
5
+ 'set_thumbnail',
6
+ 'reply_to_comment',
7
+ 'moderate',
8
+ 'publish',
9
+ 'download_video',
10
+ 'clip_segment',
11
+ 'revenue'
12
+ ];
13
+ export function isAllowedInV0(action) {
14
+ return !V0_LOCKED_PATTERNS.some((pattern) => action.includes(pattern));
15
+ }
16
+ export function classifyActionRisk(action) {
17
+ if (!isAllowedInV0(action)) {
18
+ const destructive = ['delete', 'download_video', 'clip_segment', 'revenue'].some((pattern) => action.includes(pattern));
19
+ return {
20
+ riskTier: destructive ? 5 : 4,
21
+ authTier: action.includes('download') || action.includes('clip') ? 'experimental' : 'oauth_write',
22
+ allowedInV0: false,
23
+ requiredControls: destructive ? ['approval', 'double_confirmation', 'audit'] : ['approval', 'preview', 'audit']
24
+ };
25
+ }
26
+ if (action.includes('transcript') || action.includes('comments') || action.includes('planning')) {
27
+ return { riskTier: 1, authTier: 'api_key', allowedInV0: true, requiredControls: ['rate_limit', 'privacy_warning'] };
28
+ }
29
+ return { riskTier: 0, authTier: 'api_key', allowedInV0: true, requiredControls: ['rate_limit'] };
30
+ }
31
+ //# sourceMappingURL=risk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk.js","sourceRoot":"","sources":["../../../src/core/risk.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,QAAQ;IACR,cAAc;IACd,QAAQ;IACR,eAAe;IACf,kBAAkB;IAClB,UAAU;IACV,SAAS;IACT,gBAAgB;IAChB,cAAc;IACd,SAAS;CACD,CAAC;AASX,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QACxH,OAAO;YACL,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa;YACjG,WAAW,EAAE,KAAK;YAClB,gBAAgB,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,qBAAqB,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC;SAChH,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QAChG,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,YAAY,EAAE,iBAAiB,CAAC,EAAE,CAAC;IACtH,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,YAAY,CAAC,EAAE,CAAC;AACnG,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ import { createInterface } from 'node:readline';
3
+ import { createToolHandlers, V0_TOOL_NAMES } from './server.js';
4
+ import { redactSecrets } from './core/redaction.js';
5
+ const handlers = createToolHandlers();
6
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: false });
7
+ process.stdout.on('error', (caught) => {
8
+ if (caught.code === 'EPIPE')
9
+ process.exit(0);
10
+ throw caught;
11
+ });
12
+ function respond(id, result) {
13
+ process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result })}\n`);
14
+ }
15
+ function error(id, code, message) {
16
+ process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, error: { code, message: redactSecrets(message) } })}\n`);
17
+ }
18
+ rl.on('line', async (line) => {
19
+ if (!line.trim())
20
+ return;
21
+ try {
22
+ const request = JSON.parse(line);
23
+ if (request.method === 'initialize') {
24
+ respond(request.id, { protocolVersion: '2024-11-05', capabilities: { tools: {} }, serverInfo: { name: 'youtube-omni-mcp', version: '0.1.0' } });
25
+ return;
26
+ }
27
+ if (request.method === 'tools/list') {
28
+ respond(request.id, { tools: V0_TOOL_NAMES.map((name) => ({ name, description: `YouTube Omni v0 read-only tool: ${name}`, inputSchema: { type: 'object' } })) });
29
+ return;
30
+ }
31
+ if (request.method === 'tools/call') {
32
+ const name = request.params?.name;
33
+ const handler = handlers[name];
34
+ if (!handler) {
35
+ error(request.id, -32601, `Unknown tool: ${String(name)}`);
36
+ return;
37
+ }
38
+ const result = await handler(request.params?.arguments ?? {});
39
+ respond(request.id, { content: [{ type: 'text', text: JSON.stringify(result) }] });
40
+ return;
41
+ }
42
+ if (request.method === 'notifications/initialized')
43
+ return;
44
+ error(request.id, -32601, `Unknown method: ${request.method}`);
45
+ }
46
+ catch (caught) {
47
+ error(null, -32700, caught instanceof Error ? caught.message : String(caught));
48
+ }
49
+ });
50
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,150 @@
1
+ import type { ProviderHealth, YoutubeOmniProvider } from './types.js';
2
+ export declare class MockYoutubeProvider implements YoutubeOmniProvider {
3
+ id: string;
4
+ name: string;
5
+ authTier: "none";
6
+ capabilities: string[];
7
+ isConfigured(): boolean;
8
+ healthCheck(): Promise<ProviderHealth>;
9
+ searchVideos(input: {
10
+ query: string;
11
+ maxResults: number;
12
+ }): Promise<{
13
+ videos: {
14
+ videoId: string;
15
+ title: string;
16
+ channelId: string;
17
+ channelTitle: string;
18
+ publishedAt: string;
19
+ descriptionSnippet: string;
20
+ url: string;
21
+ }[];
22
+ }>;
23
+ searchChannels(input: {
24
+ query: string;
25
+ maxResults: number;
26
+ }): Promise<{
27
+ channels: {
28
+ channelId: string;
29
+ title: string;
30
+ descriptionSnippet: string;
31
+ url: string;
32
+ }[];
33
+ }>;
34
+ getTrendingVideos(input: {
35
+ maxResults: number;
36
+ }): Promise<{
37
+ videos: {
38
+ videoId: string;
39
+ title: string;
40
+ channelId: string;
41
+ channelTitle: string;
42
+ publishedAt: string;
43
+ descriptionSnippet: string;
44
+ url: string;
45
+ }[];
46
+ }>;
47
+ getVideoCategories(input: {
48
+ regionCode: string;
49
+ }): Promise<{
50
+ regionCode: string;
51
+ categories: {
52
+ id: string;
53
+ title: string;
54
+ assignable: boolean;
55
+ }[];
56
+ }>;
57
+ getVideoDetails(input: {
58
+ videoIds: string[];
59
+ includeTags?: boolean;
60
+ }): Promise<{
61
+ videos: {
62
+ videoId: string;
63
+ title: string;
64
+ channelId: string;
65
+ channelTitle: string;
66
+ publishedAt: string;
67
+ duration: string;
68
+ categoryId: string;
69
+ statistics: {
70
+ viewCount: number;
71
+ likeCount: number;
72
+ commentCount: number;
73
+ };
74
+ tags: string[];
75
+ description: string;
76
+ }[];
77
+ }>;
78
+ getChannelStatistics(input: {
79
+ channelIds: string[];
80
+ }): Promise<{
81
+ channels: {
82
+ channelId: string;
83
+ title: string;
84
+ subscriberCount: number;
85
+ viewCount: number;
86
+ videoCount: number;
87
+ publishedAt: string;
88
+ }[];
89
+ }>;
90
+ getChannelTopVideos(input: {
91
+ channelId: string;
92
+ maxResults: number;
93
+ }): Promise<{
94
+ channelId: string;
95
+ videos: {
96
+ videoId: string;
97
+ title: string;
98
+ channelId: string;
99
+ channelTitle: string;
100
+ publishedAt: string;
101
+ duration: string;
102
+ categoryId: string;
103
+ statistics: {
104
+ viewCount: number;
105
+ likeCount: number;
106
+ commentCount: number;
107
+ };
108
+ tags: string[];
109
+ description: string;
110
+ }[];
111
+ }>;
112
+ getTranscriptSegments(input: {
113
+ videoIds: string[];
114
+ lang: string;
115
+ format: 'key_segments';
116
+ }): Promise<{
117
+ transcripts: {
118
+ videoId: string;
119
+ language: string;
120
+ format: "key_segments";
121
+ intro: string;
122
+ outro: string;
123
+ availability: string;
124
+ }[];
125
+ }>;
126
+ getTranscriptFullText(input: {
127
+ videoId: string;
128
+ lang: string;
129
+ maxChars: number;
130
+ }): Promise<{
131
+ videoId: string;
132
+ language: string;
133
+ text: string;
134
+ truncated: boolean;
135
+ }>;
136
+ getVideoComments(input: {
137
+ videoId: string;
138
+ maxResults: number;
139
+ maxReplies: number;
140
+ }): Promise<{
141
+ videoId: string;
142
+ comments: {
143
+ commentId: string;
144
+ text: string;
145
+ likeCount: number;
146
+ publishedAt: string;
147
+ replyCount: number;
148
+ }[];
149
+ }>;
150
+ }