@nooticr/mcp 1.26.22 → 1.26.23

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 CHANGED
@@ -14,8 +14,8 @@ model of ours for an opinion first. You pay for the fetch and nothing else.
14
14
 
15
15
  It also **monitors a name**: `search_mentions` sweeps nine of those networks for
16
16
  every comment that says your brand, inside a date window you choose, and
17
- `search_spoken_mentions` reads the words actually said out loud in TikTok and
18
- YouTube videos for the mentions that were never typed anywhere.
17
+ `search_spoken_mentions` reads the words actually said out loud in TikTok,
18
+ YouTube and Douyin videos for the mentions that were never typed anywhere.
19
19
 
20
20
  Runs over stdio locally or as a hosted connector at `https://mcp.nooticr.com/mcp`.
21
21
  Billed against your nooticr credits; new accounts get 20 free.
@@ -52,7 +52,7 @@ npx @nooticr/mcp login # one-time sign-in (Google)
52
52
 
53
53
  ## Tools
54
54
 
55
- 49 tools, grouped by what you are trying to do. Prices are in nooticr credits and
55
+ 64 tools, grouped by what you are trying to do. Prices are in nooticr credits and
56
56
  match what the server actually charges.
57
57
 
58
58
  Seven of them — the ones under **Answer a question you actually have** — are not
@@ -67,7 +67,7 @@ them caps that fan-out with an argument.
67
67
  | Tool | Credits | What it is for |
68
68
  |------|---------|----------------|
69
69
  | `get_social_media` | 1 | The post's facts and media — contentType, title, caption, author, stats, direct media URLs, plus an inline thumbnail. Use when you want the post itself and nothing interpreted. |
70
- | `get_post_transcript` | 1 | The words actually spoken, read from the post's caption track (TikTok and YouTube). Exact rather than inferred, and far cheaper than watching the video. Use before any analysis when the wording matters. |
70
+ | `get_post_transcript` | 1 | The words actually spoken. Where the platform publishes a caption track (TikTok, Douyin, YouTube) it is read as-is — exact rather than inferred, and far cheaper than watching the video; everywhere else the post's own audio is transcribed, which needs speech-to-text configured on the server. Use before any analysis when the wording matters. |
71
71
  | `get_post_frames` | 2 | Frames sampled evenly across a post's video, returned as **images you can actually look at** — not a description of them. ffmpeg opens the stream directly rather than downloading it, so HLS works and an expired link is re-resolved on the spot. Verified live at 3/3 on TikTok, YouTube, Instagram, Douyin and X; Reddit works on video posts. A carousel or slideshow returns its own images unchanged. Each frame costs roughly 1,200 tokens of your context. |
72
72
  | `get_post_comments` | 2 | Top comments plus the themes the platform clusters them into, with which ones the creator pinned or liked. Use when you want to read what people wrote. |
73
73
 
@@ -95,7 +95,7 @@ them caps that fan-out with an argument.
95
95
  | `discover_sounds` | 2 | Trending audio with playable previews. Sound is a major ranking signal on TikTok. |
96
96
  | `discover_hashtags` | 2 | Trending hashtags with volumes and whether each is rising, cooling or steady. |
97
97
  | `find_hook_pattern` | 2 | A creator's recent posts, so their opening lines can be read as a set and turned into fill-in-the-blank templates. One `get_user_posts` call. |
98
- | `search_mentions` | 2 per network (5 for Xiaohongshu) | **Brand monitoring.** Every *comment* that names a term, across nine networks at once, grouped under the post it was left on. A brand is named far more often in the replies than in a caption, so the comment is the unit — not the post. Takes a `since` date to read a past window, and pages with `offset`/`pageSize` so a nine-network sweep does not arrive all at once. Does not read speech inside a video — `search_spoken_mentions` does, on TikTok and YouTube. |
98
+ | `search_mentions` | 2 per network (5 for Xiaohongshu) | **Brand monitoring.** Every *comment* that names a term, across nine networks at once, grouped under the post it was left on. A brand is named far more often in the replies than in a caption, so the comment is the unit — not the post. Takes a `since` date to read a past window, and pages with `offset`/`pageSize` so a nine-network sweep does not arrive all at once. Does not read speech inside a video — `search_spoken_mentions` does, on TikTok, YouTube and Douyin. |
99
99
  | `watch_creator` | free | Add a creator to your watchlist. Stores the handle only — nothing is fetched. |
