@orchyn/mcp 1.14.0 → 1.16.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 +90 -91
- 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.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
|
+
/** 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
|
|
@@ -41,24 +51,6 @@ async function computeAppDomain() {
|
|
|
41
51
|
return undefined;
|
|
42
52
|
}
|
|
43
53
|
}
|
|
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
|
-
}
|
|
62
54
|
/**
|
|
63
55
|
* Rewrite external image URLs to go through the orchyn proxy so they
|
|
64
56
|
* work inside ChatGPT's sandboxed iframe (CORS + CSP restrictions).
|
|
@@ -105,19 +97,12 @@ function proxyUrls(obj) {
|
|
|
105
97
|
return obj;
|
|
106
98
|
}
|
|
107
99
|
async function toToolResult(proxy) {
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
//
|
|
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);
|
|
118
|
-
// The Rust backend embeds HTML cards directly in the text block
|
|
119
|
-
// (type:"text", text:"<div>...</div>\n\n{json}"). We extract the HTML
|
|
120
|
-
// prefix from the first text contentBlock and prepend it.
|
|
100
|
+
// MCP Apps views (ChatGPT & Claude) render the interactive HTML card, which
|
|
101
|
+
// already embeds all thumbnails/videos. Claude's Apps bridge rejects a tool
|
|
102
|
+
// result that mixes raw `image` blocks with an app view ("could not be
|
|
103
|
+
// processed: Error processing image" + blank iframe), so we intentionally
|
|
104
|
+
// drop the standalone base64 image blocks here and keep only the text block
|
|
105
|
+
// carrying the HTML card + structured JSON.
|
|
121
106
|
const textBlock = proxy.contentBlocks.find((c) => c.type === "text");
|
|
122
107
|
const rawText = textBlock ? String(textBlock.text ?? "") : "";
|
|
123
108
|
const htmlPrefix = rawText.startsWith("<")
|
|
@@ -131,7 +116,7 @@ async function toToolResult(proxy) {
|
|
|
131
116
|
const proxiedHtml = htmlPrefix ? proxyImageUrlsInHtml(htmlPrefix) : "";
|
|
132
117
|
const text = proxiedHtml ? `${proxiedHtml}\n\n${textJson}` : textJson;
|
|
133
118
|
return {
|
|
134
|
-
content: [
|
|
119
|
+
content: [{ type: "text", text }],
|
|
135
120
|
structuredContent: proxied ?? {},
|
|
136
121
|
};
|
|
137
122
|
}
|
|
@@ -259,49 +244,68 @@ export function createMcpServer(makeClient) {
|
|
|
259
244
|
},
|
|
260
245
|
},
|
|
261
246
|
});
|
|
262
|
-
// Register
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
+
const TOOL_URIS = [
|
|
253
|
+
"analyze_post",
|
|
254
|
+
"get_social_media",
|
|
255
|
+
"discover_social_posts",
|
|
256
|
+
"get_user_posts",
|
|
257
|
+
"analyze_creator_profile",
|
|
258
|
+
"get_post_comments",
|
|
259
|
+
"search_creators",
|
|
260
|
+
"get_similar_creators",
|
|
261
|
+
"discover_sounds",
|
|
262
|
+
"check_orchyn_credits",
|
|
263
|
+
"buy_orchyn_credits",
|
|
264
|
+
"understand_social_post",
|
|
265
|
+
].map(uiResource);
|
|
266
|
+
// Build CSP resourceDomains from env so proxied thumbnails work
|
|
267
|
+
const domains = [
|
|
268
|
+
"https://*.tiktokcdn.com",
|
|
269
|
+
"https://*.cdninstagram.com",
|
|
270
|
+
"https://*.ytimg.com",
|
|
271
|
+
"https://*.googlevideo.com",
|
|
272
|
+
"https://*.licdn.com",
|
|
273
|
+
"https://*.linkedin.com",
|
|
274
|
+
];
|
|
275
|
+
const apiUrl = process.env.ORCHYN_API_URL || process.env.ORCHYN_BASE_URL;
|
|
276
|
+
if (apiUrl && apiUrl.trim()) {
|
|
277
|
+
domains.push(apiUrl.trim().replace(/\/+$/, ""));
|
|
278
|
+
}
|
|
279
|
+
for (const uri of TOOL_URIS) {
|
|
280
|
+
server.registerResource("Orchyn Interactive View", uri, { mimeType: RESOURCE_MIME_TYPE }, async () => {
|
|
281
|
+
// Computed per-request so the claude.ai sandbox origin is correct.
|
|
282
|
+
const domain = await computeAppDomain();
|
|
283
|
+
return {
|
|
284
|
+
contents: [
|
|
285
|
+
{
|
|
286
|
+
uri,
|
|
287
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
288
|
+
text: ORCHYN_UI_TEMPLATE,
|
|
289
|
+
_meta: {
|
|
290
|
+
ui: {
|
|
291
|
+
...(domain ? { domain } : {}),
|
|
292
|
+
csp: {
|
|
293
|
+
resourceDomains: domains,
|
|
294
|
+
},
|
|
295
|
+
prefersBorder: false,
|
|
291
296
|
},
|
|
292
|
-
prefersBorder: false,
|
|
293
297
|
},
|
|
294
298
|
},
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
}
|
|
299
303
|
server.registerTool("analyze_post", {
|
|
300
304
|
title: "Analyze Post",
|
|
301
305
|
description: "Analyze a social post (video, image, carousel/slideshow) from its link — " +
|
|
302
306
|
"imports the media and runs AI analysis over the actual content (video frames, carousel images, caption). " +
|
|
303
307
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Returns the full analysis once finished. First use free per user.",
|
|
304
|
-
_meta: { ui: { resourceUri:
|
|
308
|
+
_meta: { ui: { resourceUri: uiResource("analyze_post") }, "ui/resourceUri": uiResource("analyze_post") },
|
|
305
309
|
inputSchema: z
|
|
306
310
|
.object({
|
|
307
311
|
url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X, Douyin, Xiaohongshu or Bilibili)."),
|
|
@@ -330,19 +334,14 @@ export function createMcpServer(makeClient) {
|
|
|
330
334
|
const creatorHandle = String(post.creatorHandle ?? "");
|
|
331
335
|
const title = String(post.title ?? post.caption ?? "");
|
|
332
336
|
const htmlCard = buildAnalysisHtmlCard(proxied, thumbnailUrl, platform, creatorHandle, title);
|
|
333
|
-
// The
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
//
|
|
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);
|
|
337
|
+
// The interactive HTML card above already embeds the thumbnail + full
|
|
338
|
+
// video, so no standalone base64 `image` blocks are emitted. Claude's Apps
|
|
339
|
+
// bridge rejects raw image blocks mixed with an app view ("could not be
|
|
340
|
+
// processed: Error processing image" + blank iframe), so we keep only the
|
|
341
|
+
// text block carrying the card + structured JSON.
|
|
343
342
|
const textJson = JSON.stringify(proxied, null, 2);
|
|
344
343
|
return {
|
|
345
|
-
content: [
|
|
344
|
+
content: [{ type: "text", text: `${htmlCard}\n\n${textJson}` }],
|
|
346
345
|
structuredContent: proxied,
|
|
347
346
|
};
|
|
348
347
|
}
|
|
@@ -362,7 +361,7 @@ export function createMcpServer(makeClient) {
|
|
|
362
361
|
description: "Fetch a social post's media from a TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu or Bilibili URL: " +
|
|
363
362
|
"contentType (video/image/carousel/slideshow), title, caption, author, stats and direct media URLs. " +
|
|
364
363
|
"Returns an inline thumbnail image. Consumes 1 orchyn credit. First use free per user.",
|
|
365
|
-
_meta: { ui: { resourceUri:
|
|
364
|
+
_meta: { ui: { resourceUri: uiResource("get_social_media") }, "ui/resourceUri": uiResource("get_social_media") },
|
|
366
365
|
inputSchema: z
|
|
367
366
|
.object({
|
|
368
367
|
url: z.string().describe("Full public post URL."),
|
|
@@ -382,7 +381,7 @@ export function createMcpServer(makeClient) {
|
|
|
382
381
|
description: "Discover recent posts (video, image, carousel, slideshow) for a niche on YouTube, TikTok, Instagram, Douyin, Xiaohongshu, X/Twitter or Bilibili. " +
|
|
383
382
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
384
383
|
'Say "next" to paginate (offset), or "analyze the 2nd one" / "analyze all" for batch analysis. Consumes 2 orchyn credits. First use free per user.',
|
|
385
|
-
_meta: { ui: { resourceUri:
|
|
384
|
+
_meta: { ui: { resourceUri: uiResource("discover_social_posts") }, "ui/resourceUri": uiResource("discover_social_posts") },
|
|
386
385
|
inputSchema: z
|
|
387
386
|
.object({
|
|
388
387
|
niche: z.string().describe("Niche/topic, e.g. 'fitness'."),
|
|
@@ -409,7 +408,7 @@ export function createMcpServer(makeClient) {
|
|
|
409
408
|
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'). " +
|
|
410
409
|
"Each post includes title/caption, thumbnailUrl, externalUrl, views/likes/comments and inline thumbnails (up to 4) so they show in chat. " +
|
|
411
410
|
"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.",
|
|
412
|
-
_meta: { ui: { resourceUri:
|
|
411
|
+
_meta: { ui: { resourceUri: uiResource("get_user_posts") }, "ui/resourceUri": uiResource("get_user_posts") },
|
|
413
412
|
inputSchema: z
|
|
414
413
|
.object({
|
|
415
414
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp' or '@zoundsapp'."),
|
|
@@ -434,7 +433,7 @@ export function createMcpServer(makeClient) {
|
|
|
434
433
|
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, " +
|
|
435
434
|
"then synthesize a profile report — creator summary, niche, content themes, hook styles, strengths/weaknesses, " +
|
|
436
435
|
"engagement patterns, audience insights, variation ideas, collaboration fit. Consumes 15 orchyn credits. First use free per user.",
|
|
437
|
-
_meta: { ui: { resourceUri:
|
|
436
|
+
_meta: { ui: { resourceUri: uiResource("analyze_creator_profile") }, "ui/resourceUri": uiResource("analyze_creator_profile") },
|
|
438
437
|
inputSchema: z
|
|
439
438
|
.object({
|
|
440
439
|
username: z.string().describe("Creator handle, e.g. 'zoundsapp'."),
|
|
@@ -459,7 +458,7 @@ export function createMcpServer(makeClient) {
|
|
|
459
458
|
title: "Get Post Comments",
|
|
460
459
|
description: "Fetch top comments for a post URL on TikTok, Instagram, YouTube, Douyin, X/Twitter, Bilibili or LinkedIn, plus keyword clusters from TikTok Analytics " +
|
|
461
460
|
"when available — audience sentiment/audience-signal analysis. Consumes 2 orchyn credits. First use free per user.",
|
|
462
|
-
_meta: { ui: { resourceUri:
|
|
461
|
+
_meta: { ui: { resourceUri: uiResource("get_post_comments") }, "ui/resourceUri": uiResource("get_post_comments") },
|
|
463
462
|
inputSchema: z
|
|
464
463
|
.object({
|
|
465
464
|
url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/Douyin/X/Bilibili/LinkedIn)."),
|
|
@@ -479,7 +478,7 @@ export function createMcpServer(makeClient) {
|
|
|
479
478
|
title: "Search Creators",
|
|
480
479
|
description: "Search creators by niche/keyword on TikTok, Instagram, Xiaohongshu, YouTube or Douyin — username, nickname, follower count, " +
|
|
481
480
|
"signature, verified status. Use to find influencers to vet or analyze. Consumes 2 orchyn credits. First use free per user.",
|
|
482
|
-
_meta: { ui: { resourceUri:
|
|
481
|
+
_meta: { ui: { resourceUri: uiResource("search_creators") }, "ui/resourceUri": uiResource("search_creators") },
|
|
483
482
|
inputSchema: z
|
|
484
483
|
.object({
|
|
485
484
|
keyword: z.string().describe("Niche/keyword, e.g. 'fitness' or a creator name."),
|
|
@@ -503,7 +502,7 @@ export function createMcpServer(makeClient) {
|
|
|
503
502
|
title: "Get Similar Creators",
|
|
504
503
|
description: "Find lookalike creators for a given handle — TikTok similar-user recommendations or Instagram " +
|
|
505
504
|
"similar users. Useful for scaling: 'if this creator works, here are more like them'. Consumes 2 orchyn credits. First use free per user.",
|
|
506
|
-
_meta: { ui: { resourceUri:
|
|
505
|
+
_meta: { ui: { resourceUri: uiResource("get_similar_creators") }, "ui/resourceUri": uiResource("get_similar_creators") },
|
|
507
506
|
inputSchema: z
|
|
508
507
|
.object({
|
|
509
508
|
username: z.string().describe("Seed creator handle, e.g. 'zoundsapp'."),
|
|
@@ -523,7 +522,7 @@ export function createMcpServer(makeClient) {
|
|
|
523
522
|
title: "Discover Sounds",
|
|
524
523
|
description: "Discover trending sounds/music for a keyword on TikTok or Instagram — the sound is a huge ranking " +
|
|
525
524
|
"signal for TikTok virality. Returns title, artist, duration, play/cover URLs. Consumes 2 orchyn credits. First use free per user.",
|
|
526
|
-
_meta: { ui: { resourceUri:
|
|
525
|
+
_meta: { ui: { resourceUri: uiResource("discover_sounds") }, "ui/resourceUri": uiResource("discover_sounds") },
|
|
527
526
|
inputSchema: z
|
|
528
527
|
.object({
|
|
529
528
|
keyword: z.string().describe("Niche/keyword, e.g. 'gym'."),
|
|
@@ -543,7 +542,7 @@ export function createMcpServer(makeClient) {
|
|
|
543
542
|
server.registerTool("check_orchyn_credits", {
|
|
544
543
|
title: "Check Orchyn Credits",
|
|
545
544
|
description: "Check your orchyn credit balance, billing URL and pack size. No cost — call anytime to see remaining credits before running other tools.",
|
|
546
|
-
_meta: { ui: { resourceUri:
|
|
545
|
+
_meta: { ui: { resourceUri: uiResource("check_orchyn_credits") }, "ui/resourceUri": uiResource("check_orchyn_credits") },
|
|
547
546
|
inputSchema: z.object({}).strict(),
|
|
548
547
|
}, async (_args, extra) => {
|
|
549
548
|
const client = await makeClient(extra);
|
|
@@ -557,7 +556,7 @@ export function createMcpServer(makeClient) {
|
|
|
557
556
|
server.registerTool("buy_orchyn_credits", {
|
|
558
557
|
title: "Buy Orchyn Credits",
|
|
559
558
|
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.",
|
|
560
|
-
_meta: { ui: { resourceUri:
|
|
559
|
+
_meta: { ui: { resourceUri: uiResource("buy_orchyn_credits") }, "ui/resourceUri": uiResource("buy_orchyn_credits") },
|
|
561
560
|
inputSchema: z.object({}).strict(),
|
|
562
561
|
}, async (_args, extra) => {
|
|
563
562
|
const client = await makeClient(extra);
|
|
@@ -573,7 +572,7 @@ export function createMcpServer(makeClient) {
|
|
|
573
572
|
description: "Import a social post URL AND understand it with multimodal AI over the actual video/images: " +
|
|
574
573
|
"summary, hook strength, viral triggers, format breakdown and variation ideas. Includes the thumbnail. " +
|
|
575
574
|
"Supports TikTok, Instagram, YouTube, X/Twitter, Douyin, Xiaohongshu and Bilibili. Consumes 6 orchyn credits. First use free per user.",
|
|
576
|
-
_meta: { ui: { resourceUri:
|
|
575
|
+
_meta: { ui: { resourceUri: uiResource("understand_social_post") }, "ui/resourceUri": uiResource("understand_social_post") },
|
|
577
576
|
inputSchema: z
|
|
578
577
|
.object({
|
|
579
578
|
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.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",
|