@nooticr/mcp 1.26.61 → 1.26.63

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
@@ -52,7 +52,7 @@ npx @nooticr/mcp login # one-time sign-in (Google)
52
52
 
53
53
  ## Tools
54
54
 
55
- 102 tools, grouped by what you are trying to do. Prices are in nooticr credits and
55
+ 106 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
  The job tools under **Answer a question you actually have** are not
@@ -104,6 +104,10 @@ them caps that fan-out with an argument.
104
104
  | `create_brand_watch` | free to call | Schedule a recurring sweep and get emailed only what is new. `kind: "mentions"` (default) is a recurring `search_mentions` sweep for `term`/`platforms`; `kind: "competitor"` is a recurring `get_user_posts` check on one creator (`handle`/`platform`), mailing only what beats their own recent median; `kind: "report"` runs `job: {tool, args}` — `niche_report` (mailed as written every run) or `discover_social_posts` (only unseen posts) — on the schedule; `kind: "portfolio"` sweeps up to 25 `terms` (your brand, products, competitors) across the same networks under one budget and mails one digest with each term's share of voice. Two calls by design: the first returns the quote (cost per run, cadence, cost per day) and a `confirmationToken`; nothing is created or charged until you call it again with `confirm: true` and that token. Each run then bills the same as calling the matching tool yourself — 2 credits per network (5 for Xiaohongshu) for mentions, a flat 2 credits for competitor, the job tool's own price for a report (3 for `niche_report`), and a mentions sweep per term for a portfolio — whose budget is split evenly and trims networks, never terms, and which refunds any term whose sweep fails. |
105
105
  | `list_brand_watches` | free | Every watch you have scheduled: term, networks, cadence, cost per run, credits spent so far, runs made, and when the next one is due. |
106
106
  | `creative_assets_list` | free | The creative assets (voice-overs, sound effects, music, images, videos, composites) generated in your workspace, newest first, with url, kind, dimensions or duration, and provenance. Filter by `appId`, `kind`, `briefId` or `since`; page with `nextCursor`. |
107
+ | `creative_voices_search` | free | Search ElevenLabs' voice library by `query`, `language`, `accent`, `gender`, `age` or `useCase`. Returns a `voiceRef`, name, labels, language, accent and preview URL for each. Library voices only, never a cloned voice. |
108
+ | `creative_voiceover` | quoted first | A voice-over of up to 2500 characters with word timings for burned-in captions, in a voice from `creative_voices_search`. The first call returns a quote in credits and charges nothing; call again with `confirm: true` and the `confirmationToken` to generate. Settled to actual usage, never above the quote. |
109
+ | `creative_sfx` | quoted first | A short sound effect (0.5-10 s) from a description. Same quote-then-confirm handshake as `creative_voiceover`. |
110
+ | `creative_music` | quoted first | An instrumental music bed of 5-60 seconds, generated as a job: confirm the quote, then poll `creative_job_status`. May be switched off on a deployment, in which case it says so. Describe mood, tempo and instruments, not artists or songs. |
107
111
  | `creative_job_status` | free | Where an asynchronous creative job stands by `jobId`: queued, running, done, failed or canceled, with the finished assets and the credits quoted, charged and refunded. Poll after `pollAfterSeconds`. |
108
112
  | `mention_trend` | free | **What a watch has seen over time**, which nothing else here answers — every other read is about now. One point per run: when it ran, how many mentions it found, how many were new, and both counts per network, so "which network is growing" and "did it go quiet or did we stop looking" come apart. On a competitor watch each point carries the median that run measured, which turns "they beat their own median" into "and that median has doubled". Plus the mentions more than one run has seen. Free because the sweeps were billed when they ran; charging again to read what was already paid for is the thing it fixes. |
109
113
  | `update_watch_portfolio` | free | Add or remove a portfolio watch's terms by `watchId`. No new confirmation, because the per-run budget you agreed to does not move — more terms split it more ways, and the reply names any network a run no longer reaches. |