100
100
  | `unwatch_creator` | free | Drop a creator from the watchlist. |
101
101
  | `catch_up_watchlist` | 2 per creator | What everyone you watch has posted since your last catch-up. Compares against the snapshot taken last time and moves it forward, so it answers "what is new" rather than "what exists". |
@@ -117,7 +117,7 @@ them caps that fan-out with an argument.
117
117
  | `show_collab_shortlist` | free | Draws the candidates you scored, ranked, and asks the user which to approach. The scores are attributed to your model, not presented as a nooticr rating, and a candidate scored without anything having been opened is marked unverified. Makes no requests. |
118
118
  | `why_did_this_underperform` | 3 | One post against the creator's own recent distribution, with the post taken back out of its own baseline. Returns median, quartiles, ratio and percentile, so the answer can be "this is an ordinary result, not a failure". Different question from `compare_posts`, which weighs two URLs you already picked. |
119
119
  | `what_should_i_make_next` | 2 + 2 per post read + 2 (12 by default) | Demand against supply: what your commenters explicitly ask for, set beside what a niche sweep shows is already being made. A gap nobody asked for is noise; a request nobody serves is the opportunity. Falls back to your most-used hashtag when you name no niche. |
120
- | `search_spoken_mentions` | 2 per platform narrowed by niche + 2 per creator handle + 1 per transcript, up to `maxTranscripts` | **The mirror of `search_mentions`, for what was said rather than typed.** Narrows to candidate posts (a niche sweep, named handles, and/or your watchlist), transcribes only the most-viewed survivors up to a hard ceiling, and searches the words for the term — TikTok and YouTube only, and only where the platform actually supplies a caption track. Reports how many candidates were found, transcribed and matched, so the spend is legible. |
120
+ | `search_spoken_mentions` | 2 per platform narrowed by niche + 2 per creator handle + 1 per transcript, up to `maxTranscripts` | **The mirror of `search_mentions`, for what was said rather than typed.** Narrows to candidate posts (a niche sweep, named handles, and/or your watchlist), transcribes only the most-viewed survivors up to a hard ceiling, and searches the words for the term — TikTok, YouTube and Douyin, and only where the creator actually enabled captions. Reports how many candidates were found, transcribed and matched, so the spend is legible. |
121
121
 
122
122
  ### Make something
123
123
 
@@ -254,17 +254,31 @@ selectable. Free, and it makes no requests.
254
254
  ## Before an expensive call, it asks
255
255
 
256
256
  Most tools print their price in their own description, so a call costs what you
257
- already read. Two do not, because their price is set by an argument:
257
+ already read. Six do not, because their price is set by an argument — or by a
258
+ list the request never mentions:
258
259
 
259
260
  - `search_mentions` bills **per network swept**, so a bare "monitor my brand"
260
261
  sweeps all nine for 21 credits.
261
- - `catch_up_watchlist` bills **per creator**, so the price is the length of a
262
- list the request never mentions.
263
-
264
- Above 6 credits those two ask first, over MCP `elicitation` — the client shows
265
- the number and you accept or decline. Declining spends nothing and is not an
266
- error. A client that does not support elicitation is not blocked; the call runs
267
- as it always did.
262
+ - `catch_up_watchlist` bills **per creator**, so the price is the length of your
263
+ watchlist.
264
+ - `answer_my_audience` and `what_should_i_make_next` bill **per post opened**
265
+ (14 and 12 credits at their defaults), so they ask once the real post count is
266
+ known rather than on a worst case.
267
+ - `search_spoken_mentions` bills per network, per handle and per transcript, so
268
+ it asks on the worst case it could reach.
269
+ - `create_brand_watch` asks **however small the number is**, because what is
270
+ being agreed to is a recurring charge rather than one run.
271
+
272
+ Above 6 credits those first five ask first, over MCP `elicitation` — the client
273
+ shows the number and you accept or decline. Declining spends nothing and is not
274
+ an error. A client that does not support elicitation is not blocked; the call
275
+ runs as it always did.
276
+
277
+ Two rules go further than the threshold. A scheduled watch always asks, because
278
+ the standing arrangement is the thing being approved. And a watch that emails a
279
+ digest refuses to accept a `deliverTo` address unless a person actually saw the
280
+ dialog and said yes — "nobody could be asked" is a fine answer for a spend and
281
+ the wrong one for an address.
268
282
 
