@orchyn/mcp 1.15.0 → 1.16.1
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 +88 -48
- package/dist/shared/ui-template.js +32 -1
- package/package.json +1 -1
package/dist/shared/tools.js
CHANGED
|
@@ -11,13 +11,23 @@ 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.16.1";
|
|
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
|
+
/** Default UI resource URI (only used when a tool has no explicit view). */
|
|
20
20
|
const UI_RESOURCE_URI = "ui://orchyn/view";
|
|
21
|
+
/**
|
|
22
|
+
* Distinct UI resource URI per tool/view. Claude/ChatGPT create one app
|
|
23
|
+
* instance per resourceUri and key app state by it (ext-apps#558), so giving
|
|
24
|
+
* each tool its own uri avoids a shared app instance / session colliding
|
|
25
|
+
* between different tools and lets each view complete its own handshake.
|
|
26
|
+
*/
|
|
27
|
+
function uiResource(tool) {
|
|
28
|
+
const slug = tool.replace(/[^a-z0-9_]/gi, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
|
|
29
|
+
return `ui://orchyn/${slug || "view"}`;
|
|
30
|
+
}
|
|
21
31
|
/**
|
|
22
32
|
* claude.ai requires `ui.domain` on the resource == sha256("<MCP endpoint
|
|
23
33
|
* URL>")[:32] + ".claudemcpcontent.com" — the iframe is only revealed on
|
|
@@ -234,49 +244,79 @@ export function createMcpServer(makeClient) {
|
|
|
234
244
|
},
|
|
235
245
|
},
|
|
236
246
|
});
|
|
237
|
-
// Register
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
247
|
+
// Register one UI app resource per tool/view. Claude/ChatGPT render a
|
|
248
|
+
// separate sandboxed app per resourceUri and key app state by it, so a
|
|
249
|
+
// distinct URI per tool avoids a shared app instance/session colliding
|
|
250
|
+
// between different tools (ext-apps#558). Each URI serves the same generic
|
|
251
|
+
// template, which renders whichever structuredResult the tool delivers.
|
|
252
|
+
// Every tool/view gets its own named app resource so it is distinguishable
|
|
253
|
+
// in MCP controllers by BOTH a unique URI and a unique human-readable name.
|
|
254
|
+
const TOOL_NAMES = [
|
|
255
|
+
"analyze_post",
|
|
256
|
+
"get_social_media",
|
|
257
|
+
"discover_social_posts",
|
|
258
|
+
"get_user_posts",
|
|
259
|
+
"analyze_creator_profile",
|
|
260
|
+
"get_post_comments",
|
|
261
|
+
"search_creators",
|
|
262
|
+
"get_similar_creators",
|
|
263
|
+
"discover_sounds",
|
|
264
|
+
"check_orchyn_credits",
|
|
265
|
+
"buy_orchyn_credits",
|
|
266
|
+
"understand_social_post",
|
|
267
|
+
];
|
|
268
|
+
// Human-readable resource name per tool (used in resources/list + tools/list).
|
|
269
|
+
function resourceName(tool) {
|
|
270
|
+
const readable = tool
|
|
271
|
+
.split("_")
|
|
272
|
+
.map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w))
|
|
273
|
+
.join(" ");
|
|
274
|
+
return `Orchyn ${readable || "View"}`;
|
|
275
|
+
}
|
|
276
|
+
// Build CSP resourceDomains from env so proxied thumbnails work
|
|
277
|
+
const domains = [
|
|
278
|
+
"https://*.tiktokcdn.com",
|
|
279
|
+
"https://*.cdninstagram.com",
|
|
280
|
+
"https://*.ytimg.com",
|
|
281
|
+
"https://*.googlevideo.com",
|
|
282
|
+
"https://*.licdn.com",
|
|
283
|
+
"https://*.linkedin.com",
|
|
284
|
+
];
|
|
285
|
+
const apiUrl = process.env.ORCHYN_API_URL || process.env.ORCHYN_BASE_URL;
|
|
286
|
+
if (apiUrl && apiUrl.trim()) {
|
|
287
|
+
domains.push(apiUrl.trim().replace(/\/+$/, ""));
|
|
288
|
+
}
|
|
289
|
+
for (const tool of TOOL_NAMES) {
|
|
290
|
+
const uri = uiResource(tool);
|
|
291
|
+
server.registerResource(resourceName(tool), uri, { mimeType: RESOURCE_MIME_TYPE }, async () => {
|
|
292
|
+
// Computed per-request so the claude.ai sandbox origin is correct.
|
|
293
|
+
const domain = await computeAppDomain();
|
|
294
|
+
return {
|
|
295
|
+
contents: [
|
|
296
|
+
{
|
|
297
|
+
uri,
|
|
298
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
299
|
+
text: ORCHYN_UI_TEMPLATE,
|
|
300
|
+
_meta: {
|
|
301
|
+
ui: {
|
|
302
|
+
...(domain ? { domain } : {}),
|
|
303
|
+
csp: {
|
|
304
|
+
resourceDomains: domains,
|
|
305
|
+
},
|
|
306
|
+
prefersBorder: false,
|
|
266
307
|
},
|
|
267
|
-
prefersBorder: false,
|
|
268
308
|
},
|
|
269
309
|
},
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
};
|
|
273
|
-
}
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
}
|
|
274
314
|
server.registerTool("analyze_post", {
|
|
275
315
|
title: "Analyze Post",
|
|
276
316
|
description: "Analyze a social post (video, image, carousel/slideshow) from its link — " +
|
|
277
317
|
"imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). " +
|
|
278
318
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Returns the full analysis once finished. First use free per user.",
|
|
279
|
-
_meta: { ui: { resourceUri:
|
|
319
|
+
_meta: { ui: { resourceUri: uiResource("analyze_post") }, "ui/resourceUri": uiResource("analyze_post") },
|
|
280
320
|
inputSchema: z
|
|
281
321
|
.object({
|
|
282
322
|
url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X, Douyin, Xiaohongshu or Bilibili)."),
|
|
@@ -332,7 +372,7 @@ export function createMcpServer(makeClient) {
|
|
|
332
372
|
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu or Bilibili URL: " +
|
|
333
373
|
"contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. " +
|
|
334
374
|
"Returns an inline thumbnail image. Consumes 1 orchyn credit. First use free per user.",
|
|
335
|
-
_meta: { ui: { resourceUri:
|
|
375
|
+
_meta: { ui: { resourceUri: uiResource("get_social_media") }, "ui/resourceUri": uiResource("get_social_media") },
|
|
336
376
|
inputSchema: z
|
|
337
377
|
.object({
|
|
338
378
|
url: z.string().describe("Full public post URL."),
|
|
@@ -352,7 +392,7 @@ export function createMcpServer(makeClient) {
|
|
|
352
392
|
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
|
|
353
393
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
354
394
|
'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:
|
|
395
|
+
_meta: { ui: { resourceUri: uiResource("discover_social_posts") }, "ui/resourceUri": uiResource("discover_social_posts") },
|
|
356
396
|
inputSchema: z
|
|
357
397
|
.object({
|
|
358
398
|
niche: z.string().describe("Niche/topic, e.g. 'fitness'."),
|
|
@@ -379,7 +419,7 @@ export function createMcpServer(makeClient) {
|
|
|
379
419
|
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
420
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
381
421
|
"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:
|
|
422
|
+
_meta: { ui: { resourceUri: uiResource("get_user_posts") }, "ui/resourceUri": uiResource("get_user_posts") },
|
|
383
423
|
inputSchema: z
|
|
384
424
|
.object({
|
|
385
425
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."),
|
|
@@ -404,7 +444,7 @@ export function createMcpServer(makeClient) {
|
|
|
404
444
|
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
445
|
"then synthesize a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, " +
|
|
406
446
|
"engagement patterns, audience insights, variation ideas, collaboration fit. Consumes 15 orchyn credits. First use free per user.",
|
|
407
|
-
_meta: { ui: { resourceUri:
|
|
447
|
+
_meta: { ui: { resourceUri: uiResource("analyze_creator_profile") }, "ui/resourceUri": uiResource("analyze_creator_profile") },
|
|
408
448
|
inputSchema: z
|
|
409
449
|
.object({
|
|
410
450
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp'."),
|
|
@@ -429,7 +469,7 @@ export function createMcpServer(makeClient) {
|
|
|
429
469
|
title: "Get Post Comments",
|
|
430
470
|
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
471
|
"when available — audience sentiment/audience-signal analysis. Consumes 2 orchyn credits. First use free per user.",
|
|
432
|
-
_meta: { ui: { resourceUri:
|
|
472
|
+
_meta: { ui: { resourceUri: uiResource("get_post_comments") }, "ui/resourceUri": uiResource("get_post_comments") },
|
|
433
473
|
inputSchema: z
|
|
434
474
|
.object({
|
|
435
475
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili/LinkedIn)."),
|
|
@@ -449,7 +489,7 @@ export function createMcpServer(makeClient) {
|
|
|
449
489
|
title: "Search Creators",
|
|
450
490
|
description: "Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, " +
|
|
451
491
|
"signature, verified status. Use to find influencers to vet or analyze. Consumes 2 orchyn credits. First use free per user.",
|
|
452
|
-
_meta: { ui: { resourceUri:
|
|
492
|
+
_meta: { ui: { resourceUri: uiResource("search_creators") }, "ui/resourceUri": uiResource("search_creators") },
|
|
453
493
|
inputSchema: z
|
|
454
494
|
.object({
|
|
455
495
|
keyword: z.string().describe("Niche/keyword, e.g. 'fitness' or a creator name."),
|
|
@@ -473,7 +513,7 @@ export function createMcpServer(makeClient) {
|
|
|
473
513
|
title: "Get Similar Creators",
|
|
474
514
|
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram " +
|
|
475
515
|
"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:
|
|
516
|
+
_meta: { ui: { resourceUri: uiResource("get_similar_creators") }, "ui/resourceUri": uiResource("get_similar_creators") },
|
|
477
517
|
inputSchema: z
|
|
478
518
|
.object({
|
|
479
519
|
username: z.string().describe("Seed creator handle, e.g. 'zoundsapp'."),
|
|
@@ -493,7 +533,7 @@ export function createMcpServer(makeClient) {
|
|
|
493
533
|
title: "Discover Sounds",
|
|
494
534
|
description: "Discover trending sounds/music for a keyword on TikTok or Instagram — the sound is a huge ranking " +
|
|
495
535
|
"signal for TikTok virality. Returns title, artist, duration, play/cover URLs. Consumes 2 orchyn credits. First use free per user.",
|
|
496
|
-
_meta: { ui: { resourceUri:
|
|
536
|
+
_meta: { ui: { resourceUri: uiResource("discover_sounds") }, "ui/resourceUri": uiResource("discover_sounds") },
|
|
497
537
|
inputSchema: z
|
|
498
538
|
.object({
|
|
499
539
|
keyword: z.string().describe("Niche/keyword, e.g. 'gym'."),
|
|
@@ -513,7 +553,7 @@ export function createMcpServer(makeClient) {
|
|
|
513
553
|
server.registerTool("check_orchyn_credits", {
|
|
514
554
|
title: "Check Orchyn Credits",
|
|
515
555
|
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:
|
|
556
|
+
_meta: { ui: { resourceUri: uiResource("check_orchyn_credits") }, "ui/resourceUri": uiResource("check_orchyn_credits") },
|
|
517
557
|
inputSchema: z.object({}).strict(),
|
|
518
558
|
}, async (_args, extra) => {
|
|
519
559
|
const client = await makeClient(extra);
|
|
@@ -527,7 +567,7 @@ export function createMcpServer(makeClient) {
|
|
|
527
567
|
server.registerTool("buy_orchyn_credits", {
|
|
528
568
|
title: "Buy Orchyn Credits",
|
|
529
569
|
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:
|
|
570
|
+
_meta: { ui: { resourceUri: uiResource("buy_orchyn_credits") }, "ui/resourceUri": uiResource("buy_orchyn_credits") },
|
|
531
571
|
inputSchema: z.object({}).strict(),
|
|
532
572
|
}, async (_args, extra) => {
|
|
533
573
|
const client = await makeClient(extra);
|
|
@@ -543,7 +583,7 @@ export function createMcpServer(makeClient) {
|
|
|
543
583
|
description: "Import a social post URL AND understand it with multimodal AI over the actual video/images: " +
|
|
544
584
|
"summary, hook strength, viral triggers, format breakdown and variation ideas. Includes the thumbnail. " +
|
|
545
585
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Consumes 6 orchyn credits. First use free per user.",
|
|
546
|
-
_meta: { ui: { resourceUri:
|
|
586
|
+
_meta: { ui: { resourceUri: uiResource("understand_social_post") }, "ui/resourceUri": uiResource("understand_social_post") },
|
|
547
587
|
inputSchema: z
|
|
548
588
|
.object({
|
|
549
589
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/X/Douyin/Xiaohongshu/Bilibili)."),
|
|
@@ -526,10 +526,41 @@ body{font-family:system-ui,-apple-system,'Segoe UI',sans-serif;background:var(--
|
|
|
526
526
|
}
|
|
527
527
|
|
|
528
528
|
// ─── Render ───
|
|
529
|
+
// Hosts deliver the tool result in two shapes:
|
|
530
|
+
// - { structuredContent: {...} } (spec-optional, used when present)
|
|
531
|
+
// - { content: [ {type,text,...} ] } (standard MCP content blocks)
|
|
532
|
+
// Our results put the HTML card prefix + structured JSON inside the first
|
|
533
|
+
// text content block, so decode BOTH. If the result is a primitive/string,
|
|
534
|
+
// surface it as-is.
|
|
535
|
+
function extractResult(result){
|
|
536
|
+
if(!result||typeof result!=="object")return result;
|
|
537
|
+
var sc=result.structuredContent;
|
|
538
|
+
if(sc&&typeof sc==="object"&&!Array.isArray(sc))return sc;
|
|
539
|
+
if(Array.isArray(result.content)){
|
|
540
|
+
var textBlock=null;
|
|
541
|
+
for(var i=0;i<result.content.length;i++){
|
|
542
|
+
var c=result.content[i];
|
|
543
|
+
if(c&&c.type==="text"&&typeof c.text==="string"){textBlock=c.text;break;}
|
|
544
|
+
}
|
|
545
|
+
if(typeof textBlock==="string"&&textBlock!==""){
|
|
546
|
+
// Split "<html>\n\n{json}" — if there's a JSON object after the HTML,
|
|
547
|
+
// parse it; otherwise show the text itself.
|
|
548
|
+
var m=/^[\s]*<[^>]+>[\s\S]*?\n\n(\{.*\})[\s]*$/m.exec(textBlock);
|
|
549
|
+
if(m){try{var parsed=JSON.parse(m[1]);if(parsed&&typeof parsed==="object")return parsed;}catch(e){}}
|
|
550
|
+
// Single-line HTML (common): keep the whole block as text fallback.
|
|
551
|
+
if(textBlock.indexOf("<")===0){return { _html: textBlock };}
|
|
552
|
+
try{var j=JSON.parse(textBlock);if(j&&typeof j==="object")return j;}catch(e2){}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return result;
|
|
556
|
+
}
|
|
557
|
+
|
|
529
558
|
function render(result){
|
|
530
559
|
var app=document.getElementById("app");if(!app)return;
|
|
531
|
-
var d=result
|
|
560
|
+
var d=extractResult(result);
|
|
532
561
|
if(!d||typeof d!=="object"){app.innerHTML='<div class="json-block fade-in">'+esc(JSON.stringify(d,null,2))+"</div>";return;}
|
|
562
|
+
// Raw HTML-only result — inject it directly.
|
|
563
|
+
if(d._html){app.innerHTML=d._html;setTimeout(reportSize,50);return;}
|
|
533
564
|
|
|
534
565
|
// Analysis
|
|
535
566
|
if(d.analysis&&(d.post||d.url)){app.innerHTML=analysisCard(d);return;}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchyn/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.1",
|
|
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",
|