@@ -4,8 +4,10 @@
4
4
  * Both are passthroughs to the server's `POST /mcp` dispatch, which owns the
5
5
  * tenancy check (another workspace's id answers "not found").
6
6
  *
7
- * The paid creative tools (CR2-CR6) register here when they ship;
8
- * `creative_test_render` is a server test fixture and is never registered.
7
+ * The ElevenLabs half (CR2): `creative_voices_search` is free; `creative_voiceover`,
8
+ * `creative_sfx` and `creative_music` are paid and use the server's quote-then-confirm
9
+ * handshake. The remaining paid creative tools (image, video, ...) register here when
10
+ * they ship; `creative_test_render` is a server test fixture and is never registered.
9
11
  * Neither tool has a view yet: the asset list would be a gallery, which the
10
12
  * design system has no card for.
11
13
  */
@@ -64,4 +66,92 @@ export function registerCreativeTools(server, makeClient) {
64
66
  inputSchema: z.object({ jobId: z.string().uuid().describe("jobId returned when the job was started.") }).strict(),
65
67
  outputSchema: OUTPUT_SCHEMAS.creative_job_status,
66
68
  }, passthrough("creative_job_status"));
69
+ const voiceRef = z
70
+ .object({ publicOwnerId: z.string(), voiceId: z.string() })
71
+ .strict()
72
+ .describe("A voiceRef from creative_voices_search.");
73
+ const handshake = {
74
+ briefId: z.string().optional(),
75
+ confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted credits."),
76
+ confirmationToken: z.string().optional().describe("The token the quote returned."),
77
+ };
78
+ const paidAnnotations = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true };
79
+ const NO_LIKENESS = " No real people's likeness or voice, no copyrighted music, logos or characters.";
80
+ server.registerTool("creative_voices_search", {
81
+ title: "Search Creative Voices",
82
+ description: "Search ElevenLabs' voice library for a voice to use in creative_voiceover. Library voices " +
83
+ "only, never a real person's cloned voice. Returns voiceRef, name, labels, language, accent " +
84
+ "and previewUrl. No cost to call.",
85
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
86
+ inputSchema: z
87
+ .object({
88
+ query: z.string().optional(),
89
+ language: z.string().optional().describe("e.g. en, fr, es."),
90
+ accent: z.string().optional(),
91
+ gender: z.enum(["male", "female", "neutral"]).optional(),
92
+ age: z.enum(["young", "middle_aged", "old"]).optional(),
93
+ useCase: z.enum(["narration", "social_media", "advertisement", "conversational", "characters"]).optional(),
94
+ limit: z.number().int().min(1).max(20).optional(),
95
+ })
96
+ .strict(),
97
+ outputSchema: OUTPUT_SCHEMAS.creative_voices_search,
98
+ }, passthrough("creative_voices_search"));
99
+ server.registerTool("creative_voiceover", {
100
+ title: "Create Voice-over",
101
+ description: "Voice-over with word timings (for burned-in captions); the text is spoken verbatim, up to 2500 " +
102
+ "characters. Pass a voiceRef (or voiceId) from creative_voices_search; library voices only, no " +
103
+ "cloning. quality: final (default) or draft (cheaper, faster). The first call is a free price " +
104
+ "quote in credits (vendor cost plus markup, never above the quote); nothing is charged until " +
105
+ "you confirm. Show the user the credits and ask before calling again with confirm: true and " +
106
+ "the confirmationToken." + NO_LIKENESS,
107
+ annotations: paidAnnotations,
108
+ inputSchema: z
109
+ .object({
110
+ appId: z.number().int(),
111
+ text: z.string().min(1).max(2500),
112
+ voiceRef: voiceRef.optional(),
113
+ voiceId: z.string().optional(),
114
+ quality: z.enum(["final", "draft"]).optional(),
115
+ stability: z.number().min(0).max(1).optional(),
116
+ style: z.number().min(0).max(1).optional(),
117
+ speed: z.number().min(0.7).max(1.2).optional(),
118
+ ...handshake,
119
+ })
120
+ .strict(),
121
+ outputSchema: OUTPUT_SCHEMAS.creative_voiceover,
122
+ }, passthrough("creative_voiceover"));
123
+ server.registerTool("creative_sfx", {
124
+ title: "Create Sound Effect",
125
+ description: "Generate a short sound effect (0.5-10 s) from a description of up to 300 characters; the text " +
126
+ "is a prompt, not spoken. The first call is a free price quote in credits; nothing is charged until you confirm. Show the user the " +
127
+ "credits and ask before calling again with confirm: true and the confirmationToken." + NO_LIKENESS,
128
+ annotations: paidAnnotations,
129
+ inputSchema: z
130
+ .object({
131
+ appId: z.number().int(),
132
+ description: z.string().min(1).max(300),
133
+ durationSeconds: z.number().min(0.5).max(10).optional(),
134
+ ...handshake,
135
+ })
136
+ .strict(),
137
+ outputSchema: OUTPUT_SCHEMAS.creative_sfx,
138
+ }, passthrough("creative_sfx"));
139
+ server.registerTool("creative_music", {
140
+ title: "Create Music Bed",
141
+ description: "Instrumental music bed, 5-60 seconds, as an asynchronous job. Describe mood, tempo and " +
142
+ "instruments, not artists or songs. May be switched off on this deployment (it then says so " +
143
+ "and offers stock music). The first call is a free price quote in credits; nothing is charged until you confirm. Show the user the " +
144
+ "credits and ask before calling again with confirm: true and the confirmationToken, then poll " +
145
+ "creative_job_status every 10-20 s." + NO_LIKENESS,
146
+ annotations: paidAnnotations,
147
+ inputSchema: z
148
+ .object({
149
+ appId: z.number().int(),
150
+ prompt: z.string().min(1).max(500),
151
+ durationSeconds: z.number().int().min(5).max(60),
152
+ ...handshake,
153
+ })
154
+ .strict(),
155
+ outputSchema: OUTPUT_SCHEMAS.creative_music,
156
+ }, passthrough("creative_music"));
67
157
  }
