@vruum/mcp 0.11.0 → 0.12.1

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/tools.json +2 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vruum/mcp",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "mcpName": "ai.vruum/mcp",
5
5
  "description": "Stdio MCP bridge for the hosted Vruum revenue-platform MCP (https://api.vruum.ai/mcp): serves the advertised tool surface for credential-free introspection and proxies tool calls with your Vruum token.",
6
6
  "license": "MIT",
package/tools.json CHANGED
@@ -1211,7 +1211,7 @@
1211
1211
  "readOnlyHint": false,
1212
1212
  "title": "Manage Content"
1213
1213
  },
1214
- "description": "Manage Content\n\nDraft, schedule and publish LinkedIn content — the consolidated content writer.\n\nContent actions (default kind; id = post UUID unless noted):\n- draft: create a post draft (no id; `payload` = {content, topic_tags?,\n scheduled_at?, author_user_id?, author_identity?, external_link?,\n attachment_creative_id?}); bulk via `items`.\n- edit: update a draft's content/tags/identity/link/attachment\n (`payload` = the fields to change: content?, topic_tags?,\n author_identity?, external_link? — empty-string external_link clears\n it; attachment_creative_id? — explicit null DETACHES, absent key\n leaves it untouched).\n- schedule: set the publish time (`payload` = {scheduled_at}). A\n row-persisted attachment publishes with the scheduled post.\n- publish: publish the post now (no payload; the retired publish-time\n creative_id param is rejected with the pointer to edit-attach).\n- delete: remove a draft from the calendar (drafts only — published\n posts are not deletable here).\n- draft_post: never generates prose — backend generation is removed\n (VRU-676, permanently). Always returns author-in-harness guidance:\n write the post yourself and submit via draft, then schedule/publish\n (no id; `payload` = {topic?, content_type?, author_user_id?}).\n\nAttachments (VRU-726) — document (PDF carousel), image, or video, ONE\ncontract, persisted on the row (schedulable + reviewable). Golden path\nfor a PDF, entirely via MCP:\n 1. manage_content kind='ad' action='store_creative'\n payload={filename:'my-deck.pdf', size_bytes:<n>,\n content_type:'application/pdf'} → returns upload_url\n 2. curl --fail-with-body -T my-deck.pdf '<upload_url>'\n 3. store_creative again with payload={creative_id:'<id>'} → validates\n (small assets can skip 1-3: payload={asset_base64:'<b64>'})\n 4. manage_content action='draft' payload={content:'<caption>',\n attachment_creative_id:'<id>'}\n 5. get_content_review post_ids=['<post_id>'] → OPEN attachment_url and\n review the actual file (it publishes under the author's identity)\n 6. manage_content action='publish' id='<post_id>'\nThe stored filename becomes the rendered LinkedIn document title\n(override at store time via `filename`). Rules: one attachment per post;\nnot on repost_commentary; caption text required; stored assets are\npublic-at-upload (public bucket, unguessable URL). Media transfers\ninline at publish — expect tens of seconds for large files. Attachment\nfailures revert the post to draft with a structured {code, message, fix}\n422 and never burn post budget on a failed fetch; an ambiguous provider\ntimeout stays terminal `failed` (`publish_outcome_unknown`) — check\nLinkedIn before retrying, a blind retry can double-post.\n\nIdentities (VRU-659): `author_identity` is 'member' (the author's\npersonal profile — default) or 'organization' (the Company Page).\nOrganization publish requires a resolvable Company Page on the author's\nLinkedIn account: exactly one Page on the account, or a selected Page\n(manage_content kind='ad' action='set_page'); the member account still\nauthenticates and must hold admin rights on the Page.\n\n`external_link` (http/https, <=2000 chars) renders as the LinkedIn\npreview card; UTM params are stamped when the link is SAVED (missing\nutm_* added, existing ones never clobbered), so the approved draft is\nexactly what publishes. Combines fine with an attachment.\n\nBulk: edit/schedule/publish/delete take id arrays (server-side loop,\nper-item status — partial failure reported per item, never silent; max\n100). Payloads are validated by the underlying route's schema.\n\nProse gate: draft/edit lint the content and schedule/publish re-lint it\nserver-side; blocked calls return error_code='prose_gate_blocked' with\nstructured `failures[].fix`. `payload` accepts `override_reason` (taste\noverride — proceeds despite block failures, logged to the corpus;\nhonored only for owner/operator roles on the tenant, otherwise ignored\nand the gate blocks normally) and `client_rules_version` (responses\nflag `rules_changed` when the server's rules differ). Pre-check drafts\ncheaply with check_prose.\n\nAd actions (kind='ad'; id = ad-campaign UUID): approve, reject, pause,\nresume, edit_budget — `payload` carries the action's fields (e.g.\n{daily_budget_cents} for edit_budget, {rejection_reason} for reject).\nboost (no id — there's no campaign yet): run a paid LinkedIn campaign.\n`payload` carries {content_post_id (sponsor an EXISTING published post)\nXOR creative_id (a stored VIDEO creative → Direct Sponsored Content,\nMETRICS-ONLY — no engager bridge), vehicle?, objective?,\nbudget:{daily_budget_cents/total_budget_cents}, audience (see below),\nduration_days?, approval_mode?('draft'|'auto'), integration_id?}.\n\nBoost audience — exactly one of:\n- {facets}: the PRIMARY form. LinkedIn-native firmographic targeting: a\n map of facet key (titles, seniorities, industries, locations,\n staff_count_ranges) → list of LinkedIn entity URNs. 'locations' is\n REQUIRED (LinkedIn rejects campaigns without location targeting).\n Values must be entity URNs, never display names — resolve names FIRST\n via `fetch type='ads' subtype='targeting_entities'\n filters={facet, q}` (that resolve-then-boost flow is the happy path).\n- {matched_audience_id}: a PRE-EXISTING uploaded matched audience only.\n- {criteria}: partner-gated — rejected 400 with code\n matched_audience_partner_gated (LinkedIn's audience-member API needs\n partner access); the error's `fix` shows the facets form to use instead.\n\nVehicle/objective/format legality (enforced server-side BEFORE any spend;\nillegal combos 400 with the legal alternatives enumerated):\n| vehicle | objectives | source | destination CTA |\n|------------------|--------------------------------------|-------------------|-------------------------|\n| thought_leader | BRAND_AWARENESS, ENGAGEMENT only | member post only | NOT allowed |\n| page_sponsored | all (BRAND_AWARENESS, ENGAGEMENT, | org post or video | required for |\n| | WEBSITE_VISIT, WEBSITE_CONVERSION, | creative_id (DSC) | WEBSITE_VISIT / |\n| | LEAD_GENERATION, VIDEO_VIEW) | | WEBSITE_CONVERSION |\nDestination = the post's external_link (set at draft/edit via this\naction's content kind) or the video creative's cta_destination_url.\nVehicle is inferred from the post's publish identity when omitted\n(member ⇒ thought_leader, organization ⇒ page_sponsored). Objectives use\nLinkedIn's enum verbatim — do not lowercase.\n\nstore_creative (no id — there's no campaign yet): store a harness creative\nas a DRAFT (zero spend; approval is separate). Stored assets serve ads AND\norganic post attachments (attach via this action's content kind\ndraft/edit payload.attachment_creative_id — VRU-726). BASE64 `payload`\n(small PNG/JPEG/PDF; format decided by server-side sniff):\n{asset_base64 (raw base64, no data: URL — renamed from image_base64),\ngeneration_prompt (required for images only), filename?, campaign_id?\n(owned draft warming campaign to attach), generation_provenance? ({model,\ntool, generated_at, notes} allow-list, string values)}. PRESIGNED\n`payload` (the primary path for real files — mp4 ≤200MB 3s–30min, or PDF\n≤25MB): {filename, size_bytes, content_type ('video/mp4' |\n'application/pdf')} → returns a presigned upload_url; `curl\n--fail-with-body -T <file> '<upload_url>'`, then call store_creative\nagain with {creative_id} to validate/finalize (idempotent; a validation\nfailure returns the failed row with ok=false and the restart step;\nunfinalized rows are swept after 24h). VIDEO also accepts {media_url\n(public https — fetched + validated in the background)} and optional\n{thumbnail_base64}. `filename` becomes the stored file_name — for\ndocuments it is the rendered LinkedIn carousel TITLE. media_url video\nstores are ASYNC — poll `fetch type='ads' subtype='creative'\nid=<creative_id>` until upload_status leaves 'uploading' ('failed' +\nerror_message starting with a probe code means re-export/re-store;\notherwise retriable).\n\nIdentity setup (kind='ad', no id — VRU-659):\n- set_page: `payload` = {organization_urn? (urn:li:organization:<digits>),\n integration_id?}. Omit organization_urn to DISCOVER the candidate Pages\n from the connected LinkedIn accounts, then call again with one. Required\n before page_sponsored campaigns and video ads.\n- authorize_author: `payload` = {author_user_id, auto_approve?,\n integration_id?} — records the operator ATTESTATION that this team\n member consented to Thought Leader Ad sponsorship (required before a\n thought_leader boost; LinkedIn still enforces its own permission).\n Response lists the currently authorized authors.\n- revoke_author: `payload` = {author_user_id, integration_id?}.\n\nAd bulk: `id` arrays loop with per-item status (partial failure is\nreported per item, never silent; max 100). Payloads are validated by the\nunderlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1214
+ "description": "Manage Content\n\nDraft, schedule and publish LinkedIn content — the consolidated content writer.\n\nContent actions (default kind; id = post UUID unless noted):\n- draft: create a post draft (no id; `payload` = {content, topic_tags?,\n scheduled_at?, author_user_id?, author_identity?, external_link?,\n attachment_creative_id?}); bulk via `items`.\n- edit: update a draft's content/tags/identity/link/attachment\n (`payload` = the fields to change: content?, topic_tags?,\n author_identity?, external_link? — empty-string external_link clears\n it; attachment_creative_id? — explicit null DETACHES, absent key\n leaves it untouched).\n- schedule: set the publish time (`payload` = {scheduled_at}). A\n row-persisted attachment publishes with the scheduled post.\n- publish: publish the post now (no payload; the retired publish-time\n creative_id param is rejected with the pointer to edit-attach).\n- delete: remove a draft from the calendar (drafts only — published\n posts are not deletable here).\n- draft_post: never generates prose — backend generation is removed\n (VRU-676, permanently). Always returns author-in-harness guidance:\n write the post yourself and submit via draft, then schedule/publish\n (no id; `payload` = {topic?, content_type?, author_user_id?}).\n\nAttachments (VRU-726) — document (PDF carousel), image, or video, ONE\ncontract, persisted on the row (schedulable + reviewable). Golden path\nfor a PDF, entirely via MCP:\n 1. manage_content kind='ad' action='store_creative'\n payload={filename:'my-deck.pdf', size_bytes:<n>,\n content_type:'application/pdf'} → returns upload_url\n 2. curl --fail-with-body -T my-deck.pdf '<upload_url>'\n 3. store_creative again with payload={creative_id:'<id>'} → validates\n (small assets can skip 1-3: payload={asset_base64:'<b64>'})\n 4. manage_content action='draft' payload={content:'<caption>',\n attachment_creative_id:'<id>'}\n 5. get_content_review post_ids=['<post_id>'] → OPEN attachment_url and\n review the actual file (it publishes under the author's identity)\n 6. manage_content action='publish' id='<post_id>'\nThe stored filename becomes the rendered LinkedIn document title\n(override at store time via `filename`). Rules: one attachment per post;\nnot on repost_commentary; caption text required; stored assets are\npublic-at-upload (public bucket, unguessable URL). Media transfers\ninline at publish — expect tens of seconds for large files. Attachment\nfailures revert the post to draft with a structured {code, message, fix}\n422 and never burn post budget on a failed fetch; an ambiguous provider\ntimeout stays terminal `failed` (`publish_outcome_unknown`) — check\nLinkedIn before retrying, a blind retry can double-post.\n\nIdentities (VRU-659): `author_identity` is 'member' (the author's\npersonal profile — default) or 'organization' (the Company Page).\nOrganization publish requires a resolvable Company Page on the author's\nLinkedIn account: exactly one Page on the account, or a selected Page\n(manage_content kind='ad' action='set_page'); the member account still\nauthenticates and must hold admin rights on the Page.\n\n`external_link` (http/https, <=2000 chars) renders as the LinkedIn\npreview card; UTM params are stamped when the link is SAVED (missing\nutm_* added, existing ones never clobbered), so the approved draft is\nexactly what publishes. Combines fine with an attachment.\n\nBulk: edit/schedule/publish/delete take id arrays (server-side loop,\nper-item status — partial failure reported per item, never silent; max\n100). Payloads are validated by the underlying route's schema.\n\nProse gate: draft/edit lint the content and schedule/publish re-lint it\nserver-side; blocked calls return error_code='prose_gate_blocked' with\nstructured `failures[].fix`. `payload` accepts `override_reason` (taste\noverride — proceeds despite block failures, logged to the corpus;\nhonored only for owner/operator roles on the tenant, otherwise ignored\nand the gate blocks normally) and `client_rules_version` (responses\nflag `rules_changed` when the server's rules differ). Pre-check drafts\ncheaply with check_prose.\n\nAd actions (kind='ad'; id = ad-campaign UUID): reject, pause — `payload`\ncarries the action's fields (e.g. {rejection_reason} for reject). This\ntool never starts, resumes or raises paid spend: a workspace owner or\noperator approves a drafted boost in the Vruum app's Work list, or\napproves, resumes or re-budgets it with the Vruum CLI (vruum ad approve |\nresume | budget).\nboost (no id — there's no campaign yet): prepare a paid LinkedIn campaign\nas a DRAFT; it pushes nothing and spends nothing, and it appears in the\nWork list for an owner to approve or decline. `payload` carries {content_post_id (sponsor an EXISTING\npublished post) XOR creative_id (a stored VIDEO creative → Direct\nSponsored Content, METRICS-ONLY — no engager bridge), vehicle?,\nobjective?, budget:{daily_budget_cents/total_budget_cents}, audience (see\nbelow), duration_days?, integration_id?}.\n\nBoost audience — exactly one of:\n- {facets}: the PRIMARY form. LinkedIn-native firmographic targeting: a\n map of facet key (titles, seniorities, industries, locations,\n staff_count_ranges) → list of LinkedIn entity URNs. 'locations' is\n REQUIRED (LinkedIn rejects campaigns without location targeting).\n Values must be entity URNs, never display names — resolve names FIRST\n via `fetch type='ads' subtype='targeting_entities'\n filters={facet, q}` (that resolve-then-boost flow is the happy path).\n- {matched_audience_id}: a PRE-EXISTING uploaded matched audience only.\n- {criteria}: partner-gated — rejected 400 with code\n matched_audience_partner_gated (LinkedIn's audience-member API needs\n partner access); the error's `fix` shows the facets form to use instead.\n\nVehicle/objective/format legality (enforced server-side BEFORE any spend;\nillegal combos 400 with the legal alternatives enumerated):\n| vehicle | objectives | source | destination CTA |\n|------------------|--------------------------------------|-------------------|-------------------------|\n| thought_leader | BRAND_AWARENESS, ENGAGEMENT only | member post only | NOT allowed |\n| page_sponsored | all (BRAND_AWARENESS, ENGAGEMENT, | org post or video | required for |\n| | WEBSITE_VISIT, WEBSITE_CONVERSION, | creative_id (DSC) | WEBSITE_VISIT / |\n| | LEAD_GENERATION, VIDEO_VIEW) | | WEBSITE_CONVERSION |\nDestination = the post's external_link (set at draft/edit via this\naction's content kind) or the video creative's cta_destination_url.\nVehicle is inferred from the post's publish identity when omitted\n(member ⇒ thought_leader, organization ⇒ page_sponsored). Objectives use\nLinkedIn's enum verbatim — do not lowercase.\n\nstore_creative (no id — there's no campaign yet): store a harness creative\nas a DRAFT (zero spend; approval is separate). Stored assets serve ads AND\norganic post attachments (attach via this action's content kind\ndraft/edit payload.attachment_creative_id — VRU-726). BASE64 `payload`\n(small PNG/JPEG/PDF; format decided by server-side sniff):\n{asset_base64 (raw base64, no data: URL — renamed from image_base64),\ngeneration_prompt (required for images only), filename?, campaign_id?\n(owned draft warming campaign to attach), generation_provenance? ({model,\ntool, generated_at, notes} allow-list, string values)}. PRESIGNED\n`payload` (the primary path for real files — mp4 ≤200MB 3s–30min, or PDF\n≤25MB): {filename, size_bytes, content_type ('video/mp4' |\n'application/pdf')} → returns a presigned upload_url; `curl\n--fail-with-body -T <file> '<upload_url>'`, then call store_creative\nagain with {creative_id} to validate/finalize (idempotent; a validation\nfailure returns the failed row with ok=false and the restart step;\nunfinalized rows are swept after 24h). VIDEO also accepts {media_url\n(public https — fetched + validated in the background)} and optional\n{thumbnail_base64}. `filename` becomes the stored file_name — for\ndocuments it is the rendered LinkedIn carousel TITLE. media_url video\nstores are ASYNC — poll `fetch type='ads' subtype='creative'\nid=<creative_id>` until upload_status leaves 'uploading' ('failed' +\nerror_message starting with a probe code means re-export/re-store;\notherwise retriable).\n\nIdentity setup (kind='ad', no id — VRU-659):\n- set_page: `payload` = {organization_urn? (urn:li:organization:<digits>),\n integration_id?}. Omit organization_urn to DISCOVER the candidate Pages\n from the connected LinkedIn accounts, then call again with one. Required\n before page_sponsored campaigns and video ads.\n- authorize_author: `payload` = {author_user_id, auto_approve?,\n integration_id?} — records the operator ATTESTATION that this team\n member consented to Thought Leader Ad sponsorship (required before a\n thought_leader boost; LinkedIn still enforces its own permission).\n Response lists the currently authorized authors.\n- revoke_author: `payload` = {author_user_id, integration_id?}.\n\nAd bulk: `id` arrays loop with per-item status (partial failure is\nreported per item, never silent; max 100). Payloads are validated by the\nunderlying route's own schema.\n\n### Responses:\n\n**200**: Successful Response (Success Response)\nContent-Type: application/json",
1215
1215
  "inputSchema": {
1216
1216
  "properties": {
1217
1217
  "action": {
@@ -1261,7 +1261,7 @@
1261
1261
  }
1262
1262
  ],
1263
1263
  "default": "content",
1264
- "description": "Content family: 'content' (default) for LinkedIn posts, or 'ad' to manage ad campaigns (actions: approve, reject, pause, resume, edit_budget, boost, store_creative, set_page, authorize_author, revoke_author).",
1264
+ "description": "Content family: 'content' (default) for LinkedIn posts, or 'ad' to manage ad campaigns (actions: reject, pause, boost (always a draft), store_creative, set_page, authorize_author, revoke_author).",
1265
1265
  "title": "kind",
1266
1266
  "type": "string"
1267
1267
  },