@orchyn/mcp 1.1.1 → 1.2.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/dist/index.js CHANGED
@@ -18,18 +18,7 @@ import { getBaseUrl, getPublicUrl, getPort, getCredentialsFile, getTransportMode
18
18
  import { AuthManager, OrchynAuthError, createHttpTokenProvider, createStdioTokenProvider } from "./auth.js";
19
19
  import { OrchynClient, OrchynError } from "./orchyn.js";
20
20
  import { OAuthManager } from "./oauth.js";
21
- import { formatPaywallError, runVideoAnalysis, validateVideoUrl } from "./video.js";
22
- const TOOL_NAME = "analyze_video";
23
- const TOOL_DESCRIPTION = "Start an AI analysis of a TikTok, Instagram, or YouTube video from its link. " +
24
- "Requires a connected orchyn account; consumes orchyn credits (first analysis free). " +
25
- "Returns the analysis result once finished.";
26
- const TOOL_INPUT_SCHEMA = z
27
- .object({
28
- url: z
29
- .string()
30
- .describe("Public video URL (tiktok.com, instagram.com, youtube.com, youtu.be, or common shortlinks)."),
31
- })
32
- .strict();
21
+ import { formatPaywallError, runVideoAnalysis, validatePostUrl } from "./video.js";
33
22
  function toToolResult(proxy) {
34
23
  const images = proxy.contentBlocks
35
24
  .filter((c) => c.type === "image")
@@ -48,19 +37,25 @@ function toolError(prefix, err) {
48
37
  export function createServer(opts) {
49
38
  const server = new McpServer({
50
39
  name: "orchyn-mcp",
51
- version: "1.0.0",
40
+ version: "1.1.0",
52
41
  });
53
- server.registerTool(TOOL_NAME, {
54
- title: "Analyze Video",
55
- description: TOOL_DESCRIPTION,
56
- inputSchema: TOOL_INPUT_SCHEMA,
42
+ server.registerTool("analyze_post", {
43
+ title: "Analyze Post",
44
+ description: "Analyze a social post (video, image, carousel/slideshow) from its link — " +
45
+ "imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). " +
46
+ "Supports TikTok, Instagram, YouTube and X/Twitter. Returns the full analysis once finished.",
47
+ inputSchema: z
48
+ .object({
49
+ url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X or shortlinks)."),
50
+ })
51
+ .strict(),
57
52
  }, async (args, extra) => {
58
53
  let session;
59
54
  if (extra.authInfo?.token && opts.resolveSession) {
60
55
  session = opts.resolveSession(extra.authInfo.token);
61
56
  }
62
57
  const client = opts.makeClient(session);
63
- const validation = validateVideoUrl(args.url);
58
+ const validation = validatePostUrl(args.url);
64
59
  if (!validation.ok) {
65
60
  return {
66
61
  content: [{ type: "text", text: `Invalid url: ${validation.error}` }],
@@ -69,33 +64,54 @@ export function createServer(opts) {
69
64
  }
70
65
  try {
71
66
  const result = await runVideoAnalysis(client, validation.url);
72
- return {
73
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
74
- };
67
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
75
68
  }
76
69
  catch (err) {
77
70
  if (err instanceof OrchynError && err.paywall) {
78
71
  return {
79
- content: [
80
- {
81
- type: "text",
82
- text: `Analysis blocked: ${formatPaywallError(err)}\n\nHTTP ${err.status}: ${err.message}`,
83
- },
84
- ],
72
+ content: [{ type: "text", text: `Analysis blocked: ${formatPaywallError(err)}\n\nHTTP ${err.status}: ${err.message}` }],
85
73
  isError: true,
86
74
  };
87
75
  }
88
76
  const msg = err instanceof Error ? err.message : String(err);
77
+ return { content: [{ type: "text", text: `Analysis failed: ${msg}` }], isError: true };
78
+ }
79
+ });
80
+ server.registerTool("analyze_video", {
81
+ title: "Analyze Video",
82
+ description: "[Deprecated — use analyze_post] Alias of analyze_post — analyzes any post type.",
83
+ inputSchema: z
84
+ .object({
85
+ url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X or shortlinks)."),
86
+ })
87
+ .strict(),
88
+ }, async (args, extra) => {
89
+ let session;
90
+ if (extra.authInfo?.token && opts.resolveSession) {
91
+ session = opts.resolveSession(extra.authInfo.token);
92
+ }
93
+ const client = opts.makeClient(session);
94
+ const validation = validatePostUrl(args.url);
95
+ if (!validation.ok) {
89
96
  return {
90
- content: [
91
- {
92
- type: "text",
93
- text: `Video analysis failed: ${msg}`,
94
- },
95
- ],
97
+ content: [{ type: "text", text: `Invalid url: ${validation.error}` }],
96
98
  isError: true,
97
99
  };
98
100
  }
101
+ try {
102
+ const result = await runVideoAnalysis(client, validation.url);
103
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
104
+ }
105
+ catch (err) {
106
+ if (err instanceof OrchynError && err.paywall) {
107
+ return {
108
+ content: [{ type: "text", text: `Analysis blocked: ${formatPaywallError(err)}\n\nHTTP ${err.status}: ${err.message}` }],
109
+ isError: true,
110
+ };
111
+ }
112
+ const msg = err instanceof Error ? err.message : String(err);
113
+ return { content: [{ type: "text", text: `Analysis failed: ${msg}` }], isError: true };
114
+ }
99
115
  });
100
116
  server.registerTool("get_social_media", {
101
117
  title: "Get Social Media",
@@ -408,11 +424,13 @@ export async function runLogin(opts) {
408
424
  process.stdout.write(`Signed in as ${session.user?.email ?? opts.email}. Credentials saved to ${auth.getCredentialsFile()}\n`);
409
425
  return;
410
426
  }
411
- // Browser flow with a loopback listener.
427
+ // Browser flow: open the orchyn-branded login page (Google + email/password)
428
+ // at /auth/mcp-login?redirect=<loopback callback>. Both paths converge on a
429
+ // single ?code= redirect the listener exchanges for JWTs.
412
430
  const callbackPath = "/oauth/callback";
413
431
  const callbackUrl = `http://127.0.0.1:${opts.port}${callbackPath}`;
414
- const googleStart = new URL("/auth/google/start", baseUrl);
415
- googleStart.searchParams.set("redirect", callbackUrl);
432
+ const mcpLoginUrl = new URL("/auth/mcp-login", baseUrl);
433
+ mcpLoginUrl.searchParams.set("redirect", callbackUrl);
416
434
  const listener = http.createServer((req, res) => {
417
435
  const reqUrl = new URL(req.url ?? "/", "http://127.0.0.1");
418
436
  if (reqUrl.pathname === callbackPath && req.method === "GET") {
@@ -448,10 +466,10 @@ export async function runLogin(opts) {
448
466
  listener.once("error", reject);
449
467
  listener.listen(opts.port, "127.0.0.1", resolve);
450
468
  });
451
- process.stdout.write(`Open this URL in your browser to sign in with your orchyn account:\n\n ${googleStart.toString()}\n\n`);
469
+ process.stdout.write(`Open this URL in your browser to sign in with your orchyn account:\n\n ${mcpLoginUrl.toString()}\n\n`);
452
470
  try {
453
471
  const { default: open } = await import("open");
454
- await open(googleStart.toString());
472
+ await open(mcpLoginUrl.toString());
455
473
  }
456
474
  catch {
457
475
  process.stdout.write("Could not open the browser automatically. Copy the URL above into your browser.\n");
package/dist/video.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
  export const POLL_INTERVAL_MS = 2000;
6
6
  export const POLL_TIMEOUT_MS = 300_000;
7
- const SUPPORTED_HOSTS = new Set([
7
+ const SUPPORTED_HOSTS_VIDEO = new Set([
8
8
  "tiktok.com",
9
9
  "vm.tiktok.com",
10
10
  "instagram.com",
@@ -15,7 +15,13 @@ const SUPPORTED_HOSTS = new Set([
15
15
  "youtube-nocookie.com",
16
16
  "m.tiktok.com",
17
17
  ]);
18
- export function validateVideoUrl(rawUrl) {
18
+ const SUPPORTED_HOSTS_POST = new Set([
19
+ ...SUPPORTED_HOSTS_VIDEO,
20
+ "x.com",
21
+ "twitter.com",
22
+ "mobile.twitter.com",
23
+ ]);
24
+ function validateUrl(rawUrl, allowed) {
19
25
  if (typeof rawUrl !== "string" || rawUrl.trim() === "") {
20
26
  return { ok: false, error: "url must be a non-empty string." };
21
27
  }
@@ -36,7 +42,7 @@ export function validateVideoUrl(rawUrl) {
36
42
  // accept all youtube.com paths, incl. /shorts/<id>
37
43
  return { ok: true, url: parsed.toString() };
38
44
  }
39
- if (SUPPORTED_HOSTS.has(host)) {
45
+ if (allowed.has(host)) {
40
46
  return { ok: true, url: parsed.toString() };
41
47
  }
42
48
  return {
@@ -44,6 +50,12 @@ export function validateVideoUrl(rawUrl) {
44
50
  error: "url host is not supported. Supported: tiktok.com, vm.tiktok.com, instagram.com, instagr.am, youtube.com, youtu.be, m.youtube.com (and /shorts).",
45
51
  };
46
52
  }
53
+ export function validateVideoUrl(rawUrl) {
54
+ return validateUrl(rawUrl, SUPPORTED_HOSTS_VIDEO);
55
+ }
56
+ export function validatePostUrl(rawUrl) {
57
+ return validateUrl(rawUrl, SUPPORTED_HOSTS_POST);
58
+ }
47
59
  export class JobTimeoutError extends Error {
48
60
  constructor(jobId, elapsedMs, lastStatus) {
49
61
  super(`Timed out after ${Math.round(elapsedMs / 1000)}s waiting for analysis job ${jobId} to finish.` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchyn/mcp",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for orchyn - fetch, discover and understand TikTok/Instagram/YouTube/X posts with AI (media metadata, niche discovery, hook & viral analysis)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",