@@ -479,6 +479,11 @@ export const LOADING_PLANS = {
479
479
  // Creative assets (creative.ts): both are free reads.
480
480
  creative_assets_list: { label: "Reading your creative assets", kind: "list", n: 3, steps: [], free: true },
481
481
  creative_job_status: { label: "Checking the creative job", kind: "text", n: 1, steps: [], free: true },
482
+ creative_voices_search: { label: "Searching voices", kind: "list", n: 3, steps: [], free: true },
483
+ // Paid, but the first call only quotes; the charge waits for the confirmed second call.
484
+ creative_voiceover: { label: "Quoting the voice-over", kind: "text", n: 1, steps: [] },
485
+ creative_sfx: { label: "Quoting the sound effect", kind: "text", n: 1, steps: [] },
486
+ creative_music: { label: "Quoting the music bed", kind: "text", n: 1, steps: [] },
482
487
  list_brand_watches: { label: "Reading your watches", kind: "list", n: 3, steps: [], free: true },
483
488
  stop_brand_watch: { label: "Stopping the watch", kind: "text", n: 1, steps: [], free: true },
484
489
  update_watch_portfolio: { label: "Updating the portfolio", kind: "text", n: 1, steps: [], free: true },
@@ -1260,6 +1260,45 @@ export const OUTPUT_SCHEMAS = {
1260
1260
  assets: listOf(open({ id: scalar(), kind: scalar(), appId: scalar(), url: scalar(), mime: scalar(), createdAt: scalar() })),
1261
1261
  nextCursor: scalar(),
1262
1262
  }),
