@contenthero/mcp 0.2.2 → 0.2.4
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/index.d.ts +11 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -7
- package/dist/index.js.map +1 -1
- package/dist/models.d.ts +7 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/models.js +16 -0
- package/dist/models.js.map +1 -1
- package/dist/server.d.ts +30 -4
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +215 -120
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The ContentHero MCP
|
|
2
|
+
* The ContentHero MCP tool surface: intent-shaped tools over the @contenthero/sdk
|
|
3
|
+
* kernel.
|
|
3
4
|
*
|
|
4
5
|
* generate_image - smart-wait, image models
|
|
5
6
|
* generate_video - smart-wait, video models
|
|
@@ -14,12 +15,19 @@
|
|
|
14
15
|
* get_generation_status - poll an image/video outputId to its final URLs
|
|
15
16
|
* wait_for_generation - block until one or more outputIds finish (batch)
|
|
16
17
|
* get_balance - credit balance + tier
|
|
18
|
+
* ... plus the content-pipeline, brand-kit-write, inspiration, brand-account,
|
|
19
|
+
* and connected-account tools.
|
|
20
|
+
*
|
|
21
|
+
* `registerTools(server, opts)` registers the whole surface against a backend
|
|
22
|
+
* resolved PER CALL via `opts.getClient(extra)`. The stdio/npm server passes a
|
|
23
|
+
* single env-configured client (identity is in the API key); the hosted OAuth
|
|
24
|
+
* server passes a factory that resolves a per-user client from the validated
|
|
25
|
+
* token's `extra.authInfo`. Tool schemas (incl. the per-tool model enums) are
|
|
26
|
+
* fixed at registration, so the model enums are supplied via `opts.models`.
|
|
17
27
|
*
|
|
18
28
|
* Intent-shaped generate tools rather than one generate_media: each operation
|
|
19
29
|
* (generate / upscale / lip-sync) gets a tool whose schema only carries its own
|
|
20
|
-
* fields, and per-tool modelId enums prevent cross-type model misuse.
|
|
21
|
-
* video share the async smart-wait lifecycle; audio shares almost no parameters
|
|
22
|
-
* and runs synchronously.
|
|
30
|
+
* fields, and per-tool modelId enums prevent cross-type model misuse.
|
|
23
31
|
*/
|
|
24
32
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
25
33
|
import { z } from 'zod';
|
|
@@ -42,11 +50,18 @@ const POST_PLATFORMS = [
|
|
|
42
50
|
* How long the smart-wait tools (generate_image / generate_video / upscale /
|
|
43
51
|
* generate_lip_sync) wait inline before handing back the outputId to poll.
|
|
44
52
|
* Kept under the MCP SDK's default 60s client request timeout, so a slow render
|
|
45
|
-
* returns the clean "still rendering, call get_generation_status" handoff rather
|
|
46
|
-
* tripping the client's timeout.
|
|
47
|
-
* video/lip-sync jobs return the pollable pending result.
|
|
53
|
+
* returns the clean "still rendering, call get_generation_status" handoff rather
|
|
54
|
+
* than tripping the client's timeout.
|
|
48
55
|
*/
|
|
49
56
|
const SMART_WAIT_MS = 50_000;
|
|
57
|
+
/**
|
|
58
|
+
* Tool annotations drive how MCP clients group the surface. readOnlyHint=true
|
|
59
|
+
* tools list under "Read-only"; the rest list under "Interactive". publish is
|
|
60
|
+
* also flagged destructive (it pushes content to public social accounts).
|
|
61
|
+
*/
|
|
62
|
+
const READ = { readOnlyHint: true };
|
|
63
|
+
const WRITE = { readOnlyHint: false };
|
|
64
|
+
const PUBLISH = { readOnlyHint: false, destructiveHint: true };
|
|
50
65
|
/** Drop undefined values so the request payload stays minimal. */
|
|
51
66
|
function compact(obj) {
|
|
52
67
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
@@ -55,16 +70,16 @@ function buildReferences(parts) {
|
|
|
55
70
|
const refs = compact(parts);
|
|
56
71
|
return Object.keys(refs).length > 0 ? refs : undefined;
|
|
57
72
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const models =
|
|
64
|
-
const server = new McpServer({ name: 'contenthero', version: '0.2.2' });
|
|
73
|
+
/**
|
|
74
|
+
* Register the full ContentHero tool surface on `server`. Synchronous: the model
|
|
75
|
+
* enums are supplied pre-resolved, and the backend client is resolved per call.
|
|
76
|
+
*/
|
|
77
|
+
export function registerTools(server, opts) {
|
|
78
|
+
const { getClient, models } = opts;
|
|
65
79
|
// -- generate_image -------------------------------------------------------
|
|
66
80
|
server.registerTool('generate_image', {
|
|
67
81
|
title: 'Generate Image',
|
|
82
|
+
annotations: WRITE,
|
|
68
83
|
description: 'Generate one or more images from a text prompt (optionally image-to-image with reference images). Waits for the result and returns the image URLs.',
|
|
69
84
|
inputSchema: {
|
|
70
85
|
modelId: z.enum(models.image).describe(IMAGE_MODEL_GUIDANCE),
|
|
@@ -86,8 +101,9 @@ export async function buildServer(options = {}) {
|
|
|
86
101
|
.describe('References for image-to-image / editing. Each may be a URL or a previous output id (e.g. "<id>" or "<id>-2") to chain from an earlier generation.'),
|
|
87
102
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
88
103
|
},
|
|
89
|
-
}, async (args) => {
|
|
104
|
+
}, async (args, extra) => {
|
|
90
105
|
try {
|
|
106
|
+
const client = await getClient(extra);
|
|
91
107
|
const request = compact({
|
|
92
108
|
contentType: 'image',
|
|
93
109
|
modelId: args.modelId,
|
|
@@ -97,13 +113,11 @@ export async function buildServer(options = {}) {
|
|
|
97
113
|
numImages: args.numImages,
|
|
98
114
|
seed: args.seed,
|
|
99
115
|
references: buildReferences({ images: args.referenceImages }),
|
|
100
|
-
// Mode (Flux pro/flex, Kontext pro/max) rides the model-agnostic
|
|
101
|
-
// parameters passthrough, which the server reads for variant + pricing.
|
|
102
116
|
parameters: args.mode ? { mode: args.mode } : undefined,
|
|
103
117
|
});
|
|
104
118
|
if (args.getCost)
|
|
105
|
-
return costResult(await
|
|
106
|
-
const gen = await
|
|
119
|
+
return costResult(await client.estimateCost(request));
|
|
120
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
107
121
|
return completedResult(gen);
|
|
108
122
|
}
|
|
109
123
|
catch (err) {
|
|
@@ -115,6 +129,7 @@ export async function buildServer(options = {}) {
|
|
|
115
129
|
// -- generate_board -------------------------------------------------------
|
|
116
130
|
server.registerTool('generate_board', {
|
|
117
131
|
title: 'Generate Reference Board',
|
|
132
|
+
annotations: WRITE,
|
|
118
133
|
description: 'Generate a Reference Board: a dense multi-panel reference sheet (3:4, 4K) built from a source image and/or a written description, used to keep a subject on-model across later generations (feed the board back in as a referenceImage). Provide referenceImages and/or a prompt (at least one is required). Waits up to ~50s; boards render slowly (minutes), so it usually returns an outputId to poll with get_generation_status.',
|
|
119
134
|
inputSchema: {
|
|
120
135
|
boardType: z.enum(BOARD_TYPES).describe(BOARD_TYPE_GUIDANCE),
|
|
@@ -136,8 +151,9 @@ export async function buildServer(options = {}) {
|
|
|
136
151
|
boardName: z.string().optional().describe('Optional name for the board.'),
|
|
137
152
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
138
153
|
},
|
|
139
|
-
}, async (args) => {
|
|
154
|
+
}, async (args, extra) => {
|
|
140
155
|
try {
|
|
156
|
+
const client = await getClient(extra);
|
|
141
157
|
const request = compact({
|
|
142
158
|
boardType: args.boardType,
|
|
143
159
|
prompt: args.prompt,
|
|
@@ -146,8 +162,8 @@ export async function buildServer(options = {}) {
|
|
|
146
162
|
boardName: args.boardName,
|
|
147
163
|
});
|
|
148
164
|
if (args.getCost)
|
|
149
|
-
return costResult(await
|
|
150
|
-
const gen = await
|
|
165
|
+
return costResult(await client.estimateBoardCost(request));
|
|
166
|
+
const gen = await client.generateBoardAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
151
167
|
return completedResult(gen);
|
|
152
168
|
}
|
|
153
169
|
catch (err) {
|
|
@@ -159,6 +175,7 @@ export async function buildServer(options = {}) {
|
|
|
159
175
|
// -- generate_video -------------------------------------------------------
|
|
160
176
|
server.registerTool('generate_video', {
|
|
161
177
|
title: 'Generate Video',
|
|
178
|
+
annotations: WRITE,
|
|
162
179
|
description: 'Generate a video from a text prompt (optionally from a start/end frame or reference images/videos/audio). Waits up to ~50s; if the render is still running it returns an outputId to poll with get_generation_status. Seedance 2.0 has two input modes selected by which references you pass: a startFrame (and optional endFrame) runs start/end-frame mode; referenceImages / referenceVideos / referenceAudio (without a startFrame) run references mode.',
|
|
163
180
|
inputSchema: {
|
|
164
181
|
modelId: z.enum(models.video).describe(VIDEO_MODEL_GUIDANCE),
|
|
@@ -197,11 +214,10 @@ export async function buildServer(options = {}) {
|
|
|
197
214
|
.describe('Kling 3.0 multi-shot mode: an ordered list of shots, each with its own prompt and duration in seconds (1-12 each, total <=15). When provided, the video runs in multi-shot mode; only startFrame attaches as an image (it becomes the first frame of shot 1), all other shots are text-only. Audio is always on in multi-shot.'),
|
|
198
215
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
199
216
|
},
|
|
200
|
-
}, async (args) => {
|
|
217
|
+
}, async (args, extra) => {
|
|
201
218
|
try {
|
|
219
|
+
const client = await getClient(extra);
|
|
202
220
|
const klingMultiShot = Array.isArray(args.shots) && args.shots.length > 0;
|
|
203
|
-
// multiShot is on for Kling's per-shot mode or WAN's boolean toggle; both
|
|
204
|
-
// ride the model-agnostic `parameters` passthrough into genParams.
|
|
205
221
|
const wantMultiShot = klingMultiShot || args.multiShot === true;
|
|
206
222
|
const parameters = {};
|
|
207
223
|
if (wantMultiShot)
|
|
@@ -211,15 +227,9 @@ export async function buildServer(options = {}) {
|
|
|
211
227
|
const request = compact({
|
|
212
228
|
contentType: 'video',
|
|
213
229
|
modelId: args.modelId,
|
|
214
|
-
// Multi-shot puts per-shot prompts in `shots`, but some models (Kling 3.0)
|
|
215
|
-
// still require a top-level prompt to pass validation; the provider drops
|
|
216
|
-
// it in multi-shot, so a synthesized summary is harmless when none is given.
|
|
217
230
|
prompt: klingMultiShot ? args.prompt ?? args.shots.map((s) => s.prompt).join(' ') : args.prompt,
|
|
218
231
|
aspectRatio: args.aspectRatio,
|
|
219
232
|
resolution: args.resolution,
|
|
220
|
-
// Kling's per-shot total drives duration validation + pricing; the provider
|
|
221
|
-
// recomputes the per-shot timeline from `shots`. WAN multi-shot keeps the
|
|
222
|
-
// single duration field.
|
|
223
233
|
duration: klingMultiShot ? args.shots.reduce((sum, s) => sum + s.duration, 0) : args.duration,
|
|
224
234
|
audioEnabled: args.audioEnabled,
|
|
225
235
|
numGenerations: args.numGenerations,
|
|
@@ -235,8 +245,8 @@ export async function buildServer(options = {}) {
|
|
|
235
245
|
}),
|
|
236
246
|
});
|
|
237
247
|
if (args.getCost)
|
|
238
|
-
return costResult(await
|
|
239
|
-
const gen = await
|
|
248
|
+
return costResult(await client.estimateCost(request));
|
|
249
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
240
250
|
return completedResult(gen);
|
|
241
251
|
}
|
|
242
252
|
catch (err) {
|
|
@@ -248,6 +258,7 @@ export async function buildServer(options = {}) {
|
|
|
248
258
|
// -- generate_audio (synchronous) -----------------------------------------
|
|
249
259
|
server.registerTool('generate_audio', {
|
|
250
260
|
title: 'Generate Audio',
|
|
261
|
+
annotations: WRITE,
|
|
251
262
|
description: 'Generate audio with ElevenLabs: speech (TTS), music, or a sound effect. Returns the audio URL directly (synchronous, no polling).',
|
|
252
263
|
inputSchema: {
|
|
253
264
|
modelId: z.enum(models.audio).describe(AUDIO_MODEL_GUIDANCE),
|
|
@@ -264,8 +275,9 @@ export async function buildServer(options = {}) {
|
|
|
264
275
|
.describe('For sfx: how literally to follow the prompt (0 to 1).'),
|
|
265
276
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
266
277
|
},
|
|
267
|
-
}, async (args) => {
|
|
278
|
+
}, async (args, extra) => {
|
|
268
279
|
try {
|
|
280
|
+
const client = await getClient(extra);
|
|
269
281
|
const request = compact({
|
|
270
282
|
contentType: 'audio',
|
|
271
283
|
modelId: args.modelId,
|
|
@@ -277,8 +289,8 @@ export async function buildServer(options = {}) {
|
|
|
277
289
|
promptInfluence: args.promptInfluence,
|
|
278
290
|
});
|
|
279
291
|
if (args.getCost)
|
|
280
|
-
return costResult(await
|
|
281
|
-
const result = await
|
|
292
|
+
return costResult(await client.estimateCost(request));
|
|
293
|
+
const result = await client.generate(request);
|
|
282
294
|
return audioResult(result);
|
|
283
295
|
}
|
|
284
296
|
catch (err) {
|
|
@@ -288,6 +300,7 @@ export async function buildServer(options = {}) {
|
|
|
288
300
|
// -- upscale --------------------------------------------------------------
|
|
289
301
|
server.registerTool('upscale', {
|
|
290
302
|
title: 'Upscale',
|
|
303
|
+
annotations: WRITE,
|
|
291
304
|
description: 'Upscale an existing image or video to a higher resolution. Provide the source media URL and a model-supported factor. Waits for the result; if the job is still running it returns an outputId to poll with get_generation_status.',
|
|
292
305
|
inputSchema: {
|
|
293
306
|
modelId: z.enum(models.upscale).describe(UPSCALE_MODEL_GUIDANCE),
|
|
@@ -299,8 +312,9 @@ export async function buildServer(options = {}) {
|
|
|
299
312
|
.describe('Required for video upscalers: the source video length in seconds (used for pricing).'),
|
|
300
313
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of upscaling (nothing runs, nothing is charged).'),
|
|
301
314
|
},
|
|
302
|
-
}, async (args) => {
|
|
315
|
+
}, async (args, extra) => {
|
|
303
316
|
try {
|
|
317
|
+
const client = await getClient(extra);
|
|
304
318
|
const isVideo = models.upscaleContentType[args.modelId] === 'video';
|
|
305
319
|
const request = compact({
|
|
306
320
|
contentType: isVideo ? 'video' : 'image',
|
|
@@ -310,8 +324,8 @@ export async function buildServer(options = {}) {
|
|
|
310
324
|
references: isVideo ? { videos: [args.sourceUrl] } : { images: [args.sourceUrl] },
|
|
311
325
|
});
|
|
312
326
|
if (args.getCost)
|
|
313
|
-
return costResult(await
|
|
314
|
-
const gen = await
|
|
327
|
+
return costResult(await client.estimateCost(request));
|
|
328
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
315
329
|
return completedResult(gen);
|
|
316
330
|
}
|
|
317
331
|
catch (err) {
|
|
@@ -323,6 +337,7 @@ export async function buildServer(options = {}) {
|
|
|
323
337
|
// -- generate_lip_sync ----------------------------------------------------
|
|
324
338
|
server.registerTool('generate_lip_sync', {
|
|
325
339
|
title: 'Generate Lip Sync',
|
|
340
|
+
annotations: WRITE,
|
|
326
341
|
description: 'Animate a portrait image so the subject speaks. Provide imageUrl (the face) plus a voice source: either audioUrl (an existing speech clip) or script + voiceId (we synthesize the speech). Optional motionPrompt nudges expression/motion. Waits up to ~50s; if still rendering it returns an outputId to poll with get_generation_status.',
|
|
327
342
|
inputSchema: {
|
|
328
343
|
modelId: z.enum(models.lipSync).describe(LIP_SYNC_MODEL_GUIDANCE),
|
|
@@ -348,8 +363,9 @@ export async function buildServer(options = {}) {
|
|
|
348
363
|
.describe('Length of audioUrl in seconds (audio mode only; improves cost accuracy).'),
|
|
349
364
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
350
365
|
},
|
|
351
|
-
}, async (args) => {
|
|
366
|
+
}, async (args, extra) => {
|
|
352
367
|
try {
|
|
368
|
+
const client = await getClient(extra);
|
|
353
369
|
const request = compact({
|
|
354
370
|
contentType: 'video',
|
|
355
371
|
modelId: args.modelId,
|
|
@@ -365,8 +381,8 @@ export async function buildServer(options = {}) {
|
|
|
365
381
|
}),
|
|
366
382
|
});
|
|
367
383
|
if (args.getCost)
|
|
368
|
-
return costResult(await
|
|
369
|
-
const gen = await
|
|
384
|
+
return costResult(await client.estimateCost(request));
|
|
385
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
370
386
|
return completedResult(gen);
|
|
371
387
|
}
|
|
372
388
|
catch (err) {
|
|
@@ -378,6 +394,7 @@ export async function buildServer(options = {}) {
|
|
|
378
394
|
// -- transcribe -----------------------------------------------------------
|
|
379
395
|
server.registerTool('transcribe', {
|
|
380
396
|
title: 'Transcribe Audio',
|
|
397
|
+
annotations: READ,
|
|
381
398
|
description: 'Transcribe an audio URL to text (speech-to-text). Returns the transcript directly (synchronous, free, no polling).',
|
|
382
399
|
inputSchema: {
|
|
383
400
|
audioUrl: z.string().describe('Public URL of the audio file to transcribe.'),
|
|
@@ -387,9 +404,10 @@ export async function buildServer(options = {}) {
|
|
|
387
404
|
.describe('ISO language hint, e.g. "en". Auto-detected when omitted.'),
|
|
388
405
|
diarize: z.boolean().optional().describe('Label each speaker (diarization).'),
|
|
389
406
|
},
|
|
390
|
-
}, async (args) => {
|
|
407
|
+
}, async (args, extra) => {
|
|
391
408
|
try {
|
|
392
|
-
const
|
|
409
|
+
const client = await getClient(extra);
|
|
410
|
+
const t = await client.transcribe({
|
|
393
411
|
audioUrl: args.audioUrl,
|
|
394
412
|
languageCode: args.languageCode,
|
|
395
413
|
diarize: args.diarize,
|
|
@@ -403,10 +421,12 @@ export async function buildServer(options = {}) {
|
|
|
403
421
|
// -- list_avatars ---------------------------------------------------------
|
|
404
422
|
server.registerTool('list_avatars', {
|
|
405
423
|
title: 'List Avatars',
|
|
424
|
+
annotations: READ,
|
|
406
425
|
description: "List the account's avatars. Each avatar has an imageUrl (its base look) and a defaultVoiceId, which feed generate_lip_sync. Call get_avatar for full detail and the avatar's looks.",
|
|
407
|
-
}, async () => {
|
|
426
|
+
}, async (extra) => {
|
|
408
427
|
try {
|
|
409
|
-
|
|
428
|
+
const client = await getClient(extra);
|
|
429
|
+
return avatarListResult(await client.listAvatars());
|
|
410
430
|
}
|
|
411
431
|
catch (err) {
|
|
412
432
|
return errorResult(err);
|
|
@@ -415,13 +435,15 @@ export async function buildServer(options = {}) {
|
|
|
415
435
|
// -- get_avatar -----------------------------------------------------------
|
|
416
436
|
server.registerTool('get_avatar', {
|
|
417
437
|
title: 'Get Avatar',
|
|
438
|
+
annotations: READ,
|
|
418
439
|
description: 'Get one avatar by id: its base image (use as generate_lip_sync imageUrl), default voice, traits, and its looks (outfit variations).',
|
|
419
440
|
inputSchema: {
|
|
420
441
|
avatarId: z.string().describe('The avatar id from list_avatars.'),
|
|
421
442
|
},
|
|
422
|
-
}, async (args) => {
|
|
443
|
+
}, async (args, extra) => {
|
|
423
444
|
try {
|
|
424
|
-
|
|
445
|
+
const client = await getClient(extra);
|
|
446
|
+
return avatarResult(await client.getAvatar(args.avatarId));
|
|
425
447
|
}
|
|
426
448
|
catch (err) {
|
|
427
449
|
return errorResult(err);
|
|
@@ -430,10 +452,12 @@ export async function buildServer(options = {}) {
|
|
|
430
452
|
// -- list_voices ----------------------------------------------------------
|
|
431
453
|
server.registerTool('list_voices', {
|
|
432
454
|
title: 'List Voices',
|
|
455
|
+
annotations: READ,
|
|
433
456
|
description: "List the account's saved voices (favorites first). Each has a voiceId for generate_lip_sync / generate_audio (TTS). Call get_voice for full detail.",
|
|
434
|
-
}, async () => {
|
|
457
|
+
}, async (extra) => {
|
|
435
458
|
try {
|
|
436
|
-
|
|
459
|
+
const client = await getClient(extra);
|
|
460
|
+
return voiceListResult(await client.listVoices());
|
|
437
461
|
}
|
|
438
462
|
catch (err) {
|
|
439
463
|
return errorResult(err);
|
|
@@ -442,13 +466,15 @@ export async function buildServer(options = {}) {
|
|
|
442
466
|
// -- get_voice ------------------------------------------------------------
|
|
443
467
|
server.registerTool('get_voice', {
|
|
444
468
|
title: 'Get Voice',
|
|
469
|
+
annotations: READ,
|
|
445
470
|
description: 'Get one voice by its voiceId: provider, traits (accent/language/gender/age), description, and a preview URL.',
|
|
446
471
|
inputSchema: {
|
|
447
472
|
voiceId: z.string().describe('The voice id from list_voices.'),
|
|
448
473
|
},
|
|
449
|
-
}, async (args) => {
|
|
474
|
+
}, async (args, extra) => {
|
|
450
475
|
try {
|
|
451
|
-
|
|
476
|
+
const client = await getClient(extra);
|
|
477
|
+
return voiceResult(await client.getVoice(args.voiceId));
|
|
452
478
|
}
|
|
453
479
|
catch (err) {
|
|
454
480
|
return errorResult(err);
|
|
@@ -457,10 +483,12 @@ export async function buildServer(options = {}) {
|
|
|
457
483
|
// -- list_brand_kits ------------------------------------------------------
|
|
458
484
|
server.registerTool('list_brand_kits', {
|
|
459
485
|
title: 'List Brand Kits',
|
|
486
|
+
annotations: READ,
|
|
460
487
|
description: "List the account's brand kits (default first). Call get_brand_kit for one kit's full brand context (voice, visual identity, audience, sections, accounts, knowledge) to write on-brand content.",
|
|
461
|
-
}, async () => {
|
|
488
|
+
}, async (extra) => {
|
|
462
489
|
try {
|
|
463
|
-
|
|
490
|
+
const client = await getClient(extra);
|
|
491
|
+
return brandKitListResult(await client.listBrandKits());
|
|
464
492
|
}
|
|
465
493
|
catch (err) {
|
|
466
494
|
return errorResult(err);
|
|
@@ -469,13 +497,15 @@ export async function buildServer(options = {}) {
|
|
|
469
497
|
// -- get_brand_kit --------------------------------------------------------
|
|
470
498
|
server.registerTool('get_brand_kit', {
|
|
471
499
|
title: 'Get Brand Kit',
|
|
500
|
+
annotations: READ,
|
|
472
501
|
description: 'Get one brand kit in full: business overview, positioning, audience, voice profile, visual identity (logos/colors/typography), curated sections, linked brand + inspiration accounts, and a knowledge-base summary. Use it to ground on-brand generation.',
|
|
473
502
|
inputSchema: {
|
|
474
503
|
brandKitId: z.string().describe('The brand kit id from list_brand_kits.'),
|
|
475
504
|
},
|
|
476
|
-
}, async (args) => {
|
|
505
|
+
}, async (args, extra) => {
|
|
477
506
|
try {
|
|
478
|
-
|
|
507
|
+
const client = await getClient(extra);
|
|
508
|
+
return brandKitResult(await client.getBrandKit(args.brandKitId));
|
|
479
509
|
}
|
|
480
510
|
catch (err) {
|
|
481
511
|
return errorResult(err);
|
|
@@ -484,6 +514,7 @@ export async function buildServer(options = {}) {
|
|
|
484
514
|
// -- update_brand_kit -----------------------------------------------------
|
|
485
515
|
server.registerTool('update_brand_kit', {
|
|
486
516
|
title: 'Update Brand Kit',
|
|
517
|
+
annotations: WRITE,
|
|
487
518
|
description: "Update a brand kit's identity fields: business name, positioning, audience, voice profile, visual style, content strategy, etc. Only the fields you pass change. Requires a key with the brandkit:write scope. Get the current kit first with get_brand_kit.",
|
|
488
519
|
inputSchema: {
|
|
489
520
|
brandKitId: z.string().describe('The brand kit id.'),
|
|
@@ -499,10 +530,11 @@ export async function buildServer(options = {}) {
|
|
|
499
530
|
designPrinciples: z.array(z.string()).optional(),
|
|
500
531
|
contentStrategy: z.record(z.string(), z.unknown()).optional().describe('Content strategy object (free-form).'),
|
|
501
532
|
},
|
|
502
|
-
}, async (args) => {
|
|
533
|
+
}, async (args, extra) => {
|
|
503
534
|
try {
|
|
535
|
+
const client = await getClient(extra);
|
|
504
536
|
const { brandKitId, ...input } = args;
|
|
505
|
-
return brandKitResult(await
|
|
537
|
+
return brandKitResult(await client.updateBrandKit(brandKitId, input));
|
|
506
538
|
}
|
|
507
539
|
catch (err) {
|
|
508
540
|
return errorResult(err);
|
|
@@ -511,13 +543,15 @@ export async function buildServer(options = {}) {
|
|
|
511
543
|
// -- archive_brand_kit ----------------------------------------------------
|
|
512
544
|
server.registerTool('archive_brand_kit', {
|
|
513
545
|
title: 'Archive Brand Kit',
|
|
546
|
+
annotations: WRITE,
|
|
514
547
|
description: 'Archive a brand kit (reversible; ContentHero never hard-deletes). Requires the brandkit:write scope.',
|
|
515
548
|
inputSchema: {
|
|
516
549
|
brandKitId: z.string().describe('The brand kit id to archive.'),
|
|
517
550
|
},
|
|
518
|
-
}, async (args) => {
|
|
551
|
+
}, async (args, extra) => {
|
|
519
552
|
try {
|
|
520
|
-
|
|
553
|
+
const client = await getClient(extra);
|
|
554
|
+
return brandKitArchivedResult(await client.archiveBrandKit(args.brandKitId));
|
|
521
555
|
}
|
|
522
556
|
catch (err) {
|
|
523
557
|
return errorResult(err);
|
|
@@ -526,6 +560,7 @@ export async function buildServer(options = {}) {
|
|
|
526
560
|
// -- add_brand_kit_section ------------------------------------------------
|
|
527
561
|
server.registerTool('add_brand_kit_section', {
|
|
528
562
|
title: 'Add Brand Kit Section',
|
|
563
|
+
annotations: WRITE,
|
|
529
564
|
description: "Add a curated section to a brand kit (a tab + name + a list of fields). Fields are objects like { key, label, type, value }. Requires the brandkit:write scope.",
|
|
530
565
|
inputSchema: {
|
|
531
566
|
brandKitId: z.string().describe('The brand kit id.'),
|
|
@@ -534,9 +569,10 @@ export async function buildServer(options = {}) {
|
|
|
534
569
|
sortOrder: z.number().int().optional().describe('Order within the tab (default 99 = end).'),
|
|
535
570
|
fields: z.array(z.record(z.string(), z.unknown())).optional().describe('Field objects: { key, label, type, value }.'),
|
|
536
571
|
},
|
|
537
|
-
}, async (args) => {
|
|
572
|
+
}, async (args, extra) => {
|
|
538
573
|
try {
|
|
539
|
-
|
|
574
|
+
const client = await getClient(extra);
|
|
575
|
+
return brandKitSectionResult(await client.addBrandKitSection(args.brandKitId, {
|
|
540
576
|
tab: args.tab,
|
|
541
577
|
sectionName: args.sectionName,
|
|
542
578
|
sortOrder: args.sortOrder,
|
|
@@ -550,6 +586,7 @@ export async function buildServer(options = {}) {
|
|
|
550
586
|
// -- update_brand_kit_section ---------------------------------------------
|
|
551
587
|
server.registerTool('update_brand_kit_section', {
|
|
552
588
|
title: 'Update Brand Kit Section',
|
|
589
|
+
annotations: WRITE,
|
|
553
590
|
description: "Update a brand-kit section's name, order, or fields. Pass the full fields array to replace it. Requires the brandkit:write scope.",
|
|
554
591
|
inputSchema: {
|
|
555
592
|
brandKitId: z.string().describe('The brand kit id.'),
|
|
@@ -558,9 +595,10 @@ export async function buildServer(options = {}) {
|
|
|
558
595
|
sortOrder: z.number().int().optional(),
|
|
559
596
|
fields: z.array(z.record(z.string(), z.unknown())).optional().describe('Replacement field objects.'),
|
|
560
597
|
},
|
|
561
|
-
}, async (args) => {
|
|
598
|
+
}, async (args, extra) => {
|
|
562
599
|
try {
|
|
563
|
-
|
|
600
|
+
const client = await getClient(extra);
|
|
601
|
+
return brandKitSectionResult(await client.updateBrandKitSection(args.brandKitId, args.sectionId, {
|
|
564
602
|
sectionName: args.sectionName,
|
|
565
603
|
sortOrder: args.sortOrder,
|
|
566
604
|
fields: args.fields,
|
|
@@ -573,14 +611,16 @@ export async function buildServer(options = {}) {
|
|
|
573
611
|
// -- archive_brand_kit_section --------------------------------------------
|
|
574
612
|
server.registerTool('archive_brand_kit_section', {
|
|
575
613
|
title: 'Archive Brand Kit Section',
|
|
614
|
+
annotations: WRITE,
|
|
576
615
|
description: 'Archive a brand-kit section (soft delete, reversible). Use it to remove a section an agent added. Requires the brandkit:write scope.',
|
|
577
616
|
inputSchema: {
|
|
578
617
|
brandKitId: z.string().describe('The brand kit id.'),
|
|
579
618
|
sectionId: z.string().describe('The section id to archive.'),
|
|
580
619
|
},
|
|
581
|
-
}, async (args) => {
|
|
620
|
+
}, async (args, extra) => {
|
|
582
621
|
try {
|
|
583
|
-
|
|
622
|
+
const client = await getClient(extra);
|
|
623
|
+
return brandKitSectionResult(await client.archiveBrandKitSection(args.brandKitId, args.sectionId), 'Archived section');
|
|
584
624
|
}
|
|
585
625
|
catch (err) {
|
|
586
626
|
return errorResult(err);
|
|
@@ -589,6 +629,7 @@ export async function buildServer(options = {}) {
|
|
|
589
629
|
// -- list_media -----------------------------------------------------------
|
|
590
630
|
server.registerTool('list_media', {
|
|
591
631
|
title: 'List Media',
|
|
632
|
+
annotations: READ,
|
|
592
633
|
description: "List the account's recent studio outputs (generated images, videos, audio, transcripts), newest first. Reference boards are included too; filter with kind='board' (or 'creation'/'look'). Each item has an id and its variation URLs. Call get_media for one output's full detail and individual variations.",
|
|
593
634
|
inputSchema: {
|
|
594
635
|
contentType: z
|
|
@@ -602,9 +643,10 @@ export async function buildServer(options = {}) {
|
|
|
602
643
|
status: z.string().optional().describe("Status filter; defaults to 'completed'."),
|
|
603
644
|
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 20).'),
|
|
604
645
|
},
|
|
605
|
-
}, async (args) => {
|
|
646
|
+
}, async (args, extra) => {
|
|
606
647
|
try {
|
|
607
|
-
|
|
648
|
+
const client = await getClient(extra);
|
|
649
|
+
return mediaListResult(await client.listMedia({
|
|
608
650
|
contentType: args.contentType,
|
|
609
651
|
kind: args.kind,
|
|
610
652
|
status: args.status,
|
|
@@ -618,30 +660,34 @@ export async function buildServer(options = {}) {
|
|
|
618
660
|
// -- get_media ------------------------------------------------------------
|
|
619
661
|
server.registerTool('get_media', {
|
|
620
662
|
title: 'Get Media',
|
|
663
|
+
annotations: READ,
|
|
621
664
|
description: 'Get one studio output by id: its variations (URLs), prompt/script, model, and specs. The id may be the full output id, its first 8 characters, or either with a "-N" suffix to address one variation (1-based, e.g. "...abcd-2"). A whole-output id returns all variations.',
|
|
622
665
|
inputSchema: {
|
|
623
666
|
id: z
|
|
624
667
|
.string()
|
|
625
668
|
.describe('The output id (full or first-8), optionally with a "-N" variation suffix.'),
|
|
626
669
|
},
|
|
627
|
-
}, async (args) => {
|
|
670
|
+
}, async (args, extra) => {
|
|
628
671
|
try {
|
|
629
|
-
|
|
672
|
+
const client = await getClient(extra);
|
|
673
|
+
return mediaResult(await client.getMedia(args.id));
|
|
630
674
|
}
|
|
631
675
|
catch (err) {
|
|
632
676
|
return errorResult(err);
|
|
633
677
|
}
|
|
634
678
|
});
|
|
635
|
-
// -- get_generation_status
|
|
679
|
+
// -- get_generation_status ------------------------------------------------
|
|
636
680
|
server.registerTool('get_generation_status', {
|
|
637
681
|
title: 'Get Generation Status',
|
|
682
|
+
annotations: READ,
|
|
638
683
|
description: 'Get the current status of an image or video generation by its outputId (returned by generate_image / generate_video when a render is still in progress). Returns the final URLs once complete, otherwise the current status plus a poll_after_seconds hint. For a blocking wait on one or more outputIds, use wait_for_generation.',
|
|
639
684
|
inputSchema: {
|
|
640
685
|
outputId: z.string().describe('The outputId from generate_image or generate_video.'),
|
|
641
686
|
},
|
|
642
|
-
}, async (args) => {
|
|
687
|
+
}, async (args, extra) => {
|
|
643
688
|
try {
|
|
644
|
-
const
|
|
689
|
+
const client = await getClient(extra);
|
|
690
|
+
const gen = await client.getGeneration(args.outputId);
|
|
645
691
|
return generationStatusResult(gen);
|
|
646
692
|
}
|
|
647
693
|
catch (err) {
|
|
@@ -651,6 +697,7 @@ export async function buildServer(options = {}) {
|
|
|
651
697
|
// -- wait_for_generation --------------------------------------------------
|
|
652
698
|
server.registerTool('wait_for_generation', {
|
|
653
699
|
title: 'Wait For Generation',
|
|
700
|
+
annotations: READ,
|
|
654
701
|
description: 'Wait for one or more in-progress generations (outputIds from generate_image / generate_video / upscale / generate_lip_sync / generate_board) to finish, and return their final URLs. Blocks up to ~50s per call; if a render is still running it returns the current status with a poll_after_seconds hint to call again. Pass wait=false for an instant status snapshot instead of blocking.',
|
|
655
702
|
inputSchema: {
|
|
656
703
|
outputIds: z
|
|
@@ -663,19 +710,19 @@ export async function buildServer(options = {}) {
|
|
|
663
710
|
.optional()
|
|
664
711
|
.describe('Block until terminal (up to ~50s) when true (the default). false = an instant snapshot, no blocking.'),
|
|
665
712
|
},
|
|
666
|
-
}, async (args) => {
|
|
713
|
+
}, async (args, extra) => {
|
|
667
714
|
try {
|
|
715
|
+
const client = await getClient(extra);
|
|
668
716
|
const blocking = args.wait !== false;
|
|
669
717
|
const gens = await Promise.all(args.outputIds.map(async (id) => {
|
|
670
718
|
if (!blocking)
|
|
671
|
-
return
|
|
719
|
+
return client.getGeneration(id);
|
|
672
720
|
try {
|
|
673
|
-
return await
|
|
721
|
+
return await client.waitForGeneration(id, { timeoutMs: SMART_WAIT_MS });
|
|
674
722
|
}
|
|
675
723
|
catch (err) {
|
|
676
|
-
// Still rendering past the smart-wait window: hand back the current snapshot.
|
|
677
724
|
if (err instanceof GenerationTimeoutError)
|
|
678
|
-
return
|
|
725
|
+
return client.getGeneration(id);
|
|
679
726
|
throw err;
|
|
680
727
|
}
|
|
681
728
|
}));
|
|
@@ -688,6 +735,7 @@ export async function buildServer(options = {}) {
|
|
|
688
735
|
// -- list_posts -----------------------------------------------------------
|
|
689
736
|
server.registerTool('list_posts', {
|
|
690
737
|
title: 'List Posts',
|
|
738
|
+
annotations: READ,
|
|
691
739
|
description: "List the account's content-pipeline posts (newest-updated first). Filter by status, platform, pipeline_stage (id/slug/name), folder, favorite, or a title search. Call get_post for one post's full detail (destinations + assets).",
|
|
692
740
|
inputSchema: {
|
|
693
741
|
status: z.enum(['draft', 'active', 'completed', 'archived']).optional().describe('Filter by lifecycle status.'),
|
|
@@ -697,9 +745,10 @@ export async function buildServer(options = {}) {
|
|
|
697
745
|
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 50).'),
|
|
698
746
|
offset: z.number().int().min(0).optional().describe('Pagination offset.'),
|
|
699
747
|
},
|
|
700
|
-
}, async (args) => {
|
|
748
|
+
}, async (args, extra) => {
|
|
701
749
|
try {
|
|
702
|
-
|
|
750
|
+
const client = await getClient(extra);
|
|
751
|
+
return postListResult(await client.listPosts({
|
|
703
752
|
status: args.status,
|
|
704
753
|
platform: args.platform,
|
|
705
754
|
pipelineStage: args.pipelineStage,
|
|
@@ -715,13 +764,15 @@ export async function buildServer(options = {}) {
|
|
|
715
764
|
// -- get_post -------------------------------------------------------------
|
|
716
765
|
server.registerTool('get_post', {
|
|
717
766
|
title: 'Get Post',
|
|
767
|
+
annotations: READ,
|
|
718
768
|
description: "Get one post in full: its fields (title, description, script, notes, status, stage, schedule), plus its publish destinations and attached assets.",
|
|
719
769
|
inputSchema: {
|
|
720
770
|
postId: z.string().describe('The post id from list_posts.'),
|
|
721
771
|
},
|
|
722
|
-
}, async (args) => {
|
|
772
|
+
}, async (args, extra) => {
|
|
723
773
|
try {
|
|
724
|
-
|
|
774
|
+
const client = await getClient(extra);
|
|
775
|
+
return postResult(await client.getPost(args.postId));
|
|
725
776
|
}
|
|
726
777
|
catch (err) {
|
|
727
778
|
return errorResult(err);
|
|
@@ -730,10 +781,12 @@ export async function buildServer(options = {}) {
|
|
|
730
781
|
// -- list_pipeline_stages -------------------------------------------------
|
|
731
782
|
server.registerTool('list_pipeline_stages', {
|
|
732
783
|
title: 'List Pipeline Stages',
|
|
784
|
+
annotations: READ,
|
|
733
785
|
description: "List the account's pipeline stages, in order. Stages are user-customizable (renamed, reordered, added, removed), so call this to discover the real stages before placing a post; pass a stage's id (most stable), slug, or name to create_post / update_post.",
|
|
734
|
-
}, async () => {
|
|
786
|
+
}, async (extra) => {
|
|
735
787
|
try {
|
|
736
|
-
|
|
788
|
+
const client = await getClient(extra);
|
|
789
|
+
return pipelineStageListResult(await client.listPipelineStages());
|
|
737
790
|
}
|
|
738
791
|
catch (err) {
|
|
739
792
|
return errorResult(err);
|
|
@@ -742,6 +795,7 @@ export async function buildServer(options = {}) {
|
|
|
742
795
|
// -- create_post ----------------------------------------------------------
|
|
743
796
|
server.registerTool('create_post', {
|
|
744
797
|
title: 'Create Post',
|
|
798
|
+
annotations: WRITE,
|
|
745
799
|
description: "Create a content-pipeline post. The post is the container; attach platforms with add_post_destination and media with add_post_asset, then schedule_post or publish_post. `stage` accepts a stage id/slug/name (defaults to the first stage). Requires a key with the pipeline:write scope.",
|
|
746
800
|
inputSchema: {
|
|
747
801
|
title: z.string().describe('Post title (required).'),
|
|
@@ -749,9 +803,10 @@ export async function buildServer(options = {}) {
|
|
|
749
803
|
description: z.string().optional().describe('Optional description / caption draft.'),
|
|
750
804
|
stage: z.string().optional().describe('Pipeline stage id, slug, or name. Defaults to the first stage.'),
|
|
751
805
|
},
|
|
752
|
-
}, async (args) => {
|
|
806
|
+
}, async (args, extra) => {
|
|
753
807
|
try {
|
|
754
|
-
|
|
808
|
+
const client = await getClient(extra);
|
|
809
|
+
return postSummaryResult(await client.createPost({
|
|
755
810
|
title: args.title,
|
|
756
811
|
platform: args.platform,
|
|
757
812
|
description: args.description,
|
|
@@ -765,6 +820,7 @@ export async function buildServer(options = {}) {
|
|
|
765
820
|
// -- update_post ----------------------------------------------------------
|
|
766
821
|
server.registerTool('update_post', {
|
|
767
822
|
title: 'Update Post',
|
|
823
|
+
annotations: WRITE,
|
|
768
824
|
description: 'Update a post\'s fields: title, description, script, notes, status, platform, or pipeline stage (move it through the pipeline by passing `stage`). Requires the pipeline:write scope.',
|
|
769
825
|
inputSchema: {
|
|
770
826
|
postId: z.string().describe('The post id.'),
|
|
@@ -776,10 +832,11 @@ export async function buildServer(options = {}) {
|
|
|
776
832
|
script: z.string().optional(),
|
|
777
833
|
notes: z.string().optional(),
|
|
778
834
|
},
|
|
779
|
-
}, async (args) => {
|
|
835
|
+
}, async (args, extra) => {
|
|
780
836
|
try {
|
|
837
|
+
const client = await getClient(extra);
|
|
781
838
|
const { postId, ...input } = args;
|
|
782
|
-
return postSummaryResult(await
|
|
839
|
+
return postSummaryResult(await client.updatePost(postId, input), 'Updated');
|
|
783
840
|
}
|
|
784
841
|
catch (err) {
|
|
785
842
|
return errorResult(err);
|
|
@@ -788,13 +845,15 @@ export async function buildServer(options = {}) {
|
|
|
788
845
|
// -- archive_post ---------------------------------------------------------
|
|
789
846
|
server.registerTool('archive_post', {
|
|
790
847
|
title: 'Archive Post',
|
|
848
|
+
annotations: WRITE,
|
|
791
849
|
description: 'Archive a post (sets status to archived; reversible by updating the status back). ContentHero never hard-deletes. Requires the pipeline:write scope.',
|
|
792
850
|
inputSchema: {
|
|
793
851
|
postId: z.string().describe('The post id to archive.'),
|
|
794
852
|
},
|
|
795
|
-
}, async (args) => {
|
|
853
|
+
}, async (args, extra) => {
|
|
796
854
|
try {
|
|
797
|
-
|
|
855
|
+
const client = await getClient(extra);
|
|
856
|
+
return postSummaryResult(await client.archivePost(args.postId), 'Archived');
|
|
798
857
|
}
|
|
799
858
|
catch (err) {
|
|
800
859
|
return errorResult(err);
|
|
@@ -803,6 +862,7 @@ export async function buildServer(options = {}) {
|
|
|
803
862
|
// -- add_post_destination -------------------------------------------------
|
|
804
863
|
server.registerTool('add_post_destination', {
|
|
805
864
|
title: 'Add Post Destination',
|
|
865
|
+
annotations: WRITE,
|
|
806
866
|
description: "Attach a publish destination (one platform) to a post, or replace the existing one for that platform. Set connectedAccountId (from list_connected_accounts, web-only today) to make it publishable. Requires the pipeline:write scope.",
|
|
807
867
|
inputSchema: {
|
|
808
868
|
postId: z.string().describe('The post id.'),
|
|
@@ -811,9 +871,10 @@ export async function buildServer(options = {}) {
|
|
|
811
871
|
connectedAccountId: z.string().optional().describe('The connected account to publish through.'),
|
|
812
872
|
scheduledAt: z.string().optional().describe('ISO-8601 scheduled time for this destination.'),
|
|
813
873
|
},
|
|
814
|
-
}, async (args) => {
|
|
874
|
+
}, async (args, extra) => {
|
|
815
875
|
try {
|
|
816
|
-
|
|
876
|
+
const client = await getClient(extra);
|
|
877
|
+
return destinationResult(await client.addPostDestination(args.postId, {
|
|
817
878
|
platform: args.platform,
|
|
818
879
|
format: args.format,
|
|
819
880
|
connectedAccountId: args.connectedAccountId,
|
|
@@ -827,6 +888,7 @@ export async function buildServer(options = {}) {
|
|
|
827
888
|
// -- update_post_destination ----------------------------------------------
|
|
828
889
|
server.registerTool('update_post_destination', {
|
|
829
890
|
title: 'Update Post Destination',
|
|
891
|
+
annotations: WRITE,
|
|
830
892
|
description: 'Update one of a post\'s destinations (format, connected account, scheduled time, or status). Requires the pipeline:write scope.',
|
|
831
893
|
inputSchema: {
|
|
832
894
|
postId: z.string().describe('The post id.'),
|
|
@@ -836,9 +898,10 @@ export async function buildServer(options = {}) {
|
|
|
836
898
|
scheduledAt: z.string().optional().describe('ISO-8601 scheduled time, or empty to clear.'),
|
|
837
899
|
status: z.string().optional(),
|
|
838
900
|
},
|
|
839
|
-
}, async (args) => {
|
|
901
|
+
}, async (args, extra) => {
|
|
840
902
|
try {
|
|
841
|
-
|
|
903
|
+
const client = await getClient(extra);
|
|
904
|
+
return destinationResult(await client.updatePostDestination(args.postId, args.destinationId, {
|
|
842
905
|
format: args.format,
|
|
843
906
|
connectedAccountId: args.connectedAccountId,
|
|
844
907
|
scheduledAt: args.scheduledAt,
|
|
@@ -852,6 +915,7 @@ export async function buildServer(options = {}) {
|
|
|
852
915
|
// -- add_post_asset -------------------------------------------------------
|
|
853
916
|
server.registerTool('add_post_asset', {
|
|
854
917
|
title: 'Add Post Asset',
|
|
918
|
+
annotations: WRITE,
|
|
855
919
|
description: "Attach an asset to a post by URL (e.g. a generated image/video URL from get_media, or any public link). Sets the post cover from the first image. Requires the assets:write scope.",
|
|
856
920
|
inputSchema: {
|
|
857
921
|
postId: z.string().describe('The post id.'),
|
|
@@ -859,9 +923,10 @@ export async function buildServer(options = {}) {
|
|
|
859
923
|
assetUrl: z.string().describe('Public URL of the asset.'),
|
|
860
924
|
displayName: z.string().optional().describe('Optional display name.'),
|
|
861
925
|
},
|
|
862
|
-
}, async (args) => {
|
|
926
|
+
}, async (args, extra) => {
|
|
863
927
|
try {
|
|
864
|
-
|
|
928
|
+
const client = await getClient(extra);
|
|
929
|
+
return assetResult(await client.addPostAsset(args.postId, {
|
|
865
930
|
assetType: args.assetType,
|
|
866
931
|
assetUrl: args.assetUrl,
|
|
867
932
|
displayName: args.displayName,
|
|
@@ -874,6 +939,7 @@ export async function buildServer(options = {}) {
|
|
|
874
939
|
// -- schedule_post --------------------------------------------------------
|
|
875
940
|
server.registerTool('schedule_post', {
|
|
876
941
|
title: 'Schedule Post',
|
|
942
|
+
annotations: WRITE,
|
|
877
943
|
description: 'Queue a post for future publishing: set the scheduled time on the post and all its destinations (pass scheduledAt=null to clear). This only queues; use publish_post to publish now. Requires the pipeline:write scope.',
|
|
878
944
|
inputSchema: {
|
|
879
945
|
postId: z.string().describe('The post id.'),
|
|
@@ -882,9 +948,10 @@ export async function buildServer(options = {}) {
|
|
|
882
948
|
.nullable()
|
|
883
949
|
.describe('ISO-8601 timestamp to schedule, or null to clear the schedule.'),
|
|
884
950
|
},
|
|
885
|
-
}, async (args) => {
|
|
951
|
+
}, async (args, extra) => {
|
|
886
952
|
try {
|
|
887
|
-
|
|
953
|
+
const client = await getClient(extra);
|
|
954
|
+
return postSummaryResult(await client.schedulePost(args.postId, args.scheduledAt), 'Scheduled');
|
|
888
955
|
}
|
|
889
956
|
catch (err) {
|
|
890
957
|
return errorResult(err);
|
|
@@ -893,14 +960,16 @@ export async function buildServer(options = {}) {
|
|
|
893
960
|
// -- publish_post ---------------------------------------------------------
|
|
894
961
|
server.registerTool('publish_post', {
|
|
895
962
|
title: 'Publish Post',
|
|
963
|
+
annotations: PUBLISH,
|
|
896
964
|
description: "Publish a post NOW to its destinations (a single platform when `platform` is given, otherwise all). Each destination must have a connected account. Requires a key with the publish:write scope; holding that scope is the account owner's consent to autonomous publishing. Returns per-destination results.",
|
|
897
965
|
inputSchema: {
|
|
898
966
|
postId: z.string().describe('The post id to publish.'),
|
|
899
967
|
platform: z.enum(POST_PLATFORMS).optional().describe('Publish only this platform. Omit to publish all destinations.'),
|
|
900
968
|
},
|
|
901
|
-
}, async (args) => {
|
|
969
|
+
}, async (args, extra) => {
|
|
902
970
|
try {
|
|
903
|
-
|
|
971
|
+
const client = await getClient(extra);
|
|
972
|
+
return publishResult(await client.publishPost(args.postId, { platform: args.platform }));
|
|
904
973
|
}
|
|
905
974
|
catch (err) {
|
|
906
975
|
return errorResult(err);
|
|
@@ -909,10 +978,12 @@ export async function buildServer(options = {}) {
|
|
|
909
978
|
// -- list_inspiration_accounts --------------------------------------------
|
|
910
979
|
server.registerTool('list_inspiration_accounts', {
|
|
911
980
|
title: 'List Inspiration Accounts',
|
|
981
|
+
annotations: READ,
|
|
912
982
|
description: "List the creators/competitors the account tracks for inspiration. Use these as grounding for research; call list_outliers for their top content or get_inspiration_account for one account's detail.",
|
|
913
|
-
}, async () => {
|
|
983
|
+
}, async (extra) => {
|
|
914
984
|
try {
|
|
915
|
-
|
|
985
|
+
const client = await getClient(extra);
|
|
986
|
+
return trackedAccountListResult(await client.listInspirationAccounts(), 'inspiration account(s)');
|
|
916
987
|
}
|
|
917
988
|
catch (err) {
|
|
918
989
|
return errorResult(err);
|
|
@@ -921,14 +992,15 @@ export async function buildServer(options = {}) {
|
|
|
921
992
|
// -- get_inspiration_account ----------------------------------------------
|
|
922
993
|
server.registerTool('get_inspiration_account', {
|
|
923
994
|
title: 'Get Inspiration Account',
|
|
995
|
+
annotations: READ,
|
|
924
996
|
description: "Get one tracked inspiration account with its content count and a few top outliers (by score). Use it to study a specific creator.",
|
|
925
997
|
inputSchema: {
|
|
926
998
|
accountId: z.string().describe('The account id from list_inspiration_accounts.'),
|
|
927
999
|
},
|
|
928
|
-
}, async (args) => {
|
|
1000
|
+
}, async (args, extra) => {
|
|
929
1001
|
try {
|
|
930
|
-
const
|
|
931
|
-
return inspirationAccountResult(
|
|
1002
|
+
const client = await getClient(extra);
|
|
1003
|
+
return inspirationAccountResult(await client.getInspirationAccount(args.accountId));
|
|
932
1004
|
}
|
|
933
1005
|
catch (err) {
|
|
934
1006
|
return errorResult(err);
|
|
@@ -937,6 +1009,7 @@ export async function buildServer(options = {}) {
|
|
|
937
1009
|
// -- list_outliers --------------------------------------------------------
|
|
938
1010
|
server.registerTool('list_outliers', {
|
|
939
1011
|
title: 'List Outliers',
|
|
1012
|
+
annotations: READ,
|
|
940
1013
|
description: "List top-performing content (outliers) from the creators the account tracks, ranked by outlier score (how far a post overperformed its creator's baseline). Filter by platform, content type, minimum score, or a text search. Call get_inspiration_content for one item's full detail incl. transcript. This is the core research read for finding what's working.",
|
|
941
1014
|
inputSchema: {
|
|
942
1015
|
platform: z.enum(['youtube', 'instagram']).optional().describe('Filter to one platform.'),
|
|
@@ -947,9 +1020,10 @@ export async function buildServer(options = {}) {
|
|
|
947
1020
|
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 20).'),
|
|
948
1021
|
offset: z.number().int().min(0).optional().describe('Pagination offset.'),
|
|
949
1022
|
},
|
|
950
|
-
}, async (args) => {
|
|
1023
|
+
}, async (args, extra) => {
|
|
951
1024
|
try {
|
|
952
|
-
|
|
1025
|
+
const client = await getClient(extra);
|
|
1026
|
+
return outlierListResult(await client.listOutliers({
|
|
953
1027
|
platform: args.platform,
|
|
954
1028
|
contentType: args.contentType,
|
|
955
1029
|
minOutlierScore: args.minOutlierScore,
|
|
@@ -966,13 +1040,15 @@ export async function buildServer(options = {}) {
|
|
|
966
1040
|
// -- get_inspiration_content ----------------------------------------------
|
|
967
1041
|
server.registerTool('get_inspiration_content', {
|
|
968
1042
|
title: 'Get Inspiration Content',
|
|
1043
|
+
annotations: READ,
|
|
969
1044
|
description: "Get one tracked-content item in full: engagement stats, outlier score, hashtags, and the transcript when available. Use it to study exactly what a high-performing post says and does.",
|
|
970
1045
|
inputSchema: {
|
|
971
1046
|
contentId: z.string().describe('The content id from list_outliers or get_inspiration_account.'),
|
|
972
1047
|
},
|
|
973
|
-
}, async (args) => {
|
|
1048
|
+
}, async (args, extra) => {
|
|
974
1049
|
try {
|
|
975
|
-
|
|
1050
|
+
const client = await getClient(extra);
|
|
1051
|
+
return inspirationContentResult(await client.getInspirationContent(args.contentId));
|
|
976
1052
|
}
|
|
977
1053
|
catch (err) {
|
|
978
1054
|
return errorResult(err);
|
|
@@ -981,10 +1057,12 @@ export async function buildServer(options = {}) {
|
|
|
981
1057
|
// -- list_brand_accounts --------------------------------------------------
|
|
982
1058
|
server.registerTool('list_brand_accounts', {
|
|
983
1059
|
title: 'List Brand Accounts',
|
|
1060
|
+
annotations: READ,
|
|
984
1061
|
description: "List the account owner's OWN connected social accounts that ContentHero tracks for performance (distinct from list_brand_kits, which are the brand identity documents). Call get_brand_account_performance for one account's stats.",
|
|
985
|
-
}, async () => {
|
|
1062
|
+
}, async (extra) => {
|
|
986
1063
|
try {
|
|
987
|
-
|
|
1064
|
+
const client = await getClient(extra);
|
|
1065
|
+
return trackedAccountListResult(await client.listBrandAccounts(), 'brand account(s)');
|
|
988
1066
|
}
|
|
989
1067
|
catch (err) {
|
|
990
1068
|
return errorResult(err);
|
|
@@ -993,13 +1071,15 @@ export async function buildServer(options = {}) {
|
|
|
993
1071
|
// -- get_brand_account_performance ----------------------------------------
|
|
994
1072
|
server.registerTool('get_brand_account_performance', {
|
|
995
1073
|
title: 'Get Brand Account Performance',
|
|
1074
|
+
annotations: READ,
|
|
996
1075
|
description: "Get the performance summary for one of the owner's brand accounts: content count, total and average views/likes/comments, average engagement and outlier score, plus top and recent content. Use it to ground decisions in how the owner's own content actually performs.",
|
|
997
1076
|
inputSchema: {
|
|
998
1077
|
accountId: z.string().describe('The account id from list_brand_accounts.'),
|
|
999
1078
|
},
|
|
1000
|
-
}, async (args) => {
|
|
1079
|
+
}, async (args, extra) => {
|
|
1001
1080
|
try {
|
|
1002
|
-
|
|
1081
|
+
const client = await getClient(extra);
|
|
1082
|
+
return brandPerformanceResult(await client.getBrandAccountPerformance(args.accountId));
|
|
1003
1083
|
}
|
|
1004
1084
|
catch (err) {
|
|
1005
1085
|
return errorResult(err);
|
|
@@ -1008,10 +1088,12 @@ export async function buildServer(options = {}) {
|
|
|
1008
1088
|
// -- list_connected_accounts ----------------------------------------------
|
|
1009
1089
|
server.registerTool('list_connected_accounts', {
|
|
1010
1090
|
title: 'List Connected Accounts',
|
|
1091
|
+
annotations: READ,
|
|
1011
1092
|
description: "List the social accounts the owner has connected (the publish targets), default first. Use an account's id as connectedAccountId on add_post_destination, then publish_post. Read-only: connecting an account is done in the ContentHero app.",
|
|
1012
|
-
}, async () => {
|
|
1093
|
+
}, async (extra) => {
|
|
1013
1094
|
try {
|
|
1014
|
-
|
|
1095
|
+
const client = await getClient(extra);
|
|
1096
|
+
return connectedAccountListResult(await client.listConnectedAccounts());
|
|
1015
1097
|
}
|
|
1016
1098
|
catch (err) {
|
|
1017
1099
|
return errorResult(err);
|
|
@@ -1020,13 +1102,15 @@ export async function buildServer(options = {}) {
|
|
|
1020
1102
|
// -- get_connected_account ------------------------------------------------
|
|
1021
1103
|
server.registerTool('get_connected_account', {
|
|
1022
1104
|
title: 'Get Connected Account',
|
|
1105
|
+
annotations: READ,
|
|
1023
1106
|
description: "Get one connected account's detail: platform, status, and capabilities. Use it to confirm a target can publish before attaching it to a post.",
|
|
1024
1107
|
inputSchema: {
|
|
1025
1108
|
accountId: z.string().describe('The connected account id from list_connected_accounts.'),
|
|
1026
1109
|
},
|
|
1027
|
-
}, async (args) => {
|
|
1110
|
+
}, async (args, extra) => {
|
|
1028
1111
|
try {
|
|
1029
|
-
|
|
1112
|
+
const client = await getClient(extra);
|
|
1113
|
+
return connectedAccountResult(await client.getConnectedAccount(args.accountId));
|
|
1030
1114
|
}
|
|
1031
1115
|
catch (err) {
|
|
1032
1116
|
return errorResult(err);
|
|
@@ -1035,16 +1119,27 @@ export async function buildServer(options = {}) {
|
|
|
1035
1119
|
// -- get_balance ----------------------------------------------------------
|
|
1036
1120
|
server.registerTool('get_balance', {
|
|
1037
1121
|
title: 'Get Balance',
|
|
1122
|
+
annotations: READ,
|
|
1038
1123
|
description: 'Get the current ContentHero credit balance, subscription tier, and auto-top-up state.',
|
|
1039
|
-
}, async () => {
|
|
1124
|
+
}, async (extra) => {
|
|
1040
1125
|
try {
|
|
1041
|
-
const
|
|
1042
|
-
return balanceResult(
|
|
1126
|
+
const client = await getClient(extra);
|
|
1127
|
+
return balanceResult(await client.getBalance());
|
|
1043
1128
|
}
|
|
1044
1129
|
catch (err) {
|
|
1045
1130
|
return errorResult(err);
|
|
1046
1131
|
}
|
|
1047
1132
|
});
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Build a stdio-style server bound to a single env-configured client. The model
|
|
1136
|
+
* enums are resolved live from the discovery catalog (the client has a key).
|
|
1137
|
+
*/
|
|
1138
|
+
export async function buildServer(options = {}) {
|
|
1139
|
+
const getClient = options.getClient ?? defaultGetClient;
|
|
1140
|
+
const models = await resolveModelEnums(getClient);
|
|
1141
|
+
const server = new McpServer({ name: 'contenthero', version: '0.2.4' });
|
|
1142
|
+
registerTools(server, { getClient: () => getClient(), models });
|
|
1048
1143
|
return server;
|
|
1049
1144
|
}
|
|
1050
1145
|
//# sourceMappingURL=server.js.map
|