@contenthero/mcp 0.2.1 → 0.2.3
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/format.d.ts +33 -1
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +178 -0
- package/dist/format.js.map +1 -1
- 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 +630 -72
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
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,28 +15,53 @@
|
|
|
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';
|
|
26
34
|
import { GenerationTimeoutError, } from '@contenthero/sdk';
|
|
27
35
|
import { getClient as defaultGetClient } from './client.js';
|
|
28
36
|
import { resolveModelEnums, BOARD_TYPES, BOARD_TYPE_GUIDANCE, IMAGE_MODEL_GUIDANCE, VIDEO_MODEL_GUIDANCE, AUDIO_MODEL_GUIDANCE, UPSCALE_MODEL_GUIDANCE, LIP_SYNC_MODEL_GUIDANCE, } from './models.js';
|
|
29
|
-
import { audioResult, avatarListResult, avatarResult, balanceResult, brandKitListResult, brandKitResult, completedResult, costResult, mediaListResult, mediaResult, errorResult, generationBatchResult, generationStatusResult, pendingResult, transcriptResult, voiceListResult, voiceResult, } from './format.js';
|
|
37
|
+
import { assetResult, audioResult, avatarListResult, avatarResult, balanceResult, brandKitArchivedResult, brandKitListResult, brandKitResult, brandKitSectionResult, brandPerformanceResult, completedResult, connectedAccountListResult, connectedAccountResult, costResult, destinationResult, inspirationAccountResult, inspirationContentResult, mediaListResult, mediaResult, errorResult, generationBatchResult, generationStatusResult, outlierListResult, pendingResult, pipelineStageListResult, postListResult, postResult, postSummaryResult, publishResult, trackedAccountListResult, transcriptResult, voiceListResult, voiceResult, } from './format.js';
|
|
38
|
+
/** Platforms a post or destination may target. */
|
|
39
|
+
const POST_PLATFORMS = [
|
|
40
|
+
'youtube',
|
|
41
|
+
'instagram',
|
|
42
|
+
'tiktok',
|
|
43
|
+
'facebook',
|
|
44
|
+
'linkedin',
|
|
45
|
+
'x',
|
|
46
|
+
'threads',
|
|
47
|
+
'general',
|
|
48
|
+
];
|
|
30
49
|
/**
|
|
31
50
|
* How long the smart-wait tools (generate_image / generate_video / upscale /
|
|
32
51
|
* generate_lip_sync) wait inline before handing back the outputId to poll.
|
|
33
52
|
* Kept under the MCP SDK's default 60s client request timeout, so a slow render
|
|
34
|
-
* returns the clean "still rendering, call get_generation_status" handoff rather
|
|
35
|
-
* tripping the client's timeout.
|
|
36
|
-
* 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.
|
|
37
55
|
*/
|
|
38
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 };
|
|
39
65
|
/** Drop undefined values so the request payload stays minimal. */
|
|
40
66
|
function compact(obj) {
|
|
41
67
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
@@ -44,16 +70,16 @@ function buildReferences(parts) {
|
|
|
44
70
|
const refs = compact(parts);
|
|
45
71
|
return Object.keys(refs).length > 0 ? refs : undefined;
|
|
46
72
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const models =
|
|
53
|
-
const server = new McpServer({ name: 'contenthero', version: '0.2.1' });
|
|
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;
|
|
54
79
|
// -- generate_image -------------------------------------------------------
|
|
55
80
|
server.registerTool('generate_image', {
|
|
56
81
|
title: 'Generate Image',
|
|
82
|
+
annotations: WRITE,
|
|
57
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.',
|
|
58
84
|
inputSchema: {
|
|
59
85
|
modelId: z.enum(models.image).describe(IMAGE_MODEL_GUIDANCE),
|
|
@@ -75,8 +101,9 @@ export async function buildServer(options = {}) {
|
|
|
75
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.'),
|
|
76
102
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
77
103
|
},
|
|
78
|
-
}, async (args) => {
|
|
104
|
+
}, async (args, extra) => {
|
|
79
105
|
try {
|
|
106
|
+
const client = await getClient(extra);
|
|
80
107
|
const request = compact({
|
|
81
108
|
contentType: 'image',
|
|
82
109
|
modelId: args.modelId,
|
|
@@ -86,13 +113,11 @@ export async function buildServer(options = {}) {
|
|
|
86
113
|
numImages: args.numImages,
|
|
87
114
|
seed: args.seed,
|
|
88
115
|
references: buildReferences({ images: args.referenceImages }),
|
|
89
|
-
// Mode (Flux pro/flex, Kontext pro/max) rides the model-agnostic
|
|
90
|
-
// parameters passthrough, which the server reads for variant + pricing.
|
|
91
116
|
parameters: args.mode ? { mode: args.mode } : undefined,
|
|
92
117
|
});
|
|
93
118
|
if (args.getCost)
|
|
94
|
-
return costResult(await
|
|
95
|
-
const gen = await
|
|
119
|
+
return costResult(await client.estimateCost(request));
|
|
120
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
96
121
|
return completedResult(gen);
|
|
97
122
|
}
|
|
98
123
|
catch (err) {
|
|
@@ -104,6 +129,7 @@ export async function buildServer(options = {}) {
|
|
|
104
129
|
// -- generate_board -------------------------------------------------------
|
|
105
130
|
server.registerTool('generate_board', {
|
|
106
131
|
title: 'Generate Reference Board',
|
|
132
|
+
annotations: WRITE,
|
|
107
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.',
|
|
108
134
|
inputSchema: {
|
|
109
135
|
boardType: z.enum(BOARD_TYPES).describe(BOARD_TYPE_GUIDANCE),
|
|
@@ -125,8 +151,9 @@ export async function buildServer(options = {}) {
|
|
|
125
151
|
boardName: z.string().optional().describe('Optional name for the board.'),
|
|
126
152
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
127
153
|
},
|
|
128
|
-
}, async (args) => {
|
|
154
|
+
}, async (args, extra) => {
|
|
129
155
|
try {
|
|
156
|
+
const client = await getClient(extra);
|
|
130
157
|
const request = compact({
|
|
131
158
|
boardType: args.boardType,
|
|
132
159
|
prompt: args.prompt,
|
|
@@ -135,8 +162,8 @@ export async function buildServer(options = {}) {
|
|
|
135
162
|
boardName: args.boardName,
|
|
136
163
|
});
|
|
137
164
|
if (args.getCost)
|
|
138
|
-
return costResult(await
|
|
139
|
-
const gen = await
|
|
165
|
+
return costResult(await client.estimateBoardCost(request));
|
|
166
|
+
const gen = await client.generateBoardAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
140
167
|
return completedResult(gen);
|
|
141
168
|
}
|
|
142
169
|
catch (err) {
|
|
@@ -148,6 +175,7 @@ export async function buildServer(options = {}) {
|
|
|
148
175
|
// -- generate_video -------------------------------------------------------
|
|
149
176
|
server.registerTool('generate_video', {
|
|
150
177
|
title: 'Generate Video',
|
|
178
|
+
annotations: WRITE,
|
|
151
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.',
|
|
152
180
|
inputSchema: {
|
|
153
181
|
modelId: z.enum(models.video).describe(VIDEO_MODEL_GUIDANCE),
|
|
@@ -186,11 +214,10 @@ export async function buildServer(options = {}) {
|
|
|
186
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.'),
|
|
187
215
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
188
216
|
},
|
|
189
|
-
}, async (args) => {
|
|
217
|
+
}, async (args, extra) => {
|
|
190
218
|
try {
|
|
219
|
+
const client = await getClient(extra);
|
|
191
220
|
const klingMultiShot = Array.isArray(args.shots) && args.shots.length > 0;
|
|
192
|
-
// multiShot is on for Kling's per-shot mode or WAN's boolean toggle; both
|
|
193
|
-
// ride the model-agnostic `parameters` passthrough into genParams.
|
|
194
221
|
const wantMultiShot = klingMultiShot || args.multiShot === true;
|
|
195
222
|
const parameters = {};
|
|
196
223
|
if (wantMultiShot)
|
|
@@ -200,15 +227,9 @@ export async function buildServer(options = {}) {
|
|
|
200
227
|
const request = compact({
|
|
201
228
|
contentType: 'video',
|
|
202
229
|
modelId: args.modelId,
|
|
203
|
-
// Multi-shot puts per-shot prompts in `shots`, but some models (Kling 3.0)
|
|
204
|
-
// still require a top-level prompt to pass validation; the provider drops
|
|
205
|
-
// it in multi-shot, so a synthesized summary is harmless when none is given.
|
|
206
230
|
prompt: klingMultiShot ? args.prompt ?? args.shots.map((s) => s.prompt).join(' ') : args.prompt,
|
|
207
231
|
aspectRatio: args.aspectRatio,
|
|
208
232
|
resolution: args.resolution,
|
|
209
|
-
// Kling's per-shot total drives duration validation + pricing; the provider
|
|
210
|
-
// recomputes the per-shot timeline from `shots`. WAN multi-shot keeps the
|
|
211
|
-
// single duration field.
|
|
212
233
|
duration: klingMultiShot ? args.shots.reduce((sum, s) => sum + s.duration, 0) : args.duration,
|
|
213
234
|
audioEnabled: args.audioEnabled,
|
|
214
235
|
numGenerations: args.numGenerations,
|
|
@@ -224,8 +245,8 @@ export async function buildServer(options = {}) {
|
|
|
224
245
|
}),
|
|
225
246
|
});
|
|
226
247
|
if (args.getCost)
|
|
227
|
-
return costResult(await
|
|
228
|
-
const gen = await
|
|
248
|
+
return costResult(await client.estimateCost(request));
|
|
249
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
229
250
|
return completedResult(gen);
|
|
230
251
|
}
|
|
231
252
|
catch (err) {
|
|
@@ -237,6 +258,7 @@ export async function buildServer(options = {}) {
|
|
|
237
258
|
// -- generate_audio (synchronous) -----------------------------------------
|
|
238
259
|
server.registerTool('generate_audio', {
|
|
239
260
|
title: 'Generate Audio',
|
|
261
|
+
annotations: WRITE,
|
|
240
262
|
description: 'Generate audio with ElevenLabs: speech (TTS), music, or a sound effect. Returns the audio URL directly (synchronous, no polling).',
|
|
241
263
|
inputSchema: {
|
|
242
264
|
modelId: z.enum(models.audio).describe(AUDIO_MODEL_GUIDANCE),
|
|
@@ -253,8 +275,9 @@ export async function buildServer(options = {}) {
|
|
|
253
275
|
.describe('For sfx: how literally to follow the prompt (0 to 1).'),
|
|
254
276
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
255
277
|
},
|
|
256
|
-
}, async (args) => {
|
|
278
|
+
}, async (args, extra) => {
|
|
257
279
|
try {
|
|
280
|
+
const client = await getClient(extra);
|
|
258
281
|
const request = compact({
|
|
259
282
|
contentType: 'audio',
|
|
260
283
|
modelId: args.modelId,
|
|
@@ -266,8 +289,8 @@ export async function buildServer(options = {}) {
|
|
|
266
289
|
promptInfluence: args.promptInfluence,
|
|
267
290
|
});
|
|
268
291
|
if (args.getCost)
|
|
269
|
-
return costResult(await
|
|
270
|
-
const result = await
|
|
292
|
+
return costResult(await client.estimateCost(request));
|
|
293
|
+
const result = await client.generate(request);
|
|
271
294
|
return audioResult(result);
|
|
272
295
|
}
|
|
273
296
|
catch (err) {
|
|
@@ -277,6 +300,7 @@ export async function buildServer(options = {}) {
|
|
|
277
300
|
// -- upscale --------------------------------------------------------------
|
|
278
301
|
server.registerTool('upscale', {
|
|
279
302
|
title: 'Upscale',
|
|
303
|
+
annotations: WRITE,
|
|
280
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.',
|
|
281
305
|
inputSchema: {
|
|
282
306
|
modelId: z.enum(models.upscale).describe(UPSCALE_MODEL_GUIDANCE),
|
|
@@ -288,8 +312,9 @@ export async function buildServer(options = {}) {
|
|
|
288
312
|
.describe('Required for video upscalers: the source video length in seconds (used for pricing).'),
|
|
289
313
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of upscaling (nothing runs, nothing is charged).'),
|
|
290
314
|
},
|
|
291
|
-
}, async (args) => {
|
|
315
|
+
}, async (args, extra) => {
|
|
292
316
|
try {
|
|
317
|
+
const client = await getClient(extra);
|
|
293
318
|
const isVideo = models.upscaleContentType[args.modelId] === 'video';
|
|
294
319
|
const request = compact({
|
|
295
320
|
contentType: isVideo ? 'video' : 'image',
|
|
@@ -299,8 +324,8 @@ export async function buildServer(options = {}) {
|
|
|
299
324
|
references: isVideo ? { videos: [args.sourceUrl] } : { images: [args.sourceUrl] },
|
|
300
325
|
});
|
|
301
326
|
if (args.getCost)
|
|
302
|
-
return costResult(await
|
|
303
|
-
const gen = await
|
|
327
|
+
return costResult(await client.estimateCost(request));
|
|
328
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
304
329
|
return completedResult(gen);
|
|
305
330
|
}
|
|
306
331
|
catch (err) {
|
|
@@ -312,6 +337,7 @@ export async function buildServer(options = {}) {
|
|
|
312
337
|
// -- generate_lip_sync ----------------------------------------------------
|
|
313
338
|
server.registerTool('generate_lip_sync', {
|
|
314
339
|
title: 'Generate Lip Sync',
|
|
340
|
+
annotations: WRITE,
|
|
315
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.',
|
|
316
342
|
inputSchema: {
|
|
317
343
|
modelId: z.enum(models.lipSync).describe(LIP_SYNC_MODEL_GUIDANCE),
|
|
@@ -337,8 +363,9 @@ export async function buildServer(options = {}) {
|
|
|
337
363
|
.describe('Length of audioUrl in seconds (audio mode only; improves cost accuracy).'),
|
|
338
364
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
339
365
|
},
|
|
340
|
-
}, async (args) => {
|
|
366
|
+
}, async (args, extra) => {
|
|
341
367
|
try {
|
|
368
|
+
const client = await getClient(extra);
|
|
342
369
|
const request = compact({
|
|
343
370
|
contentType: 'video',
|
|
344
371
|
modelId: args.modelId,
|
|
@@ -354,8 +381,8 @@ export async function buildServer(options = {}) {
|
|
|
354
381
|
}),
|
|
355
382
|
});
|
|
356
383
|
if (args.getCost)
|
|
357
|
-
return costResult(await
|
|
358
|
-
const gen = await
|
|
384
|
+
return costResult(await client.estimateCost(request));
|
|
385
|
+
const gen = await client.generateAndWait(request, { timeoutMs: SMART_WAIT_MS });
|
|
359
386
|
return completedResult(gen);
|
|
360
387
|
}
|
|
361
388
|
catch (err) {
|
|
@@ -367,6 +394,7 @@ export async function buildServer(options = {}) {
|
|
|
367
394
|
// -- transcribe -----------------------------------------------------------
|
|
368
395
|
server.registerTool('transcribe', {
|
|
369
396
|
title: 'Transcribe Audio',
|
|
397
|
+
annotations: READ,
|
|
370
398
|
description: 'Transcribe an audio URL to text (speech-to-text). Returns the transcript directly (synchronous, free, no polling).',
|
|
371
399
|
inputSchema: {
|
|
372
400
|
audioUrl: z.string().describe('Public URL of the audio file to transcribe.'),
|
|
@@ -376,9 +404,10 @@ export async function buildServer(options = {}) {
|
|
|
376
404
|
.describe('ISO language hint, e.g. "en". Auto-detected when omitted.'),
|
|
377
405
|
diarize: z.boolean().optional().describe('Label each speaker (diarization).'),
|
|
378
406
|
},
|
|
379
|
-
}, async (args) => {
|
|
407
|
+
}, async (args, extra) => {
|
|
380
408
|
try {
|
|
381
|
-
const
|
|
409
|
+
const client = await getClient(extra);
|
|
410
|
+
const t = await client.transcribe({
|
|
382
411
|
audioUrl: args.audioUrl,
|
|
383
412
|
languageCode: args.languageCode,
|
|
384
413
|
diarize: args.diarize,
|
|
@@ -392,10 +421,12 @@ export async function buildServer(options = {}) {
|
|
|
392
421
|
// -- list_avatars ---------------------------------------------------------
|
|
393
422
|
server.registerTool('list_avatars', {
|
|
394
423
|
title: 'List Avatars',
|
|
424
|
+
annotations: READ,
|
|
395
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.",
|
|
396
|
-
}, async () => {
|
|
426
|
+
}, async (extra) => {
|
|
397
427
|
try {
|
|
398
|
-
|
|
428
|
+
const client = await getClient(extra);
|
|
429
|
+
return avatarListResult(await client.listAvatars());
|
|
399
430
|
}
|
|
400
431
|
catch (err) {
|
|
401
432
|
return errorResult(err);
|
|
@@ -404,13 +435,15 @@ export async function buildServer(options = {}) {
|
|
|
404
435
|
// -- get_avatar -----------------------------------------------------------
|
|
405
436
|
server.registerTool('get_avatar', {
|
|
406
437
|
title: 'Get Avatar',
|
|
438
|
+
annotations: READ,
|
|
407
439
|
description: 'Get one avatar by id: its base image (use as generate_lip_sync imageUrl), default voice, traits, and its looks (outfit variations).',
|
|
408
440
|
inputSchema: {
|
|
409
441
|
avatarId: z.string().describe('The avatar id from list_avatars.'),
|
|
410
442
|
},
|
|
411
|
-
}, async (args) => {
|
|
443
|
+
}, async (args, extra) => {
|
|
412
444
|
try {
|
|
413
|
-
|
|
445
|
+
const client = await getClient(extra);
|
|
446
|
+
return avatarResult(await client.getAvatar(args.avatarId));
|
|
414
447
|
}
|
|
415
448
|
catch (err) {
|
|
416
449
|
return errorResult(err);
|
|
@@ -419,10 +452,12 @@ export async function buildServer(options = {}) {
|
|
|
419
452
|
// -- list_voices ----------------------------------------------------------
|
|
420
453
|
server.registerTool('list_voices', {
|
|
421
454
|
title: 'List Voices',
|
|
455
|
+
annotations: READ,
|
|
422
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.",
|
|
423
|
-
}, async () => {
|
|
457
|
+
}, async (extra) => {
|
|
424
458
|
try {
|
|
425
|
-
|
|
459
|
+
const client = await getClient(extra);
|
|
460
|
+
return voiceListResult(await client.listVoices());
|
|
426
461
|
}
|
|
427
462
|
catch (err) {
|
|
428
463
|
return errorResult(err);
|
|
@@ -431,13 +466,15 @@ export async function buildServer(options = {}) {
|
|
|
431
466
|
// -- get_voice ------------------------------------------------------------
|
|
432
467
|
server.registerTool('get_voice', {
|
|
433
468
|
title: 'Get Voice',
|
|
469
|
+
annotations: READ,
|
|
434
470
|
description: 'Get one voice by its voiceId: provider, traits (accent/language/gender/age), description, and a preview URL.',
|
|
435
471
|
inputSchema: {
|
|
436
472
|
voiceId: z.string().describe('The voice id from list_voices.'),
|
|
437
473
|
},
|
|
438
|
-
}, async (args) => {
|
|
474
|
+
}, async (args, extra) => {
|
|
439
475
|
try {
|
|
440
|
-
|
|
476
|
+
const client = await getClient(extra);
|
|
477
|
+
return voiceResult(await client.getVoice(args.voiceId));
|
|
441
478
|
}
|
|
442
479
|
catch (err) {
|
|
443
480
|
return errorResult(err);
|
|
@@ -446,10 +483,12 @@ export async function buildServer(options = {}) {
|
|
|
446
483
|
// -- list_brand_kits ------------------------------------------------------
|
|
447
484
|
server.registerTool('list_brand_kits', {
|
|
448
485
|
title: 'List Brand Kits',
|
|
486
|
+
annotations: READ,
|
|
449
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.",
|
|
450
|
-
}, async () => {
|
|
488
|
+
}, async (extra) => {
|
|
451
489
|
try {
|
|
452
|
-
|
|
490
|
+
const client = await getClient(extra);
|
|
491
|
+
return brandKitListResult(await client.listBrandKits());
|
|
453
492
|
}
|
|
454
493
|
catch (err) {
|
|
455
494
|
return errorResult(err);
|
|
@@ -458,13 +497,130 @@ export async function buildServer(options = {}) {
|
|
|
458
497
|
// -- get_brand_kit --------------------------------------------------------
|
|
459
498
|
server.registerTool('get_brand_kit', {
|
|
460
499
|
title: 'Get Brand Kit',
|
|
500
|
+
annotations: READ,
|
|
461
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.',
|
|
462
502
|
inputSchema: {
|
|
463
503
|
brandKitId: z.string().describe('The brand kit id from list_brand_kits.'),
|
|
464
504
|
},
|
|
465
|
-
}, async (args) => {
|
|
505
|
+
}, async (args, extra) => {
|
|
466
506
|
try {
|
|
467
|
-
|
|
507
|
+
const client = await getClient(extra);
|
|
508
|
+
return brandKitResult(await client.getBrandKit(args.brandKitId));
|
|
509
|
+
}
|
|
510
|
+
catch (err) {
|
|
511
|
+
return errorResult(err);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
// -- update_brand_kit -----------------------------------------------------
|
|
515
|
+
server.registerTool('update_brand_kit', {
|
|
516
|
+
title: 'Update Brand Kit',
|
|
517
|
+
annotations: WRITE,
|
|
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.",
|
|
519
|
+
inputSchema: {
|
|
520
|
+
brandKitId: z.string().describe('The brand kit id.'),
|
|
521
|
+
name: z.string().optional(),
|
|
522
|
+
businessName: z.string().optional(),
|
|
523
|
+
websiteUrl: z.string().optional(),
|
|
524
|
+
primaryOffer: z.string().optional(),
|
|
525
|
+
nicheDefinition: z.string().optional(),
|
|
526
|
+
positioning: z.record(z.string(), z.unknown()).optional().describe('Positioning object (free-form).'),
|
|
527
|
+
audience: z.record(z.string(), z.unknown()).optional().describe('Audience object (free-form).'),
|
|
528
|
+
voiceProfile: z.record(z.string(), z.unknown()).optional().describe('Voice profile object (tone, style, ...).'),
|
|
529
|
+
visualStyle: z.string().optional(),
|
|
530
|
+
designPrinciples: z.array(z.string()).optional(),
|
|
531
|
+
contentStrategy: z.record(z.string(), z.unknown()).optional().describe('Content strategy object (free-form).'),
|
|
532
|
+
},
|
|
533
|
+
}, async (args, extra) => {
|
|
534
|
+
try {
|
|
535
|
+
const client = await getClient(extra);
|
|
536
|
+
const { brandKitId, ...input } = args;
|
|
537
|
+
return brandKitResult(await client.updateBrandKit(brandKitId, input));
|
|
538
|
+
}
|
|
539
|
+
catch (err) {
|
|
540
|
+
return errorResult(err);
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
// -- archive_brand_kit ----------------------------------------------------
|
|
544
|
+
server.registerTool('archive_brand_kit', {
|
|
545
|
+
title: 'Archive Brand Kit',
|
|
546
|
+
annotations: WRITE,
|
|
547
|
+
description: 'Archive a brand kit (reversible; ContentHero never hard-deletes). Requires the brandkit:write scope.',
|
|
548
|
+
inputSchema: {
|
|
549
|
+
brandKitId: z.string().describe('The brand kit id to archive.'),
|
|
550
|
+
},
|
|
551
|
+
}, async (args, extra) => {
|
|
552
|
+
try {
|
|
553
|
+
const client = await getClient(extra);
|
|
554
|
+
return brandKitArchivedResult(await client.archiveBrandKit(args.brandKitId));
|
|
555
|
+
}
|
|
556
|
+
catch (err) {
|
|
557
|
+
return errorResult(err);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
// -- add_brand_kit_section ------------------------------------------------
|
|
561
|
+
server.registerTool('add_brand_kit_section', {
|
|
562
|
+
title: 'Add Brand Kit Section',
|
|
563
|
+
annotations: WRITE,
|
|
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.",
|
|
565
|
+
inputSchema: {
|
|
566
|
+
brandKitId: z.string().describe('The brand kit id.'),
|
|
567
|
+
tab: z.string().describe('The tab the section belongs to (e.g. "voice", "overview").'),
|
|
568
|
+
sectionName: z.string().describe('The section title.'),
|
|
569
|
+
sortOrder: z.number().int().optional().describe('Order within the tab (default 99 = end).'),
|
|
570
|
+
fields: z.array(z.record(z.string(), z.unknown())).optional().describe('Field objects: { key, label, type, value }.'),
|
|
571
|
+
},
|
|
572
|
+
}, async (args, extra) => {
|
|
573
|
+
try {
|
|
574
|
+
const client = await getClient(extra);
|
|
575
|
+
return brandKitSectionResult(await client.addBrandKitSection(args.brandKitId, {
|
|
576
|
+
tab: args.tab,
|
|
577
|
+
sectionName: args.sectionName,
|
|
578
|
+
sortOrder: args.sortOrder,
|
|
579
|
+
fields: args.fields,
|
|
580
|
+
}), 'Added section');
|
|
581
|
+
}
|
|
582
|
+
catch (err) {
|
|
583
|
+
return errorResult(err);
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
// -- update_brand_kit_section ---------------------------------------------
|
|
587
|
+
server.registerTool('update_brand_kit_section', {
|
|
588
|
+
title: 'Update Brand Kit Section',
|
|
589
|
+
annotations: WRITE,
|
|
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.",
|
|
591
|
+
inputSchema: {
|
|
592
|
+
brandKitId: z.string().describe('The brand kit id.'),
|
|
593
|
+
sectionId: z.string().describe('The section id (from get_brand_kit).'),
|
|
594
|
+
sectionName: z.string().optional(),
|
|
595
|
+
sortOrder: z.number().int().optional(),
|
|
596
|
+
fields: z.array(z.record(z.string(), z.unknown())).optional().describe('Replacement field objects.'),
|
|
597
|
+
},
|
|
598
|
+
}, async (args, extra) => {
|
|
599
|
+
try {
|
|
600
|
+
const client = await getClient(extra);
|
|
601
|
+
return brandKitSectionResult(await client.updateBrandKitSection(args.brandKitId, args.sectionId, {
|
|
602
|
+
sectionName: args.sectionName,
|
|
603
|
+
sortOrder: args.sortOrder,
|
|
604
|
+
fields: args.fields,
|
|
605
|
+
}), 'Updated section');
|
|
606
|
+
}
|
|
607
|
+
catch (err) {
|
|
608
|
+
return errorResult(err);
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
// -- archive_brand_kit_section --------------------------------------------
|
|
612
|
+
server.registerTool('archive_brand_kit_section', {
|
|
613
|
+
title: 'Archive Brand Kit Section',
|
|
614
|
+
annotations: WRITE,
|
|
615
|
+
description: 'Archive a brand-kit section (soft delete, reversible). Use it to remove a section an agent added. Requires the brandkit:write scope.',
|
|
616
|
+
inputSchema: {
|
|
617
|
+
brandKitId: z.string().describe('The brand kit id.'),
|
|
618
|
+
sectionId: z.string().describe('The section id to archive.'),
|
|
619
|
+
},
|
|
620
|
+
}, async (args, extra) => {
|
|
621
|
+
try {
|
|
622
|
+
const client = await getClient(extra);
|
|
623
|
+
return brandKitSectionResult(await client.archiveBrandKitSection(args.brandKitId, args.sectionId), 'Archived section');
|
|
468
624
|
}
|
|
469
625
|
catch (err) {
|
|
470
626
|
return errorResult(err);
|
|
@@ -473,6 +629,7 @@ export async function buildServer(options = {}) {
|
|
|
473
629
|
// -- list_media -----------------------------------------------------------
|
|
474
630
|
server.registerTool('list_media', {
|
|
475
631
|
title: 'List Media',
|
|
632
|
+
annotations: READ,
|
|
476
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.",
|
|
477
634
|
inputSchema: {
|
|
478
635
|
contentType: z
|
|
@@ -486,9 +643,10 @@ export async function buildServer(options = {}) {
|
|
|
486
643
|
status: z.string().optional().describe("Status filter; defaults to 'completed'."),
|
|
487
644
|
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 20).'),
|
|
488
645
|
},
|
|
489
|
-
}, async (args) => {
|
|
646
|
+
}, async (args, extra) => {
|
|
490
647
|
try {
|
|
491
|
-
|
|
648
|
+
const client = await getClient(extra);
|
|
649
|
+
return mediaListResult(await client.listMedia({
|
|
492
650
|
contentType: args.contentType,
|
|
493
651
|
kind: args.kind,
|
|
494
652
|
status: args.status,
|
|
@@ -502,30 +660,34 @@ export async function buildServer(options = {}) {
|
|
|
502
660
|
// -- get_media ------------------------------------------------------------
|
|
503
661
|
server.registerTool('get_media', {
|
|
504
662
|
title: 'Get Media',
|
|
663
|
+
annotations: READ,
|
|
505
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.',
|
|
506
665
|
inputSchema: {
|
|
507
666
|
id: z
|
|
508
667
|
.string()
|
|
509
668
|
.describe('The output id (full or first-8), optionally with a "-N" variation suffix.'),
|
|
510
669
|
},
|
|
511
|
-
}, async (args) => {
|
|
670
|
+
}, async (args, extra) => {
|
|
512
671
|
try {
|
|
513
|
-
|
|
672
|
+
const client = await getClient(extra);
|
|
673
|
+
return mediaResult(await client.getMedia(args.id));
|
|
514
674
|
}
|
|
515
675
|
catch (err) {
|
|
516
676
|
return errorResult(err);
|
|
517
677
|
}
|
|
518
678
|
});
|
|
519
|
-
// -- get_generation_status
|
|
679
|
+
// -- get_generation_status ------------------------------------------------
|
|
520
680
|
server.registerTool('get_generation_status', {
|
|
521
681
|
title: 'Get Generation Status',
|
|
682
|
+
annotations: READ,
|
|
522
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.',
|
|
523
684
|
inputSchema: {
|
|
524
685
|
outputId: z.string().describe('The outputId from generate_image or generate_video.'),
|
|
525
686
|
},
|
|
526
|
-
}, async (args) => {
|
|
687
|
+
}, async (args, extra) => {
|
|
527
688
|
try {
|
|
528
|
-
const
|
|
689
|
+
const client = await getClient(extra);
|
|
690
|
+
const gen = await client.getGeneration(args.outputId);
|
|
529
691
|
return generationStatusResult(gen);
|
|
530
692
|
}
|
|
531
693
|
catch (err) {
|
|
@@ -535,6 +697,7 @@ export async function buildServer(options = {}) {
|
|
|
535
697
|
// -- wait_for_generation --------------------------------------------------
|
|
536
698
|
server.registerTool('wait_for_generation', {
|
|
537
699
|
title: 'Wait For Generation',
|
|
700
|
+
annotations: READ,
|
|
538
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.',
|
|
539
702
|
inputSchema: {
|
|
540
703
|
outputIds: z
|
|
@@ -547,19 +710,19 @@ export async function buildServer(options = {}) {
|
|
|
547
710
|
.optional()
|
|
548
711
|
.describe('Block until terminal (up to ~50s) when true (the default). false = an instant snapshot, no blocking.'),
|
|
549
712
|
},
|
|
550
|
-
}, async (args) => {
|
|
713
|
+
}, async (args, extra) => {
|
|
551
714
|
try {
|
|
715
|
+
const client = await getClient(extra);
|
|
552
716
|
const blocking = args.wait !== false;
|
|
553
717
|
const gens = await Promise.all(args.outputIds.map(async (id) => {
|
|
554
718
|
if (!blocking)
|
|
555
|
-
return
|
|
719
|
+
return client.getGeneration(id);
|
|
556
720
|
try {
|
|
557
|
-
return await
|
|
721
|
+
return await client.waitForGeneration(id, { timeoutMs: SMART_WAIT_MS });
|
|
558
722
|
}
|
|
559
723
|
catch (err) {
|
|
560
|
-
// Still rendering past the smart-wait window: hand back the current snapshot.
|
|
561
724
|
if (err instanceof GenerationTimeoutError)
|
|
562
|
-
return
|
|
725
|
+
return client.getGeneration(id);
|
|
563
726
|
throw err;
|
|
564
727
|
}
|
|
565
728
|
}));
|
|
@@ -569,19 +732,414 @@ export async function buildServer(options = {}) {
|
|
|
569
732
|
return errorResult(err);
|
|
570
733
|
}
|
|
571
734
|
});
|
|
735
|
+
// -- list_posts -----------------------------------------------------------
|
|
736
|
+
server.registerTool('list_posts', {
|
|
737
|
+
title: 'List Posts',
|
|
738
|
+
annotations: READ,
|
|
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).",
|
|
740
|
+
inputSchema: {
|
|
741
|
+
status: z.enum(['draft', 'active', 'completed', 'archived']).optional().describe('Filter by lifecycle status.'),
|
|
742
|
+
platform: z.enum(POST_PLATFORMS).optional().describe('Filter by the post platform.'),
|
|
743
|
+
pipelineStage: z.string().optional().describe('Filter by a pipeline stage id, slug, or name.'),
|
|
744
|
+
search: z.string().optional().describe('Case-insensitive title search.'),
|
|
745
|
+
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 50).'),
|
|
746
|
+
offset: z.number().int().min(0).optional().describe('Pagination offset.'),
|
|
747
|
+
},
|
|
748
|
+
}, async (args, extra) => {
|
|
749
|
+
try {
|
|
750
|
+
const client = await getClient(extra);
|
|
751
|
+
return postListResult(await client.listPosts({
|
|
752
|
+
status: args.status,
|
|
753
|
+
platform: args.platform,
|
|
754
|
+
pipelineStage: args.pipelineStage,
|
|
755
|
+
search: args.search,
|
|
756
|
+
limit: args.limit,
|
|
757
|
+
offset: args.offset,
|
|
758
|
+
}));
|
|
759
|
+
}
|
|
760
|
+
catch (err) {
|
|
761
|
+
return errorResult(err);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
// -- get_post -------------------------------------------------------------
|
|
765
|
+
server.registerTool('get_post', {
|
|
766
|
+
title: 'Get Post',
|
|
767
|
+
annotations: READ,
|
|
768
|
+
description: "Get one post in full: its fields (title, description, script, notes, status, stage, schedule), plus its publish destinations and attached assets.",
|
|
769
|
+
inputSchema: {
|
|
770
|
+
postId: z.string().describe('The post id from list_posts.'),
|
|
771
|
+
},
|
|
772
|
+
}, async (args, extra) => {
|
|
773
|
+
try {
|
|
774
|
+
const client = await getClient(extra);
|
|
775
|
+
return postResult(await client.getPost(args.postId));
|
|
776
|
+
}
|
|
777
|
+
catch (err) {
|
|
778
|
+
return errorResult(err);
|
|
779
|
+
}
|
|
780
|
+
});
|
|
781
|
+
// -- list_pipeline_stages -------------------------------------------------
|
|
782
|
+
server.registerTool('list_pipeline_stages', {
|
|
783
|
+
title: 'List Pipeline Stages',
|
|
784
|
+
annotations: READ,
|
|
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.",
|
|
786
|
+
}, async (extra) => {
|
|
787
|
+
try {
|
|
788
|
+
const client = await getClient(extra);
|
|
789
|
+
return pipelineStageListResult(await client.listPipelineStages());
|
|
790
|
+
}
|
|
791
|
+
catch (err) {
|
|
792
|
+
return errorResult(err);
|
|
793
|
+
}
|
|
794
|
+
});
|
|
795
|
+
// -- create_post ----------------------------------------------------------
|
|
796
|
+
server.registerTool('create_post', {
|
|
797
|
+
title: 'Create Post',
|
|
798
|
+
annotations: WRITE,
|
|
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.",
|
|
800
|
+
inputSchema: {
|
|
801
|
+
title: z.string().describe('Post title (required).'),
|
|
802
|
+
platform: z.enum(POST_PLATFORMS).describe('Primary platform for the post.'),
|
|
803
|
+
description: z.string().optional().describe('Optional description / caption draft.'),
|
|
804
|
+
stage: z.string().optional().describe('Pipeline stage id, slug, or name. Defaults to the first stage.'),
|
|
805
|
+
},
|
|
806
|
+
}, async (args, extra) => {
|
|
807
|
+
try {
|
|
808
|
+
const client = await getClient(extra);
|
|
809
|
+
return postSummaryResult(await client.createPost({
|
|
810
|
+
title: args.title,
|
|
811
|
+
platform: args.platform,
|
|
812
|
+
description: args.description,
|
|
813
|
+
stage: args.stage,
|
|
814
|
+
}), 'Created');
|
|
815
|
+
}
|
|
816
|
+
catch (err) {
|
|
817
|
+
return errorResult(err);
|
|
818
|
+
}
|
|
819
|
+
});
|
|
820
|
+
// -- update_post ----------------------------------------------------------
|
|
821
|
+
server.registerTool('update_post', {
|
|
822
|
+
title: 'Update Post',
|
|
823
|
+
annotations: WRITE,
|
|
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.',
|
|
825
|
+
inputSchema: {
|
|
826
|
+
postId: z.string().describe('The post id.'),
|
|
827
|
+
title: z.string().optional(),
|
|
828
|
+
description: z.string().optional(),
|
|
829
|
+
platform: z.enum(POST_PLATFORMS).optional(),
|
|
830
|
+
status: z.enum(['draft', 'active', 'completed', 'archived']).optional(),
|
|
831
|
+
stage: z.string().optional().describe('Move the post to this stage (id, slug, or name).'),
|
|
832
|
+
script: z.string().optional(),
|
|
833
|
+
notes: z.string().optional(),
|
|
834
|
+
},
|
|
835
|
+
}, async (args, extra) => {
|
|
836
|
+
try {
|
|
837
|
+
const client = await getClient(extra);
|
|
838
|
+
const { postId, ...input } = args;
|
|
839
|
+
return postSummaryResult(await client.updatePost(postId, input), 'Updated');
|
|
840
|
+
}
|
|
841
|
+
catch (err) {
|
|
842
|
+
return errorResult(err);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
// -- archive_post ---------------------------------------------------------
|
|
846
|
+
server.registerTool('archive_post', {
|
|
847
|
+
title: 'Archive Post',
|
|
848
|
+
annotations: WRITE,
|
|
849
|
+
description: 'Archive a post (sets status to archived; reversible by updating the status back). ContentHero never hard-deletes. Requires the pipeline:write scope.',
|
|
850
|
+
inputSchema: {
|
|
851
|
+
postId: z.string().describe('The post id to archive.'),
|
|
852
|
+
},
|
|
853
|
+
}, async (args, extra) => {
|
|
854
|
+
try {
|
|
855
|
+
const client = await getClient(extra);
|
|
856
|
+
return postSummaryResult(await client.archivePost(args.postId), 'Archived');
|
|
857
|
+
}
|
|
858
|
+
catch (err) {
|
|
859
|
+
return errorResult(err);
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
// -- add_post_destination -------------------------------------------------
|
|
863
|
+
server.registerTool('add_post_destination', {
|
|
864
|
+
title: 'Add Post Destination',
|
|
865
|
+
annotations: WRITE,
|
|
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.",
|
|
867
|
+
inputSchema: {
|
|
868
|
+
postId: z.string().describe('The post id.'),
|
|
869
|
+
platform: z.enum(POST_PLATFORMS).describe('Destination platform.'),
|
|
870
|
+
format: z.string().optional().describe("Platform format, e.g. 'post', 'reel', 'story', 'short', 'thread'."),
|
|
871
|
+
connectedAccountId: z.string().optional().describe('The connected account to publish through.'),
|
|
872
|
+
scheduledAt: z.string().optional().describe('ISO-8601 scheduled time for this destination.'),
|
|
873
|
+
},
|
|
874
|
+
}, async (args, extra) => {
|
|
875
|
+
try {
|
|
876
|
+
const client = await getClient(extra);
|
|
877
|
+
return destinationResult(await client.addPostDestination(args.postId, {
|
|
878
|
+
platform: args.platform,
|
|
879
|
+
format: args.format,
|
|
880
|
+
connectedAccountId: args.connectedAccountId,
|
|
881
|
+
scheduledAt: args.scheduledAt,
|
|
882
|
+
}));
|
|
883
|
+
}
|
|
884
|
+
catch (err) {
|
|
885
|
+
return errorResult(err);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
// -- update_post_destination ----------------------------------------------
|
|
889
|
+
server.registerTool('update_post_destination', {
|
|
890
|
+
title: 'Update Post Destination',
|
|
891
|
+
annotations: WRITE,
|
|
892
|
+
description: 'Update one of a post\'s destinations (format, connected account, scheduled time, or status). Requires the pipeline:write scope.',
|
|
893
|
+
inputSchema: {
|
|
894
|
+
postId: z.string().describe('The post id.'),
|
|
895
|
+
destinationId: z.string().describe('The destination id (from get_post).'),
|
|
896
|
+
format: z.string().optional(),
|
|
897
|
+
connectedAccountId: z.string().optional(),
|
|
898
|
+
scheduledAt: z.string().optional().describe('ISO-8601 scheduled time, or empty to clear.'),
|
|
899
|
+
status: z.string().optional(),
|
|
900
|
+
},
|
|
901
|
+
}, async (args, extra) => {
|
|
902
|
+
try {
|
|
903
|
+
const client = await getClient(extra);
|
|
904
|
+
return destinationResult(await client.updatePostDestination(args.postId, args.destinationId, {
|
|
905
|
+
format: args.format,
|
|
906
|
+
connectedAccountId: args.connectedAccountId,
|
|
907
|
+
scheduledAt: args.scheduledAt,
|
|
908
|
+
status: args.status,
|
|
909
|
+
}));
|
|
910
|
+
}
|
|
911
|
+
catch (err) {
|
|
912
|
+
return errorResult(err);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
// -- add_post_asset -------------------------------------------------------
|
|
916
|
+
server.registerTool('add_post_asset', {
|
|
917
|
+
title: 'Add Post Asset',
|
|
918
|
+
annotations: WRITE,
|
|
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.",
|
|
920
|
+
inputSchema: {
|
|
921
|
+
postId: z.string().describe('The post id.'),
|
|
922
|
+
assetType: z.enum(['image', 'video', 'audio', 'document', 'link']).describe('The kind of asset.'),
|
|
923
|
+
assetUrl: z.string().describe('Public URL of the asset.'),
|
|
924
|
+
displayName: z.string().optional().describe('Optional display name.'),
|
|
925
|
+
},
|
|
926
|
+
}, async (args, extra) => {
|
|
927
|
+
try {
|
|
928
|
+
const client = await getClient(extra);
|
|
929
|
+
return assetResult(await client.addPostAsset(args.postId, {
|
|
930
|
+
assetType: args.assetType,
|
|
931
|
+
assetUrl: args.assetUrl,
|
|
932
|
+
displayName: args.displayName,
|
|
933
|
+
}));
|
|
934
|
+
}
|
|
935
|
+
catch (err) {
|
|
936
|
+
return errorResult(err);
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
// -- schedule_post --------------------------------------------------------
|
|
940
|
+
server.registerTool('schedule_post', {
|
|
941
|
+
title: 'Schedule Post',
|
|
942
|
+
annotations: WRITE,
|
|
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.',
|
|
944
|
+
inputSchema: {
|
|
945
|
+
postId: z.string().describe('The post id.'),
|
|
946
|
+
scheduledAt: z
|
|
947
|
+
.string()
|
|
948
|
+
.nullable()
|
|
949
|
+
.describe('ISO-8601 timestamp to schedule, or null to clear the schedule.'),
|
|
950
|
+
},
|
|
951
|
+
}, async (args, extra) => {
|
|
952
|
+
try {
|
|
953
|
+
const client = await getClient(extra);
|
|
954
|
+
return postSummaryResult(await client.schedulePost(args.postId, args.scheduledAt), 'Scheduled');
|
|
955
|
+
}
|
|
956
|
+
catch (err) {
|
|
957
|
+
return errorResult(err);
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
// -- publish_post ---------------------------------------------------------
|
|
961
|
+
server.registerTool('publish_post', {
|
|
962
|
+
title: 'Publish Post',
|
|
963
|
+
annotations: PUBLISH,
|
|
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.",
|
|
965
|
+
inputSchema: {
|
|
966
|
+
postId: z.string().describe('The post id to publish.'),
|
|
967
|
+
platform: z.enum(POST_PLATFORMS).optional().describe('Publish only this platform. Omit to publish all destinations.'),
|
|
968
|
+
},
|
|
969
|
+
}, async (args, extra) => {
|
|
970
|
+
try {
|
|
971
|
+
const client = await getClient(extra);
|
|
972
|
+
return publishResult(await client.publishPost(args.postId, { platform: args.platform }));
|
|
973
|
+
}
|
|
974
|
+
catch (err) {
|
|
975
|
+
return errorResult(err);
|
|
976
|
+
}
|
|
977
|
+
});
|
|
978
|
+
// -- list_inspiration_accounts --------------------------------------------
|
|
979
|
+
server.registerTool('list_inspiration_accounts', {
|
|
980
|
+
title: 'List Inspiration Accounts',
|
|
981
|
+
annotations: READ,
|
|
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.",
|
|
983
|
+
}, async (extra) => {
|
|
984
|
+
try {
|
|
985
|
+
const client = await getClient(extra);
|
|
986
|
+
return trackedAccountListResult(await client.listInspirationAccounts(), 'inspiration account(s)');
|
|
987
|
+
}
|
|
988
|
+
catch (err) {
|
|
989
|
+
return errorResult(err);
|
|
990
|
+
}
|
|
991
|
+
});
|
|
992
|
+
// -- get_inspiration_account ----------------------------------------------
|
|
993
|
+
server.registerTool('get_inspiration_account', {
|
|
994
|
+
title: 'Get Inspiration Account',
|
|
995
|
+
annotations: READ,
|
|
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.",
|
|
997
|
+
inputSchema: {
|
|
998
|
+
accountId: z.string().describe('The account id from list_inspiration_accounts.'),
|
|
999
|
+
},
|
|
1000
|
+
}, async (args, extra) => {
|
|
1001
|
+
try {
|
|
1002
|
+
const client = await getClient(extra);
|
|
1003
|
+
return inspirationAccountResult(await client.getInspirationAccount(args.accountId));
|
|
1004
|
+
}
|
|
1005
|
+
catch (err) {
|
|
1006
|
+
return errorResult(err);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
// -- list_outliers --------------------------------------------------------
|
|
1010
|
+
server.registerTool('list_outliers', {
|
|
1011
|
+
title: 'List Outliers',
|
|
1012
|
+
annotations: READ,
|
|
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.",
|
|
1014
|
+
inputSchema: {
|
|
1015
|
+
platform: z.enum(['youtube', 'instagram']).optional().describe('Filter to one platform.'),
|
|
1016
|
+
contentType: z.string().optional().describe("Filter by content type, e.g. 'video', 'short', 'reel'."),
|
|
1017
|
+
minOutlierScore: z.number().optional().describe('Only content at or above this outlier score.'),
|
|
1018
|
+
search: z.string().optional().describe('Text search across title, creator, handle, and description.'),
|
|
1019
|
+
sortBy: z.enum(['score', 'date', 'views']).optional().describe("Sort order (default 'score')."),
|
|
1020
|
+
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 20).'),
|
|
1021
|
+
offset: z.number().int().min(0).optional().describe('Pagination offset.'),
|
|
1022
|
+
},
|
|
1023
|
+
}, async (args, extra) => {
|
|
1024
|
+
try {
|
|
1025
|
+
const client = await getClient(extra);
|
|
1026
|
+
return outlierListResult(await client.listOutliers({
|
|
1027
|
+
platform: args.platform,
|
|
1028
|
+
contentType: args.contentType,
|
|
1029
|
+
minOutlierScore: args.minOutlierScore,
|
|
1030
|
+
search: args.search,
|
|
1031
|
+
sortBy: args.sortBy,
|
|
1032
|
+
limit: args.limit,
|
|
1033
|
+
offset: args.offset,
|
|
1034
|
+
}));
|
|
1035
|
+
}
|
|
1036
|
+
catch (err) {
|
|
1037
|
+
return errorResult(err);
|
|
1038
|
+
}
|
|
1039
|
+
});
|
|
1040
|
+
// -- get_inspiration_content ----------------------------------------------
|
|
1041
|
+
server.registerTool('get_inspiration_content', {
|
|
1042
|
+
title: 'Get Inspiration Content',
|
|
1043
|
+
annotations: READ,
|
|
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.",
|
|
1045
|
+
inputSchema: {
|
|
1046
|
+
contentId: z.string().describe('The content id from list_outliers or get_inspiration_account.'),
|
|
1047
|
+
},
|
|
1048
|
+
}, async (args, extra) => {
|
|
1049
|
+
try {
|
|
1050
|
+
const client = await getClient(extra);
|
|
1051
|
+
return inspirationContentResult(await client.getInspirationContent(args.contentId));
|
|
1052
|
+
}
|
|
1053
|
+
catch (err) {
|
|
1054
|
+
return errorResult(err);
|
|
1055
|
+
}
|
|
1056
|
+
});
|
|
1057
|
+
// -- list_brand_accounts --------------------------------------------------
|
|
1058
|
+
server.registerTool('list_brand_accounts', {
|
|
1059
|
+
title: 'List Brand Accounts',
|
|
1060
|
+
annotations: READ,
|
|
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.",
|
|
1062
|
+
}, async (extra) => {
|
|
1063
|
+
try {
|
|
1064
|
+
const client = await getClient(extra);
|
|
1065
|
+
return trackedAccountListResult(await client.listBrandAccounts(), 'brand account(s)');
|
|
1066
|
+
}
|
|
1067
|
+
catch (err) {
|
|
1068
|
+
return errorResult(err);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
// -- get_brand_account_performance ----------------------------------------
|
|
1072
|
+
server.registerTool('get_brand_account_performance', {
|
|
1073
|
+
title: 'Get Brand Account Performance',
|
|
1074
|
+
annotations: READ,
|
|
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.",
|
|
1076
|
+
inputSchema: {
|
|
1077
|
+
accountId: z.string().describe('The account id from list_brand_accounts.'),
|
|
1078
|
+
},
|
|
1079
|
+
}, async (args, extra) => {
|
|
1080
|
+
try {
|
|
1081
|
+
const client = await getClient(extra);
|
|
1082
|
+
return brandPerformanceResult(await client.getBrandAccountPerformance(args.accountId));
|
|
1083
|
+
}
|
|
1084
|
+
catch (err) {
|
|
1085
|
+
return errorResult(err);
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1088
|
+
// -- list_connected_accounts ----------------------------------------------
|
|
1089
|
+
server.registerTool('list_connected_accounts', {
|
|
1090
|
+
title: 'List Connected Accounts',
|
|
1091
|
+
annotations: READ,
|
|
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.",
|
|
1093
|
+
}, async (extra) => {
|
|
1094
|
+
try {
|
|
1095
|
+
const client = await getClient(extra);
|
|
1096
|
+
return connectedAccountListResult(await client.listConnectedAccounts());
|
|
1097
|
+
}
|
|
1098
|
+
catch (err) {
|
|
1099
|
+
return errorResult(err);
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
// -- get_connected_account ------------------------------------------------
|
|
1103
|
+
server.registerTool('get_connected_account', {
|
|
1104
|
+
title: 'Get Connected Account',
|
|
1105
|
+
annotations: READ,
|
|
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.",
|
|
1107
|
+
inputSchema: {
|
|
1108
|
+
accountId: z.string().describe('The connected account id from list_connected_accounts.'),
|
|
1109
|
+
},
|
|
1110
|
+
}, async (args, extra) => {
|
|
1111
|
+
try {
|
|
1112
|
+
const client = await getClient(extra);
|
|
1113
|
+
return connectedAccountResult(await client.getConnectedAccount(args.accountId));
|
|
1114
|
+
}
|
|
1115
|
+
catch (err) {
|
|
1116
|
+
return errorResult(err);
|
|
1117
|
+
}
|
|
1118
|
+
});
|
|
572
1119
|
// -- get_balance ----------------------------------------------------------
|
|
573
1120
|
server.registerTool('get_balance', {
|
|
574
1121
|
title: 'Get Balance',
|
|
1122
|
+
annotations: READ,
|
|
575
1123
|
description: 'Get the current ContentHero credit balance, subscription tier, and auto-top-up state.',
|
|
576
|
-
}, async () => {
|
|
1124
|
+
}, async (extra) => {
|
|
577
1125
|
try {
|
|
578
|
-
const
|
|
579
|
-
return balanceResult(
|
|
1126
|
+
const client = await getClient(extra);
|
|
1127
|
+
return balanceResult(await client.getBalance());
|
|
580
1128
|
}
|
|
581
1129
|
catch (err) {
|
|
582
1130
|
return errorResult(err);
|
|
583
1131
|
}
|
|
584
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.3' });
|
|
1142
|
+
registerTools(server, { getClient: () => getClient(), models });
|
|
585
1143
|
return server;
|
|
586
1144
|
}
|
|
587
1145
|
//# sourceMappingURL=server.js.map
|