269
283
  ## Prerequisites
270
284
 
@@ -35,6 +35,23 @@
35
35
  * documentation; it is the steering, and it lands in the model's context.
36
36
  */
37
37
  import { BACKEND_CALL_CREDITS, costOf } from "./spend.js";
38
+ import { numberOf } from "./performance.js";
39
+ /**
40
+ * Every fan-out is capped; the cap is an argument, and the argument is clamped.
41
+ *
42
+ * Lives here rather than beside its first caller in jobs.ts because the
43
+ * evidence plans need it too, and for a while did not have it: three of them
44
+ * documented a `max` in their zod schema and then passed the argument straight
45
+ * through, so the ceiling a host read was never enforced and `limit: 500`
46
+ * fetched 500. One clamp for both files, and the import already runs this way
47
+ * round — jobs.ts imports evidence.ts, never the reverse.
48
+ */
49
+ export function clamp(value, fallback, min, max) {
50
+ const n = Math.floor(numberOf(value ?? fallback));
51
+ if (!n)
52
+ return fallback;
53
+ return Math.min(max, Math.max(min, n));
54
+ }
38
55
  const url = (a) => String(a.url ?? "");
39
56
  /**
40
57
  * Platforms whose accounts can be found by NAME. This is the backend's
@@ -217,24 +234,33 @@ export const EVIDENCE_PLANS = {
217
234
  args: (a) => ({
218
235
  username: String(a.username ?? ""),
219
236
  platform: a.platform,
220
- limit: Number(a.limit ?? 12),
237
+ limit: clamp(a.limit, 12, 1, 30),
221
238
  }),
222
- guidance: (a) => [
223
- `Recent posts by ${a.username}, with their stats.`,
224
- "",
225
- "Work out this creator's niche, recurring themes, hook formula, what",
226
- "over- and under-performs for them, and who their audience is. Use the",
227
- "spread of the numbers, not just the best post. Name the posts you are",
228
- "reasoning from.",
229
- ownIt,
230
- ].join("\n"),
239
+ guidance: (a) => {
240
+ const focus = String(a.focus ?? "").trim();
241
+ return [
242
+ `Recent posts by ${a.username}, with their stats.`,
243
+ "",
244
+ "Work out this creator's niche, recurring themes, hook formula, what",
245
+ "over- and under-performs for them, and who their audience is. Use the",
246
+ "spread of the numbers, not just the best post. Name the posts you are",
247
+ "reasoning from.",
248
+ ...(focus
249
+ ? [
250
+ "",
251
+ `Focus for this pass, as asked: ${focus}. Still cover the profile as a whole, but weight what you cover toward this.`,
252
+ ]
253
+ : []),
254
+ ownIt,
255
+ ].join("\n");
256
+ },
231
257
  },
232
258
  find_hook_pattern: {
233
259
  via: "get_user_posts",
234
260
  args: (a) => ({
235
261
  username: String(a.username ?? ""),
236
262
  platform: a.platform,
237
- limit: Number(a.limit ?? 12),
263
+ limit: clamp(a.limit, 12, 1, 40),
238
264
  }),
239
265
  guidance: (a) => [
240
266
  `Recent posts by ${a.username}. Their opening lines are the material.`,
@@ -251,7 +277,7 @@ export const EVIDENCE_PLANS = {
251
277
  args: (a) => ({
252
278
  niche: String(a.niche ?? ""),
253
279
  platform: a.platform,
254
- limit: Number(a.count ?? 12),
280
+ limit: clamp(a.count, 12, 1, 40),
255
281
  }),
256
282
  guidance: (a) => [
257
283
  `Recent posts in the "${a.niche}" niche, with their stats.`,
@@ -56,19 +56,12 @@
56
56
  import { z } from "zod";
57
57
  import { OUTPUT_SCHEMAS } from "./output-schemas.js";
58
58
  import { platformFromUrl, postSlug } from "./comment-review.js";
59
- import { handleMissGuidance, ownIt, PLATFORM_ARG } from "./evidence.js";
59
+ import { clamp, handleMissGuidance, ownIt, PLATFORM_ARG } from "./evidence.js";
60
60
  import { confirmSpend, costOf, declinedResult, MAX_SPOKEN_HANDLE_CALLS, MAX_SPOKEN_TRANSCRIPTS, SPOKEN_PLATFORMS, } from "./spend.js";
61
61
  import { distributionOf, excluding, numberOf, standing, } from "./performance.js";
62
62
  import { normaliseHandle, watchEntryId, watchlistOwner } from "./watchlist.js";
63
63
  import { viewMeta } from "./view-meta.js";
64
64
  import { extractLinks, COLLAB_RUBRIC, vettingGuidance } from "./collab.js";
65
- /** Every fan-out is capped; the cap is an argument, and the argument is clamped. */
66
- function clamp(value, fallback, min, max) {
67
- const n = Math.floor(numberOf(value ?? fallback));
68
- if (!n)
69
- return fallback;
70
- return Math.min(max, Math.max(min, n));
71
- }
72
65
  const rowsOf = (value) => Array.isArray(value) ? value.filter((r) => r && typeof r === "object") : [];
