@facelessad/mcp 2.1.1 → 2.2.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/README.md CHANGED
@@ -34,7 +34,7 @@ parameters at all.
34
34
 
35
35
  There is also a hosted version that needs no install: add
36
36
  `https://facelessad.com/mcp` as a custom connector in Claude and authorise it
37
- with your account. Same twelve tools, same fields.
37
+ with your account. Same thirteen tools, same fields.
38
38
 
39
39
  ## Tools
40
40
 
package/SKILL.md CHANGED
@@ -29,10 +29,12 @@ Keys are created at https://facelessad.com/developers. There is also a hosted
29
29
  server that needs no install and no key: add `https://facelessad.com/mcp` as
30
30
  a custom connector and authorise it. Same thirteen tools, same fields.
31
31
 
32
- **The API needs a paid plan.** Trial credits cover the web app but do not
33
- open API keys or the hosted connector. If a call answers
34
- `api_access_required`, the user has to subscribe — that is a billing state,
35
- not a broken key, so tell them rather than retrying.
32
+ **The API needs a paid plan or an active trial.** The $15 trial (300
33
+ credits, 7 days) opens API keys and the hosted connector for its duration.
34
+ If a call answers `api_access_required`, the subscription has ended or the
35
+ trial has expired or run out of credits that is a billing state, not a
36
+ broken key, so tell the user to subscribe (or start the trial) rather than
37
+ retrying.
36
38
 
37
39
  ## Test mode — build the integration before spending anything
38
40
 
@@ -65,7 +67,7 @@ and aspect ratios. Every id you send is validated against that same list, and
65
67
  an unknown one is rejected with a message naming the valid set — never
66
68
  silently swapped.
67
69
 
68
- ## The twelve tools
70
+ ## The thirteen tools
69
71
 
70
72
  | tool | what it does |
71
73
  |---|---|
@@ -275,9 +277,10 @@ Every tool returns the API's own JSON; on failure `{ok:false, error, code}`.
275
277
 
276
278
  **Access and capacity**
277
279
 
278
- - `api_access_required` — no active paid plan. Trial credits do not open the API.
280
+ - `api_access_required` — no active paid plan or live trial. An active $15 trial opens the API for its 7 days.
279
281
  - `insufficient_credits` — the body carries the estimate and the balance; nothing was spent.
280
282
  - `too_many_active` — the plan's queue is full (Starter 10, Growth 25, Scale 50; app and API share it). Wait for a build to finish, then retry.
283
+ - `test_quota_exceeded` — 100 test runs per day per account (`"test": true` only). Real runs are unaffected; the quota resets at 00:00 UTC and the body carries `retryAfter`.
281
284
  - `rate_limited` — over 300 calls this hour (`facelessad_estimate` has its own 600). `retryAfter` gives the seconds until the counter resets on the hour.
282
285
  - `unauthorized` — the key is unknown or revoked.
283
286
 
package/index.js CHANGED
@@ -78,12 +78,13 @@ function qs(q) {
78
78
  return [...q.keys()].length ? '?' + q.toString() : '';
79
79
  }
80
80
 
81
- const server = new McpServer({ name: 'facelessad', version: '2.0.0' });
81
+ const server = new McpServer({ name: 'facelessad', version: '2.2.0' });
82
82
 
83
83
  server.tool(
84
84
  'facelessad_list_tools',
85
85
  'The FacelessAd registry: available video tools with their styles, ad structures, hook formulas, duration bounds and aspect ratios. Call this FIRST to learn valid ids — style/structure/hook ids passed to other tools are validated against this list. Styles flagged genBg:true generate an AI photo background behind every part (+6 credits per part in the estimate); their finished videos expose bg-N parts for background regeneration.',
86
86
  {},
87
+ { title: 'List video tools', readOnlyHint: true },
87
88
  async () => result(await api('GET', '/api/v1/tools'))
88
89
  );
89
90
 
@@ -91,6 +92,7 @@ server.tool(
91
92
  'facelessad_balance',
92
93
  'The account plan and current credit balance.',
93
94
  {},
95
+ { title: 'Credit balance', readOnlyHint: true },
94
96
  async () => result(await api('GET', '/api/v1/balance'))
95
97
  );
96
98
 
@@ -98,6 +100,7 @@ server.tool(
98
100
  'facelessad_list_brand_kits',
99
101
  'The brands on this account: id, name, whether it is the default, how many of the brand questions are answered (filled / questions), and whether it carries a colour and a logo. Call this before naming brand_kit_id — ids are per account and cannot be guessed.',
100
102
  {},
103
+ { title: 'List brand kits', readOnlyHint: true },
101
104
  async () => result(await api('GET', '/api/v1/brand-kits'))
102
105
  );
103
106
 
@@ -108,6 +111,7 @@ server.tool(
108
111
  language: z.string().optional().describe('Filter, e.g. "English (US)"'),
109
112
  gender: z.string().optional().describe('Filter: "female" | "male"'),
110
113
  },
