@contenthero/mcp 0.4.0 → 0.4.2

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/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, 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 post or destination may target. */
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 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 postDestinationSchema = z.object({
251
- platform: z.enum(POST_PLATFORMS).describe('The platform. This is the KEY: one destination per platform.'),
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-destination override of the post's schedule."),
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
  });
@@ -1357,7 +1357,7 @@ export function registerTools(server, opts) {
1357
1357
  }, async (args, extra) => {
1358
1358
  try {
1359
1359
  const client = await getClient(extra);
1360
- return uploadedMediaResult(await client.importMedia({
1360
+ return importedMediaResult(await client.importMedia({
1361
1361
  url: args.url,
1362
1362
  contentType: args.contentType,
1363
1363
  fileName: args.fileName,
@@ -1423,7 +1423,7 @@ export function registerTools(server, opts) {
1423
1423
  server.registerTool('get_platform', {
1424
1424
  title: 'Get Platform',
1425
1425
  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 destination's platformSettings against this instead of guessing the fields. Optionally pass a format to narrow the result.",
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 post's platformSettings against this instead of guessing the fields. Optionally pass a format to narrow the result.",
1427
1427
  inputSchema: {
1428
1428
  platform: z
1429
1429
  .enum(POST_PLATFORMS)
@@ -1583,13 +1583,13 @@ export function registerTools(server, opts) {
1583
1583
  });
1584
1584
  // -- list_cards -----------------------------------------------------------
1585
1585
  server.registerTool('list_cards', {
1586
- title: 'List Posts',
1586
+ title: 'List Cards',
1587
1587
  annotations: READ,
1588
- 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_card for one post's full detail (destinations + assets).",
1588
+ description: "List the account's content-pipeline posts (newest-updated first). Filter by status, platform, stage (id/slug/name), folder, favorite, or a title search. Call get_card for one post's full detail (posts + assets).",
1589
1589
  inputSchema: {
1590
1590
  status: z.enum(['draft', 'active', 'completed', 'archived']).optional().describe('Filter by lifecycle status.'),
1591
1591
  platform: z.enum(POST_PLATFORMS).optional().describe('Filter by the post platform.'),
1592
- pipelineStage: z.string().optional().describe('Filter by a pipeline stage id, slug, or name.'),
1592
+ stage: z.string().optional().describe('Filter by a stage id, slug, or name.'),
1593
1593
  search: z.string().optional().describe('Case-insensitive title search.'),
1594
1594
  limit: z.number().int().min(1).max(100).optional().describe('How many to return (default 50).'),
1595
1595
  offset: z.number().int().min(0).optional().describe('Pagination offset.'),
@@ -1600,7 +1600,7 @@ export function registerTools(server, opts) {
1600
1600
  return cardListResult(await client.listCards({
1601
1601
  status: args.status,
1602
1602
  platform: args.platform,
1603
- pipelineStage: args.pipelineStage,
1603
+ stage: args.stage,
1604
1604
  search: args.search,
1605
1605
  limit: args.limit,
1606
1606
  offset: args.offset,
@@ -1612,16 +1612,16 @@ export function registerTools(server, opts) {
1612
1612
  });
1613
1613
  // -- get_card -------------------------------------------------------------
1614
1614
  server.registerTool('get_card', {
1615
- title: 'Get Post',
1615
+ title: 'Get Card',
1616
1616
  annotations: READ,
1617
- description: "Get one post in full: its fields (title, description, script, notes, status, stage, schedule), plus its publish destinations and attached assets.",
1617
+ description: "Get one post in full: its fields (title, description, script, notes, status, stage, schedule), plus its publish posts and attached assets.",
1618
1618
  inputSchema: {
1619
- postId: z.string().describe('The post id from list_cards.'),
1619
+ cardId: z.string().describe('The card id from list_cards.'),
1620
1620
  },
1621
1621
  }, async (args, extra) => {
1622
1622
  try {
1623
1623
  const client = await getClient(extra);
1624
- return cardResult(await client.getCard(args.postId));
1624
+ return cardResult(await client.getCard(args.cardId));
1625
1625
  }
1626
1626
  catch (err) {
1627
1627
  return errorResult(err);
@@ -1666,7 +1666,7 @@ export function registerTools(server, opts) {
1666
1666
  server.registerTool('create_space', {
1667
1667
  title: 'Create Space',
1668
1668
  annotations: WRITE,
1669
- description: "Create a space: a new planner board with its own stages. duplicateFrom copies another space's STAGES, never its cards, so the new board arrives with the columns and none of the work. Requires the pipeline:write scope.",
1669
+ description: "Create a space: a new planner board with its own stages. duplicateFrom copies another space's STAGES, never its cards, so the new board arrives with the columns and none of the work. Requires the planner:write scope.",
1670
1670
  inputSchema: {
1671
1671
  name: z.string().describe('The space name.'),
1672
1672
  coverUrl: z.string().optional().describe('A cover image URL for the space tile.'),
@@ -1692,7 +1692,7 @@ export function registerTools(server, opts) {
1692
1692
  server.registerTool('update_space', {
1693
1693
  title: 'Update Space',
1694
1694
  annotations: WRITE,
1695
- description: "Rename a space or change its cover. This is a PATCH: a field you omit is left alone, so renaming does not disturb the cover. Pass coverUrl as an empty string to REMOVE the cover. To favorite or archive a space, use the `favorite` and `archive` tools with assetType 'space' instead. Requires the pipeline:write scope.",
1695
+ description: "Rename a space or change its cover. This is a PATCH: a field you omit is left alone, so renaming does not disturb the cover. Pass coverUrl as an empty string to REMOVE the cover. To favorite or archive a space, use the `favorite` and `archive` tools with assetType 'space' instead. Requires the planner:write scope.",
1696
1696
  inputSchema: {
1697
1697
  spaceId: z.string().describe('The space id to update.'),
1698
1698
  name: z.string().optional().describe('A new name.'),
@@ -1717,7 +1717,7 @@ export function registerTools(server, opts) {
1717
1717
  server.registerTool('delete_space', {
1718
1718
  title: 'Delete Space',
1719
1719
  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, destinations and schedules. Archive the space instead if you want it out of the way. Requires the pipeline:write scope.',
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, posts and schedules. Archive the space instead if you want it out of the way. Requires the planner:write scope.',
1721
1721
  inputSchema: { spaceId: z.string().describe('The space id to delete.') },
1722
1722
  }, async (args, extra) => {
1723
1723
  try {
@@ -1733,7 +1733,7 @@ export function registerTools(server, opts) {
1733
1733
  server.registerTool('list_stages', {
1734
1734
  title: 'List Pipeline Stages',
1735
1735
  annotations: READ,
1736
- 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_card / update_card.",
1736
+ description: "List the account's 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_card / update_card.",
1737
1737
  }, async (extra) => {
1738
1738
  try {
1739
1739
  const client = await getClient(extra);
@@ -1745,9 +1745,9 @@ export function registerTools(server, opts) {
1745
1745
  });
1746
1746
  // -- create_card ----------------------------------------------------------
1747
1747
  server.registerTool('create_card', {
1748
- title: 'Create Post',
1748
+ title: 'Create Card',
1749
1749
  annotations: WRITE,
1750
- 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_card. `stage` accepts a stage id/slug/name (defaults to the first stage). Requires a key with the pipeline:write scope.",
1750
+ 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
1751
  inputSchema: {
1752
1752
  title: z.string().describe('Post title (required).'),
1753
1753
  platform: z.enum(POST_PLATFORMS).describe('Primary platform for the post.'),
@@ -1780,15 +1780,23 @@ export function registerTools(server, opts) {
1780
1780
  });
1781
1781
  // -- update_card ----------------------------------------------------------
1782
1782
  server.registerTool('update_card', {
1783
- title: 'Update Post',
1783
+ title: 'Update Card',
1784
1784
  annotations: WRITE,
1785
- description: "Update a post: its fields (title, description, script, notes, status, platform, cover, pipeline stage), its DESTINATIONS (which platforms it publishes to), its ASSETS (the media on it, in order), and its SCHEDULE. destinations and assets are DECLARATIVE: pass the WHOLE set, because anything you leave out is removed. Destinations 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 post AND every destination (pass null to clear); give a destination its own scheduledAt to override it for that platform. To publish NOW, use publish_card. Requires the pipeline:write scope.",
1785
+ 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
1786
  inputSchema: {
1787
- postId: z.string().describe('The post id.'),
1787
+ cardId: z.string().describe('The card id.'),
1788
1788
  title: z.string().optional(),
1789
1789
  platform: z.enum(POST_PLATFORMS).optional(),
1790
1790
  status: z.enum(['draft', 'active', 'completed', 'archived']).optional(),
1791
1791
  stage: z.string().optional().describe('Move the post to this stage (id, slug, or name).'),
1792
+ spaceId: z
1793
+ .string()
1794
+ .optional()
1795
+ .describe("Move the card to a different space (id or slug). Without a stage, it lands in that space's stage whose slug matches its current one, or that space's first stage."),
1796
+ cardIds: z
1797
+ .array(z.string())
1798
+ .optional()
1799
+ .describe('Update several cards at once. Fields that describe ONE card (title, notes, script, cover) still require exactly one.'),
1792
1800
  script: z.string().optional(),
1793
1801
  notes: z.string().optional(),
1794
1802
  coverUrl: z.string().optional().describe('Public URL for the post cover.'),
@@ -1804,11 +1812,11 @@ export function registerTools(server, opts) {
1804
1812
  .string()
1805
1813
  .nullable()
1806
1814
  .optional()
1807
- .describe('ISO time to publish. Sets the post AND every destination. null clears the schedule.'),
1808
- destinations: z
1809
- .array(postDestinationSchema)
1815
+ .describe('ISO time to publish. Sets the card AND every post. null clears the schedule.'),
1816
+ posts: z
1817
+ .array(cardPostSchema)
1810
1818
  .optional()
1811
- .describe("The post's destinations, each { platform, format?, connectedAccountId?, platformSpecificData?, scheduledAt?, status? }. REPLACES the set, keyed by platform; [] detaches all."),
1819
+ .describe("The card's posts, each { platform, format?, connectedAccountId?, platformSpecificData?, scheduledAt?, status? }. REPLACES the set, keyed by platform; [] detaches all."),
1812
1820
  assets: z
1813
1821
  .array(postAssetSchema)
1814
1822
  .optional()
@@ -1817,14 +1825,22 @@ export function registerTools(server, opts) {
1817
1825
  }, async (args, extra) => {
1818
1826
  try {
1819
1827
  const client = await getClient(extra);
1820
- const { postId, destinations, assets, ...input } = args;
1828
+ const { cardId, cardIds, posts, assets, ...input } = args;
1821
1829
  // The two declarative arrays are `unknown[]` in the schema (their entries are free-form objects the
1822
1830
  // server validates), so they are cast at this one boundary rather than duplicating the shape in zod.
1823
- return postSummaryResult(await client.updateCard(postId, {
1831
+ const patch = {
1824
1832
  ...input,
1825
- ...(destinations !== undefined ? { destinations: destinations } : {}),
1833
+ ...(posts !== undefined ? { posts: posts } : {}),
1826
1834
  ...(assets !== undefined ? { assets: assets } : {}),
1827
- }), 'Updated');
1835
+ };
1836
+ // `cardIds` widens the path id, matching update_folder's folder_id / folder_ids. One card still
1837
+ // goes through updateCard so the single-card response shape is unchanged for every caller.
1838
+ const targets = cardIds?.length ? cardIds : [cardId];
1839
+ if (targets.length > 1) {
1840
+ const cards = await client.updateCards(targets, patch);
1841
+ return text(`Updated ${cards.length} cards.`);
1842
+ }
1843
+ return postSummaryResult(await client.updateCard(targets[0], patch), 'Updated');
1828
1844
  }
1829
1845
  catch (err) {
1830
1846
  return errorResult(err);
@@ -1849,7 +1865,7 @@ export function registerTools(server, opts) {
1849
1865
  server.registerTool('create_tag', {
1850
1866
  title: 'Create Tag',
1851
1867
  annotations: WRITE,
1852
- description: "Create a tag in the account's tag library (the name is lowercased). Tags organize posts; apply them with the `tags` field on create_card / update_card. Requires the pipeline:write scope.",
1868
+ description: "Create a tag in the account's tag library (the name is lowercased). Tags organize posts; apply them with the `tags` field on create_card / update_card. Requires the planner:write scope.",
1853
1869
  inputSchema: {
1854
1870
  name: z.string().describe('The tag name (lowercased on save).'),
1855
1871
  },
@@ -1866,7 +1882,7 @@ export function registerTools(server, opts) {
1866
1882
  server.registerTool('update_tag', {
1867
1883
  title: 'Update Tag',
1868
1884
  annotations: WRITE,
1869
- description: 'Rename a tag (preserves its assignments on all posts). To detach a tag from one post, set that post\'s `tags` without it via update_card. Requires the pipeline:write scope.',
1885
+ description: 'Rename a tag (preserves its assignments on all posts). To detach a tag from one post, set that post\'s `tags` without it via update_card. Requires the planner:write scope.',
1870
1886
  inputSchema: {
1871
1887
  tagId: z.string().describe('The tag id (from list_tags).'),
1872
1888
  name: z.string().describe('The new tag name (lowercased on save).'),
@@ -1884,7 +1900,7 @@ export function registerTools(server, opts) {
1884
1900
  server.registerTool('delete_tag', {
1885
1901
  title: 'Delete Tag',
1886
1902
  annotations: WRITE,
1887
- description: "Delete a tag from the account's library. This DESTROYS the tag and removes it from every post it was on. To just detach a tag from one post, set that post's `tags` without it via update_card instead. Requires the pipeline:write scope.",
1903
+ description: "Delete a tag from the account's library. This DESTROYS the tag and removes it from every post it was on. To just detach a tag from one post, set that post's `tags` without it via update_card instead. Requires the planner:write scope.",
1888
1904
  inputSchema: {
1889
1905
  tagId: z.string().describe('The tag id (from list_tags).'),
1890
1906
  },
@@ -1897,19 +1913,19 @@ export function registerTools(server, opts) {
1897
1913
  return errorResult(err);
1898
1914
  }
1899
1915
  });
1900
- // -- publish_card ---------------------------------------------------------
1901
- server.registerTool('publish_card', {
1916
+ // -- publish_post ---------------------------------------------------------
1917
+ server.registerTool('publish_post', {
1902
1918
  title: 'Publish Post',
1903
1919
  annotations: PUBLISH,
1904
- 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.",
1920
+ 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.",
1905
1921
  inputSchema: {
1906
- postId: z.string().describe('The post id to publish.'),
1907
- platform: z.enum(POST_PLATFORMS).optional().describe('Publish only this platform. Omit to publish all destinations.'),
1922
+ cardId: z.string().describe('The card id to publish.'),
1923
+ platform: z.enum(POST_PLATFORMS).optional().describe('Publish only this platform. Omit to publish all posts.'),
1908
1924
  },
1909
1925
  }, async (args, extra) => {
1910
1926
  try {
1911
1927
  const client = await getClient(extra);
1912
- return publishResult(await client.publishCard(args.postId, { platform: args.platform }));
1928
+ return publishResult(await client.publishPost(args.cardId, { platform: args.platform }));
1913
1929
  }
1914
1930
  catch (err) {
1915
1931
  return errorResult(err);
@@ -2031,7 +2047,7 @@ export function registerTools(server, opts) {
2031
2047
  server.registerTool('list_connected_accounts', {
2032
2048
  title: 'List Connected Accounts',
2033
2049
  annotations: READ,
2034
- 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_card. Read-only: connecting an account is done in the ContentHero app.",
2050
+ 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.",
2035
2051
  }, async (extra) => {
2036
2052
  try {
2037
2053
  const client = await getClient(extra);