73
66
  const structuredOf = (res) => (res.structured ?? {});
74
67
  const reason = (err) => (err instanceof Error ? err.message : String(err));
@@ -1572,9 +1565,12 @@ export function registerJobTools(server, makeClient, store) {
1572
1565
  "speech recognition — see get_post_transcript), and searches them for a term. Narrows to " +
1573
1566
  "candidate posts first (a niche/keyword sweep, named creator handles, and/or your " +
1574
1567
  "watchlist), transcribes only the most-viewed survivors up to a hard ceiling you set, and " +
1575
- "returns the matched line with surrounding context so tone can be judged. TikTok and " +
1576
- "YouTube only — the only two networks whose posts carry a caption track this cheaply — " +
1577
- "and even there a video with no captions is invisible to this tool; coverage is real but " +
1568
+ "returns the matched line with surrounding context so tone can be judged. TikTok, YouTube " +
1569
+ "and Douyin — the networks whose post detail already carries a caption track, which is " +
1570
+ "what makes a transcript one cheap read rather than a wait. Every other network's audio " +
1571
+ "can be transcribed too, at the same price, but not yet from here: see " +
1572
+ "get_post_transcript for that route. Even on these three a video whose creator disabled " +
1573
+ "captions is invisible to this tool; coverage is real but " +
1578
1574
  "partial, and the result says how many candidates were found, transcribed and matched so " +
1579
1575
  "the gap is never silent. Costs 2 nooticr credits per platform a niche is searched on, 2 " +
1580
1576
  "per creator handle checked (including ones added by useWatchlist), and 1 per transcript " +
@@ -355,7 +355,7 @@ export function registerPrompts(server) {
355
355
  `than the caption, because that is usually where a brand gets named. 2 credits per network ` +
356
356
  `swept, 5 for Xiaohongshu — a bare call with no platforms sweeps all nine for 21 credits, so ` +
357
357
  `say that number before you spend it if the user has not already narrowed it.\n` +
358
- `2. search_spoken_mentions for "${brand}"${niche ? ` narrowed by "${niche}"` : ", narrowed by the niche you just found"} — the words said out loud on TikTok and ` +
358
+ `2. search_spoken_mentions for "${brand}"${niche ? ` narrowed by "${niche}"` : ", narrowed by the niche you just found"} — the words said out loud on TikTok, Douyin and ` +
359
359
  `YouTube that search_mentions cannot see, because it only reads text. 2 credits per platform ` +
360
360
  `the niche is searched on, 2 per named handle checked, 1 per transcript actually fetched up ` +
361
361
  `to maxTranscripts — pricier than search_mentions, which is why it runs second.\n` +
@@ -11,17 +11,31 @@ export const SEARCH_PLATFORMS = [
11
11
  "weibo",
12
12
  ];
13
13
  /**
14
- * The only two networks `search_spoken_mentions` can reach.
14
+ * The networks `search_spoken_mentions` can reach on the caption-track route.
15
15
  *
16
- * Both publish a caption track their post detail already carries; everywhere
17
- * else the words would have to be inferred from the audio, which is a
18
- * different tool at a different price. Named here rather than inline because
19
- * three places have to agree on it — the tool's own enum, the filter it puts
20
- * every requested platform through, and the wait that prices the sweep. A
21
- * platform the filter drops but the price counts is a credit quoted for a
22
- * call that never happens.
16
+ * These three are the ones whose post detail already carries a caption track,
17
+ * so a transcript is one synchronous read at 1 credit —
18
+ * `TRANSCRIPT_CAPTION_PLATFORMS` in nooticr-server, mirrored here.
19
+ *
20
+ * This list is the *fast path*, not the boundary of what could be searched:
21
+ * `get_post_transcript`'s `_` arm transcribes any other platform's audio with
22
+ * whisper, behind the same tool at the same price. It stays a fast path rather
23
+ * than the whole story for two reasons that are about the deployment, not the
24
+ * network — speech-to-text is gated on a `WHISPER_MODEL` no environment sets
25
+ * yet, and the listening path byte-downloads the media, so an HLS-only
26
+ * platform (Reddit) and one that carries no media URL at all (Bilibili) cannot
27
+ * be reached even once it is configured. Widening past these three waits on
28
+ * nooticr-server#39 and #42; the earlier wording here claimed instead that
29
+ * listening was "a different tool at a different price", which was wrong on
30
+ * both halves and is why Douyin sat unreachable behind a two-network limit
31
+ * despite reading captions exactly like the other two.
32
+ *
33
+ * Named here rather than inline because three places have to agree on it — the
34
+ * tool's own enum, the filter it puts every requested platform through, and the
35
+ * wait that prices the sweep. A platform the filter drops but the price counts
36
+ * is a credit quoted for a call that never happens.
23
37
  */
24
- export const SPOKEN_PLATFORMS = ["tiktok", "youtube"];
38
+ export const SPOKEN_PLATFORMS = ["tiktok", "youtube", "douyin"];
25
39
  /** Two per network, except the one that costs an order of magnitude more upstream. */
26
40
  export const CREDITS_PER_NETWORK = 2;
27
41
  export const XIAOHONGSHU_CREDITS = 5;
@@ -3,7 +3,7 @@ export const TOOL_DEFINITIONS = [
3
3
  {
4
4
  name: "analyze_post",
5
5
  title: "Analyze Post",
6
- description: "Frames sampled evenly across a social post (video, image, carousel/slideshow), returned as real images you can look at, together with the post's transcript, caption and stats. It hands you the material rather than a verdict: read the frames and the words, then work out the hook, the structure, the visual style, the CTA and the audience yourself, citing the frame or line behind each claim. Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo and Bilibili. Use when the visuals are the point — framing, editing, on-screen text; analyze_post_fast reads the same post without the frames for one credit less. Fans out to get_post_frames and get_post_transcript: 3 nooticr credits, 2 for the frames plus 1 for the transcript.",
6
+ description: "Frames sampled evenly across a social post (video, image, carousel/slideshow), returned as real images you can look at, together with the post's transcript, caption and stats. It hands you the material rather than a verdict: read the frames and the words, then work out the hook, the structure, the visual style, the CTA and the audience yourself, citing the frame or line behind each claim. Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo, Bilibili and LinkedIn. Use when the visuals are the point — framing, editing, on-screen text; analyze_post_fast reads the same post without the frames for one credit less. Fans out to get_post_frames and get_post_transcript: 3 nooticr credits, 2 for the frames plus 1 for the transcript.",
7
7
  inputSchema: z.object({ url: z.string().describe("Public post URL (TikTok/Instagram/YouTube/X/Reddit/Douyin/Xiaohongshu/Weibo/Bilibili/LinkedIn).") }).strict(),
8
8
  },
9
9
  {
@@ -57,7 +57,7 @@ export const TOOL_DEFINITIONS = [
57
57
  {
58
58
  name: "understand_social_post",
59
59
  title: "Understand Social Post",
60
- description: "The same frames and transcript analyze_post returns, asked a different question: describe what physically happens on screen, in order, with every observation anchored to a frame. Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo and Bilibili. Use when you need the events rather than the strategy; analyze_post puts the strategic question to the same material. Fans out to get_post_frames and get_post_transcript: 3 nooticr credits, 2 for the frames plus 1 for the transcript.",
60
+ description: "The same frames and transcript analyze_post returns, asked a different question: describe what physically happens on screen, in order, with every observation anchored to a frame. Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo, Bilibili and LinkedIn. Use when you need the events rather than the strategy; analyze_post puts the strategic question to the same material. Fans out to get_post_frames and get_post_transcript: 3 nooticr credits, 2 for the frames plus 1 for the transcript.",
61
61
  inputSchema: z.object({ url: z.string().describe("Full public post URL (TikTok/Instagram/YouTube/X/Reddit/Douyin/Xiaohongshu/Weibo/Bilibili/LinkedIn)."), focus: z.string().optional().describe("Extra instruction, e.g. 'focus on the CTA'.") }).strict(),
62
62
  },
63
63
  {
@@ -369,7 +369,7 @@ export const TOOL_DEFINITIONS = [
369
369
  {
370
370
  name: "search_spoken_mentions",
371
371
  title: "Search Spoken Mentions",
372
- description: "Brand mentions people SAY but never type. search_mentions reads captions, post bodies and comments — text — so a video that names a brand only out loud is invisible to it. This reads the words actually spoken, from the platform's own caption track, and searches them for a term. Narrows to candidate posts first (a niche sweep, named creator handles, and/or your watchlist), transcribes only the most-viewed survivors up to a hard ceiling you set, and returns the matched line with surrounding context. TikTok and YouTube only — the only two networks whose posts carry a caption track this cheaply — and even there a video with no captions is invisible to this tool; the result says how many candidates were found, transcribed and matched. Use when a term might be spoken on camera but not written anywhere. Consumes 2 nooticr credits per platform a niche is searched on, 2 per creator handle checked, and 1 per transcript actually fetched, capped by maxTranscripts.",
372
+ description: "Brand mentions people SAY but never type. search_mentions reads captions, post bodies and comments — text — so a video that names a brand only out loud is invisible to it. This reads the words actually spoken, from the platform's own caption track, and searches them for a term. Narrows to candidate posts first (a niche sweep, named creator handles, and/or your watchlist), transcribes only the most-viewed survivors up to a hard ceiling you set, and returns the matched line with surrounding context. TikTok, YouTube and Douyin — the networks whose post detail already carries a caption track; every other network's audio can be transcribed at the same price but not yet from here, and even on these three a video whose creator disabled captions is invisible to this tool; the result says how many candidates were found, transcribed and matched. Use when a term might be spoken on camera but not written anywhere. Consumes 2 nooticr credits per platform a niche is searched on, 2 per creator handle checked, and 1 per transcript actually fetched, capped by maxTranscripts.",
373
373
  inputSchema: z.object({ term: z.string().describe("Brand, product or person to listen for, e.g. 'nooticr'."), platforms: z.array(z.enum(["tiktok", "youtube"])).optional().describe("Which networks to check (default: both)."), niche: z.string().optional().describe("Niche or keyword for a candidate sweep. Provide this, usernames, useWatchlist, or any combination."), usernames: z.array(z.string()).optional().describe("Specific creator handles to check."), useWatchlist: z.boolean().optional().describe("Also check your watchlisted creators on tiktok or youtube."), candidateLimit: z.number().int().optional().describe("Posts pulled per narrowing call (default 15, max 25)."), maxTranscripts: z.number().int().optional().describe("Hard ceiling on transcripts fetched (default 8, max 20).") }).strict(),
374
374
  },
375
375
  {
@@ -24,7 +24,7 @@ import { loadingPlansJson } from "./loading-plans.js";
24
24
  import { registerHandoff } from "./handoff.js";
25
25
  import { registerCollabTools } from "./collab.js";
26
26
  /** Current MCP server version — bumped on every deploy for traceability. */
27
- export const MCP_SERVER_VERSION = "1.26.22";
27
+ export const MCP_SERVER_VERSION = "1.26.23";
28
28
  /** MCP Apps extension identifier */
29
29
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
30
30
  /** MIME type for MCP Apps HTML resources */
@@ -737,7 +737,7 @@ export function createMcpServer(rawMakeClient, opts) {
737
737
  "lands and who it is aimed at, citing the frame or line behind each claim. " +
738
738
  "It fans out to two fetches and you pay for both. " +
739
739
  `${costSentence("analyze_post")} Each frame costs roughly 1,200 tokens of your context. ` +
740
- "Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo and Bilibili. " +
740
+ "Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo, Bilibili and LinkedIn. " +
741
741
  "Use when the visuals are the point; analyze_post_fast reads the same post without the frames for one credit less.",
742
742
  _meta: {
743
743
  ui: { resourceUri: uiResource("analyze_post") },
@@ -896,7 +896,7 @@ export function createMcpServer(rawMakeClient, opts) {
896
896
  .enum(["tiktok", "instagram", "youtube", "douyin", "xiaohongshu", "twitter", "bilibili", "linkedin", "reddit", "weibo"])
897
897
  .optional()
898
898
  .describe("Which platform (default tiktok)."),
899
- limit: z.number().int().optional().describe("Posts to fetch (default 6; first 3 analyzed)."),
899
+ limit: z.number().int().optional().describe("Posts to fetch (default 12, max 30)."),
900
900
  focus: z.string().optional().describe("Extra instruction for the profile synthesis."),
901
901
  })
902
902
  .strict(),
@@ -1775,7 +1775,7 @@ export function createMcpServer(rawMakeClient, opts) {
1775
1775
  .object({
1776
1776
  niche: z.string().describe("Niche or topic, e.g. 'home fitness'."),
1777
1777
  platform: z.string().optional().describe("Platform to survey (default tiktok)."),
1778
- count: z.number().int().optional().describe("Posts to survey (default 20, max 40)."),
1778
+ count: z.number().int().optional().describe("Posts to survey (default 12, max 40)."),
1779
1779
  })
1780
1780
  .strict(),
1781
1781
  }, async (args, extra) => {
@@ -1807,7 +1807,7 @@ export function createMcpServer(rawMakeClient, opts) {
1807
1807
  .object({
1808
1808
  username: z.string().describe("Creator handle, with or without @."),
1809
1809
  platform: z.string().optional().describe("Platform (default tiktok)."),
1810
- limit: z.number().int().optional().describe("Posts to read (default 20, max 40)."),
1810
+ limit: z.number().int().optional().describe("Posts to read (default 12, max 40)."),
1811
1811
  })
1812
1812
  .strict(),
1813
1813
  }, async (args, extra) => {
@@ -2011,7 +2011,7 @@ export function createMcpServer(rawMakeClient, opts) {
2011
2011
  "what physically happens on screen, in order, with every observation anchored to a frame. " +
2012
2012
  "It fans out to two fetches and you pay for both. " +
2013
2013
  `${costSentence("understand_social_post")} Each frame costs roughly 1,200 tokens of your context. ` +
2014
- "Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo and Bilibili. " +
2014
+ "Supports TikTok, Instagram, YouTube, X, Reddit, Douyin, Xiaohongshu, Weibo, Bilibili and LinkedIn. " +
2015
2015
  "Use when you need the events rather than the strategy; analyze_post puts the strategic question to the same material.",
2016
2016
  _meta: {
2017
2017
  ui: { resourceUri: uiResource("understand_social_post") },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nooticr/mcp",
3
3
  "mcpName": "com.nooticr/mcp",
4
- "version": "1.26.22",
4
+ "version": "1.26.23",
5
5
  "description": "MCP server for nooticr - fetch, discover and understand TikTok/Instagram/YouTube/X/Reddit/LinkedIn/Weibo posts with AI (media metadata, niche discovery, brand monitoring, hook & viral analysis)",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",