@contenthero/mcp 0.4.1 → 0.4.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 +42 -9
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +91 -27
- package/dist/format.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +198 -68
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -37,8 +37,8 @@ import { z } from 'zod';
|
|
|
37
37
|
import { GenerationTimeoutError, pendingOutputId, } from '@contenthero/sdk';
|
|
38
38
|
import { getClient as defaultGetClient } from './client.js';
|
|
39
39
|
import { resolveModelEnums, BOARD_TYPES, BOARD_TYPE_GUIDANCE, IMAGE_MODEL_GUIDANCE, VIDEO_MODEL_GUIDANCE, AUDIO_MODEL_GUIDANCE, EDIT_AUDIO_MODEL_GUIDANCE, UPSCALE_MODEL_GUIDANCE, LIP_SYNC_MODEL_GUIDANCE, } from './models.js';
|
|
40
|
-
import { audioResult, avatarListResult, avatarResult, balanceResult, brandKitListResult, brandKitResult, brandKnowledgeListResult, brandKnowledgeDetailResult, brandKnowledgeSearchResult, brandKnowledgeItemResult, completedResult, connectedAccountListResult, connectedAccountResult, costResult, accountDetailResult, inspirationContentResult, mediaListResult, mediaSearchResult, folderListResult, folderContentsResult, mediaBatchResult, mediaUploadResult, importedMediaResult, uploadedMediaResult, tagListResult, tagResult, tagDeletedResult, modelListResult, modelResult, platformListResult, platformResult, elementListResult, elementResult, elementDeletedResult, errorResult, generationBatchResult, outlierListResult, enhanceClipsResult, pendingResult, stageListResult, spaceDeletedResult, spaceListResult, spaceResult, cardListResult, cardResult, postSummaryResult, publishResult, statusActionResult, editorOpsResult, text, projectDetailResult, liveContextResult, projectListResult, projectCreatedResult, projectDeletedResult, layerTypesResult, timelineTypesResult, editorTranscriptResult, exportJobResult, exportFormatsResult, trackedAccountListResult, transcriptResult, voiceListResult, voiceResult, } from './format.js';
|
|
41
|
-
/** Platforms a
|
|
40
|
+
import { audioResult, avatarListResult, avatarResult, avatarPendingResult, balanceResult, brandKitListResult, brandKitResult, brandKnowledgeListResult, brandKnowledgeDetailResult, brandKnowledgeSearchResult, brandKnowledgeItemResult, completedResult, connectedAccountListResult, connectedAccountResult, costResult, accountDetailResult, inspirationContentResult, mediaListResult, mediaSearchResult, folderListResult, folderContentsResult, mediaBatchResult, mediaUploadResult, importedMediaResult, uploadedMediaResult, tagListResult, tagResult, tagDeletedResult, modelListResult, modelResult, platformListResult, platformResult, elementListResult, elementResult, elementDeletedResult, errorResult, generationBatchResult, outlierListResult, enhanceClipsResult, pendingResult, stageListResult, spaceDeletedResult, spaceListResult, spaceResult, cardListResult, cardResult, postSummaryResult, publishResult, statusActionResult, editorOpsResult, text, projectDetailResult, liveContextResult, projectListResult, projectCreatedResult, projectDeletedResult, layerTypesResult, timelineTypesResult, editorTranscriptResult, exportJobResult, exportFormatsResult, trackedAccountListResult, transcriptResult, voiceListResult, voiceResult, } from './format.js';
|
|
41
|
+
/** Platforms a card or one of its posts may target. */
|
|
42
42
|
const POST_PLATFORMS = [
|
|
43
43
|
'youtube',
|
|
44
44
|
'instagram',
|
|
@@ -247,11 +247,11 @@ export function registerTools(server, opts) {
|
|
|
247
247
|
handleOrUrl: z.string().describe('A profile url, or a handle when platform is given.'),
|
|
248
248
|
}),
|
|
249
249
|
]);
|
|
250
|
-
const
|
|
251
|
-
platform: z.enum(POST_PLATFORMS).describe('The platform. This is the KEY: one
|
|
250
|
+
const cardPostSchema = z.object({
|
|
251
|
+
platform: z.enum(POST_PLATFORMS).describe('The platform. This is the KEY: one post per platform.'),
|
|
252
252
|
format: z.string().optional(),
|
|
253
253
|
connectedAccountId: z.string().nullable().optional(),
|
|
254
|
-
scheduledAt: z.string().nullable().optional().describe("Per-
|
|
254
|
+
scheduledAt: z.string().nullable().optional().describe("Per-post override of the card's schedule."),
|
|
255
255
|
platformSpecificData: z.record(z.string(), z.unknown()).optional().describe('The publish payload for this platform.'),
|
|
256
256
|
status: z.string().optional(),
|
|
257
257
|
});
|
|
@@ -286,6 +286,10 @@ export function registerTools(server, opts) {
|
|
|
286
286
|
.array(z.string())
|
|
287
287
|
.optional()
|
|
288
288
|
.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.'),
|
|
289
|
+
avatarId: z
|
|
290
|
+
.string()
|
|
291
|
+
.optional()
|
|
292
|
+
.describe('Optional avatar id from list_avatars. File the result onto that avatar as a new LOOK (one appearance of a reusable character: same person, different outfit, setting or framing) instead of saving a standalone library output. Combine with a referenceImage of the avatar to keep the subject on-model.'),
|
|
289
293
|
...PLACEMENT_INPUT_FIELDS,
|
|
290
294
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
291
295
|
},
|
|
@@ -302,6 +306,7 @@ export function registerTools(server, opts) {
|
|
|
302
306
|
seed: args.seed,
|
|
303
307
|
references: buildReferences({ images: args.referenceImages }),
|
|
304
308
|
parameters: args.mode ? { mode: args.mode } : undefined,
|
|
309
|
+
avatarId: args.avatarId,
|
|
305
310
|
projectId: args.projectId,
|
|
306
311
|
placement: args.placement,
|
|
307
312
|
playheadFrame: args.playheadFrame,
|
|
@@ -344,6 +349,10 @@ export function registerTools(server, opts) {
|
|
|
344
349
|
.optional()
|
|
345
350
|
.describe('Number of board variations (1-4). Defaults to 1.'),
|
|
346
351
|
boardName: z.string().optional().describe('Optional name for the board.'),
|
|
352
|
+
avatarId: z
|
|
353
|
+
.string()
|
|
354
|
+
.optional()
|
|
355
|
+
.describe('Optional avatar id from list_avatars. Associate the board with that avatar, so a character sheet built for an avatar stays filed against it.'),
|
|
347
356
|
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of generating (nothing runs, nothing is charged).'),
|
|
348
357
|
},
|
|
349
358
|
}, async (args, extra) => {
|
|
@@ -355,6 +364,7 @@ export function registerTools(server, opts) {
|
|
|
355
364
|
referenceImages: args.referenceImages,
|
|
356
365
|
numImages: args.numImages,
|
|
357
366
|
boardName: args.boardName,
|
|
367
|
+
avatarId: args.avatarId,
|
|
358
368
|
});
|
|
359
369
|
if (args.getCost)
|
|
360
370
|
return costResult(await client.estimateBoardCost(request));
|
|
@@ -735,6 +745,115 @@ export function registerTools(server, opts) {
|
|
|
735
745
|
return errorResult(err);
|
|
736
746
|
}
|
|
737
747
|
});
|
|
748
|
+
// -- create_avatar --------------------------------------------------------
|
|
749
|
+
server.registerTool('create_avatar', {
|
|
750
|
+
title: 'Create Avatar',
|
|
751
|
+
annotations: WRITE,
|
|
752
|
+
description: "Create a reusable character and start generating its first look. SPENDS CREDITS (pass getCost to preview the price without creating anything). Returns as soon as the record exists: the avatar is NOT usable yet, it sits at status 'processing' with no image until its first look finishes, so poll get_avatar until status is 'completed'. Supply referenceImageUrls to make the avatar a likeness of a real person from their photos; omit them to invent a character from the description and traits.",
|
|
753
|
+
inputSchema: {
|
|
754
|
+
name: z.string().describe('Avatar name, at least 3 characters.'),
|
|
755
|
+
age: z.string().describe("Apparent age, e.g. '20s', '35', 'middle-aged'. Required: the prompt writer describes the character from these traits."),
|
|
756
|
+
gender: z.string().describe('Gender presentation. Required, same reason as age.'),
|
|
757
|
+
ethnicity: z.string().optional().describe('Optional ethnicity, for a more specific likeness.'),
|
|
758
|
+
niche: z.array(z.string()).optional().describe('Content niches this character is for, e.g. ["fitness","nutrition"].'),
|
|
759
|
+
style: z.string().optional().describe('Visual style hint for the portrait, e.g. "editorial", "cinematic". Not stored on the avatar.'),
|
|
760
|
+
description: z
|
|
761
|
+
.string()
|
|
762
|
+
.optional()
|
|
763
|
+
.describe('Free-text description of the character. The strongest single input when no reference photos are given.'),
|
|
764
|
+
defaultVoiceId: z.string().optional().describe('A voiceId from list_voices, used as this avatar the default voice.'),
|
|
765
|
+
referenceImageUrls: z
|
|
766
|
+
.array(z.string())
|
|
767
|
+
.optional()
|
|
768
|
+
.describe('Photos of a REAL PERSON to anchor identity to: each a URL or a previous output id. Only use photos of someone who has agreed to being cloned.'),
|
|
769
|
+
getCost: z.boolean().optional().describe('Return the credit cost estimate instead of creating (nothing runs, nothing is charged).'),
|
|
770
|
+
},
|
|
771
|
+
}, async (args, extra) => {
|
|
772
|
+
try {
|
|
773
|
+
const client = await getClient(extra);
|
|
774
|
+
if (args.getCost) {
|
|
775
|
+
const creditsEstimate = await client.estimateAvatarCost();
|
|
776
|
+
// `modelId` is the first thing `costResult` names in its sentence, and `contentType` only
|
|
777
|
+
// admits image/video/audio. An avatar is none of those: the price is the fixed
|
|
778
|
+
// avatar-creation fee, not the cost of the portrait model, so say that rather than pick a
|
|
779
|
+
// media kind that would misdescribe it.
|
|
780
|
+
return costResult({ getCost: true, creditsEstimate, modelId: 'avatar creation' });
|
|
781
|
+
}
|
|
782
|
+
const created = await client.createAvatar({
|
|
783
|
+
name: args.name,
|
|
784
|
+
age: args.age,
|
|
785
|
+
gender: args.gender,
|
|
786
|
+
ethnicity: args.ethnicity,
|
|
787
|
+
niche: args.niche,
|
|
788
|
+
style: args.style,
|
|
789
|
+
description: args.description,
|
|
790
|
+
defaultVoiceId: args.defaultVoiceId,
|
|
791
|
+
referenceImageUrls: args.referenceImageUrls,
|
|
792
|
+
});
|
|
793
|
+
return avatarPendingResult(created);
|
|
794
|
+
}
|
|
795
|
+
catch (err) {
|
|
796
|
+
return errorResult(err);
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
// -- update_avatar --------------------------------------------------------
|
|
800
|
+
server.registerTool('update_avatar', {
|
|
801
|
+
title: 'Update Avatar',
|
|
802
|
+
annotations: WRITE,
|
|
803
|
+
description: "Update an avatar and/or change its looks. Fields: name, defaultLookId (also becomes the avatar's profile photo), defaultVoiceId. Looks are changed through ops, the same shape update_timeline and update_canvas use: add_look files images the account ALREADY OWNS onto the avatar, remove_look trashes one (recoverable for 30 days). Ops run before the fields, so one call can add a look and make it the default. To GENERATE a new look instead of filing an existing image, call generate_image with avatarId.",
|
|
804
|
+
inputSchema: {
|
|
805
|
+
avatarId: z.string().describe('The avatar id from list_avatars.'),
|
|
806
|
+
name: z.string().optional().describe('New name, at least 3 characters.'),
|
|
807
|
+
defaultLookId: z
|
|
808
|
+
.string()
|
|
809
|
+
.optional()
|
|
810
|
+
.describe("A look id from get_avatar. Becomes the avatar's default look AND its profile photo."),
|
|
811
|
+
defaultVoiceId: z.string().nullable().optional().describe('A voiceId from list_voices, or null to clear it.'),
|
|
812
|
+
ops: z
|
|
813
|
+
.array(z.union([
|
|
814
|
+
z.object({
|
|
815
|
+
op: z.literal('add_look'),
|
|
816
|
+
imageUrls: z
|
|
817
|
+
.array(z.string())
|
|
818
|
+
.describe('Images the account already owns, as URLs: an upload, a creation, an editor export, or another avatar look. Anything not owned by this account is skipped rather than failing the call.'),
|
|
819
|
+
}),
|
|
820
|
+
z.object({
|
|
821
|
+
op: z.literal('remove_look'),
|
|
822
|
+
lookId: z.string().describe('A look id from get_avatar.'),
|
|
823
|
+
}),
|
|
824
|
+
]))
|
|
825
|
+
.optional()
|
|
826
|
+
.describe('Look changes, applied in order before the field updates. NOT a transaction: a failure part-way leaves earlier ops applied.'),
|
|
827
|
+
},
|
|
828
|
+
}, async (args, extra) => {
|
|
829
|
+
try {
|
|
830
|
+
const client = await getClient(extra);
|
|
831
|
+
const { avatarId, ...request } = args;
|
|
832
|
+
const updated = await client.updateAvatar(avatarId, request);
|
|
833
|
+
return avatarResult(updated.avatar);
|
|
834
|
+
}
|
|
835
|
+
catch (err) {
|
|
836
|
+
return errorResult(err);
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
// -- delete_avatar --------------------------------------------------------
|
|
840
|
+
server.registerTool('delete_avatar', {
|
|
841
|
+
title: 'Delete Avatar',
|
|
842
|
+
annotations: WRITE,
|
|
843
|
+
description: "Delete an avatar. Soft: the avatar stops appearing, but ITS LOOKS SURVIVE as library images and can be filed onto another avatar with update_avatar's add_look. Use this to retire a duplicate or an abandoned character, after moving any looks worth keeping.",
|
|
844
|
+
inputSchema: {
|
|
845
|
+
avatarId: z.string().describe('The avatar id from list_avatars.'),
|
|
846
|
+
},
|
|
847
|
+
}, async (args, extra) => {
|
|
848
|
+
try {
|
|
849
|
+
const client = await getClient(extra);
|
|
850
|
+
await client.deleteAvatar(args.avatarId);
|
|
851
|
+
return text(`Avatar ${args.avatarId} deleted. Its looks are retained and can be filed onto another avatar with update_avatar add_look.`);
|
|
852
|
+
}
|
|
853
|
+
catch (err) {
|
|
854
|
+
return errorResult(err);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
738
857
|
// -- list_voices ----------------------------------------------------------
|
|
739
858
|
server.registerTool('list_voices', {
|
|
740
859
|
title: 'List Voices',
|
|
@@ -1157,11 +1276,11 @@ export function registerTools(server, opts) {
|
|
|
1157
1276
|
annotations: READ,
|
|
1158
1277
|
description: "Return the contents of one folder. The folder id is either one of the account's own folder ids or a built-in derived-folder key. A manual folder returns exactly the items filed in it; a smart folder computes its members live from its saved query; a derived folder returns its built-in set. Items are media (with kind and a description) and, in manual folders, entities such as projects or posts.",
|
|
1159
1278
|
inputSchema: {
|
|
1160
|
-
|
|
1279
|
+
folderId: z.string().describe('A folder id, or a derived-folder key (recents, favorites, edits, canvas, cards).'),
|
|
1161
1280
|
},
|
|
1162
1281
|
}, async (args, extra) => {
|
|
1163
1282
|
try {
|
|
1164
|
-
const r = await (await getClient(extra)).getFolder(args.
|
|
1283
|
+
const r = await (await getClient(extra)).getFolder(args.folderId);
|
|
1165
1284
|
return folderContentsResult(r.folder, r.items);
|
|
1166
1285
|
}
|
|
1167
1286
|
catch (err) {
|
|
@@ -1176,11 +1295,11 @@ export function registerTools(server, opts) {
|
|
|
1176
1295
|
name: z.string().describe('The folder name.'),
|
|
1177
1296
|
type: z.enum(['manual', 'smart']).optional().describe("'manual' (a collection you file items into) or 'smart' (a saved live query). Defaults to manual."),
|
|
1178
1297
|
query: smartQuerySchema,
|
|
1179
|
-
|
|
1298
|
+
parentId: z.string().optional().describe('Nest the new folder under this parent folder id.'),
|
|
1180
1299
|
},
|
|
1181
1300
|
}, async (args, extra) => {
|
|
1182
1301
|
try {
|
|
1183
|
-
const f = await (await getClient(extra)).createFolder({ name: args.name, type: args.type, query: args.query, parentId: args.
|
|
1302
|
+
const f = await (await getClient(extra)).createFolder({ name: args.name, type: args.type, query: args.query, parentId: args.parentId });
|
|
1184
1303
|
return text(`Created ${f.type} folder "${f.name}" (id ${f.id}).`);
|
|
1185
1304
|
}
|
|
1186
1305
|
catch (err) {
|
|
@@ -1188,32 +1307,32 @@ export function registerTools(server, opts) {
|
|
|
1188
1307
|
}
|
|
1189
1308
|
});
|
|
1190
1309
|
/**
|
|
1191
|
-
* One item's universal identity. NO
|
|
1192
|
-
*
|
|
1310
|
+
* One item's universal identity. NO folderId: the folder is named by the tool's own folderId /
|
|
1311
|
+
* folderIds now, which is what lets one call file many items into many folders.
|
|
1193
1312
|
*/
|
|
1194
1313
|
const itemRefBodySchema = z.object({
|
|
1195
|
-
|
|
1196
|
-
|
|
1314
|
+
sourceTable: z.string().describe("The item's source table (e.g. as returned by list_media / get_media)."),
|
|
1315
|
+
sourceRecordId: z.string().describe("The item's source record id."),
|
|
1197
1316
|
variant: z.number().int().optional().describe('The variation index (default 0 for single-asset items).'),
|
|
1198
1317
|
});
|
|
1199
1318
|
server.registerTool('update_folder', {
|
|
1200
1319
|
title: 'Update Folder',
|
|
1201
1320
|
annotations: WRITE,
|
|
1202
|
-
description: "Update the account's own folders: rename one, MOVE folders under a different parent (or to the top level with a null parent), change a smart folder's saved query, and FILE or UNFILE items. addItems/removeItems are DELTAS of {
|
|
1321
|
+
description: "Update the account's own folders: rename one, MOVE folders under a different parent (or to the top level with a null parent), change a smart folder's saved query, and FILE or UNFILE items. addItems/removeItems are DELTAS of { sourceTable, sourceRecordId, variant? }, not a list to replace, because an item can sit in several folders at once and a replace would silently unfile it from the others. Filing never moves or copies anything: it adds a pointer, and only manual folders accept items (a smart folder computes its own membership). Pass folderIds to patch several folders at once, which crossed with addItems files the same items into all of them; renaming and re-querying still need exactly one folder. NOTE the asymmetry: nesting a FOLDER via parentId is a MOVE (a folder has one parent), while filing an ITEM is a pointer that leaves its other folders alone.",
|
|
1203
1322
|
inputSchema: {
|
|
1204
|
-
|
|
1205
|
-
|
|
1323
|
+
folderId: z.string().describe('The folder id to update.'),
|
|
1324
|
+
folderIds: z
|
|
1206
1325
|
.array(z.string())
|
|
1207
1326
|
.optional()
|
|
1208
1327
|
.describe('Patch several folders at once. Attribute fields (name, query) still need exactly one.'),
|
|
1209
1328
|
name: z.string().optional().describe('A new name.'),
|
|
1210
|
-
|
|
1329
|
+
parentId: z.string().nullable().optional().describe('A new parent folder id, or null to move to the top level. MOVES the folder.'),
|
|
1211
1330
|
query: smartQuerySchema,
|
|
1212
|
-
|
|
1331
|
+
addItems: z
|
|
1213
1332
|
.array(itemRefBodySchema)
|
|
1214
1333
|
.optional()
|
|
1215
1334
|
.describe('File these items into the folder(s). A delta: their other folders are untouched.'),
|
|
1216
|
-
|
|
1335
|
+
removeItems: z
|
|
1217
1336
|
.array(itemRefBodySchema)
|
|
1218
1337
|
.optional()
|
|
1219
1338
|
.describe('Unfile these items. Only the pointer goes; the asset is never deleted.'),
|
|
@@ -1223,17 +1342,17 @@ export function registerTools(server, opts) {
|
|
|
1223
1342
|
const client = await getClient(extra);
|
|
1224
1343
|
const patch = {
|
|
1225
1344
|
name: args.name,
|
|
1226
|
-
parentId: args.
|
|
1345
|
+
parentId: args.parentId,
|
|
1227
1346
|
query: args.query,
|
|
1228
|
-
addItems: args.
|
|
1229
|
-
removeItems: args.
|
|
1347
|
+
addItems: args.addItems,
|
|
1348
|
+
removeItems: args.removeItems,
|
|
1230
1349
|
};
|
|
1231
|
-
const targets = args.
|
|
1350
|
+
const targets = args.folderIds?.length ? args.folderIds : [args.folderId];
|
|
1232
1351
|
const folders = targets.length > 1
|
|
1233
1352
|
? await client.updateFolders(targets, patch)
|
|
1234
1353
|
: [await client.updateFolder(targets[0], patch)];
|
|
1235
|
-
const filed = args.
|
|
1236
|
-
const unfiled = args.
|
|
1354
|
+
const filed = args.addItems?.length ?? 0;
|
|
1355
|
+
const unfiled = args.removeItems?.length ?? 0;
|
|
1237
1356
|
const what = [
|
|
1238
1357
|
filed ? `filed ${filed} item(s)` : null,
|
|
1239
1358
|
unfiled ? `unfiled ${unfiled} item(s)` : null,
|
|
@@ -1249,11 +1368,11 @@ export function registerTools(server, opts) {
|
|
|
1249
1368
|
title: 'Delete Folder',
|
|
1250
1369
|
annotations: WRITE,
|
|
1251
1370
|
description: "Delete one of the account's own folders and everything nested under it. This removes the folder structure only; the media and entities inside are pointers, so the underlying assets are never deleted. Confirm intent before deleting a folder that contains items.",
|
|
1252
|
-
inputSchema: {
|
|
1371
|
+
inputSchema: { folderId: z.string().describe('The folder id to delete.') },
|
|
1253
1372
|
}, async (args, extra) => {
|
|
1254
1373
|
try {
|
|
1255
|
-
await (await getClient(extra)).deleteFolder(args.
|
|
1256
|
-
return text(`Deleted folder ${args.
|
|
1374
|
+
await (await getClient(extra)).deleteFolder(args.folderId);
|
|
1375
|
+
return text(`Deleted folder ${args.folderId}.`);
|
|
1257
1376
|
}
|
|
1258
1377
|
catch (err) {
|
|
1259
1378
|
return errorResult(err);
|
|
@@ -1423,7 +1542,7 @@ export function registerTools(server, opts) {
|
|
|
1423
1542
|
server.registerTool('get_platform', {
|
|
1424
1543
|
title: 'Get Platform',
|
|
1425
1544
|
annotations: READ,
|
|
1426
|
-
description: "Get one platform's full publishing shape: the fields, options (enums), and character limits a post requires per format (post, reel, short, story, thread). Ground a
|
|
1545
|
+
description: "Get one platform's full publishing shape: the fields, options (enums), and character limits a post requires per format (post, reel, short, story, thread). Ground a post's platformSettings against this instead of guessing the fields. Optionally pass a format to narrow the result.",
|
|
1427
1546
|
inputSchema: {
|
|
1428
1547
|
platform: z
|
|
1429
1548
|
.enum(POST_PLATFORMS)
|
|
@@ -1462,11 +1581,11 @@ export function registerTools(server, opts) {
|
|
|
1462
1581
|
title: 'Get Element',
|
|
1463
1582
|
annotations: READ,
|
|
1464
1583
|
description: "Get one saved reference element by id: its name, category, description, and images.",
|
|
1465
|
-
inputSchema: {
|
|
1584
|
+
inputSchema: { elementId: z.string().describe('The element id.') },
|
|
1466
1585
|
}, async (args, extra) => {
|
|
1467
1586
|
try {
|
|
1468
1587
|
const client = await getClient(extra);
|
|
1469
|
-
return elementResult(await client.getElement(args.
|
|
1588
|
+
return elementResult(await client.getElement(args.elementId));
|
|
1470
1589
|
}
|
|
1471
1590
|
catch (err) {
|
|
1472
1591
|
return errorResult(err);
|
|
@@ -1505,7 +1624,7 @@ export function registerTools(server, opts) {
|
|
|
1505
1624
|
annotations: WRITE,
|
|
1506
1625
|
description: "Update a saved element's name, description, or category.",
|
|
1507
1626
|
inputSchema: {
|
|
1508
|
-
|
|
1627
|
+
elementId: z.string().describe('The element id.'),
|
|
1509
1628
|
name: z.string().optional(),
|
|
1510
1629
|
description: z.string().optional(),
|
|
1511
1630
|
category: z.enum(['auto', 'character', 'location', 'prop']).optional(),
|
|
@@ -1513,7 +1632,7 @@ export function registerTools(server, opts) {
|
|
|
1513
1632
|
}, async (args, extra) => {
|
|
1514
1633
|
try {
|
|
1515
1634
|
const client = await getClient(extra);
|
|
1516
|
-
return elementResult(await client.updateElement(args.
|
|
1635
|
+
return elementResult(await client.updateElement(args.elementId, { name: args.name, description: args.description, category: args.category }), 'Updated');
|
|
1517
1636
|
}
|
|
1518
1637
|
catch (err) {
|
|
1519
1638
|
return errorResult(err);
|
|
@@ -1524,12 +1643,12 @@ export function registerTools(server, opts) {
|
|
|
1524
1643
|
title: 'Delete Element',
|
|
1525
1644
|
annotations: WRITE,
|
|
1526
1645
|
description: 'Delete a saved reference element.',
|
|
1527
|
-
inputSchema: {
|
|
1646
|
+
inputSchema: { elementId: z.string().describe('The element id.') },
|
|
1528
1647
|
}, async (args, extra) => {
|
|
1529
1648
|
try {
|
|
1530
1649
|
const client = await getClient(extra);
|
|
1531
|
-
await client.deleteElement(args.
|
|
1532
|
-
return elementDeletedResult(args.
|
|
1650
|
+
await client.deleteElement(args.elementId);
|
|
1651
|
+
return elementDeletedResult(args.elementId);
|
|
1533
1652
|
}
|
|
1534
1653
|
catch (err) {
|
|
1535
1654
|
return errorResult(err);
|
|
@@ -1583,14 +1702,18 @@ export function registerTools(server, opts) {
|
|
|
1583
1702
|
});
|
|
1584
1703
|
// -- list_cards -----------------------------------------------------------
|
|
1585
1704
|
server.registerTool('list_cards', {
|
|
1586
|
-
title: 'List
|
|
1705
|
+
title: 'List Cards',
|
|
1587
1706
|
annotations: READ,
|
|
1588
|
-
description: "List
|
|
1707
|
+
description: "List one SPACE's cards (newest-updated first). ⚠️ SCOPED, NOT COMPLETE: without spaceId this lists the account's DEFAULT space only, and cards on any other board are absent with nothing in the response saying so (search misses them too). Call list_spaces FIRST and pass spaceId unless you specifically mean the default board. Filter by status, platform, stage (id/slug/name), favorite, or a title search. Call get_card for one card's full detail (posts + assets).",
|
|
1589
1708
|
inputSchema: {
|
|
1709
|
+
spaceId: z
|
|
1710
|
+
.string()
|
|
1711
|
+
.optional()
|
|
1712
|
+
.describe("Which space's board to list, from list_spaces. Omit ONLY when you mean the account's default space; omitting it does not search every board."),
|
|
1590
1713
|
status: z.enum(['draft', 'active', 'completed', 'archived']).optional().describe('Filter by lifecycle status.'),
|
|
1591
1714
|
platform: z.enum(POST_PLATFORMS).optional().describe('Filter by the post platform.'),
|
|
1592
|
-
stage: z.string().optional().describe('Filter by a stage id, slug, or name.'),
|
|
1593
|
-
search: z.string().optional().describe('Case-insensitive title search.'),
|
|
1715
|
+
stage: z.string().optional().describe('Filter by a stage id, slug, or name. Resolved within the chosen space.'),
|
|
1716
|
+
search: z.string().optional().describe('Case-insensitive title search, scoped to the chosen space.'),
|
|
1594
1717
|
limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 50).'),
|
|
1595
1718
|
offset: z.number().int().min(0).optional().describe('Pagination offset.'),
|
|
1596
1719
|
},
|
|
@@ -1598,6 +1721,7 @@ export function registerTools(server, opts) {
|
|
|
1598
1721
|
try {
|
|
1599
1722
|
const client = await getClient(extra);
|
|
1600
1723
|
return cardListResult(await client.listCards({
|
|
1724
|
+
spaceId: args.spaceId,
|
|
1601
1725
|
status: args.status,
|
|
1602
1726
|
platform: args.platform,
|
|
1603
1727
|
stage: args.stage,
|
|
@@ -1612,16 +1736,16 @@ export function registerTools(server, opts) {
|
|
|
1612
1736
|
});
|
|
1613
1737
|
// -- get_card -------------------------------------------------------------
|
|
1614
1738
|
server.registerTool('get_card', {
|
|
1615
|
-
title: 'Get
|
|
1739
|
+
title: 'Get Card',
|
|
1616
1740
|
annotations: READ,
|
|
1617
|
-
description: "Get one post in full: its fields (title, description, script, notes, status, stage, schedule), plus its publish
|
|
1741
|
+
description: "Get one post in full: its fields (title, description, script, notes, status, stage, schedule), plus its publish posts and attached assets.",
|
|
1618
1742
|
inputSchema: {
|
|
1619
|
-
|
|
1743
|
+
cardId: z.string().describe('The card id from list_cards.'),
|
|
1620
1744
|
},
|
|
1621
1745
|
}, async (args, extra) => {
|
|
1622
1746
|
try {
|
|
1623
1747
|
const client = await getClient(extra);
|
|
1624
|
-
return cardResult(await client.getCard(args.
|
|
1748
|
+
return cardResult(await client.getCard(args.cardId));
|
|
1625
1749
|
}
|
|
1626
1750
|
catch (err) {
|
|
1627
1751
|
return errorResult(err);
|
|
@@ -1717,7 +1841,7 @@ export function registerTools(server, opts) {
|
|
|
1717
1841
|
server.registerTool('delete_space', {
|
|
1718
1842
|
title: 'Delete Space',
|
|
1719
1843
|
annotations: WRITE,
|
|
1720
|
-
description: 'Delete a space. The server REFUSES a space that still holds cards and names the count, because the delete cascades to every card in it along with their covers, captions,
|
|
1844
|
+
description: 'Delete a space. The server REFUSES a space that still holds cards and names the count, because the delete cascades to every card in it along with their covers, captions, posts and schedules. Archive the space instead if you want it out of the way. Requires the planner:write scope.',
|
|
1721
1845
|
inputSchema: { spaceId: z.string().describe('The space id to delete.') },
|
|
1722
1846
|
}, async (args, extra) => {
|
|
1723
1847
|
try {
|
|
@@ -1733,11 +1857,17 @@ export function registerTools(server, opts) {
|
|
|
1733
1857
|
server.registerTool('list_stages', {
|
|
1734
1858
|
title: 'List Pipeline Stages',
|
|
1735
1859
|
annotations: READ,
|
|
1736
|
-
description: "List
|
|
1737
|
-
|
|
1860
|
+
description: "List one SPACE's stages, in order. ⚠️ STAGES ARE PER-SPACE: without spaceId this is the account's DEFAULT space, and two spaces can each hold a stage named 'Published' with different ids, so a stage name resolved against the wrong space is a different column. Stages are user-customizable (renamed, reordered, added, removed), so call this to discover the real stages before placing a card; pass a stage's id (most stable), slug, or name to create_card / update_card.",
|
|
1861
|
+
inputSchema: {
|
|
1862
|
+
spaceId: z
|
|
1863
|
+
.string()
|
|
1864
|
+
.optional()
|
|
1865
|
+
.describe("Which space's stages, from list_spaces. Omit only when you mean the account's default space."),
|
|
1866
|
+
},
|
|
1867
|
+
}, async (args, extra) => {
|
|
1738
1868
|
try {
|
|
1739
1869
|
const client = await getClient(extra);
|
|
1740
|
-
return stageListResult(await client.listStages());
|
|
1870
|
+
return stageListResult(await client.listStages({ spaceId: args.spaceId }));
|
|
1741
1871
|
}
|
|
1742
1872
|
catch (err) {
|
|
1743
1873
|
return errorResult(err);
|
|
@@ -1745,9 +1875,9 @@ export function registerTools(server, opts) {
|
|
|
1745
1875
|
});
|
|
1746
1876
|
// -- create_card ----------------------------------------------------------
|
|
1747
1877
|
server.registerTool('create_card', {
|
|
1748
|
-
title: 'Create
|
|
1878
|
+
title: 'Create Card',
|
|
1749
1879
|
annotations: WRITE,
|
|
1750
|
-
description: "Create a content
|
|
1880
|
+
description: "Create a card, the container in the content pipeline. A card holds the work (title, script, notes, cover) and the posts that publish it. Attach posts and media by passing `posts` and `assets` to update_card, then publish with publish_post. `stage` accepts a stage id/slug/name (defaults to the first stage). Requires a key with the planner:write scope.",
|
|
1751
1881
|
inputSchema: {
|
|
1752
1882
|
title: z.string().describe('Post title (required).'),
|
|
1753
1883
|
platform: z.enum(POST_PLATFORMS).describe('Primary platform for the post.'),
|
|
@@ -1780,11 +1910,11 @@ export function registerTools(server, opts) {
|
|
|
1780
1910
|
});
|
|
1781
1911
|
// -- update_card ----------------------------------------------------------
|
|
1782
1912
|
server.registerTool('update_card', {
|
|
1783
|
-
title: 'Update
|
|
1913
|
+
title: 'Update Card',
|
|
1784
1914
|
annotations: WRITE,
|
|
1785
|
-
description: "Update a
|
|
1915
|
+
description: "Update a card: its fields (title, description, script, notes, status, platform, cover, stage), its POSTS (one per platform, which is how it publishes), its ASSETS (the media on it, in order), and its SCHEDULE. posts and assets are DECLARATIVE: pass the WHOLE set, because anything you leave out is removed. Posts key on platform. Assets key on id, and THE ARRAY ORDER IS THE carousel ORDER, so reordering is just sending the same ids in a different order; keep an existing asset by id, add a new one by assetUrl or outputId. scheduledAt sets the time on the card AND every post (pass null to clear); give a post its own scheduledAt to override it for that platform. To publish NOW, use publish_post. Pass spaceId to MOVE the card to another space; without a stage it lands in the target space's stage whose slug matches its current one, or that space's first stage. Pass cardIds to update several cards at once, which crossed with spaceId is how a selection moves in one call; fields that describe ONE card (title, notes, script, cover) still need exactly one. Requires the planner:write scope.",
|
|
1786
1916
|
inputSchema: {
|
|
1787
|
-
|
|
1917
|
+
cardId: z.string().describe('The card id.'),
|
|
1788
1918
|
title: z.string().optional(),
|
|
1789
1919
|
platform: z.enum(POST_PLATFORMS).optional(),
|
|
1790
1920
|
status: z.enum(['draft', 'active', 'completed', 'archived']).optional(),
|
|
@@ -1812,11 +1942,11 @@ export function registerTools(server, opts) {
|
|
|
1812
1942
|
.string()
|
|
1813
1943
|
.nullable()
|
|
1814
1944
|
.optional()
|
|
1815
|
-
.describe('ISO time to publish. Sets the
|
|
1816
|
-
|
|
1817
|
-
.array(
|
|
1945
|
+
.describe('ISO time to publish. Sets the card AND every post. null clears the schedule.'),
|
|
1946
|
+
posts: z
|
|
1947
|
+
.array(cardPostSchema)
|
|
1818
1948
|
.optional()
|
|
1819
|
-
.describe("The
|
|
1949
|
+
.describe("The card's posts, each { platform, format?, connectedAccountId?, platformSpecificData?, scheduledAt?, status? }. REPLACES the set, keyed by platform; [] detaches all."),
|
|
1820
1950
|
assets: z
|
|
1821
1951
|
.array(postAssetSchema)
|
|
1822
1952
|
.optional()
|
|
@@ -1825,17 +1955,17 @@ export function registerTools(server, opts) {
|
|
|
1825
1955
|
}, async (args, extra) => {
|
|
1826
1956
|
try {
|
|
1827
1957
|
const client = await getClient(extra);
|
|
1828
|
-
const {
|
|
1958
|
+
const { cardId, cardIds, posts, assets, ...input } = args;
|
|
1829
1959
|
// The two declarative arrays are `unknown[]` in the schema (their entries are free-form objects the
|
|
1830
1960
|
// server validates), so they are cast at this one boundary rather than duplicating the shape in zod.
|
|
1831
1961
|
const patch = {
|
|
1832
1962
|
...input,
|
|
1833
|
-
...(
|
|
1963
|
+
...(posts !== undefined ? { posts: posts } : {}),
|
|
1834
1964
|
...(assets !== undefined ? { assets: assets } : {}),
|
|
1835
1965
|
};
|
|
1836
|
-
// `cardIds` widens the path id, matching update_folder's
|
|
1966
|
+
// `cardIds` widens the path id, matching update_folder's folderId / folderIds. One card still
|
|
1837
1967
|
// goes through updateCard so the single-card response shape is unchanged for every caller.
|
|
1838
|
-
const targets = cardIds?.length ? cardIds : [
|
|
1968
|
+
const targets = cardIds?.length ? cardIds : [cardId];
|
|
1839
1969
|
if (targets.length > 1) {
|
|
1840
1970
|
const cards = await client.updateCards(targets, patch);
|
|
1841
1971
|
return text(`Updated ${cards.length} cards.`);
|
|
@@ -1913,19 +2043,19 @@ export function registerTools(server, opts) {
|
|
|
1913
2043
|
return errorResult(err);
|
|
1914
2044
|
}
|
|
1915
2045
|
});
|
|
1916
|
-
// --
|
|
1917
|
-
server.registerTool('
|
|
2046
|
+
// -- publish_post ---------------------------------------------------------
|
|
2047
|
+
server.registerTool('publish_post', {
|
|
1918
2048
|
title: 'Publish Post',
|
|
1919
2049
|
annotations: PUBLISH,
|
|
1920
|
-
description: "Publish a
|
|
2050
|
+
description: "Publish a card's posts NOW: every post on the card, or only the named platform's post when `platform` is given. Each post 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 one result per post.",
|
|
1921
2051
|
inputSchema: {
|
|
1922
|
-
|
|
1923
|
-
platform: z.enum(POST_PLATFORMS).optional().describe('Publish only this platform. Omit to publish all
|
|
2052
|
+
cardId: z.string().describe('The card id to publish.'),
|
|
2053
|
+
platform: z.enum(POST_PLATFORMS).optional().describe('Publish only this platform. Omit to publish all posts.'),
|
|
1924
2054
|
},
|
|
1925
2055
|
}, async (args, extra) => {
|
|
1926
2056
|
try {
|
|
1927
2057
|
const client = await getClient(extra);
|
|
1928
|
-
return publishResult(await client.
|
|
2058
|
+
return publishResult(await client.publishPost(args.cardId, { platform: args.platform }));
|
|
1929
2059
|
}
|
|
1930
2060
|
catch (err) {
|
|
1931
2061
|
return errorResult(err);
|
|
@@ -2047,7 +2177,7 @@ export function registerTools(server, opts) {
|
|
|
2047
2177
|
server.registerTool('list_connected_accounts', {
|
|
2048
2178
|
title: 'List Connected Accounts',
|
|
2049
2179
|
annotations: READ,
|
|
2050
|
-
description: "List the social accounts the owner has connected (the publish targets), default first. Use an account's id as connectedAccountId on
|
|
2180
|
+
description: "List the social accounts the owner has connected (the publish targets), default first. Use an account's id as connectedAccountId on a post in update_card, then publish_post. Read-only: connecting an account is done in the ContentHero app.",
|
|
2051
2181
|
}, async (extra) => {
|
|
2052
2182
|
try {
|
|
2053
2183
|
const client = await getClient(extra);
|