@orchyn/mcp 1.13.0 → 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/dist/shared/tools.js +47 -17
- package/dist/shared/ui-template.js +32 -4
- package/package.json +1 -1
package/dist/shared/tools.js
CHANGED
|
@@ -11,13 +11,36 @@ 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
|
+
}
|
|
21
44
|
/** Convert an arbitrary http(s) URL to a base64 MCP `image` data block. */
|
|
22
45
|
async function fetchAsBase64Image(url, mimeType) {
|
|
23
46
|
try {
|
|
@@ -87,9 +110,11 @@ async function toToolResult(proxy) {
|
|
|
87
110
|
// JPEG thumbnails (HEIC already transcoded), so convert those URLs to
|
|
88
111
|
// base64 data here — guaranteeing clients render a valid image.
|
|
89
112
|
const imgBlocks = proxy.contentBlocks.filter((c) => c.type === "image");
|
|
90
|
-
|
|
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
|
|
91
116
|
? fetchAsBase64Image(String(c.url), String(c.mimeType ?? "image/jpeg"))
|
|
92
|
-
: Promise.resolve({ type: "image", data: String(c.data ?? ""), mimeType: 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);
|
|
93
118
|
// The Rust backend embeds HTML cards directly in the text block
|
|
94
119
|
// (type:"text", text:"<div>...</div>\n\n{json}"). We extract the HTML
|
|
95
120
|
// prefix from the first text contentBlock and prepend it.
|
|
@@ -251,6 +276,7 @@ export function createMcpServer(makeClient) {
|
|
|
251
276
|
if (apiUrl && apiUrl.trim()) {
|
|
252
277
|
domains.push(apiUrl.trim().replace(/\/+$/, ""));
|
|
253
278
|
}
|
|
279
|
+
const domain = await computeAppDomain();
|
|
254
280
|
return {
|
|
255
281
|
contents: [
|
|
256
282
|
{
|
|
@@ -259,9 +285,11 @@ export function createMcpServer(makeClient) {
|
|
|
259
285
|
text: ORCHYN_UI_TEMPLATE,
|
|
260
286
|
_meta: {
|
|
261
287
|
ui: {
|
|
288
|
+
...(domain ? { domain } : {}),
|
|
262
289
|
csp: {
|
|
263
290
|
resourceDomains: domains,
|
|
264
291
|
},
|
|
292
|
+
prefersBorder: false,
|
|
265
293
|
},
|
|
266
294
|
},
|
|
267
295
|
},
|
|
@@ -273,7 +301,7 @@ export function createMcpServer(makeClient) {
|
|
|
273
301
|
description: "Analyze a social post (video, image, carousel/slideshow) from its link — " +
|
|
274
302
|
"imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). " +
|
|
275
303
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Returns the full analysis once finished. First use free per user.",
|
|
276
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
304
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
277
305
|
inputSchema: z
|
|
278
306
|
.object({
|
|
279
307
|
url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X, Douyin, Xiaohongshu or Bilibili)."),
|
|
@@ -305,11 +333,13 @@ export function createMcpServer(makeClient) {
|
|
|
305
333
|
// The backend attaches `_inlineImages` as permanent orchyn public URLs
|
|
306
334
|
// (re-hosted into storage, HEIC already transcoded to JPEG). The SDK's
|
|
307
335
|
// ImageContent only accepts base64 `data`, so fetch each URL and encode it.
|
|
308
|
-
|
|
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 ?? [])
|
|
309
339
|
.filter((img) => typeof img === "object" && img !== null)
|
|
310
340
|
.map((img) => "url" in img && img.url
|
|
311
341
|
? fetchAsBase64Image(String(img.url), String(img.mimeType ?? "image/jpeg"))
|
|
312
|
-
: Promise.resolve({ type: "image", data: String(img.data ?? ""), mimeType: 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);
|
|
313
343
|
const textJson = JSON.stringify(proxied, null, 2);
|
|
314
344
|
return {
|
|
315
345
|
content: [...images, { type: "text", text: `${htmlCard}\n\n${textJson}` }],
|
|
@@ -332,7 +362,7 @@ export function createMcpServer(makeClient) {
|
|
|
332
362
|
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu or Bilibili URL: " +
|
|
333
363
|
"contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. " +
|
|
334
364
|
"Returns an inline thumbnail image. Consumes 1 orchyn credit. First use free per user.",
|
|
335
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
365
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
336
366
|
inputSchema: z
|
|
337
367
|
.object({
|
|
338
368
|
url: z.string().describe("Full public post URL."),
|
|
@@ -352,7 +382,7 @@ export function createMcpServer(makeClient) {
|
|
|
352
382
|
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
|
|
353
383
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
354
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.',
|
|
355
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
385
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
356
386
|
inputSchema: z
|
|
357
387
|
.object({
|
|
358
388
|
niche: z.string().describe("Niche/topic, e.g. 'fitness'."),
|
|
@@ -379,7 +409,7 @@ export function createMcpServer(makeClient) {
|
|
|
379
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'). " +
|
|
380
410
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
381
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.",
|
|
382
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
412
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
383
413
|
inputSchema: z
|
|
384
414
|
.object({
|
|
385
415
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."),
|
|
@@ -404,7 +434,7 @@ export function createMcpServer(makeClient) {
|
|
|
404
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, " +
|
|
405
435
|
"then synthesize a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, " +
|
|
406
436
|
"engagement patterns, audience insights, variation ideas, collaboration fit. Consumes 15 orchyn credits. First use free per user.",
|
|
407
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
437
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
408
438
|
inputSchema: z
|
|
409
439
|
.object({
|
|
410
440
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp'."),
|
|
@@ -429,7 +459,7 @@ export function createMcpServer(makeClient) {
|
|
|
429
459
|
title: "Get Post Comments",
|
|
430
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 " +
|
|
431
461
|
"when available — audience sentiment/audience-signal analysis. Consumes 2 orchyn credits. First use free per user.",
|
|
432
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
462
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
433
463
|
inputSchema: z
|
|
434
464
|
.object({
|
|
435
465
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili/LinkedIn)."),
|
|
@@ -449,7 +479,7 @@ export function createMcpServer(makeClient) {
|
|
|
449
479
|
title: "Search Creators",
|
|
450
480
|
description: "Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, " +
|
|
451
481
|
"signature, verified status. Use to find influencers to vet or analyze. Consumes 2 orchyn credits. First use free per user.",
|
|
452
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
482
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
453
483
|
inputSchema: z
|
|
454
484
|
.object({
|
|
455
485
|
keyword: z.string().describe("Niche/keyword, e.g. 'fitness' or a creator name."),
|
|
@@ -473,7 +503,7 @@ export function createMcpServer(makeClient) {
|
|
|
473
503
|
title: "Get Similar Creators",
|
|
474
504
|
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram " +
|
|
475
505
|
"similar users. Useful for scaling: 'if this creator works, here are more like them'. Consumes 2 orchyn credits. First use free per user.",
|
|
476
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
506
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
477
507
|
inputSchema: z
|
|
478
508
|
.object({
|
|
479
509
|
username: z.string().describe("Seed creator handle, e.g. 'zoundsapp'."),
|
|
@@ -493,7 +523,7 @@ export function createMcpServer(makeClient) {
|
|
|
493
523
|
title: "Discover Sounds",
|
|
494
524
|
description: "Discover trending sounds/music for a keyword on TikTok or Instagram — the sound is a huge ranking " +
|
|
495
525
|
"signal for TikTok virality. Returns title, artist, duration, play/cover URLs. Consumes 2 orchyn credits. First use free per user.",
|
|
496
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
526
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
497
527
|
inputSchema: z
|
|
498
528
|
.object({
|
|
499
529
|
keyword: z.string().describe("Niche/keyword, e.g. 'gym'."),
|
|
@@ -513,7 +543,7 @@ export function createMcpServer(makeClient) {
|
|
|
513
543
|
server.registerTool("check_orchyn_credits", {
|
|
514
544
|
title: "Check Orchyn Credits",
|
|
515
545
|
description: "Check your orchyn credit balance, billing URL and pack size. No cost — call anytime to see remaining credits before running other tools.",
|
|
516
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
546
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
517
547
|
inputSchema: z.object({}).strict(),
|
|
518
548
|
}, async (_args, extra) => {
|
|
519
549
|
const client = await makeClient(extra);
|
|
@@ -527,7 +557,7 @@ export function createMcpServer(makeClient) {
|
|
|
527
557
|
server.registerTool("buy_orchyn_credits", {
|
|
528
558
|
title: "Buy Orchyn Credits",
|
|
529
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.",
|
|
530
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
560
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
531
561
|
inputSchema: z.object({}).strict(),
|
|
532
562
|
}, async (_args, extra) => {
|
|
533
563
|
const client = await makeClient(extra);
|
|
@@ -543,7 +573,7 @@ export function createMcpServer(makeClient) {
|
|
|
543
573
|
description: "Import a social post URL AND understand it with multimodal AI over the actual video/images: " +
|
|
544
574
|
"summary, hook strength, viral triggers, format breakdown and variation ideas. Includes the thumbnail. " +
|
|
545
575
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Consumes 6 orchyn credits. First use free per user.",
|
|
546
|
-
_meta: { ui: { resourceUri: UI_RESOURCE_URI } },
|
|
576
|
+
_meta: { ui: { resourceUri: UI_RESOURCE_URI }, "ui/resourceUri": UI_RESOURCE_URI },
|
|
547
577
|
inputSchema: z
|
|
548
578
|
.object({
|
|
549
579
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/X/Douyin/Xiaohongshu/Bilibili)."),
|
|
@@ -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:"";
|
|
@@ -269,14 +270,40 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
269
270
|
+'</div></div>';
|
|
270
271
|
},3000);
|
|
271
272
|
|
|
272
|
-
// 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
|
+
}
|
|
273
286
|
send("ui/initialize",{
|
|
274
287
|
protocolVersion:"2026-01-26",
|
|
288
|
+
appInfo:{name:"orchyn-view",version:"2.0.0"},
|
|
289
|
+
appCapabilities:{availableDisplayModes:["inline"]},
|
|
275
290
|
capabilities:{},
|
|
276
291
|
clientInfo:{name:"orchyn-view",version:"2.0.0"}
|
|
277
|
-
}).then(
|
|
278
|
-
|
|
279
|
-
|
|
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);
|
|
280
307
|
|
|
281
308
|
// ─── Helpers ───
|
|
282
309
|
function esc(s){return String(s||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""");}
|
|
@@ -563,6 +590,7 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
563
590
|
if(d.post||d.platform){app.innerHTML=postCard(d.post||d,true);return;}
|
|
564
591
|
// Fallback
|
|
565
592
|
app.innerHTML='<div class="json-block fade-in">'+esc(JSON.stringify(d,null,2))+"</div>";
|
|
593
|
+
setTimeout(reportSize,50);
|
|
566
594
|
}
|
|
567
595
|
|
|
568
596
|
function updateProgress(params){
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchyn/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.0",
|
|
4
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",
|