1263
+ creative_voices_search: open({
1264
+ voices: listOf(open({ voiceRef: open({ publicOwnerId: scalar(), voiceId: scalar() }), name: scalar(), language: scalar(), accent: scalar(), previewUrl: scalar() })),
1265
+ }),
1266
+ // The paid tools answer a quote first, then the generated assets (or a job for music).
1267
+ creative_voiceover: open({
1268
+ quote: open({ credits: scalar() }).nullish().describe("First call: what generating will cost. Nothing was generated or charged yet."),
1269
+ confirmationToken: scalar().describe("Pass this back with confirm: true to generate."),
1270
+ expiresAt: scalar(),
1271
+ next: scalar(),
1272
+ assetId: scalar(),
1273
+ url: scalar(),
1274
+ key: scalar(),
1275
+ durationMs: scalar(),
1276
+ words: listOf(open({ text: scalar(), start: scalar(), end: scalar() })),
1277
+ wordsTruncated: scalar(),
1278
+ creativeCredits: open({}).nullish(),
1279
+ }),
1280
+ creative_sfx: open({
1281
+ quote: open({ credits: scalar() }).nullish().describe("First call: what generating will cost. Nothing was generated or charged yet."),
1282
+ confirmationToken: scalar().describe("Pass this back with confirm: true to generate."),
1283
+ expiresAt: scalar(),
1284
+ next: scalar(),
1285
+ assetId: scalar(),
1286
+ url: scalar(),
1287
+ key: scalar(),
1288
+ durationMs: scalar(),
1289
+ creativeCredits: open({}).nullish(),
1290
+ }),
1291
+ creative_music: open({
1292
+ quote: open({ credits: scalar() }).nullish().describe("First call: what generating will cost. Nothing was generated or charged yet."),
1293
+ confirmationToken: scalar().describe("Pass this back with confirm: true to start the job."),
1294
+ expiresAt: scalar(),
1295
+ next: scalar(),
1296
+ jobId: scalar().describe("Poll creative_job_status with this."),
1297
+ status: scalar(),
1298
+ creativeCredits: open({}).nullish(),
1299
+ refused: scalar().describe("not_enabled when music generation is switched off for this account."),
1300
+ alternative: scalar(),
1301
+ }),
1263
1302
  creative_job_status: open({
1264
1303
  jobId: scalar(),
1265
1304
  tool: scalar(),
@@ -270,6 +270,30 @@ export const TOOL_DEFINITIONS = [
270
270
  description: "List the creative assets (voice-overs, sound effects, music, images, videos, composites) generated in this workspace, newest first. Optional filters: appId, kind, briefId, since (ISO timestamp). Pass the returned nextCursor as cursor for the next page. No cost to call.",
271
271
  inputSchema: z.object({ appId: z.number().int().optional().describe("Only this product's assets."), kind: z.enum(["voiceover", "sfx", "music", "image", "video", "composite"]).optional(), briefId: z.string().optional(), since: z.string().optional().describe("ISO 8601 timestamp."), limit: z.number().int().min(1).max(100).optional(), cursor: z.string().optional().describe("nextCursor from the previous page.") }).strict(),
272
272
  },
273
+ {
274
+ name: "creative_voices_search",
275
+ title: "Search Creative Voices",
276
+ description: "Search ElevenLabs' voice library for a voice to use in creative_voiceover. Library voices only, never a real person's cloned voice. Returns voiceRef, name, labels, language, accent and previewUrl. No cost to call.",
277
+ inputSchema: z.object({ query: z.string().optional(), language: z.string().optional().describe("e.g. en, fr, es."), accent: z.string().optional(), gender: z.enum(["male", "female", "neutral"]).optional(), age: z.enum(["young", "middle_aged", "old"]).optional(), useCase: z.enum(["narration", "social_media", "advertisement", "conversational", "characters"]).optional(), limit: z.number().int().min(1).max(20).optional() }).strict(),
278
+ },
279
+ {
280
+ name: "creative_voiceover",
281
+ title: "Create Voice-over",
282
+ description: "Voice-over with word timings (for burned-in captions); the text is spoken verbatim, up to 2500 characters. Pass a voiceRef (or voiceId) from creative_voices_search; library voices only, no cloning. quality: final (default) or draft (cheaper, faster). The first call is a free price quote in credits; nothing is charged until you confirm (vendor cost plus markup, never above the quote); nothing is charged until you confirm. Show the user the credits and ask before calling again with confirm: true and the confirmationToken. No real people's likeness or voice, no copyrighted music, logos or characters.",
283
+ inputSchema: z.object({ appId: z.number().int(), text: z.string().min(1).max(2500), voiceRef: z.object({ publicOwnerId: z.string(), voiceId: z.string() }).strict().optional().describe("A voiceRef from creative_voices_search."), voiceId: z.string().optional(), quality: z.enum(["final", "draft"]).optional(), stability: z.number().min(0).max(1).optional(), style: z.number().min(0).max(1).optional(), speed: z.number().min(0.7).max(1.2).optional(), briefId: z.string().optional(), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted credits."), confirmationToken: z.string().optional().describe("The token the quote returned.") }).strict(),
284
+ },
285
+ {
286
+ name: "creative_sfx",
287
+ title: "Create Sound Effect",
288
+ description: "Generate a short sound effect (0.5-10 s) from a description of up to 300 characters; the text is a prompt, not spoken. The first call is a free price quote in credits; nothing is charged until you confirm. Show the user the credits and ask before calling again with confirm: true and the confirmationToken. No real people's likeness or voice, no copyrighted music, logos or characters.",
289
+ inputSchema: z.object({ appId: z.number().int(), description: z.string().min(1).max(300), durationSeconds: z.number().min(0.5).max(10).optional(), briefId: z.string().optional(), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted credits."), confirmationToken: z.string().optional().describe("The token the quote returned.") }).strict(),
290
+ },
291
+ {
292
+ name: "creative_music",
293
+ title: "Create Music Bed",
294
+ description: "Instrumental music bed, 5-60 seconds, as an asynchronous job. Describe mood, tempo and instruments, not artists or songs. May be switched off on this deployment (it then says so and offers stock music). The first call is a free price quote in credits; nothing is charged until you confirm. Show the user the credits and ask before calling again with confirm: true and the confirmationToken, then poll creative_job_status every 10-20 s. No real people's likeness or voice, no copyrighted music, logos or characters.",
295
+ inputSchema: z.object({ appId: z.number().int(), prompt: z.string().min(1).max(500), durationSeconds: z.number().int().min(5).max(60), briefId: z.string().optional(), confirm: z.boolean().optional().describe("Set true only once the user has agreed to the quoted credits."), confirmationToken: z.string().optional().describe("The token the quote returned.") }).strict(),
296
+ },
273
297
  {
274
298
  name: "creative_job_status",
275
299
  title: "Creative Job Status",
@@ -34,7 +34,7 @@ import { registerAmazonTools } from "./amazon.js";
34
34
  import { registerMarketplaceTools } from "./marketplace.js";
35
35
  import { DEFAULT_NOOTICR_SETTINGS, DEFAULT_SOCIAL_PLATFORMS, MemorySettingsStore, SETTINGS_READ_TOOL, SETTINGS_UPDATE_TOOL, settingsOwnerFromAuth, } from "./settings.js";
36
36
  /** Current MCP server version — bumped on every deploy for traceability. */
37
- export const MCP_SERVER_VERSION = "1.26.61";
37
+ export const MCP_SERVER_VERSION = "1.26.63";
38
38
  /** MCP Apps extension identifier */
39
39
  const UI_EXTENSION = "io.modelcontextprotocol/ui";
40
40
  /** MIME type for MCP Apps HTML resources */
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.61",
4
+ "version": "1.26.63",
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
  "homepage": "https://mcp.nooticr.com",
7
7
  "repository": {