@facelessad/mcp 1.0.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 +43 -0
- package/index.js +157 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @facelessad/mcp
|
|
2
|
+
|
|
3
|
+
FacelessAd as MCP tools — let your AI assistant create faceless video ads.
|
|
4
|
+
|
|
5
|
+
Works with any MCP-speaking harness: Claude Desktop, Claude Code, Cursor,
|
|
6
|
+
Windsurf, OpenClaw. Seven tools, each a single call to the FacelessAd API;
|
|
7
|
+
the tool/style registry lives on the server, so new tools and styles are
|
|
8
|
+
available the day they ship without updating this package.
|
|
9
|
+
|
|
10
|
+
## Setup (Claude Desktop)
|
|
11
|
+
|
|
12
|
+
`claude_desktop_config.json`:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"facelessad": {
|
|
18
|
+
"command": "npx",
|
|
19
|
+
"args": ["-y", "@facelessad/mcp"],
|
|
20
|
+
"env": { "FACELESSAD_API_KEY": "fa_live_..." }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Create a key at https://facelessad.com/developers. OpenClaw users: add the
|
|
27
|
+
same block under `openclaw mcp` config and verify with
|
|
28
|
+
`openclaw mcp doctor --probe`.
|
|
29
|
+
|
|
30
|
+
## Tools
|
|
31
|
+
|
|
32
|
+
| Tool | What it does |
|
|
33
|
+
|------|--------------|
|
|
34
|
+
| `facelessad_list_tools` | Registry: tools, styles, structures, hooks, durations |
|
|
35
|
+
| `facelessad_create_video` | Create an ad; returns an id immediately |
|
|
36
|
+
| `facelessad_get_video` | Status + download URL when done |
|
|
37
|
+
| `facelessad_list_videos` | Your videos, newest first |
|
|
38
|
+
| `facelessad_estimate` | Upper-bound credit cost without creating |
|
|
39
|
+
| `facelessad_balance` | Plan + credits |
|
|
40
|
+
| `facelessad_voices` | Curated voice pool |
|
|
41
|
+
|
|
42
|
+
Videos build in the background (3–10 min); the assistant polls
|
|
43
|
+
`facelessad_get_video`. You are only charged for steps that succeed.
|
package/index.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @facelessad/mcp — FacelessAd as MCP tools.
|
|
4
|
+
*
|
|
5
|
+
* A thin mirror, not a second brain: every one of the seven tools is a
|
|
6
|
+
* single /api/v1 call, and nothing is decided locally. The style and
|
|
7
|
+
* structure registry lives on the server (facelessad_list_tools), so new
|
|
8
|
+
* tools and styles are available to the assistant the day they ship,
|
|
9
|
+
* without updating this package.
|
|
10
|
+
*
|
|
11
|
+
* Transport is stdio — the form every listed harness speaks (Claude
|
|
12
|
+
* Desktop, Claude Code, Cursor, Windsurf, OpenClaw) and the one that needs
|
|
13
|
+
* no hosted server from us.
|
|
14
|
+
*
|
|
15
|
+
* Auth: FACELESSAD_API_KEY (create one at facelessad.com/developers).
|
|
16
|
+
* FACELESSAD_API_URL overrides the API base (testing).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
20
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
21
|
+
import { z } from 'zod';
|
|
22
|
+
|
|
23
|
+
const API = (process.env.FACELESSAD_API_URL || 'https://facelessad.com').replace(/\/+$/, '');
|
|
24
|
+
const KEY = process.env.FACELESSAD_API_KEY || '';
|
|
25
|
+
|
|
26
|
+
async function api(method, p, body) {
|
|
27
|
+
if (!KEY) {
|
|
28
|
+
return { ok: false, error: 'FACELESSAD_API_KEY is not set. Create a key at ' + API + '/developers and add it to this server\'s env.', code: 'missing_key' };
|
|
29
|
+
}
|
|
30
|
+
let res;
|
|
31
|
+
try {
|
|
32
|
+
res = await fetch(API + p, {
|
|
33
|
+
method,
|
|
34
|
+
headers: {
|
|
35
|
+
'Authorization': 'Bearer ' + KEY,
|
|
36
|
+
...(body ? { 'Content-Type': 'application/json' } : {}),
|
|
37
|
+
},
|
|
38
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
39
|
+
});
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return { ok: false, error: 'Could not reach ' + API + ' — ' + (e.cause?.code || e.message), code: 'network' };
|
|
42
|
+
}
|
|
43
|
+
const data = await res.json().catch(() => null);
|
|
44
|
+
return data ?? { ok: false, error: 'HTTP ' + res.status, code: 'http_' + res.status };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Every tool returns the API's JSON verbatim; errors are surfaced with
|
|
48
|
+
* the API's own wording so the assistant can correct its call. */
|
|
49
|
+
function result(data) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
52
|
+
isError: data?.ok !== true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const server = new McpServer({ name: 'facelessad', version: '1.0.0' });
|
|
57
|
+
|
|
58
|
+
server.tool(
|
|
59
|
+
'facelessad_list_tools',
|
|
60
|
+
'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.',
|
|
61
|
+
{},
|
|
62
|
+
async () => result(await api('GET', '/api/v1/tools'))
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
server.tool(
|
|
66
|
+
'facelessad_balance',
|
|
67
|
+
'The account plan and current credit balance.',
|
|
68
|
+
{},
|
|
69
|
+
async () => result(await api('GET', '/api/v1/balance'))
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
server.tool(
|
|
73
|
+
'facelessad_voices',
|
|
74
|
+
'The curated voice-over pool (id, name, gender, accent, language). Only these ids are accepted as a specific voice.',
|
|
75
|
+
{
|
|
76
|
+
language: z.string().optional().describe('Filter, e.g. "English (US)"'),
|
|
77
|
+
gender: z.string().optional().describe('Filter: "female" | "male"'),
|
|
78
|
+
},
|
|
79
|
+
async ({ language, gender }) => {
|
|
80
|
+
const q = new URLSearchParams();
|
|
81
|
+
if (language) q.set('language', language);
|
|
82
|
+
if (gender) q.set('gender', gender);
|
|
83
|
+
return result(await api('GET', '/api/v1/voices' + (q.size ? '?' + q : '')));
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// The create/estimate input — one schema, used by both. Field names mirror
|
|
88
|
+
// POST /api/v1/videos exactly; nothing is renamed or defaulted here.
|
|
89
|
+
const createShape = {
|
|
90
|
+
tool: z.string().describe('Tool id from facelessad_list_tools (e.g. "motion-graphics", "animated-ad")'),
|
|
91
|
+
materials: z.object({
|
|
92
|
+
landing_page_url: z.string().optional().describe('http(s) URL of the product/landing page'),
|
|
93
|
+
text: z.string().optional().describe('Free-text brief (min 20 chars if no URL)'),
|
|
94
|
+
}).describe('What the ad is about: a URL, free text, or both'),
|
|
95
|
+
duration: z.number().int().optional().describe('Seconds; per-tool bounds from the registry (default 30)'),
|
|
96
|
+
aspect_ratio: z.string().optional().describe('"9:16" | "1:1" | "4:5" | "16:9" (default per tool)'),
|
|
97
|
+
language: z.string().optional().describe('Default "English (US)"'),
|
|
98
|
+
style: z.string().optional().describe('Style id for the tool (registry). Omit to let the server pick'),
|
|
99
|
+
style_hint: z.string().optional().describe('Free-form style wish when no exact style id fits'),
|
|
100
|
+
ad_structure: z.string().optional().describe('Ad structure id for the tool (registry)'),
|
|
101
|
+
hook_formula: z.string().optional().describe('Hook formula id (registry)'),
|
|
102
|
+
video_mode: z.enum(['continuous', 'cuts']).optional(),
|
|
103
|
+
brand_color: z.string().optional().describe('Hex like #4A9BFF'),
|
|
104
|
+
brand_name: z.string().optional(),
|
|
105
|
+
cta: z.string().optional().describe('Call to action text'),
|
|
106
|
+
voice: z.object({
|
|
107
|
+
id: z.string().optional().describe('A voice id from facelessad_voices'),
|
|
108
|
+
gender: z.enum(['female', 'male', 'any']).optional().describe('Narrows the automatic pick'),
|
|
109
|
+
}).optional().describe('Omit for an automatic pick from the curated pool'),
|
|
110
|
+
voice_over: z.boolean().optional().describe('false = silent video (only on tools that support the toggle)'),
|
|
111
|
+
music: z.boolean().optional(),
|
|
112
|
+
sfx: z.boolean().optional(),
|
|
113
|
+
captions: z.boolean().optional(),
|
|
114
|
+
use_brand_kit: z.boolean().optional().describe('Default true'),
|
|
115
|
+
use_winners: z.boolean().optional().describe('Default true'),
|
|
116
|
+
name: z.string().optional().describe('Display name in My Files'),
|
|
117
|
+
product_image_url: z.string().optional().describe('REQUIRED for product-showcase: public https URL of the product photo'),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
server.tool(
|
|
121
|
+
'facelessad_estimate',
|
|
122
|
+
'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.',
|
|
123
|
+
createShape,
|
|
124
|
+
async (input) => result(await api('POST', '/api/v1/estimate', input))
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
server.tool(
|
|
128
|
+
'facelessad_create_video',
|
|
129
|
+
'Create a faceless video ad. 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.',
|
|
130
|
+
createShape,
|
|
131
|
+
async (input) => result(await api('POST', '/api/v1/videos', input))
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
server.tool(
|
|
135
|
+
'facelessad_get_video',
|
|
136
|
+
'Status of one video by id. When finished, includes a download URL valid ~1 hour — call again for a fresh one rather than storing it.',
|
|
137
|
+
{ id: z.union([z.string(), z.number()]).describe('The id returned by facelessad_create_video') },
|
|
138
|
+
async ({ id }) => result(await api('GET', '/api/v1/videos/' + encodeURIComponent(String(id))))
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
server.tool(
|
|
142
|
+
'facelessad_list_videos',
|
|
143
|
+
'The user\'s videos, newest first (id, tool, name, status, createdAt).',
|
|
144
|
+
{
|
|
145
|
+
limit: z.number().int().optional().describe('1–100, default 25'),
|
|
146
|
+
offset: z.number().int().optional(),
|
|
147
|
+
},
|
|
148
|
+
async ({ limit, offset }) => {
|
|
149
|
+
const q = new URLSearchParams();
|
|
150
|
+
if (limit) q.set('limit', String(limit));
|
|
151
|
+
if (offset) q.set('offset', String(offset));
|
|
152
|
+
return result(await api('GET', '/api/v1/videos' + (q.size ? '?' + q : '')));
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const transport = new StdioServerTransport();
|
|
157
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@facelessad/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "FacelessAd as MCP tools — let your AI assistant create faceless video ads (Claude Desktop, Claude Code, Cursor, Windsurf, OpenClaw).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"facelessad-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
19
|
+
"zod": "^4.4.3"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"facelessad",
|
|
23
|
+
"mcp",
|
|
24
|
+
"modelcontextprotocol",
|
|
25
|
+
"video",
|
|
26
|
+
"ads",
|
|
27
|
+
"ai"
|
|
28
|
+
],
|
|
29
|
+
"homepage": "https://facelessad.com/developers"
|
|
30
|
+
}
|