@orchyn/mcp 1.12.3 → 1.14.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 +26 -9
- package/dist/shared/tools.js +140 -62
- package/dist/shared/ui-template.js +127 -30
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
MCP (Model Context Protocol) server for [orchyn](https://orchyn.com) — fetch,
|
|
4
4
|
discover and understand social posts (TikTok, Instagram, YouTube, X/Twitter,
|
|
5
|
-
Douyin, Xiaohongshu, Bilibili) with your orchyn account and orchyn
|
|
5
|
+
Douyin, Xiaohongshu, Bilibili, LinkedIn) with your orchyn account and orchyn
|
|
6
|
+
credits.
|
|
6
7
|
|
|
7
8
|
## Install (one link)
|
|
8
9
|
|
|
@@ -38,12 +39,12 @@ npx @orchyn/mcp login # one-time sign-in (Google)
|
|
|
38
39
|
|
|
39
40
|
| Tool | Credits | Description |
|
|
40
41
|
|------|---------|-------------|
|
|
41
|
-
| `analyze_post` | first free* | **Preferred.** Analyze any post (video, image, carousel/slideshow) from a TikTok/Instagram/YouTube/X-Twitter/Douyin/Xiaohongshu/Bilibili URL — imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). Returns a `jobId` to poll via `GET /ai/analyze-post`. *First analysis free per workspace via the dashboard free grant.* |
|
|
42
|
+
| `analyze_post` | first free* | **Preferred.** Analyze any post (video, image, carousel/slideshow, text) from a TikTok/Instagram/YouTube/X-Twitter/Douyin/Xiaohongshu/Bilibili/LinkedIn URL — imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). Returns a `jobId` to poll via `GET /ai/analyze-post`. *First analysis free per workspace via the dashboard free grant.* |
|
|
42
43
|
| `get_social_media` | 1 | Fetch a post's media from a URL: `contentType` (video/image/carousel/slideshow), title, caption, author, stats, direct media URLs **+ inline thumbnail image in chat**. |
|
|
43
44
|
| `discover_social_posts` | 2 | **Preferred.** Find recent posts (video/image/carousel/slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. Each post includes title/caption, views/likes/comments, author, `externalUrl` + **inline thumbnails (4 at a time)** — see *Images in chat* below. Supports `limit`/`offset` pagination (“next”). |
|
|
44
|
-
| `get_user_posts` | 2 | List recent posts by a creator handle (e.g. `@zoundsapp`) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or
|
|
45
|
-
| `analyze_creator_profile` | 15 | Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or
|
|
46
|
-
| `get_post_comments` | 2 | Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter or
|
|
45
|
+
| `get_user_posts` | 2 | List recent posts by a creator handle (e.g. `@zoundsapp`) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn (LinkedIn uses the profile `public_id`, e.g. `billgates`) — title/caption, `thumbnailUrl`, `externalUrl`, views/likes/comments, **inline thumbnails (up to 4)**. Use to scan a whole profile or spot patterns across an account. |
|
|
46
|
+
| `analyze_creator_profile` | 15 | Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn: fetches recent posts, runs multimodal AI on up to 3, then synthesizes a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, engagement patterns, audience insights, variation ideas, collaboration fit. |
|
|
47
|
+
| `get_post_comments` | 2 | Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter, Bilibili or LinkedIn, plus keyword clusters from TikTok Analytics when available — audience sentiment/audience-signal analysis. |
|
|
47
48
|
| `search_creators` | 2 | Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, signature, verified status. Use to find influencers to vet or analyze. |
|
|
48
49
|
| `get_similar_creators` | 2 | Find lookalike creators for a given handle (TikTok similar-user recommendations or Instagram similar users) — “if this creator works, here are more like them”. |
|
|
49
50
|
| `discover_sounds` | 2 | Discover trending sounds/music for a keyword on TikTok or Instagram — sound choice is a huge ranking signal for TikTok virality. Returns title, artist, duration, play/cover URLs. |
|
|
@@ -57,9 +58,23 @@ All tools require a connected orchyn account and are billed against your orchyn
|
|
|
57
58
|
|
|
58
59
|
`*` `analyze_post` also bills against **workspace** credits (first free grant); the other tools bill against **per-user MCP** credits (never tied to an app/workspace).
|
|
59
60
|
|
|
60
|
-
##
|
|
61
|
+
## Interactive cards in Claude / ChatGPT chat
|
|
61
62
|
|
|
62
|
-
Every tool that returns posts also renders **inline
|
|
63
|
+
Every tool that returns posts also renders **inline interactive cards** directly
|
|
64
|
+
in the chat (MCP Apps `ui://orchyn/view` resource rendered in a sandboxed
|
|
65
|
+
iframe):
|
|
66
|
+
|
|
67
|
+
- **Video posts** (TikTok/IG/YouTube/Douyin/LinkedIn) — an inline `<video>`
|
|
68
|
+
player with the thumbnail as poster, playing the re-hosted permanent MP4
|
|
69
|
+
(no expiring CDN tokens).
|
|
70
|
+
- **Carousels / slideshows** — a horizontally scrollable strip of every slide
|
|
71
|
+
with an image count chip.
|
|
72
|
+
- **Single images** — inline thumbnail.
|
|
73
|
+
- **Text-only posts** (LinkedIn / X) — a styled quote block of the post text.
|
|
74
|
+
- **Official brand marks** — each card shows the platform's real logo
|
|
75
|
+
(simple-icons) in its brand color instead of an emoji.
|
|
76
|
+
|
|
77
|
+
Also **inline thumbnails** render for the model / plain-text clients:
|
|
63
78
|
|
|
64
79
|
- `get_social_media` / `understand_social_post` / `analyze_post` — up to 4 frames inline (poster + carousel slides). The full `mediaItems[].preview_url` + `thumbnailUrl` stay in `structuredContent` for the model to reason over.
|
|
65
80
|
- `discover_social_posts` / `get_user_posts` / `analyze_creator_profile` — each returned post shows its thumbnail inline (up to 4 at once) together with its title/caption + views/likes/comments. Say **"next"** or **"show more"** — Claude will re-call with `offset`/`limit` pagination. Say **"analyze the 2nd one"** — Claude calls `analyze_post` or `understand_social_post` on that URL.
|
|
@@ -256,8 +271,10 @@ per the MCP 2025-03-26 spec):
|
|
|
256
271
|
- Douyin: `douyin.com/*`
|
|
257
272
|
- Xiaohongshu: `xiaohongshu.com/*`, `xhslink.com/*`
|
|
258
273
|
- Bilibili: `bilibili.com/*`, `b23.tv/*`
|
|
274
|
+
- LinkedIn: `linkedin.com/*` (posts, profile URLs)
|
|
259
275
|
|
|
260
|
-
All tools accept these hosts and handle **video, image, carousel
|
|
276
|
+
All tools accept these hosts and handle **video, image, carousel, slideshow
|
|
277
|
+
and text** posts.
|
|
261
278
|
|
|
262
279
|
## Troubleshooting
|
|
263
280
|
|
|
@@ -302,7 +319,7 @@ package for **Claude Desktop**, **Cursor**, and **OpenAI Agents SDK**.
|
|
|
302
319
|
```bash
|
|
303
320
|
npm install
|
|
304
321
|
npm run build # tsc
|
|
305
|
-
npm test # vitest (
|
|
322
|
+
npm test # vitest (66 unit tests, mocked fetch — no network)
|
|
306
323
|
```
|
|
307
324
|
|
|
308
325
|
## License
|
package/dist/shared/tools.js
CHANGED
|
@@ -11,13 +11,54 @@ import { OrchynError } from "./orchyn.js";
|
|
|
11
11
|
import { formatPaywallError, runVideoAnalysis, validatePostUrl } from "./video.js";
|
|
12
12
|
import { ORCHYN_UI_TEMPLATE } from "./ui-template.js";
|
|
13
13
|
/** Current MCP server version — bumped on every deploy for traceability. */
|
|
14
|
-
export const MCP_SERVER_VERSION = "1.
|
|
14
|
+
export const MCP_SERVER_VERSION = "1.14.0";
|
|
15
15
|
/** MCP Apps extension identifier */
|
|
16
16
|
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
17
17
|
/** MIME type for MCP Apps HTML resources */
|
|
18
18
|
const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
19
19
|
/** Single UI resource URI shared by all tools */
|
|
20
20
|
const UI_RESOURCE_URI = "ui://orchyn/view";
|
|
21
|
+
/**
|
|
22
|
+
* claude.ai requires `ui.domain` on the resource == sha256("<MCP endpoint
|
|
23
|
+
* URL>")[:32] + ".claudemcpcontent.com" — the iframe is only revealed on
|
|
24
|
+
* that dedicated sandbox origin. The endpoint is the worker's public origin
|
|
25
|
+
* + "/mcp". stdio runs have no public URL, so the field is omitted and the
|
|
26
|
+
* host falls back to its default per-conversation origin.
|
|
27
|
+
*/
|
|
28
|
+
async function computeAppDomain() {
|
|
29
|
+
const publicUrl = (process.env.PUBLIC_URL || "").trim().replace(/\/+$/, "");
|
|
30
|
+
if (!publicUrl)
|
|
31
|
+
return undefined;
|
|
32
|
+
try {
|
|
33
|
+
const endpoint = `${publicUrl}/mcp`;
|
|
34
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(endpoint));
|
|
35
|
+
const hex = Array.from(new Uint8Array(digest))
|
|
36
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
37
|
+
.join("");
|
|
38
|
+
return `${hex.slice(0, 32)}.claudemcpcontent.com`;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Convert an arbitrary http(s) URL to a base64 MCP `image` data block. */
|
|
45
|
+
async function fetchAsBase64Image(url, mimeType) {
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(12_000) });
|
|
48
|
+
if (!res.ok)
|
|
49
|
+
return { type: "image", data: "", mimeType };
|
|
50
|
+
const buf = await res.arrayBuffer();
|
|
51
|
+
const bytes = new Uint8Array(buf);
|
|
52
|
+
let bin = "";
|
|
53
|
+
for (let i = 0; i < bytes.length; i += 0x8000) {
|
|
54
|
+
bin += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
55
|
+
}
|
|
56
|
+
return { type: "image", data: btoa(bin), mimeType };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return { type: "image", data: "", mimeType };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
21
62
|
/**
|
|
22
63
|
* Rewrite external image URLs to go through the orchyn proxy so they
|
|
23
64
|
* work inside ChatGPT's sandboxed iframe (CORS + CSP restrictions).
|
|
@@ -52,7 +93,7 @@ function proxyUrls(obj) {
|
|
|
52
93
|
if (obj && typeof obj === "object") {
|
|
53
94
|
const out = {};
|
|
54
95
|
for (const [k, v] of Object.entries(obj)) {
|
|
55
|
-
if (["thumbnailUrl", "preview_url", "coverUrl", "avatarUrl", "avatar_thumb", "image_url", "url"].includes(k) && typeof v === "string") {
|
|
96
|
+
if (["thumbnailUrl", "preview_url", "coverUrl", "avatarUrl", "avatar_thumb", "image_url", "videoUrl", "video_url", "url"].includes(k) && typeof v === "string") {
|
|
56
97
|
out[k] = proxyImageUrl(v);
|
|
57
98
|
}
|
|
58
99
|
else {
|
|
@@ -63,14 +104,17 @@ function proxyUrls(obj) {
|
|
|
63
104
|
}
|
|
64
105
|
return obj;
|
|
65
106
|
}
|
|
66
|
-
function toToolResult(proxy) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
async function toToolResult(proxy) {
|
|
108
|
+
// The MCP SDK's ImageContent only accepts `data` (base64). The orchyn
|
|
109
|
+
// backend now emits `url` image blocks pointing at permanent, re-hosted
|
|
110
|
+
// JPEG thumbnails (HEIC already transcoded), so convert those URLs to
|
|
111
|
+
// base64 data here — guaranteeing clients render a valid image.
|
|
112
|
+
const imgBlocks = proxy.contentBlocks.filter((c) => c.type === "image");
|
|
113
|
+
// Skip image blocks whose base64 is empty (fetch failed) — clients reject
|
|
114
|
+
// them with "could not be processed: Error processing image".
|
|
115
|
+
const images = (await Promise.all(imgBlocks.map((c) => c.url
|
|
116
|
+
? fetchAsBase64Image(String(c.url), String(c.mimeType ?? "image/jpeg"))
|
|
117
|
+
: Promise.resolve({ type: "image", data: String(c.data ?? ""), mimeType: String(c.mimeType ?? "image/jpeg") })))).filter((img) => img.type === "image" && img.data.length > 0);
|
|
74
118
|
// The Rust backend embeds HTML cards directly in the text block
|
|
75
119
|
// (type:"text", text:"<div>...</div>\n\n{json}"). We extract the HTML
|
|
76
120
|
// prefix from the first text contentBlock and prepend it.
|
|
@@ -91,9 +135,9 @@ function toToolResult(proxy) {
|
|
|
91
135
|
structuredContent: proxied ?? {},
|
|
92
136
|
};
|
|
93
137
|
}
|
|
94
|
-
/** Replace external image src
|
|
138
|
+
/** Replace external image/video src, poster and href URLs in HTML with proxied versions. */
|
|
95
139
|
function proxyImageUrlsInHtml(html) {
|
|
96
|
-
return html.replace(/(src|href)="(https?:\/\/[^"']+?)"/g, (_match, attr, url) => {
|
|
140
|
+
return html.replace(/(src|href|poster)="(https?:\/\/[^"']+?)"/g, (_match, attr, url) => {
|
|
97
141
|
return `${attr}="${proxyImageUrl(url)}"`;
|
|
98
142
|
});
|
|
99
143
|
}
|
|
@@ -101,19 +145,36 @@ function toolError(prefix, err) {
|
|
|
101
145
|
const msg = err instanceof Error ? err.message : String(err);
|
|
102
146
|
return { content: [{ type: "text", text: `${prefix}: ${msg}` }], isError: true };
|
|
103
147
|
}
|
|
104
|
-
/**
|
|
105
|
-
function
|
|
106
|
-
const
|
|
107
|
-
tiktok: "
|
|
108
|
-
|
|
148
|
+
/** Official brand color per platform (matches ui-template.ts pColor). */
|
|
149
|
+
function platformBrandColor(p) {
|
|
150
|
+
const map = {
|
|
151
|
+
tiktok: "#000000", douyin: "#000000", instagram: "#E4405F",
|
|
152
|
+
youtube: "#FF0000", xiaohongshu: "#FF2442", x: "#000000",
|
|
153
|
+
twitter: "#1DA1F2", bilibili: "#00A1D6", linkedin: "#0A66C2",
|
|
109
154
|
};
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
return map[p] || "#6b7280";
|
|
156
|
+
}
|
|
157
|
+
/** Official brand mark (simple-icons path) as an inline SVG inheriting currentColor. */
|
|
158
|
+
function platformSvgMark(p, size = 14) {
|
|
159
|
+
const paths = {
|
|
160
|
+
tiktok: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
|
|
161
|
+
douyin: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
|
|
162
|
+
instagram: "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077",
|
|
163
|
+
youtube: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
|
|
164
|
+
x: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
|
|
165
|
+
twitter: "M21.543 7.104c.015.211.015.423.015.636 0 6.507-4.954 14.01-14.01 14.01v-.003A13.94 13.94 0 0 1 0 19.539a9.88 9.88 0 0 0 7.287-2.041 4.93 4.93 0 0 1-4.6-3.42 4.916 4.916 0 0 0 2.223-.084A4.926 4.926 0 0 1 .96 9.167v-.062a4.887 4.887 0 0 0 2.235.616A4.928 4.928 0 0 1 1.67 3.148 13.98 13.98 0 0 0 11.82 8.292a4.929 4.929 0 0 1 8.39-4.49 9.868 9.868 0 0 0 3.128-1.196 4.941 4.941 0 0 1-2.165 2.724A9.828 9.828 0 0 0 24 4.555a10.019 10.019 0 0 1-2.457 2.549z",
|
|
166
|
+
xiaohongshu: "M22.405 9.879c.002.016.01.02.07.019h.725a.797.797 0 0 0 .78-.972.794.794 0 0 0-.884-.618.795.795 0 0 0-.692.794c0 .101-.002.666.001.777zm-11.509 4.808c-.203.001-1.353.004-1.685.003a2.528 2.528 0 0 1-.766-.126.025.025 0 0 0-.03.014L7.7 16.127a.025.025 0 0 0 .01.032c.111.06.336.124.495.124.66.01 1.32.002 1.981 0 .01 0 .02-.006.023-.015l.712-1.545a.025.025 0 0 0-.024-.036zM.477 9.91c-.071 0-.076.002-.076.01a.834.834 0 0 0-.01.08c-.027.397-.038.495-.234 3.06-.012.24-.034.389-.135.607-.026.057-.033.042.003.112.046.092.681 1.523.787 1.74.008.015.011.02.017.02.008 0 .033-.026.047-.044.147-.187.268-.391.371-.606.306-.635.44-1.325.486-1.706.014-.11.021-.22.03-.33l.204-2.616.022-.293c.003-.029 0-.033-.03-.034zm7.203 3.757a1.427 1.427 0 0 1-.135-.607c-.004-.084-.031-.39-.235-3.06a.443.443 0 0 0-.01-.082c-.004-.011-.052-.008-.076-.008h-1.48c-.03.001-.034.005-.03.034l.021.293c.076.982.153 1.964.233 2.946.05.4.186 1.085.487 1.706.103.215.223.419.37.606.015.018.037.051.048.049.02-.003.742-1.642.804-1.765.036-.07.03-.055.003-.112zm3.861-.913h-.872a.126.126 0 0 1-.116-.178l1.178-2.625a.025.025 0 0 0-.023-.035l-1.318-.003a.148.148 0 0 1-.135-.21l.876-1.954a.025.025 0 0 0-.023-.035h-1.56c-.01 0-.02.006-.024.015l-.926 2.068c-.085.169-.314.634-.399.938a.534.534 0 0 0-.02.191.46.46 0 0 0 .23.378.981.981 0 0 0 .46.119h.59c.041 0-.688 1.482-.834 1.972a.53.53 0 0 0-.023.172.465.465 0 0 0 .23.398c.15.092.342.12.475.12l1.66-.001c.01 0 .02-.006.023-.015l.575-1.28a.025.025 0 0 0-.024-.035zm-6.93-4.937H3.1a.032.032 0 0 0-.034.033c0 1.048-.01 2.795-.01 6.829 0 .288-.269.262-.28.262h-.74c-.04.001-.044.004-.04.047.001.037.465 1.064.555 1.263.01.02.03.033.051.033.157.003.767.009.938-.014.153-.02.3-.06.438-.132.3-.156.49-.419.595-.765.052-.172.075-.353.075-.533.002-2.33 0-4.66-.007-6.991a.032.032 0 0 0-.032-.032zm11.784 6.896c0-.014-.01-.021-.024-.022h-1.465c-.048-.001-.049-.002-.05-.049v-4.66c0-.072-.005-.07.07-.07h.863c.08 0 .075.004.075-.074V8.393c0-.082.006-.076-.08-.076h-3.5c-.064 0-.075-.006-.075.073v1.445c0 .083-.006.077.08.077h.854c.075 0 .07-.004.07.07v4.624c0 .095.008.084-.085.084-.37 0-1.11-.002-1.304 0-.048.001-.06.03-.06.03l-.697 1.519s-.014.025-.008.036c.006.01.013.008.058.008 1.748.003 3.495.002 5.243.002.03-.001.034-.006.035-.033v-1.539zm4.177-3.43c0 .013-.007.023-.02.024-.346.006-.692.004-1.037.004-.014-.002-.022-.01-.022-.024-.005-.434-.007-.869-.01-1.303 0-.072-.006-.071.07-.07l.733-.003c.041 0 .081.002.12.015.093.025.16.107.165.204.006.431.002 1.153.001 1.153zm2.67.244a1.953 1.953 0 0 0-.883-.222h-.18c-.04-.001-.04-.003-.042-.04V10.21c0-.132-.007-.263-.025-.394a1.823 1.823 0 0 0-.153-.53 1.533 1.533 0 0 0-.677-.71 2.167 2.167 0 0 0-1-.258c-.153-.003-.567 0-.72 0-.07 0-.068.004-.068-.065V7.76c0-.031-.01-.041-.046-.039H17.93s-.016 0-.023.007c-.006.006-.008.012-.008.023v.546c-.008.036-.057.015-.082.022h-.95c-.022.002-.028.008-.03.032v1.481c0 .09-.004.082.082.082h.913c.082 0 .072.128.072.128V11.19s.003.117-.06.117h-1.482c-.068 0-.06.082-.06.082v1.445s-.01.068.064.068h1.457c.082 0 .076-.006.076.079v3.225c0 .088-.007.081.082.081h1.43c.09 0 .082.007.082-.08v-3.27c0-.029.006-.035.033-.035l2.323-.003c.098 0 .191.02.28.061a.46.46 0 0 1 .274.407c.008.395.003.79.003 1.185 0 .259-.107.367-.33.367h-1.218c-.023.002-.029.008-.028.033.184.437.374.871.57 1.303a.045.045 0 0 0 .04.026c.17.005.34.002.51.003.15-.002.517.004.666-.01a2.03 2.03 0 0 0 .408-.075c.59-.18.975-.698.976-1.313v-1.981c0-.128-.01-.254-.034-.38 0 .078-.029-.641-.724-.998z",
|
|
167
|
+
bilibili: "M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373Z",
|
|
168
|
+
linkedin: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
|
|
169
|
+
unknown: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
|
|
114
170
|
};
|
|
115
|
-
const
|
|
116
|
-
|
|
171
|
+
const d = paths[p] || paths.unknown;
|
|
172
|
+
return `<svg viewBox="0 0 24 24" width="${size}" height="${size}" fill="currentColor" style="vertical-align:-2px;flex-shrink:0;display:inline-block" aria-hidden="true"><path d="${d}"/></svg>`;
|
|
173
|
+
}
|
|
174
|
+
/** Build an HTML card for analyze_post results. */
|
|
175
|
+
function buildAnalysisHtmlCard(result, thumbnailUrl, platform, creatorHandle, title) {
|
|
176
|
+
const brandSvg = platformSvgMark(platform, 14);
|
|
177
|
+
const color = platformBrandColor(platform);
|
|
117
178
|
const post = (result.post ?? {});
|
|
118
179
|
const analysis = (result.analysis ?? {});
|
|
119
180
|
const views = typeof post.views === "number" ? post.views.toLocaleString() : "—";
|
|
@@ -126,12 +187,19 @@ function buildAnalysisHtmlCard(result, thumbnailUrl, platform, creatorHandle, ti
|
|
|
126
187
|
const viralTriggers = Array.isArray(analysis.viralTriggers) ? analysis.viralTriggers : [];
|
|
127
188
|
const variationIdeas = Array.isArray(analysis.variationIdeas) ? analysis.variationIdeas.slice(0, 3) : [];
|
|
128
189
|
const suggestedHook = typeof analysis.suggestedHook === "string" ? analysis.suggestedHook : "";
|
|
190
|
+
const videoUrl = typeof post.videoUrl === "string"
|
|
191
|
+
? post.videoUrl
|
|
192
|
+
: Array.isArray(post.mediaItems)
|
|
193
|
+
? post.mediaItems.find((m) => m.kind === "video" && typeof m.preview_url === "string")?.preview_url
|
|
194
|
+
: undefined;
|
|
129
195
|
let html = `<div style="font-family:system-ui,-apple-system,sans-serif;max-width:600px;background:#0d1117;color:#e6edf3;border-radius:12px;overflow:hidden;margin:0 auto">`;
|
|
130
|
-
// Thumbnail
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
html += `<
|
|
134
|
-
|
|
196
|
+
// Thumbnail / video
|
|
197
|
+
const badgeHtml = `<span style="background:${color};color:#fff;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:600;display:inline-flex;align-items:center;gap:4px">${brandSvg} ${platform}</span>`;
|
|
198
|
+
if (videoUrl) {
|
|
199
|
+
html += `<div style="position:relative"><video src="${videoUrl}" controls preload="metadata" playsinline poster="${thumbnailUrl}" style="width:100%;max-height:400px;object-fit:contain;display:block;background:#000" onerror="this.outerHTML='<img src="${thumbnailUrl}" style="width:100%;max-height:400px;object-fit:cover;display:block"/>'">Your browser doesn't support video playback.</video><div style="position:absolute;bottom:12px;left:12px;display:flex;gap:8px;flex-wrap:wrap">${badgeHtml}</div></div>`;
|
|
200
|
+
}
|
|
201
|
+
else if (thumbnailUrl) {
|
|
202
|
+
html += `<div style="position:relative"><img src="${thumbnailUrl}" style="width:100%;max-height:400px;object-fit:cover;display:block" onerror="this.style.display='none'" /><div style="position:absolute;bottom:12px;left:12px;display:flex;gap:8px;flex-wrap:wrap">${badgeHtml}</div></div>`;
|
|
135
203
|
}
|
|
136
204
|
// Header
|
|
137
205
|
html += `<div style="padding:16px 20px">`;
|
|
@@ -201,11 +269,14 @@ export function createMcpServer(makeClient) {
|
|
|
201
269
|
"https://*.cdninstagram.com",
|
|
202
270
|
"https://*.ytimg.com",
|
|
203
271
|
"https://*.googlevideo.com",
|
|
272
|
+
"https://*.licdn.com",
|
|
273
|
+
"https://*.linkedin.com",
|
|
204
274
|
];
|
|
205
275
|
const apiUrl = process.env.ORCHYN_API_URL || process.env.ORCHYN_BASE_URL;
|
|
206
276
|
if (apiUrl && apiUrl.trim()) {
|
|
207
277
|
domains.push(apiUrl.trim().replace(/\/+$/, ""));
|
|
208
278
|
}
|
|
279
|
+
const domain = await computeAppDomain();
|
|
209
280
|
return {
|
|
210
281
|
contents: [
|
|
211
282
|
{
|
|
@@ -214,9 +285,11 @@ export function createMcpServer(makeClient) {
|
|
|
214
285
|
text: ORCHYN_UI_TEMPLATE,
|
|
215
286
|
_meta: {
|
|
216
287
|
ui: {
|
|
288
|
+
...(domain ? { domain } : {}),
|
|
217
289
|
csp: {
|
|
218
290
|
resourceDomains: domains,
|
|
219
291
|
},
|
|
292
|
+
prefersBorder: false,
|
|
220
293
|
},
|
|
221
294
|
},
|
|
222
295
|
},
|
|
@@ -228,7 +301,7 @@ export function createMcpServer(makeClient) {
|
|
|
228
301
|
description: "Analyze a social post (video, image, carousel/slideshow) from its link — " +
|
|
229
302
|
"imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). " +
|
|
230
303
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Returns the full analysis once finished. First use free per user.",
|
|
231
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
304
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
232
305
|
inputSchema: z
|
|
233
306
|
.object({
|
|
234
307
|
url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X, Douyin, Xiaohongshu or Bilibili)."),
|
|
@@ -257,11 +330,16 @@ export function createMcpServer(makeClient) {
|
|
|
257
330
|
const creatorHandle = String(post.creatorHandle ?? "");
|
|
258
331
|
const title = String(post.title ?? post.caption ?? "");
|
|
259
332
|
const htmlCard = buildAnalysisHtmlCard(proxied, thumbnailUrl, platform, creatorHandle, title);
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
333
|
+
// The backend attaches `_inlineImages` as permanent orchyn public URLs
|
|
334
|
+
// (re-hosted into storage, HEIC already transcoded to JPEG). The SDK's
|
|
335
|
+
// ImageContent only accepts base64 `data`, so fetch each URL and encode it.
|
|
336
|
+
// Skip image blocks whose base64 is empty (fetch failed) — clients
|
|
337
|
+
// reject them with "could not be processed: Error processing image".
|
|
338
|
+
const images = (await Promise.all((result.inlineImages ?? [])
|
|
339
|
+
.filter((img) => typeof img === "object" && img !== null)
|
|
340
|
+
.map((img) => "url" in img && img.url
|
|
341
|
+
? fetchAsBase64Image(String(img.url), String(img.mimeType ?? "image/jpeg"))
|
|
342
|
+
: Promise.resolve({ type: "image", data: String(img.data ?? ""), mimeType: String(img.mimeType ?? "image/jpeg") })))).filter((img) => img.type === "image" && img.data.length > 0);
|
|
265
343
|
const textJson = JSON.stringify(proxied, null, 2);
|
|
266
344
|
return {
|
|
267
345
|
content: [...images, { type: "text", text: `${htmlCard}\n\n${textJson}` }],
|
|
@@ -284,7 +362,7 @@ export function createMcpServer(makeClient) {
|
|
|
284
362
|
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu or Bilibili URL: " +
|
|
285
363
|
"contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. " +
|
|
286
364
|
"Returns an inline thumbnail image. Consumes 1 orchyn credit. First use free per user.",
|
|
287
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
365
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
288
366
|
inputSchema: z
|
|
289
367
|
.object({
|
|
290
368
|
url: z.string().describe("Full public post URL."),
|
|
@@ -293,7 +371,7 @@ export function createMcpServer(makeClient) {
|
|
|
293
371
|
}, async (args, extra) => {
|
|
294
372
|
const client = await makeClient(extra);
|
|
295
373
|
try {
|
|
296
|
-
return toToolResult(await client.callTool("get_social_media", { url: args.url }));
|
|
374
|
+
return await toToolResult(await client.callTool("get_social_media", { url: args.url }));
|
|
297
375
|
}
|
|
298
376
|
catch (err) {
|
|
299
377
|
return toolError("get_social_media failed", err);
|
|
@@ -304,7 +382,7 @@ export function createMcpServer(makeClient) {
|
|
|
304
382
|
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
|
|
305
383
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
306
384
|
'Say "next" to paginate (offset), or "analyze the 2nd one" / "analyze all" for batch analysis. Consumes 2 orchyn credits. First use free per user.',
|
|
307
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
385
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
308
386
|
inputSchema: z
|
|
309
387
|
.object({
|
|
310
388
|
niche: z.string().describe("Niche/topic, e.g. 'fitness'."),
|
|
@@ -320,7 +398,7 @@ export function createMcpServer(makeClient) {
|
|
|
320
398
|
}, async (args, extra) => {
|
|
321
399
|
const client = await makeClient(extra);
|
|
322
400
|
try {
|
|
323
|
-
return toToolResult(await client.callTool("discover_social_posts", { ...args }));
|
|
401
|
+
return await toToolResult(await client.callTool("discover_social_posts", { ...args }));
|
|
324
402
|
}
|
|
325
403
|
catch (err) {
|
|
326
404
|
return toolError("discover_social_posts failed", err);
|
|
@@ -328,15 +406,15 @@ export function createMcpServer(makeClient) {
|
|
|
328
406
|
});
|
|
329
407
|
server.registerTool("get_user_posts", {
|
|
330
408
|
title: "Get User Posts",
|
|
331
|
-
description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or
|
|
409
|
+
description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn (LinkedIn uses the profile public_id, e.g. 'billgates'). " +
|
|
332
410
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
333
411
|
"Use this when Claude needs to pull more posts from the same account to spot a pattern, or to scan a whole profile. Consumes 2 orchyn credits. First use free per user.",
|
|
334
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
412
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
335
413
|
inputSchema: z
|
|
336
414
|
.object({
|
|
337
415
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."),
|
|
338
416
|
platform: z
|
|
339
|
-
.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili"])
|
|
417
|
+
.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin"])
|
|
340
418
|
.optional()
|
|
341
419
|
.describe("Which platform (default tiktok)."),
|
|
342
420
|
limit: z.number().int().optional().describe("Max posts (default 6)."),
|
|
@@ -345,7 +423,7 @@ export function createMcpServer(makeClient) {
|
|
|
345
423
|
}, async (args, extra) => {
|
|
346
424
|
const client = await makeClient(extra);
|
|
347
425
|
try {
|
|
348
|
-
return toToolResult(await client.callTool("get_user_posts", { ...args }));
|
|
426
|
+
return await toToolResult(await client.callTool("get_user_posts", { ...args }));
|
|
349
427
|
}
|
|
350
428
|
catch (err) {
|
|
351
429
|
return toolError("get_user_posts failed", err);
|
|
@@ -353,15 +431,15 @@ export function createMcpServer(makeClient) {
|
|
|
353
431
|
});
|
|
354
432
|
server.registerTool("analyze_creator_profile", {
|
|
355
433
|
title: "Analyze Creator Profile",
|
|
356
|
-
description: "Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or
|
|
434
|
+
description: "Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter, Bilibili or LinkedIn: fetch recent posts, run multimodal AI on up to 3, " +
|
|
357
435
|
"then synthesize a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, " +
|
|
358
436
|
"engagement patterns, audience insights, variation ideas, collaboration fit. Consumes 15 orchyn credits. First use free per user.",
|
|
359
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
437
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
360
438
|
inputSchema: z
|
|
361
439
|
.object({
|
|
362
440
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp'."),
|
|
363
441
|
platform: z
|
|
364
|
-
.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili"])
|
|
442
|
+
.enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin"])
|
|
365
443
|
.optional()
|
|
366
444
|
.describe("Which platform (default tiktok)."),
|
|
367
445
|
limit: z.number().int().optional().describe("Posts to fetch (default 6; first 3 analyzed)."),
|
|
@@ -371,7 +449,7 @@ export function createMcpServer(makeClient) {
|
|
|
371
449
|
}, async (args, extra) => {
|
|
372
450
|
const client = await makeClient(extra);
|
|
373
451
|
try {
|
|
374
|
-
return toToolResult(await client.callTool("analyze_creator_profile", { ...args }));
|
|
452
|
+
return await toToolResult(await client.callTool("analyze_creator_profile", { ...args }));
|
|
375
453
|
}
|
|
376
454
|
catch (err) {
|
|
377
455
|
return toolError("analyze_creator_profile failed", err);
|
|
@@ -379,19 +457,19 @@ export function createMcpServer(makeClient) {
|
|
|
379
457
|
});
|
|
380
458
|
server.registerTool("get_post_comments", {
|
|
381
459
|
title: "Get Post Comments",
|
|
382
|
-
description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter or
|
|
460
|
+
description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter, Bilibili or LinkedIn, plus keyword clusters from TikTok Analytics " +
|
|
383
461
|
"when available — audience sentiment/audience-signal analysis. Consumes 2 orchyn credits. First use free per user.",
|
|
384
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
462
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
385
463
|
inputSchema: z
|
|
386
464
|
.object({
|
|
387
|
-
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili)."),
|
|
465
|
+
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili/LinkedIn)."),
|
|
388
466
|
limit: z.number().int().optional().describe("Max comments (default 20)."),
|
|
389
467
|
})
|
|
390
468
|
.strict(),
|
|
391
469
|
}, async (args, extra) => {
|
|
392
470
|
const client = await makeClient(extra);
|
|
393
471
|
try {
|
|
394
|
-
return toToolResult(await client.callTool("get_post_comments", { ...args }));
|
|
472
|
+
return await toToolResult(await client.callTool("get_post_comments", { ...args }));
|
|
395
473
|
}
|
|
396
474
|
catch (err) {
|
|
397
475
|
return toolError("get_post_comments failed", err);
|
|
@@ -401,7 +479,7 @@ export function createMcpServer(makeClient) {
|
|
|
401
479
|
title: "Search Creators",
|
|
402
480
|
description: "Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, " +
|
|
403
481
|
"signature, verified status. Use to find influencers to vet or analyze. Consumes 2 orchyn credits. First use free per user.",
|
|
404
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
482
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
405
483
|
inputSchema: z
|
|
406
484
|
.object({
|
|
407
485
|
keyword: z.string().describe("Niche/keyword, e.g. 'fitness' or a creator name."),
|
|
@@ -415,7 +493,7 @@ export function createMcpServer(makeClient) {
|
|
|
415
493
|
}, async (args, extra) => {
|
|
416
494
|
const client = await makeClient(extra);
|
|
417
495
|
try {
|
|
418
|
-
return toToolResult(await client.callTool("search_creators", { ...args }));
|
|
496
|
+
return await toToolResult(await client.callTool("search_creators", { ...args }));
|
|
419
497
|
}
|
|
420
498
|
catch (err) {
|
|
421
499
|
return toolError("search_creators failed", err);
|
|
@@ -425,7 +503,7 @@ export function createMcpServer(makeClient) {
|
|
|
425
503
|
title: "Get Similar Creators",
|
|
426
504
|
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram " +
|
|
427
505
|
"similar users. Useful for scaling: 'if this creator works, here are more like them'. Consumes 2 orchyn credits. First use free per user.",
|
|
428
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
506
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
429
507
|
inputSchema: z
|
|
430
508
|
.object({
|
|
431
509
|
username: z.string().describe("Seed creator handle, e.g. 'zoundsapp'."),
|
|
@@ -435,7 +513,7 @@ export function createMcpServer(makeClient) {
|
|
|
435
513
|
}, async (args, extra) => {
|
|
436
514
|
const client = await makeClient(extra);
|
|
437
515
|
try {
|
|
438
|
-
return toToolResult(await client.callTool("get_similar_creators", { ...args }));
|
|
516
|
+
return await toToolResult(await client.callTool("get_similar_creators", { ...args }));
|
|
439
517
|
}
|
|
440
518
|
catch (err) {
|
|
441
519
|
return toolError("get_similar_creators failed", err);
|
|
@@ -445,7 +523,7 @@ export function createMcpServer(makeClient) {
|
|
|
445
523
|
title: "Discover Sounds",
|
|
446
524
|
description: "Discover trending sounds/music for a keyword on TikTok or Instagram — the sound is a huge ranking " +
|
|
447
525
|
"signal for TikTok virality. Returns title, artist, duration, play/cover URLs. Consumes 2 orchyn credits. First use free per user.",
|
|
448
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
526
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
449
527
|
inputSchema: z
|
|
450
528
|
.object({
|
|
451
529
|
keyword: z.string().describe("Niche/keyword, e.g. 'gym'."),
|
|
@@ -456,7 +534,7 @@ export function createMcpServer(makeClient) {
|
|
|
456
534
|
}, async (args, extra) => {
|
|
457
535
|
const client = await makeClient(extra);
|
|
458
536
|
try {
|
|
459
|
-
return toToolResult(await client.callTool("discover_sounds", { ...args }));
|
|
537
|
+
return await toToolResult(await client.callTool("discover_sounds", { ...args }));
|
|
460
538
|
}
|
|
461
539
|
catch (err) {
|
|
462
540
|
return toolError("discover_sounds failed", err);
|
|
@@ -465,12 +543,12 @@ export function createMcpServer(makeClient) {
|
|
|
465
543
|
server.registerTool("check_orchyn_credits", {
|
|
466
544
|
title: "Check Orchyn Credits",
|
|
467
545
|
description: "Check your orchyn credit balance, billing URL and pack size. No cost — call anytime to see remaining credits before running other tools.",
|
|
468
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
546
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
469
547
|
inputSchema: z.object({}).strict(),
|
|
470
548
|
}, async (_args, extra) => {
|
|
471
549
|
const client = await makeClient(extra);
|
|
472
550
|
try {
|
|
473
|
-
return toToolResult(await client.callTool("check_orchyn_credits", {}));
|
|
551
|
+
return await toToolResult(await client.callTool("check_orchyn_credits", {}));
|
|
474
552
|
}
|
|
475
553
|
catch (err) {
|
|
476
554
|
return toolError("check_orchyn_credits failed", err);
|
|
@@ -479,12 +557,12 @@ export function createMcpServer(makeClient) {
|
|
|
479
557
|
server.registerTool("buy_orchyn_credits", {
|
|
480
558
|
title: "Buy Orchyn Credits",
|
|
481
559
|
description: "Buy an MCP credit pack via Stripe Checkout. Returns a secure checkout URL — open it in your browser to pay. Credits are added automatically after payment. No cost to call.",
|
|
482
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
560
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
483
561
|
inputSchema: z.object({}).strict(),
|
|
484
562
|
}, async (_args, extra) => {
|
|
485
563
|
const client = await makeClient(extra);
|
|
486
564
|
try {
|
|
487
|
-
return toToolResult(await client.callTool("buy_orchyn_credits", {}));
|
|
565
|
+
return await toToolResult(await client.callTool("buy_orchyn_credits", {}));
|
|
488
566
|
}
|
|
489
567
|
catch (err) {
|
|
490
568
|
return toolError("buy_orchyn_credits failed", err);
|
|
@@ -495,7 +573,7 @@ export function createMcpServer(makeClient) {
|
|
|
495
573
|
description: "Import a social post URL AND understand it with multimodal AI over the actual video/images: " +
|
|
496
574
|
"summary, hook strength, viral triggers, format breakdown and variation ideas. Includes the thumbnail. " +
|
|
497
575
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Consumes 6 orchyn credits. First use free per user.",
|
|
498
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
576
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
499
577
|
inputSchema: z
|
|
500
578
|
.object({
|
|
501
579
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/X/Douyin/Xiaohongshu/Bilibili)."),
|
|
@@ -508,7 +586,7 @@ export function createMcpServer(makeClient) {
|
|
|
508
586
|
}, async (args, extra) => {
|
|
509
587
|
const client = await makeClient(extra);
|
|
510
588
|
try {
|
|
511
|
-
return toToolResult(await client.callTool("understand_social_post", { ...args }));
|
|
589
|
+
return await toToolResult(await client.callTool("understand_social_post", { ...args }));
|
|
512
590
|
}
|
|
513
591
|
catch (err) {
|
|
514
592
|
return toolError("understand_social_post failed", err);
|
|
@@ -75,7 +75,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
75
75
|
.gallery{display:flex;flex-wrap:wrap;gap:10px;}
|
|
76
76
|
.card{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;width:300px;background:var(--card);display:inline-block;vertical-align:top;box-shadow:var(--shadow-sm);transition:var(--transition);cursor:default;}
|
|
77
77
|
.card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);border-color:var(--muted);}
|
|
78
|
-
.card-wide{
|
|
78
|
+
.card-wide{width:100%;}
|
|
79
79
|
.card img{width:100%;height:200px;object-fit:cover;display:block;transition:transform .3s ease;}
|
|
80
80
|
.card:hover img{transform:scale(1.02);}
|
|
81
81
|
.card-wide img{max-height:340px;}
|
|
@@ -214,6 +214,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
214
214
|
clearTimeout(loadingTimer);
|
|
215
215
|
clearTimeout(fallbackTimer);
|
|
216
216
|
render(d.params);
|
|
217
|
+
setTimeout(reportSize,50);
|
|
217
218
|
}
|
|
218
219
|
if(d.method==="ui/notifications/tool-input-partial"){
|
|
219
220
|
currentTool=d.params&&d.params.name?d.params.name:"";
|
|
@@ -221,10 +222,22 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
221
222
|
}
|
|
222
223
|
});
|
|
223
224
|
|
|
224
|
-
// Show
|
|
225
|
+
// Show loading state after 300ms — branded spinner until tool name arrives,
|
|
226
|
+
// then switch to the correct skeleton type.
|
|
225
227
|
loadingTimer=setTimeout(function(){
|
|
226
228
|
var app=document.getElementById("app");
|
|
227
|
-
if(app)
|
|
229
|
+
if(!app)return;
|
|
230
|
+
if(currentTool){
|
|
231
|
+
// Tool name already known → show correct skeleton
|
|
232
|
+
app.innerHTML=renderSkeleton(currentTool);
|
|
233
|
+
}else{
|
|
234
|
+
// Tool name not yet known → show branded spinner (neutral)
|
|
235
|
+
app.innerHTML='<div class="loading-container fade-in">'
|
|
236
|
+
+'<div class="orchyn-logo"><svg width="40" height="40" viewBox="0 0 48 48" fill="none"><g fill="var(--accent)" transform="translate(24 24)"><circle r="4.1"/><g id="rl"><path d="M-2.85 -5.2 L0 -20.6 L2.85 -5.2 L1.15 1.1 L-1.15 1.1 Z"/></g><use href="#rl" transform="rotate(45)"/><use href="#rl" transform="rotate(90)"/><use href="#rl" transform="rotate(135)"/><use href="#rl" transform="rotate(180)"/><use href="#rl" transform="rotate(225)"/><use href="#rl" transform="rotate(270)"/><use href="#rl" transform="rotate(315)"/></g></svg></div>'
|
|
237
|
+
+'<div class="loading-brand">Orchyn</div>'
|
|
238
|
+
+'<div class="loading-text">Loading…</div>'
|
|
239
|
+
+'</div>';
|
|
240
|
+
}
|
|
228
241
|
},300);
|
|
229
242
|
|
|
230
243
|
// Fallback: if no tool-result arrives within 3s, check if data is embedded
|
|
@@ -257,20 +270,63 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
257
270
|
+'</div></div>';
|
|
258
271
|
},3000);
|
|
259
272
|
|
|
260
|
-
// MCP Apps handshake
|
|
273
|
+
// MCP Apps handshake.
|
|
274
|
+
// Claude's McpUiInitializeRequestSchema requires appInfo, appCapabilities
|
|
275
|
+
// and protocolVersion (legacy hosts read capabilities/clientInfo), so send
|
|
276
|
+
// both shapes. Critically, ui/notifications/initialized must be sent
|
|
277
|
+
// UNCONDITIONALLY — Claude keeps the widget iframe reserved-but-hidden
|
|
278
|
+
// until it receives that notification, so a reply we don't recognize must
|
|
279
|
+
// never deadlock the handshake.
|
|
280
|
+
var initializedSent=false;
|
|
281
|
+
function sendInitialized(){
|
|
282
|
+
if(initializedSent)return;
|
|
283
|
+
initializedSent=true;
|
|
284
|
+
window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/initialized",params:{}},"*");
|
|
285
|
+
}
|
|
261
286
|
send("ui/initialize",{
|
|
262
287
|
protocolVersion:"2026-01-26",
|
|
288
|
+
appInfo:{name:"orchyn-view",version:"2.0.0"},
|
|
289
|
+
appCapabilities:{availableDisplayModes:["inline"]},
|
|
263
290
|
capabilities:{},
|
|
264
291
|
clientInfo:{name:"orchyn-view",version:"2.0.0"}
|
|
265
|
-
}).then(
|
|
266
|
-
|
|
267
|
-
|
|
292
|
+
}).then(sendInitialized).catch(sendInitialized);
|
|
293
|
+
// Unconditional fallback: never wait on a reply shape we don't recognize.
|
|
294
|
+
setTimeout(sendInitialized, 500);
|
|
295
|
+
|
|
296
|
+
// Report content size so flexible hosts (Claude) size the iframe correctly.
|
|
297
|
+
// Params MUST be real numbers — claude.ai throws on null/missing width.
|
|
298
|
+
function reportSize(){
|
|
299
|
+
try{
|
|
300
|
+
var h=document.documentElement.scrollHeight||document.body.scrollHeight||400;
|
|
301
|
+
var w=document.documentElement.scrollWidth||document.body.scrollWidth||400;
|
|
302
|
+
window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/size-changed",params:{height:h,width:w}},"*");
|
|
303
|
+
}catch(e){}
|
|
304
|
+
}
|
|
305
|
+
window.addEventListener("resize",reportSize);
|
|
306
|
+
setTimeout(reportSize,600);
|
|
268
307
|
|
|
269
308
|
// ─── Helpers ───
|
|
270
309
|
function esc(s){return String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");}
|
|
271
310
|
function fmtNum(n){n=Number(n)||0;return n>=1e6?(n/1e6).toFixed(1)+"M":n>=1e3?(n/1e3).toFixed(1)+"K":String(n);}
|
|
272
|
-
function pColor(p){return{tiktok:"#000",
|
|
273
|
-
|
|
311
|
+
function pColor(p){return{tiktok:"#000",douyin:"#000",instagram:"#E4405F",youtube:"#FF0000",xiaohongshu:"#FF2442",x:"#000",twitter:"#1DA1F2",bilibili:"#00A1D6",linkedin:"#0A66C2"}[p]||"#6B7280";}
|
|
312
|
+
// Official brand mark (simple-icons path) as an inline SVG that inherits
|
|
313
|
+
// the current text color via fill="currentColor" — matches badge accents.
|
|
314
|
+
function pSvg(p,size){
|
|
315
|
+
var s=size||14,paths={
|
|
316
|
+
tiktok:"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
|
|
317
|
+
instagram:"M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077",
|
|
318
|
+
youtube:"M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
|
|
319
|
+
x:"M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
|
|
320
|
+
twitter:"M21.543 7.104c.015.211.015.423.015.636 0 6.507-4.954 14.01-14.01 14.01v-.003A13.94 13.94 0 0 1 0 19.539a9.88 9.88 0 0 0 7.287-2.041 4.93 4.93 0 0 1-4.6-3.42 4.916 4.916 0 0 0 2.223-.084A4.926 4.926 0 0 1 .96 9.167v-.062a4.887 4.887 0 0 0 2.235.616A4.928 4.928 0 0 1 1.67 3.148 13.98 13.98 0 0 0 11.82 8.292a4.929 4.929 0 0 1 8.39-4.49 9.868 9.868 0 0 0 3.128-1.196 4.941 4.941 0 0 1-2.165 2.724A9.828 9.828 0 0 0 24 4.555a10.019 10.019 0 0 1-2.457 2.549z",
|
|
321
|
+
douyin:"M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
|
|
322
|
+
xiaohongshu:"M22.405 9.879c.002.016.01.02.07.019h.725a.797.797 0 0 0 .78-.972.794.794 0 0 0-.884-.618.795.795 0 0 0-.692.794c0 .101-.002.666.001.777zm-11.509 4.808c-.203.001-1.353.004-1.685.003a2.528 2.528 0 0 1-.766-.126.025.025 0 0 0-.03.014L7.7 16.127a.025.025 0 0 0 .01.032c.111.06.336.124.495.124.66.01 1.32.002 1.981 0 .01 0 .02-.006.023-.015l.712-1.545a.025.025 0 0 0-.024-.036zM.477 9.91c-.071 0-.076.002-.076.01a.834.834 0 0 0-.01.08c-.027.397-.038.495-.234 3.06-.012.24-.034.389-.135.607-.026.057-.033.042.003.112.046.092.681 1.523.787 1.74.008.015.011.02.017.02.008 0 .033-.026.047-.044.147-.187.268-.391.371-.606.306-.635.44-1.325.486-1.706.014-.11.021-.22.03-.33l.204-2.616.022-.293c.003-.029 0-.033-.03-.034zm7.203 3.757a1.427 1.427 0 0 1-.135-.607c-.004-.084-.031-.39-.235-3.06a.443.443 0 0 0-.01-.082c-.004-.011-.052-.008-.076-.008h-1.48c-.03.001-.034.005-.03.034l.021.293c.076.982.153 1.964.233 2.946.05.4.186 1.085.487 1.706.103.215.223.419.37.606.015.018.037.051.048.049.02-.003.742-1.642.804-1.765.036-.07.03-.055.003-.112zm3.861-.913h-.872a.126.126 0 0 1-.116-.178l1.178-2.625a.025.025 0 0 0-.023-.035l-1.318-.003a.148.148 0 0 1-.135-.21l.876-1.954a.025.025 0 0 0-.023-.035h-1.56c-.01 0-.02.006-.024.015l-.926 2.068c-.085.169-.314.634-.399.938a.534.534 0 0 0-.02.191.46.46 0 0 0 .23.378.981.981 0 0 0 .46.119h.59c.041 0-.688 1.482-.834 1.972a.53.53 0 0 0-.023.172.465.465 0 0 0 .23.398c.15.092.342.12.475.12l1.66-.001c.01 0 .02-.006.023-.015l.575-1.28a.025.025 0 0 0-.024-.035zm-6.93-4.937H3.1a.032.032 0 0 0-.034.033c0 1.048-.01 2.795-.01 6.829 0 .288-.269.262-.28.262h-.74c-.04.001-.044.004-.04.047.001.037.465 1.064.555 1.263.01.02.03.033.051.033.157.003.767.009.938-.014.153-.02.3-.06.438-.132.3-.156.49-.419.595-.765.052-.172.075-.353.075-.533.002-2.33 0-4.66-.007-6.991a.032.032 0 0 0-.032-.032zm11.784 6.896c0-.014-.01-.021-.024-.022h-1.465c-.048-.001-.049-.002-.05-.049v-4.66c0-.072-.005-.07.07-.07h.863c.08 0 .075.004.075-.074V8.393c0-.082.006-.076-.08-.076h-3.5c-.064 0-.075-.006-.075.073v1.445c0 .083-.006.077.08.077h.854c.075 0 .07-.004.07.07v4.624c0 .095.008.084-.085.084-.37 0-1.11-.002-1.304 0-.048.001-.06.03-.06.03l-.697 1.519s-.014.025-.008.036c.006.01.013.008.058.008 1.748.003 3.495.002 5.243.002.03-.001.034-.006.035-.033v-1.539zm4.177-3.43c0 .013-.007.023-.02.024-.346.006-.692.004-1.037.004-.014-.002-.022-.01-.022-.024-.005-.434-.007-.869-.01-1.303 0-.072-.006-.071.07-.07l.733-.003c.041 0 .081.002.12.015.093.025.16.107.165.204.006.431.002 1.153.001 1.153zm2.67.244a1.953 1.953 0 0 0-.883-.222h-.18c-.04-.001-.04-.003-.042-.04V10.21c0-.132-.007-.263-.025-.394a1.823 1.823 0 0 0-.153-.53 1.533 1.533 0 0 0-.677-.71 2.167 2.167 0 0 0-1-.258c-.153-.003-.567 0-.72 0-.07 0-.068.004-.068-.065V7.76c0-.031-.01-.041-.046-.039H17.93s-.016 0-.023.007c-.006.006-.008.012-.008.023v.546c-.008.036-.057.015-.082.022h-.95c-.022.002-.028.008-.03.032v1.481c0 .09-.004.082.082.082h.913c.082 0 .072.128.072.128V11.19s.003.117-.06.117h-1.482c-.068 0-.06.082-.06.082v1.445s-.01.068.064.068h1.457c.082 0 .076-.006.076.079v3.225c0 .088-.007.081.082.081h1.43c.09 0 .082.007.082-.08v-3.27c0-.029.006-.035.033-.035l2.323-.003c.098 0 .191.02.28.061a.46.46 0 0 1 .274.407c.008.395.003.79.003 1.185 0 .259-.107.367-.33.367h-1.218c-.023.002-.029.008-.028.033.184.437.374.871.57 1.303a.045.045 0 0 0 .04.026c.17.005.34.002.51.003.15-.002.517.004.666-.01a2.03 2.03 0 0 0 .408-.075c.59-.18.975-.698.976-1.313v-1.981c0-.128-.01-.254-.034-.38 0 .078-.029-.641-.724-.998z",
|
|
323
|
+
bilibili:"M17.813 4.653h.854c1.51.054 2.769.578 3.773 1.574 1.004.995 1.524 2.249 1.56 3.76v7.36c-.036 1.51-.556 2.769-1.56 3.773s-2.262 1.524-3.773 1.56H5.333c-1.51-.036-2.769-.556-3.773-1.56S.036 18.858 0 17.347v-7.36c.036-1.511.556-2.765 1.56-3.76 1.004-.996 2.262-1.52 3.773-1.574h.774l-1.174-1.12a1.234 1.234 0 0 1-.373-.906c0-.356.124-.658.373-.907l.027-.027c.267-.249.573-.373.92-.373.347 0 .653.124.92.373L9.653 4.44c.071.071.134.142.187.213h4.267a.836.836 0 0 1 .16-.213l2.853-2.747c.267-.249.573-.373.92-.373.347 0 .662.151.929.4.267.249.391.551.391.907 0 .355-.124.657-.373.906zM5.333 7.24c-.746.018-1.373.276-1.88.773-.506.498-.769 1.13-.786 1.894v7.52c.017.764.28 1.395.786 1.893.507.498 1.134.756 1.88.773h13.334c.746-.017 1.373-.275 1.88-.773.506-.498.769-1.129.786-1.893v-7.52c-.017-.765-.28-1.396-.786-1.894-.507-.497-1.134-.755-1.88-.773zM8 11.107c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c0-.373.129-.689.386-.947.258-.257.574-.386.947-.386zm8 0c.373 0 .684.124.933.373.25.249.383.569.4.96v1.173c-.017.391-.15.711-.4.96-.249.25-.56.374-.933.374s-.684-.125-.933-.374c-.25-.249-.383-.569-.4-.96V12.44c.017-.391.15-.711.4-.96.249-.249.56-.373.933-.373Z",
|
|
324
|
+
linkedin:"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
|
|
325
|
+
unknown:"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"
|
|
326
|
+
};
|
|
327
|
+
var d=paths[p]||paths.unknown;
|
|
328
|
+
return '<svg viewBox="0 0 24 24" width="'+s+'" height="'+s+'" fill="currentColor" style="vertical-align:-2px;flex-shrink:0;display:inline-block" aria-hidden="true"><path d="'+d+'"/></svg>';
|
|
329
|
+
}
|
|
274
330
|
|
|
275
331
|
function copyText(text){
|
|
276
332
|
navigator.clipboard.writeText(text).catch(function(){});
|
|
@@ -291,7 +347,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
291
347
|
+'<span style="font-size:13px;font-weight:600;color:var(--muted)">Orchyn</span></div>';
|
|
292
348
|
// Single URL tools → 1 wide card skeleton
|
|
293
349
|
if(SINGLE_URL_TOOLS[toolName]){
|
|
294
|
-
return brandHeader+'<div class="fade-in"
|
|
350
|
+
return brandHeader+'<div class="fade-in">'
|
|
295
351
|
+'<div class="skeleton-card" style="width:100%">'
|
|
296
352
|
+'<div class="skeleton skeleton-thumb" style="height:220px"></div>'
|
|
297
353
|
+'<div style="padding:14px 16px">'
|
|
@@ -313,7 +369,21 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
313
369
|
+'<div class="skeleton" style="width:140px;height:14px"></div>'
|
|
314
370
|
+'</div></div>';
|
|
315
371
|
}
|
|
316
|
-
// Gallery tools → multiple small card skeletons
|
|
372
|
+
// Gallery tools → multiple small card skeletons
|
|
373
|
+
// Unknown tool → default to 1 wide card (most common case: single URL tools)
|
|
374
|
+
if(!toolName||SINGLE_URL_TOOLS[toolName]||CREDIT_TOOLS[toolName]){
|
|
375
|
+
// Already handled above, but catch unknown tools with single card
|
|
376
|
+
if(!toolName){
|
|
377
|
+
return brandHeader+'<div class="fade-in" style="max-width:520px">'
|
|
378
|
+
+'<div class="skeleton-card" style="width:100%">'
|
|
379
|
+
+'<div class="skeleton skeleton-thumb" style="height:220px"></div>'
|
|
380
|
+
+'<div style="padding:14px 16px">'
|
|
381
|
+
+'<div class="skeleton skeleton-badge"></div>'
|
|
382
|
+
+'<div class="skeleton skeleton-line" style="width:90%"></div>'
|
|
383
|
+
+'<div class="skeleton skeleton-line medium"></div>'
|
|
384
|
+
+'</div></div></div>';
|
|
385
|
+
}
|
|
386
|
+
}
|
|
317
387
|
var count=toolName==='discover_sounds'?4:toolName==='search_creators'||toolName==='get_similar_creators'?3:6;
|
|
318
388
|
return brandHeader+'<div class="skeleton-gallery stagger">'
|
|
319
389
|
+Array(count).fill(0).map(function(){
|
|
@@ -328,7 +398,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
328
398
|
|
|
329
399
|
// ─── Post Card ───
|
|
330
400
|
function postCard(p,wide){
|
|
331
|
-
var platform=p.platform||"unknown",color=pColor(platform),
|
|
401
|
+
var platform=p.platform||"unknown",color=pColor(platform),brandSvg=pSvg(platform);
|
|
332
402
|
var title=p.title||p.caption||"",handle=p.creatorHandle||"",url=p.externalUrl||"";
|
|
333
403
|
var thumb=p.thumbnailUrl||"",ct=p.contentType||"post";
|
|
334
404
|
var views=p.views||0,likes=p.likes||0,comments=p.comments||0,shares=p.shares||0;
|
|
@@ -338,11 +408,42 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
338
408
|
.map(function(s){return'<span class="stat-pill">'+s[1]+" "+fmtNum(s[0])+"</span>";}).join("");
|
|
339
409
|
var handleHtml=handle?'<span class="handle">@'+esc(handle)+"</span>":"";
|
|
340
410
|
var linkHtml=url?'<a href="'+esc(url)+'" target="_blank" class="btn" style="background:'+color+'">View on '+esc(platform)+' →</a>':"";
|
|
341
|
-
|
|
411
|
+
// Prefer video playback over a static thumbnail when the post has one
|
|
412
|
+
// (videoUrl, or a video mediaItem preview_url). Videos are proxied /
|
|
413
|
+
// re-hosted to a permanent orchyn URL so they play inside the CSP.
|
|
414
|
+
var video="",images=[];
|
|
415
|
+
if(typeof p.videoUrl==="string"&&p.videoUrl)video=p.videoUrl;
|
|
416
|
+
if(p.mediaItems&&p.mediaItems.length){
|
|
417
|
+
for(var i=0;i<p.mediaItems.length;i++){
|
|
418
|
+
var m=p.mediaItems[i];
|
|
419
|
+
if(m.kind==="video"&&m.preview_url){if(!video)video=m.preview_url;}
|
|
420
|
+
else if(m.kind==="image"&&m.preview_url&&images.length<8)images.push(m.preview_url);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
var mediaHtml="";
|
|
424
|
+
var vh=wide?"340px":"200px";
|
|
425
|
+
var bodyText=p.caption||p.text||"";
|
|
426
|
+
if(video){
|
|
427
|
+
mediaHtml='<video controls preload="metadata" playsinline poster="'+esc(thumb)+'" style="width:100%;height:'+vh+';object-fit:cover;display:block;background:#000">'
|
|
428
|
+
+'Your browser doesn\'t support video playback.</video>';
|
|
429
|
+
}else if(images.length>1){
|
|
430
|
+
// Carousel / multi-image gallery — horizontally scrollable strip.
|
|
431
|
+
var strip=images.map(function(u,i){
|
|
432
|
+
return '<img src="'+esc(u)+'" alt="slide '+(i+1)+'" loading="lazy" style="height:'+vh+';max-width:85%;object-fit:cover;border-radius:6px;flex-shrink:0;display:block"/>';
|
|
433
|
+
}).join("");
|
|
434
|
+
mediaHtml='<div style="display:flex;gap:6px;overflow-x:auto;padding:8px;background:var(--border);border-bottom:1px solid var(--border)">'+strip+'</div>'
|
|
435
|
+
+'<div style="display:flex;align-items:center;gap:6px;padding:6px 10px;font-size:11px;color:var(--muted);font-weight:600;border-bottom:1px solid var(--border)">🖼 '+images.length+' images</div>';
|
|
436
|
+
}else if(thumb){
|
|
437
|
+
mediaHtml='<img src="'+esc(thumb)+'" alt="thumbnail" loading="lazy"/>';
|
|
438
|
+
}else if(bodyText){
|
|
439
|
+
// Text-only post (LinkedIn / X) — styled quote block.
|
|
440
|
+
mediaHtml='<div style="padding:16px 18px;border-bottom:1px solid var(--border);background:var(--card)">'
|
|
441
|
+
+'<div style="font-size:13px;line-height:1.55;color:var(--fg);white-space:pre-line">'+esc(bodyText.length>400?bodyText.slice(0,400)+"…":bodyText)+'</div></div>';
|
|
442
|
+
}
|
|
342
443
|
var ctTag=wide?'<span style="font-size:12px;color:var(--muted);text-transform:capitalize">'+esc(ct)+"</span>":"";
|
|
343
|
-
return '<div class="'+cls+'">'+
|
|
444
|
+
return '<div class="'+cls+'">'+mediaHtml+'<div class="card-body">'
|
|
344
445
|
+'<div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">'
|
|
345
|
-
+'<span class="badge" style="background:'+color+'15;color:'+color+'">'+
|
|
446
|
+
+'<span class="badge" style="background:'+color+'15;color:'+color+';display:inline-flex;align-items:center;gap:4px">'+brandSvg+" "+esc(platform)+"</span>"
|
|
346
447
|
+ctTag+handleHtml+"</div>"
|
|
347
448
|
+'<div class="title">'+esc(title.length>160?title.slice(0,160)+"…":title)+"</div>"
|
|
348
449
|
+'<div class="stats">'+statsHtml+"</div>"
|
|
@@ -351,13 +452,13 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
351
452
|
|
|
352
453
|
// ─── Creator Card ───
|
|
353
454
|
function creatorCard(c){
|
|
354
|
-
var platform=c.platform||"tiktok",color=pColor(platform),
|
|
455
|
+
var platform=c.platform||"tiktok",color=pColor(platform),brandSvg=pSvg(platform,22);
|
|
355
456
|
var username=c.username||c.uniqueId||"",nickname=c.nickname||c.displayName||username;
|
|
356
457
|
var followers=c.followers||c.followerCount||0,sig=(c.signature||c.bio||"").slice(0,100);
|
|
357
458
|
var verified=c.verified||false,avatar=c.avatarUrl||c.avatar_thumb||"";
|
|
358
459
|
var avatarHtml=avatar
|
|
359
460
|
?'<img class="avatar" src="'+esc(avatar)+'" alt="avatar" loading="lazy"/>'
|
|
360
|
-
:'<div class="avatar-placeholder" style="background:'+color+'20">'+
|
|
461
|
+
:'<div class="avatar-placeholder" style="background:'+color+'20">'+brandSvg+"</div>";
|
|
361
462
|
var vBadge=verified?'<span class="verified" style="background:'+color+'">✓</span>':"";
|
|
362
463
|
return '<div class="creator-card">'
|
|
363
464
|
+'<div style="display:flex;align-items:center;gap:12px;margin-bottom:10px">'
|
|
@@ -489,29 +590,25 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
489
590
|
if(d.post||d.platform){app.innerHTML=postCard(d.post||d,true);return;}
|
|
490
591
|
// Fallback
|
|
491
592
|
app.innerHTML='<div class="json-block fade-in">'+esc(JSON.stringify(d,null,2))+"</div>";
|
|
593
|
+
setTimeout(reportSize,50);
|
|
492
594
|
}
|
|
493
595
|
|
|
494
596
|
function updateProgress(params){
|
|
495
597
|
var app=document.getElementById("app");
|
|
496
598
|
if(!app)return;
|
|
497
|
-
//
|
|
498
|
-
// re-render with the correct skeleton type
|
|
599
|
+
// Learn the tool name
|
|
499
600
|
var toolName=params&¶ms.name?params.name:currentTool;
|
|
500
601
|
if(toolName&&toolName!==currentTool){
|
|
501
602
|
currentTool=toolName;
|
|
502
|
-
//
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
el.innerHTML=renderSkeleton(currentTool);
|
|
603
|
+
// Switch from spinner to correct skeleton if still loading
|
|
604
|
+
if(app.querySelector(".loading-spinner")||app.querySelector(".loading-container")||app.querySelector(".skeleton")){
|
|
605
|
+
app.innerHTML=renderSkeleton(currentTool);
|
|
506
606
|
}
|
|
507
607
|
}
|
|
508
|
-
|
|
509
|
-
app.
|
|
510
|
-
+'<div
|
|
511
|
-
|
|
512
|
-
+'<div class="loading-text">'+esc(text.length>80?text.slice(0,80)+"…":text)+'</div>'
|
|
513
|
-
+'<div class="progress-bar" style="width:200px"><div class="progress-fill" style="width:60%"></div></div>'
|
|
514
|
-
+'</div>';
|
|
608
|
+
// Add a progress bar below the existing content if not already present
|
|
609
|
+
if(!app.querySelector(".progress-bar")){
|
|
610
|
+
app.innerHTML=app.innerHTML+'<div style="text-align:center;margin-top:12px"><div class="progress-bar" style="width:200px;margin:0 auto"><div class="progress-fill" style="width:60%"></div></div></div>';
|
|
611
|
+
}
|
|
515
612
|
}
|
|
516
613
|
|
|
517
614
|
// Global helpers
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchyn/mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for orchyn - fetch, discover and understand TikTok/Instagram/YouTube/X posts with AI (media metadata, niche discovery, hook & viral analysis)",
|
|
3
|
+
"version": "1.14.0",
|
|
4
|
+
"description": "MCP server for orchyn - fetch, discover and understand TikTok/Instagram/YouTube/X/LinkedIn posts with AI (media metadata, niche discovery, hook & viral analysis)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"files": [
|