114
+ { title: 'List voices', readOnlyHint: true },
111
115
  async ({ language, gender }) => {
112
116
  const q = new URLSearchParams();
113
117
  if (language) q.set('language', language);
@@ -214,6 +218,7 @@ server.tool(
214
218
  'facelessad_estimate',
215
219
  'The upper-bound credit cost of a video, without creating it. Takes the same input as facelessad_create_video. The user is only charged for steps that actually succeed.',
216
220
  createShape,
221
+ { title: 'Estimate cost', readOnlyHint: true },
217
222
  async (input) => result(await api('POST', '/api/v1/estimate', input))
218
223
  );
219
224
 
@@ -221,6 +226,7 @@ server.tool(
221
226
  'facelessad_create_video',
222
227
  'Create a faceless video ad. THIS SPENDS THE USER\'S CREDITS — run facelessad_estimate first and tell the user the number before calling this, unless they have already approved the cost. Returns immediately with an id and status "queued"; the video builds in the background (typically 3–10 minutes) and lands in the user\'s My Files. Poll facelessad_get_video for progress — do not wait synchronously.',
223
228
  createShape,
229
+ { title: 'Create video', readOnlyHint: false, destructiveHint: false },
224
230
  async (input) => result(await api('POST', '/api/v1/videos', input))
225
231
  );
226
232
 
@@ -228,6 +234,7 @@ server.tool(
228
234
  'facelessad_get_video',
229
235
  'Status of one video by id. When finished, includes a download URL valid ~24 hours — call again for a fresh one rather than storing it.',
230
236
  { id: z.union([z.string(), z.number()]).describe('The id returned by facelessad_create_video') },
237
+ { title: 'Video status', readOnlyHint: true },
231
238
  async ({ id }) => result(await api('GET', '/api/v1/videos/' + encodeURIComponent(String(id))))
232
239
  );
233
240
 
@@ -238,6 +245,7 @@ server.tool(
238
245
  limit: z.number().int().optional().describe('1–100, default 25'),
239
246
  offset: z.number().int().optional(),
240
247
  },
248
+ { title: 'List videos', readOnlyHint: true },
241
249
  async ({ limit, offset }) => {
242
250
  const q = new URLSearchParams();
243
251
  if (limit) q.set('limit', String(limit));
@@ -255,6 +263,7 @@ server.tool(
255
263
  'facelessad_list_parts',
256
264
  'List the parts of a finished video so one of them can be regenerated. Model A / image-card videos return scene clips and image cards (uid, type, duration, current motion prompt, image_prompt, preview links). Graphics tools return graphics_block parts (gfx-0, gfx-1, ...) — and on background-image styles (styles flagged genBg in facelessad_list_tools) ALSO a background_image layer per part (bg-0, bg-1, ...), regenerated with prompt. Product Showcase returns BOTH layers per scene: the product clip and the graphics block over it. Each part says whether it is regenerable and which field it needs (regenerate_requires).',
257
265
  { video_id: z.string().describe('Video id from facelessad_create_video') },
266
+ { title: 'List video parts', readOnlyHint: true },
258
267
  async ({ video_id }) => result(await api('GET', '/api/v1/videos/' + encodeURIComponent(video_id) + '/parts'))
259
268
  );
260
269
 
@@ -268,6 +277,7 @@ server.tool(
268
277
  image_prompt: z.string().optional().describe('New image description (max 900 chars) — required for image cards'),
269
278
  instruction: z.string().optional().describe('Plain-language change for a graphics_block part (max 900 chars)'),
270
279
  },
280
+ { title: 'Regenerate video part', readOnlyHint: false, destructiveHint: true },
271
281
  async ({ video_id, part_uid, prompt, image_prompt, instruction }) => {
272
282
  const body = {};
273
283
  if (prompt) body.prompt = prompt;
@@ -281,6 +291,7 @@ server.tool(
281
291
  'facelessad_regenerate_video',
282
292
  'Rebuild a WHOLE video from the same request that created it — the answer for continuous videos, whose clips chain into each other and cannot be fixed part by part. The result is a NEW video with its own id (the original is untouched) and it bills like a normal new generation: script, images and clips are all rolled again. Only for videos created through the API, within 7 days. For cuts videos prefer facelessad_regenerate_part.',
283
293
  { video_id: z.string().describe('Id of the video to rebuild') },
294
+ { title: 'Rebuild video', readOnlyHint: false, destructiveHint: false },
284
295
  async ({ video_id }) => result(await api('POST', '/api/v1/videos/' + encodeURIComponent(video_id) + '/regenerate', {}))
285
296
  );
286
297
 
@@ -291,6 +302,7 @@ server.tool(
291
302
  'facelessad_cancel_video',
292
303
  'Cancel a video that is still QUEUED. The queue slot is freed immediately and nothing is charged, because a queued build has not started any work. A build that has already started cannot be cancelled (409 not_cancellable) — check facelessad_get_video first if unsure.',
293
304
  { video_id: z.string().describe('Id of the queued video to cancel') },
305
+ { title: 'Cancel queued video', readOnlyHint: false, destructiveHint: true },
294
306
  async ({ video_id }) => result(await api('POST', '/api/v1/videos/' + encodeURIComponent(video_id) + '/cancel', {}))
295
307
  );
296
308
 
@@ -307,6 +319,7 @@ server.tool(
307
319
  caption_text_color: z.string().optional().describe('Hex colour of the rest of the caption text'),
308
320
  caption_font_size: z.number().int().optional().describe('Caption size 2-40 (default 10)'),
309
321
  },
322
+ { title: 'Change render settings', readOnlyHint: false, destructiveHint: true },
310
323
  async ({ video_id, ...rest }) => {
311
324
  const body = {};
312
325
  for (const [k, v] of Object.entries(rest)) if (v !== undefined) body[k] = v;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@facelessad/mcp",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "FacelessAd as MCP tools \u2014 let your AI assistant create faceless video ads (Claude Desktop, Claude Code, Cursor, Windsurf, OpenClaw).",
5
5
  "license": "MIT",
6
6
  "type": "module",