@orchyn/mcp 1.7.4 → 1.9.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/shared/tools.js +56 -4
- package/dist/shared/ui-template.js +354 -0
- package/package.json +1 -1
package/dist/shared/tools.js
CHANGED
|
@@ -9,6 +9,13 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { OrchynError } from "./orchyn.js";
|
|
11
11
|
import { formatPaywallError, runVideoAnalysis, validatePostUrl } from "./video.js";
|
|
12
|
+
import { ORCHYN_UI_TEMPLATE } from "./ui-template.js";
|
|
13
|
+
/** MCP Apps extension identifier */
|
|
14
|
+
const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
15
|
+
/** MIME type for MCP Apps HTML resources */
|
|
16
|
+
const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
17
|
+
/** Single UI resource URI shared by all tools */
|
|
18
|
+
const UI_RESOURCE_URI = "ui://orchyn/view";
|
|
12
19
|
function toToolResult(proxy) {
|
|
13
20
|
const images = proxy.contentBlocks
|
|
14
21
|
.filter((c) => c.type === "image")
|
|
@@ -17,7 +24,17 @@ function toToolResult(proxy) {
|
|
|
17
24
|
data: String(c.data ?? ""),
|
|
18
25
|
mimeType: String(c.mimeType ?? "image/jpeg"),
|
|
19
26
|
}));
|
|
20
|
-
|
|
27
|
+
// The Rust backend embeds HTML cards directly in the text block
|
|
28
|
+
// (type:"text", text:"<div>...</div>\n\n{json}"). We extract the HTML
|
|
29
|
+
// prefix from the first text contentBlock and prepend it.
|
|
30
|
+
const textBlock = proxy.contentBlocks.find((c) => c.type === "text");
|
|
31
|
+
const rawText = textBlock ? String(textBlock.text ?? "") : "";
|
|
32
|
+
const htmlPrefix = rawText.startsWith("<")
|
|
33
|
+
? rawText.substring(0, rawText.indexOf("\n\n{")).trimEnd()
|
|
34
|
+
: "";
|
|
35
|
+
const structured = proxy.structured;
|
|
36
|
+
const textJson = JSON.stringify(structured ?? {}, null, 2);
|
|
37
|
+
const text = htmlPrefix ? `${htmlPrefix}\n\n${textJson}` : textJson;
|
|
21
38
|
return { content: [...images, { type: "text", text }] };
|
|
22
39
|
}
|
|
23
40
|
function toolError(prefix, err) {
|
|
@@ -25,15 +42,39 @@ function toolError(prefix, err) {
|
|
|
25
42
|
return { content: [{ type: "text", text: `${prefix}: ${msg}` }], isError: true };
|
|
26
43
|
}
|
|
27
44
|
export function createMcpServer(makeClient) {
|
|
28
|
-
const server = new McpServer({
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
const server = new McpServer({ name: "orchyn-mcp", version: "1.8.0" }, {
|
|
46
|
+
capabilities: {
|
|
47
|
+
resources: {},
|
|
48
|
+
extensions: {
|
|
49
|
+
[UI_EXTENSION]: { mimeTypes: [RESOURCE_MIME_TYPE] },
|
|
50
|
+
},
|
|
51
|
+
},
|
|
31
52
|
});
|
|
53
|
+
// Register the single UI resource that all tools share.
|
|
54
|
+
// The host reads this resource, renders it in a sandboxed iframe,
|
|
55
|
+
// and pushes tool results via ui/notifications/tool-result.
|
|
56
|
+
server.registerResource("Orchyn Interactive View", UI_RESOURCE_URI, { mimeType: RESOURCE_MIME_TYPE }, async () => ({
|
|
57
|
+
contents: [
|
|
58
|
+
{
|
|
59
|
+
uri: UI_RESOURCE_URI,
|
|
60
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
61
|
+
text: ORCHYN_UI_TEMPLATE,
|
|
62
|
+
_meta: {
|
|
63
|
+
ui: {
|
|
64
|
+
csp: {
|
|
65
|
+
resourceDomains: ["https://*.tiktokcdn.com", "https://*.cdninstagram.com", "https://*.ytimg.com", "https://*.googlevideo.com"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
}));
|
|
32
72
|
server.registerTool("analyze_post", {
|
|
33
73
|
title: "Analyze Post",
|
|
34
74
|
description: "Analyze a social post (video, image, carousel/slideshow) from its link — " +
|
|
35
75
|
"imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). " +
|
|
36
76
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Returns the full analysis once finished. First use free per user.",
|
|
77
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
37
78
|
inputSchema: z
|
|
38
79
|
.object({
|
|
39
80
|
url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X, Douyin, Xiaohongshu or Bilibili)."),
|
|
@@ -77,6 +118,7 @@ export function createMcpServer(makeClient) {
|
|
|
77
118
|
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu or Bilibili URL: " +
|
|
78
119
|
"contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. " +
|
|
79
120
|
"Returns an inline thumbnail image. Consumes 1 orchyn credit. First use free per user.",
|
|
121
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
80
122
|
inputSchema: z
|
|
81
123
|
.object({
|
|
82
124
|
url: z.string().describe("Full public post URL."),
|
|
@@ -96,6 +138,7 @@ export function createMcpServer(makeClient) {
|
|
|
96
138
|
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
|
|
97
139
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
98
140
|
'Say "next" to paginate (offset), or "analyze the 2nd one" / "analyze all" for batch analysis. Consumes 2 orchyn credits. First use free per user.',
|
|
141
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
99
142
|
inputSchema: z
|
|
100
143
|
.object({
|
|
101
144
|
niche: z.string().describe("Niche/topic, e.g. 'fitness'."),
|
|
@@ -122,6 +165,7 @@ export function createMcpServer(makeClient) {
|
|
|
122
165
|
description: "List recent posts by a creator handle (e.g. @zoundsapp) on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
|
|
123
166
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
124
167
|
"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.",
|
|
168
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
125
169
|
inputSchema: z
|
|
126
170
|
.object({
|
|
127
171
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."),
|
|
@@ -146,6 +190,7 @@ export function createMcpServer(makeClient) {
|
|
|
146
190
|
description: "Deep-dive a whole creator profile on TikTok, Instagram, YouTube, Douyin, Xiaohongshu, X/Twitter or Bilibili: fetch recent posts, run multimodal AI on up to 3, " +
|
|
147
191
|
"then synthesize a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, " +
|
|
148
192
|
"engagement patterns, audience insights, variation ideas, collaboration fit. Consumes 15 orchyn credits. First use free per user.",
|
|
193
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
149
194
|
inputSchema: z
|
|
150
195
|
.object({
|
|
151
196
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp'."),
|
|
@@ -170,6 +215,7 @@ export function createMcpServer(makeClient) {
|
|
|
170
215
|
title: "Get Post Comments",
|
|
171
216
|
description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter or Bilibili, plus keyword clusters from TikTok Analytics " +
|
|
172
217
|
"when available — audience sentiment/audience-signal analysis. Consumes 2 orchyn credits. First use free per user.",
|
|
218
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
173
219
|
inputSchema: z
|
|
174
220
|
.object({
|
|
175
221
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili)."),
|
|
@@ -189,6 +235,7 @@ export function createMcpServer(makeClient) {
|
|
|
189
235
|
title: "Search Creators",
|
|
190
236
|
description: "Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, " +
|
|
191
237
|
"signature, verified status. Use to find influencers to vet or analyze. Consumes 2 orchyn credits. First use free per user.",
|
|
238
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
192
239
|
inputSchema: z
|
|
193
240
|
.object({
|
|
194
241
|
keyword: z.string().describe("Niche/keyword, e.g. 'fitness' or a creator name."),
|
|
@@ -212,6 +259,7 @@ export function createMcpServer(makeClient) {
|
|
|
212
259
|
title: "Get Similar Creators",
|
|
213
260
|
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram " +
|
|
214
261
|
"similar users. Useful for scaling: 'if this creator works, here are more like them'. Consumes 2 orchyn credits. First use free per user.",
|
|
262
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
215
263
|
inputSchema: z
|
|
216
264
|
.object({
|
|
217
265
|
username: z.string().describe("Seed creator handle, e.g. 'zoundsapp'."),
|
|
@@ -231,6 +279,7 @@ export function createMcpServer(makeClient) {
|
|
|
231
279
|
title: "Discover Sounds",
|
|
232
280
|
description: "Discover trending sounds/music for a keyword on TikTok or Instagram — the sound is a huge ranking " +
|
|
233
281
|
"signal for TikTok virality. Returns title, artist, duration, play/cover URLs. Consumes 2 orchyn credits. First use free per user.",
|
|
282
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
234
283
|
inputSchema: z
|
|
235
284
|
.object({
|
|
236
285
|
keyword: z.string().describe("Niche/keyword, e.g. 'gym'."),
|
|
@@ -250,6 +299,7 @@ export function createMcpServer(makeClient) {
|
|
|
250
299
|
server.registerTool("check_orchyn_credits", {
|
|
251
300
|
title: "Check Orchyn Credits",
|
|
252
301
|
description: "Check your orchyn credit balance, billing URL and pack size. No cost — call anytime to see remaining credits before running other tools.",
|
|
302
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
253
303
|
inputSchema: z.object({}).strict(),
|
|
254
304
|
}, async (_args, extra) => {
|
|
255
305
|
const client = await makeClient(extra);
|
|
@@ -263,6 +313,7 @@ export function createMcpServer(makeClient) {
|
|
|
263
313
|
server.registerTool("buy_orchyn_credits", {
|
|
264
314
|
title: "Buy Orchyn Credits",
|
|
265
315
|
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.",
|
|
316
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
266
317
|
inputSchema: z.object({}).strict(),
|
|
267
318
|
}, async (_args, extra) => {
|
|
268
319
|
const client = await makeClient(extra);
|
|
@@ -278,6 +329,7 @@ export function createMcpServer(makeClient) {
|
|
|
278
329
|
description: "Import a social post URL AND understand it with multimodal AI over the actual video/images: " +
|
|
279
330
|
"summary, hook strength, viral triggers, format breakdown and variation ideas. Includes the thumbnail. " +
|
|
280
331
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Consumes 6 orchyn credits. First use free per user.",
|
|
332
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
281
333
|
inputSchema: z
|
|
282
334
|
.object({
|
|
283
335
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/X/Douyin/Xiaohongshu/Bilibili)."),
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single unified HTML template for all Orchyn MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* The host renders this in a sandboxed iframe. After the MCP Apps handshake,
|
|
5
|
+
* the host pushes the tool result via `ui/notifications/tool-result`.
|
|
6
|
+
* The template auto-detects the data shape and renders the appropriate view.
|
|
7
|
+
*
|
|
8
|
+
* No external dependencies — pure HTML + CSS + JS, fully inline.
|
|
9
|
+
*/
|
|
10
|
+
export const ORCHYN_UI_TEMPLATE = `<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8"/>
|
|
14
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
15
|
+
<title>Orchyn</title>
|
|
16
|
+
<style>
|
|
17
|
+
:root {
|
|
18
|
+
--bg: #ffffff; --fg: #111827; --muted: #6b7280; --border: #e5e7eb;
|
|
19
|
+
--card-bg: #ffffff; --tag-bg: #f3f4f6; --accent: #14151a;
|
|
20
|
+
--tiktok: #000; --instagram: #E1306C; --youtube: #FF0000;
|
|
21
|
+
--douyin: #000; --xiaohongshu: #FF2442; --twitter: #1DA1F2; --bilibili: #00A1D6;
|
|
22
|
+
}
|
|
23
|
+
@media(prefers-color-scheme:dark){
|
|
24
|
+
:root {
|
|
25
|
+
--bg: #1a1a2e; --fg: #f0f0f0; --muted: #9ca3af; --border: #374151;
|
|
26
|
+
--card-bg: #16213e; --tag-bg: #1f2937; --accent: #e2e8f0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
* { margin:0; padding:0; box-sizing:border-box; }
|
|
30
|
+
body { font-family: system-ui,-apple-system,sans-serif; background:var(--bg); color:var(--fg); padding:12px; }
|
|
31
|
+
.loading { color:var(--muted); font-size:14px; text-align:center; padding:40px 0; }
|
|
32
|
+
.gallery { display:flex; flex-wrap:wrap; gap:10px; }
|
|
33
|
+
.card {
|
|
34
|
+
border:1px solid var(--border); border-radius:12px; overflow:hidden;
|
|
35
|
+
width:300px; background:var(--card-bg); display:inline-block;
|
|
36
|
+
vertical-align:top; box-shadow:0 1px 3px rgba(0,0,0,0.06);
|
|
37
|
+
transition: box-shadow 0.2s;
|
|
38
|
+
}
|
|
39
|
+
.card:hover { box-shadow:0 4px 12px rgba(0,0,0,0.12); }
|
|
40
|
+
.card-wide { max-width:520px; width:100%; }
|
|
41
|
+
.card img { width:100%; height:200px; object-fit:cover; display:block; }
|
|
42
|
+
.card-wide img { max-height:340px; }
|
|
43
|
+
.card-body { padding:12px 14px; }
|
|
44
|
+
.badge {
|
|
45
|
+
display:inline-block; padding:2px 8px; border-radius:999px;
|
|
46
|
+
font-size:11px; font-weight:700; text-transform:capitalize;
|
|
47
|
+
}
|
|
48
|
+
.handle { font-size:12px; color:var(--muted); }
|
|
49
|
+
.title { font-size:14px; font-weight:600; line-height:1.3; margin:6px 0; }
|
|
50
|
+
.card-wide .title { font-size:16px; font-weight:700; }
|
|
51
|
+
.stats { display:flex; flex-wrap:wrap; gap:4px; align-items:center; }
|
|
52
|
+
.stat {
|
|
53
|
+
display:inline-flex; align-items:center; gap:3px;
|
|
54
|
+
font-size:12px; color:var(--muted);
|
|
55
|
+
}
|
|
56
|
+
.stat-pill {
|
|
57
|
+
font-size:13px; color:var(--fg); background:var(--tag-bg);
|
|
58
|
+
padding:4px 10px; border-radius:999px;
|
|
59
|
+
}
|
|
60
|
+
.dot { color:var(--border); margin:0 2px; }
|
|
61
|
+
.btn {
|
|
62
|
+
display:inline-block; margin-top:10px; padding:6px 14px;
|
|
63
|
+
color:#fff; border-radius:999px; text-decoration:none;
|
|
64
|
+
font-size:12px; font-weight:600; transition:opacity 0.2s;
|
|
65
|
+
}
|
|
66
|
+
.btn:hover { opacity:0.85; }
|
|
67
|
+
.section { margin-bottom:12px; }
|
|
68
|
+
.section-label {
|
|
69
|
+
font-size:11px; font-weight:700; color:var(--muted);
|
|
70
|
+
text-transform:uppercase; letter-spacing:0.05em; margin-bottom:4px;
|
|
71
|
+
}
|
|
72
|
+
.section-text { font-size:13px; color:var(--fg); line-height:1.5; }
|
|
73
|
+
.tag {
|
|
74
|
+
display:inline-block; padding:3px 10px; border-radius:999px;
|
|
75
|
+
font-size:12px; margin:2px;
|
|
76
|
+
}
|
|
77
|
+
.bar-track {
|
|
78
|
+
height:8px; background:var(--tag-bg); border-radius:999px;
|
|
79
|
+
overflow:hidden; flex:1;
|
|
80
|
+
}
|
|
81
|
+
.bar-fill { height:100%; border-radius:999px; transition:width 0.4s ease; }
|
|
82
|
+
.quote-box {
|
|
83
|
+
background:var(--tag-bg); padding:8px 12px; border-radius:8px;
|
|
84
|
+
font-size:13px; font-weight:600; line-height:1.4; margin:4px 0;
|
|
85
|
+
}
|
|
86
|
+
.comment-row {
|
|
87
|
+
padding:8px 0; border-bottom:1px solid var(--border);
|
|
88
|
+
display:flex; justify-content:space-between; align-items:flex-start; gap:8px;
|
|
89
|
+
}
|
|
90
|
+
.comment-row:last-child { border-bottom:none; }
|
|
91
|
+
.creator-card {
|
|
92
|
+
border:1px solid var(--border); border-radius:12px; padding:14px;
|
|
93
|
+
width:220px; background:var(--card-bg); display:inline-block;
|
|
94
|
+
vertical-align:top; margin:4px; box-shadow:0 1px 3px rgba(0,0,0,0.05);
|
|
95
|
+
}
|
|
96
|
+
.avatar {
|
|
97
|
+
width:48px; height:48px; border-radius:50%; object-fit:cover;
|
|
98
|
+
}
|
|
99
|
+
.avatar-placeholder {
|
|
100
|
+
width:48px; height:48px; border-radius:50%;
|
|
101
|
+
display:flex; align-items:center; justify-content:center; font-size:20px;
|
|
102
|
+
}
|
|
103
|
+
.verified {
|
|
104
|
+
display:inline-block; width:16px; height:16px; border-radius:50%;
|
|
105
|
+
color:#fff; font-size:10px; line-height:16px; text-align:center; margin-left:4px;
|
|
106
|
+
}
|
|
107
|
+
.sound-card {
|
|
108
|
+
border:1px solid var(--border); border-radius:12px; padding:12px;
|
|
109
|
+
width:260px; background:var(--card-bg); display:inline-block;
|
|
110
|
+
vertical-align:top; margin:4px; box-shadow:0 1px 3px rgba(0,0,0,0.05);
|
|
111
|
+
}
|
|
112
|
+
.sound-cover {
|
|
113
|
+
width:64px; height:64px; border-radius:8px; object-fit:cover;
|
|
114
|
+
}
|
|
115
|
+
.credits-card {
|
|
116
|
+
border:1px solid var(--border); border-radius:14px; max-width:360px;
|
|
117
|
+
background:var(--card-bg); margin:8px 0; padding:20px;
|
|
118
|
+
box-shadow:0 2px 8px rgba(0,0,0,0.06);
|
|
119
|
+
}
|
|
120
|
+
.tier-badge {
|
|
121
|
+
display:inline-block; padding:3px 10px; background:#eff6ff;
|
|
122
|
+
border-radius:999px; font-size:11px; color:#1e40af; font-weight:600;
|
|
123
|
+
}
|
|
124
|
+
.free-tag {
|
|
125
|
+
display:inline-block; padding:3px 8px; background:#d1fae5;
|
|
126
|
+
border-radius:999px; font-size:11px; color:#065f46; margin:2px;
|
|
127
|
+
}
|
|
128
|
+
.json-block {
|
|
129
|
+
background:var(--tag-bg); border:1px solid var(--border);
|
|
130
|
+
border-radius:8px; padding:12px; font-size:12px;
|
|
131
|
+
font-family:monospace; white-space:pre-wrap; word-break:break-all;
|
|
132
|
+
max-height:300px; overflow-y:auto; margin-top:8px;
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
135
|
+
</head>
|
|
136
|
+
<body>
|
|
137
|
+
<div id="app"><div class="loading">Loading…</div></div>
|
|
138
|
+
<script>
|
|
139
|
+
(function(){
|
|
140
|
+
let nextId=1;
|
|
141
|
+
const pending=new Map();
|
|
142
|
+
|
|
143
|
+
function send(method,params){
|
|
144
|
+
const id=nextId++;
|
|
145
|
+
return new Promise((resolve,reject)=>{
|
|
146
|
+
pending.set(id,{resolve,reject});
|
|
147
|
+
window.parent.postMessage({jsonrpc:"2.0",id,method,params},"*");
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
window.addEventListener("message",function(ev){
|
|
152
|
+
const d=ev.data;
|
|
153
|
+
if(!d||typeof d!=="object")return;
|
|
154
|
+
if(d.id&&pending.has(d.id)){
|
|
155
|
+
const p=pending.get(d.id);
|
|
156
|
+
pending.delete(d.id);
|
|
157
|
+
if(d.error)p.reject(new Error(d.error.message||JSON.stringify(d.error)));
|
|
158
|
+
else p.resolve(d.result);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if(d.method==="ui/notifications/tool-result"){
|
|
162
|
+
render(d.params);
|
|
163
|
+
}
|
|
164
|
+
if(d.method==="ui/notifications/tool-input"){
|
|
165
|
+
// Tool input available — could show loading state
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// MCP Apps handshake
|
|
170
|
+
send("ui/initialize",{
|
|
171
|
+
protocolVersion:"2026-01-26",
|
|
172
|
+
capabilities:{},
|
|
173
|
+
clientInfo:{name:"orchyn-view",version:"1.0.0"}
|
|
174
|
+
}).then(function(){
|
|
175
|
+
window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/initialized",params:{}},"*");
|
|
176
|
+
}).catch(function(){});
|
|
177
|
+
|
|
178
|
+
function esc(s){return String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");}
|
|
179
|
+
function fmtNum(n){n=Number(n)||0;return n>=1e6?(n/1e6).toFixed(1)+"M":n>=1e3?(n/1e3).toFixed(1)+"K":String(n);}
|
|
180
|
+
function pColor(p){return{tiktok:"#000",instagram:"#E1306C",youtube:"#FF0000",douyin:"#000",xiaohongshu:"#FF2442",twitter:"#1DA1F2",x:"#1DA1F2",bilibili:"#00A1D6"}[p]||"#6B7280";}
|
|
181
|
+
function pEmoji(p){return{tiktok:"🎵",instagram:"📸",youtube:"▶️",douyin:"🎶",xiaohongshu:"📕",twitter:"🐦",x:"🐦",bilibili:"📺"}[p]||"🔗";}
|
|
182
|
+
|
|
183
|
+
function postCard(p,wide){
|
|
184
|
+
var platform=p.platform||"unknown";
|
|
185
|
+
var color=pColor(platform);
|
|
186
|
+
var emoji=pEmoji(platform);
|
|
187
|
+
var title=p.title||p.caption||"";
|
|
188
|
+
var handle=p.creatorHandle||"";
|
|
189
|
+
var url=p.externalUrl||"";
|
|
190
|
+
var thumb=p.thumbnailUrl||"";
|
|
191
|
+
var ct=p.contentType||"post";
|
|
192
|
+
var views=p.views||0,likes=p.likes||0,comments=p.comments||0,shares=p.shares||0;
|
|
193
|
+
var cls=wide?"card card-wide":"card";
|
|
194
|
+
var statsHtml="";
|
|
195
|
+
[[views,"👁️"],[likes,"❤️"],[comments,"💬"],[shares,"🔄"]].forEach(function(s){
|
|
196
|
+
if(s[0]>0)statsHtml+='<span class="stat">'+s[1]+" "+fmtNum(s[0])+"</span>";
|
|
197
|
+
});
|
|
198
|
+
var handleHtml=handle?'<span class="handle">@'+esc(handle)+"</span>":"";
|
|
199
|
+
var linkHtml=url?'<a href="'+esc(url)+'" target="_blank" class="btn" style="background:'+color+'">View on '+esc(platform)+" →</a>":"";
|
|
200
|
+
var thumbHtml=thumb?'<img src="'+esc(thumb)+'" alt="thumbnail"/>':"";
|
|
201
|
+
var ctTag=wide?'<span style="font-size:12px;color:var(--muted);text-transform:capitalize">'+esc(ct)+"</span>":"";
|
|
202
|
+
return '<div class="'+cls+'">'+thumbHtml+'<div class="card-body">'
|
|
203
|
+
+'<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px">'
|
|
204
|
+
+'<span class="badge" style="background:'+color+'15;color:'+color+'">'+emoji+" "+esc(platform)+"</span>"
|
|
205
|
+
+ctTag+handleHtml+"</div>"
|
|
206
|
+
+'<div class="title">'+esc(title.length>160?title.slice(0,160)+"…":title)+"</div>"
|
|
207
|
+
+'<div class="stats">'+statsHtml+"</div>"
|
|
208
|
+
+linkHtml+"</div></div>";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function creatorCard(c){
|
|
212
|
+
var platform=c.platform||"tiktok";
|
|
213
|
+
var color=pColor(platform);
|
|
214
|
+
var emoji=pEmoji(platform);
|
|
215
|
+
var username=c.username||c.uniqueId||"";
|
|
216
|
+
var nickname=c.nickname||c.displayName||username;
|
|
217
|
+
var followers=c.followers||c.followerCount||0;
|
|
218
|
+
var sig=(c.signature||c.bio||"").slice(0,100);
|
|
219
|
+
var verified=c.verified||false;
|
|
220
|
+
var avatar=c.avatarUrl||c.avatar_thumb||"";
|
|
221
|
+
var avatarHtml=avatar
|
|
222
|
+
?'<img class="avatar" src="'+esc(avatar)+'" alt="avatar"/>'
|
|
223
|
+
:'<div class="avatar-placeholder" style="background:'+color+'20">'+emoji+"</div>";
|
|
224
|
+
var vBadge=verified?'<span class="verified" style="background:'+color+'">✓</span>':"";
|
|
225
|
+
return '<div class="creator-card">'
|
|
226
|
+
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">'
|
|
227
|
+
+avatarHtml+"<div>"
|
|
228
|
+
+'<div style="display:flex;align-items:center"><span style="font-size:14px;font-weight:700">'+esc(nickname)+"</span>"+vBadge+"</div>"
|
|
229
|
+
+'<div class="handle">@'+esc(username)+"</div></div></div>"
|
|
230
|
+
+'<div style="font-size:12px;color:var(--fg);font-weight:600;margin-bottom:4px">'+fmtNum(followers)+" followers</div>"
|
|
231
|
+
+sig?'<div style="font-size:12px;color:var(--muted);line-height:1.4">'+esc(sig)+"</div>":""
|
|
232
|
+
+"</div>";
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function analysisCard(d){
|
|
236
|
+
var a=d.analysis||{};
|
|
237
|
+
var postHtml=postCard(d.post||d,true);
|
|
238
|
+
var sections="";
|
|
239
|
+
if(a.summary)sections+='<div class="section"><div class="section-label">Summary</div><div class="section-text">'+esc(a.summary)+"</div></div>";
|
|
240
|
+
if(a.hookStrength!=null){
|
|
241
|
+
var hs=Number(a.hookStrength);
|
|
242
|
+
var pct=Math.min(hs/10*100,100);
|
|
243
|
+
var bc=hs>=7?"#10b981":hs>=4?"#f59e0b":"#ef4444";
|
|
244
|
+
sections+='<div class="section"><div class="section-label">Hook Strength</div>'
|
|
245
|
+
+'<div style="display:flex;align-items:center;gap:8px"><div class="bar-track"><div class="bar-fill" style="width:'+pct+"%;background:"+bc+'"></div></div>'
|
|
246
|
+
+'<span style="font-size:13px;font-weight:700">'+hs+"/10</span></div></div>";
|
|
247
|
+
}
|
|
248
|
+
if(a.niche)sections+='<div class="section"><span class="tag" style="background:var(--tag-bg);color:var(--fg)">📍 '+esc(a.niche)+"</span></div>";
|
|
249
|
+
if(a.whyItWorks)sections+='<div class="section"><div class="section-label">Why It Works</div><div class="section-text">'+esc((a.whyItWorks||"").slice(0,300))+"</div></div>";
|
|
250
|
+
if(a.emotionalArc)sections+='<div class="section"><div class="section-label">Emotional Arc</div><div class="section-text">'+esc(a.emotionalArc)+"</div></div>";
|
|
251
|
+
if(a.viralTriggers&&a.viralTriggers.length){
|
|
252
|
+
var vt=a.viralTriggers.slice(0,6).map(function(t){return'<span class="tag" style="background:#fef3c7;color:#92400e">🔥 '+esc(t)+"</span>";}).join("");
|
|
253
|
+
sections+='<div class="section"><div class="section-label">Viral Triggers</div>'+vt+"</div>";
|
|
254
|
+
}
|
|
255
|
+
if(a.suggestedHook)sections+='<div class="section"><div class="section-label">Suggested Hook</div><div class="quote-box">💡 '+esc(a.suggestedHook)+"</div></div>";
|
|
256
|
+
if(a.suggestedHashtags&&a.suggestedHashtags.length){
|
|
257
|
+
var sh=a.suggestedHashtags.slice(0,8).map(function(t){return'<span class="tag" style="background:#ede9fe;color:#6d28d9">#'+esc(t.replace(/^#/,""))+"</span>";}).join("");
|
|
258
|
+
sections+='<div class="section"><div class="section-label">Hashtags</div>'+sh+"</div>";
|
|
259
|
+
}
|
|
260
|
+
if(a.formatBreakdown)sections+='<div class="section"><div class="section-label">Format</div><div class="section-text">'+esc((a.formatBreakdown||"").slice(0,250))+"</div></div>";
|
|
261
|
+
if(a.negativeSignals&&a.negativeSignals.length){
|
|
262
|
+
var ns=a.negativeSignals.slice(0,5).map(function(n){return'<span class="tag" style="background:#fef2f2;color:#991b1b">⚠️ '+esc(n)+"</span>";}).join("");
|
|
263
|
+
sections+='<div class="section"><div class="section-label">Weaknesses</div>'+ns+"</div>";
|
|
264
|
+
}
|
|
265
|
+
if(a.variationIdeas&&a.variationIdeas.length){
|
|
266
|
+
var vi=a.variationIdeas.slice(0,5).map(function(v){return"<li>"+esc(v)+"</li>";}).join("");
|
|
267
|
+
sections+='<div class="section"><div class="section-label">Variation Ideas</div><ul style="margin:0;padding-left:18px">'+vi+"</ul></div>";
|
|
268
|
+
}
|
|
269
|
+
if(!sections)return postHtml;
|
|
270
|
+
return postHtml+'<div class="card card-wide" style="margin-top:8px"><div class="card-body">'
|
|
271
|
+
+'<div style="font-size:15px;font-weight:700;margin-bottom:14px">📊 AI Analysis</div>'
|
|
272
|
+
+sections+"</div></div>";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function render(result){
|
|
276
|
+
var app=document.getElementById("app");
|
|
277
|
+
if(!app)return;
|
|
278
|
+
var d=result&&result.structuredContent?result.structuredContent:result;
|
|
279
|
+
if(!d||typeof d!=="object"){app.innerHTML='<div class="json-block">'+esc(JSON.stringify(d,null,2))+"</div>";return;}
|
|
280
|
+
|
|
281
|
+
// Detect shape
|
|
282
|
+
if(d.analysis&&(d.post||d.url))return void(app.innerHTML=analysisCard(d));
|
|
283
|
+
if(d.posts&&Array.isArray(d.posts)){
|
|
284
|
+
var gallery=d.posts.map(function(p){return postCard(p,false);}).join("");
|
|
285
|
+
return void(app.innerHTML='<div class="gallery">'+gallery+"</div>");
|
|
286
|
+
}
|
|
287
|
+
if(d.creators&&Array.isArray(d.creators)){
|
|
288
|
+
var cg=d.creators.map(function(c){return creatorCard(c);}).join("");
|
|
289
|
+
return void(app.innerHTML='<div class="gallery">'+cg+"</div>");
|
|
290
|
+
}
|
|
291
|
+
if(d.comments&&Array.isArray(d.comments)){
|
|
292
|
+
var ch=d.comments.slice(0,15).map(function(c){
|
|
293
|
+
var user=c.username||c.user||"";
|
|
294
|
+
var text=(c.text||c.comment||"").slice(0,120);
|
|
295
|
+
var likes=c.likes||c.likeCount||0;
|
|
296
|
+
return '<div class="comment-row"><div style="flex:1"><span style="font-size:12px;font-weight:600">@'+esc(user)+"</span>"
|
|
297
|
+
+'<div style="font-size:13px;color:var(--fg);line-height:1.4;margin-top:2px">'+esc(text)+"</div></div>"
|
|
298
|
+
+(likes>0?'<span style="font-size:11px;color:var(--muted);white-space:nowrap">❤️ '+fmtNum(likes)+"</span>":"")+"</div>";
|
|
299
|
+
}).join("");
|
|
300
|
+
return void(app.innerHTML='<div class="card card-wide"><div class="card-body">'
|
|
301
|
+
+'<div style="font-size:15px;font-weight:700;margin-bottom:10px">💬 Comments</div>'+ch+"</div></div>");
|
|
302
|
+
}
|
|
303
|
+
if(d.sounds&&Array.isArray(d.sounds)){
|
|
304
|
+
var snds=d.sounds.map(function(s){
|
|
305
|
+
var color=pColor(s.platform||"tiktok");
|
|
306
|
+
var cover=s.coverUrl?'<img class="sound-cover" src="'+esc(s.coverUrl)+'" alt="cover"/>'
|
|
307
|
+
:'<div class="sound-cover" style="background:'+color+'15;display:flex;align-items:center;justify-content:center;font-size:24px">🎵</div>';
|
|
308
|
+
var play=s.playUrl||s.url||"";
|
|
309
|
+
var playBtn=play?'<a href="'+esc(play)+'" target="_blank" class="btn" style="background:'+color+';margin-top:6px;padding:4px 12px;font-size:11px">▶ Play</a>':"";
|
|
310
|
+
return '<div class="sound-card"><div style="display:flex;gap:10px;align-items:center">'
|
|
311
|
+
+cover+'<div style="flex:1;min-width:0">'
|
|
312
|
+
+'<div style="font-size:13px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+esc(s.title||"")+"</div>"
|
|
313
|
+
+'<div style="font-size:12px;color:var(--muted)">'+esc(s.artist||s.author||"")+(s.duration?" • "+esc(s.duration):"")+"</div>"
|
|
314
|
+
+playBtn+"</div></div></div>";
|
|
315
|
+
}).join("");
|
|
316
|
+
return void(app.innerHTML='<div class="gallery">'+snds+"</div>");
|
|
317
|
+
}
|
|
318
|
+
if(d.balance!=null||d.tier){
|
|
319
|
+
var bal=Number(d.balance)||0;
|
|
320
|
+
var tier=d.tier||"";
|
|
321
|
+
var ff=d.firstFreeTools||[];
|
|
322
|
+
var freeHtml="";
|
|
323
|
+
if(ff.length){
|
|
324
|
+
var tags=ff.map(function(t){return'<span class="free-tag">✨ '+esc(t)+" free</span>";}).join("");
|
|
325
|
+
freeHtml='<div style="margin-top:10px"><div class="section-label">Free first uses remaining</div><div style="display:flex;flex-wrap:wrap;gap:4px">'+tags+"</div></div>";
|
|
326
|
+
}
|
|
327
|
+
return void(app.innerHTML='<div class="credits-card">'
|
|
328
|
+
+'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">'
|
|
329
|
+
+'<div style="font-size:15px;font-weight:700">💰 Your Credits</div>'
|
|
330
|
+
+(tier?'<span class="tier-badge">'+esc(tier)+"</span>":"")+"</div>"
|
|
331
|
+
+'<div style="font-size:32px;font-weight:800;margin:8px 0">'+bal+"</div>"
|
|
332
|
+
+'<div style="font-size:13px;color:var(--muted)">credits remaining</div>'
|
|
333
|
+
+freeHtml+"</div>");
|
|
334
|
+
}
|
|
335
|
+
if(d.checkoutUrl||d.packs){
|
|
336
|
+
return void(app.innerHTML='<div class="card card-wide"><div class="card-body">'
|
|
337
|
+
+'<div style="font-size:15px;font-weight:700;margin-bottom:12px">🛒 Credit Packs</div>'
|
|
338
|
+
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px">'
|
|
339
|
+
+'<div style="border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center"><div style="font-size:13px;font-weight:600">Starter</div><div style="font-size:22px;font-weight:800;margin:4px 0">$12.50</div><div style="font-size:12px;color:var(--muted)">500 credits</div></div>'
|
|
340
|
+
+'<div style="border:2px solid var(--accent);border-radius:10px;padding:14px;text-align:center;box-shadow:0 4px 12px rgba(0,0,0,0.08)"><div style="font-size:13px;font-weight:600">Pro</div><div style="font-size:22px;font-weight:800;margin:4px 0">$40</div><div style="font-size:12px;color:var(--muted)">2,000 credits</div></div>'
|
|
341
|
+
+'<div style="border:1px solid var(--border);border-radius:10px;padding:14px;text-align:center"><div style="font-size:13px;font-weight:600">Scale</div><div style="font-size:22px;font-weight:800;margin:4px 0">$85</div><div style="font-size:12px;color:var(--muted)">5,000 credits</div></div>'
|
|
342
|
+
+"</div></div></div>");
|
|
343
|
+
}
|
|
344
|
+
// Single post (get_social_media)
|
|
345
|
+
if(d.post||d.platform){
|
|
346
|
+
return void(app.innerHTML=postCard(d.post||d,true));
|
|
347
|
+
}
|
|
348
|
+
// Fallback: pretty-print JSON
|
|
349
|
+
app.innerHTML='<div class="json-block">'+esc(JSON.stringify(d,null,2))+"</div>";
|
|
350
|
+
}
|
|
351
|
+
})();
|
|
352
|
+
</script>
|
|
353
|
+
</body>
|
|
354
|
+
</html>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchyn/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.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",
|