Documentation / Integrations
MCP (AI assistants)
MCP (Model Context Protocol) lets AI assistants like Claude operate Power Author through a set of structured tools — keyword research, modifier authoring, combination editing, and public hub setup — using a project-bound API key.
What MCP is
MCP is an open protocol for exposing tools and resources to AI assistants. Power Author ships an MCP server at /api/mcp; once you connect it to your assistant of choice, the assistant can call tools like list_combinations, add_keyword, update_combination, and more — exactly the same operations available in the dashboard, just driven by the assistant.
Prerequisites
- Generate an MCP API key in Workspace Settings → MCP & Tools. You can scope it to a single project (recommended for most workflows) or leave it workspace-wide and pass
projectIdon each tool call. - Add the server to your assistant. The connect card on the same settings page provides ready-to-paste configs for Cursor, Claude Code, and Claude Desktop.
- Workspace-wide keys: call
list_projectsfirst to find the rightprojectIdfor the project you want to work in.
Combination public hub workflow
For the underlying concepts — what a hub page is, when to use one, the three layout options, required fields — see Combinations → Public Hub Pages. The MCP tools below are the operational surface.
Which layout to pick
grouped(default) — section per IMMEDIATE parent of each article's varying-tag. Best for shallow (1–2 level) role trees, or when you want every L2 visible as its own section.grouped_by_top— section per TOP-LEVEL (L1) ancestor. Best for 3+ level trees (e.g. field → role → niche), so all articles in a field sit together under one section.flat— one grid, no section headings. Best when there's no meaningful grouping or very few articles.
Operations
- Enable hub:
update_combinationwith{ id, publishAsHub: true, slug: "cover-letter-examples", hubNavStyle: "grouped_by_top" }.hubNavStyledefaults to"grouped"when omitted. - Switch layout on a published hub:
update_combinationwith{ id, hubNavStyle: "grouped_by_top" }. This does NOT call unpublish — the new layout ships on the next Push hub to site or full publish run. - Author copy:
upsert_combination_hub_copywith{ combinationId, languageCode: "en", title, description, metaDescription }. At least one of the three text fields is required; omitted fields are left untouched on update. - Read back / verify:
get_combinationreturns core combination columns (includinghubNavStyle) plus atranslationsmap keyed by language code.list_combinationsreturns the same hub fields across every combination in one call — useful for auditing layouts. - Disable hub:
update_combinationwith{ id, publishAsHub: false }. This triggers a destination unpublish call BEFORE the toggle is persisted; if the unpublish fails, the toggle change is not applied and the tool returns an error.
Modifier descriptions workflow
- Find candidates:
list_modifierswith{ groupName: "role", level: 1 }returns all L1 role modifiers, now including their currentdescriptionfor the project's default (source) language. - Author one:
update_modifier_descriptionwith{ modifierId, description }. WhenlanguageCodeis omitted the project's default language is used — matching the typical “set the English description” flow. - Author many:
bulk_update_modifier_descriptionswith{ updates: [{ modifierId, description }, ...] }. Recommended for the “describe every L1 role” workflow. Returns per-row success or error. - Other locales come from the workspace Translate Taxonomy run; manual writes to non-source locales are allowed but may be overwritten by a later translation run.
Worked example — cover-letter hub grouped by field (L1)
Mirroring the example in Combinations → Public Hub Pages: a cover-letter-examples hub for a job-posting platform whose role tree has three levels (field → role → niche), so we want one section per field. End-to-end as MCP tool calls (the platform stays anonymous):
// 1. Find the combination you want to publish as a hub, and inspect
// its current settings + copy before changing anything.
list_combinations({}) // returns hubNavStyle on every row
get_combination({ id: 12 }) // detail for the chosen combination
// 2. Enable the hub with the L1 layout in one call. The role tree under
// this combination has 3 levels, so 'grouped_by_top' (one section per
// field) is the right pick.
update_combination({
id: 12,
publishAsHub: true,
slug: "cover-letter-examples",
hubNavStyle: "grouped_by_top"
})
// 3. Author the source-language hub copy (H1, intro, SEO meta).
upsert_combination_hub_copy({
combinationId: 12,
languageCode: "en",
title: "Cover Letter Examples for 1,000+ Roles",
description: "Browse cover letter examples for over a thousand job titles, organized by field. Each example shows two formats side by side, with notes on when to use each.",
metaDescription: "Cover letter examples for 1,000+ job titles, organized by field. See traditional and modern formats side by side."
})
// 4. Author one description paragraph per L1 (field) modifier - these
// render as the intro under each section heading on the hub.
list_modifiers({ groupName: "role", level: 1 }) // returns existing description for source language
bulk_update_modifier_descriptions({
updates: [
{ modifierId: 101, description: "Healthcare roles spanning patient care, clinical support, and allied health." },
{ modifierId: 102, description: "Engineering roles across software, hardware, and infrastructure." },
{ modifierId: 103, description: "Education roles from early childhood through higher education." }
]
})
// 5. Verify before the next publish run.
get_combination({ id: 12 })
// --- Migration variant ---------------------------------------------------
// Switch an EXISTING 'grouped' hub (parent-walk) to 'grouped_by_top' (L1
// buckets). Safe in-place change - no unpublish call. The new layout ships
// on the next 'Push hub to site' or full publish run.
update_combination({ id: 12, hubNavStyle: "grouped_by_top" })
// --- Audit variant -------------------------------------------------------
// See every combination's hub settings in one call (publishAsHub, slug,
// hubNavStyle), then drill in for the ones that need attention.
list_combinations({})Pruning the live catalogue by role (unpublish / republish)
When a site publishes one article per (combination × varying modifier) — e.g. five article types × a thousand roles — the useful unit for shrinking the catalogue is the role, not the keyword. delete_keyword removes one article type across every role and delete_modifier is permanent, so neither fits. The tools below change publish state only: nothing is deleted, generated content and modifier links survive, and the same URLs can be brought back later.
How it works
- Every real unpublish belongs to a prune batch.
dryRun: falseneedsbatch: { name, reason }(orbatchIdto add to an existing batch). Each article the run takes down is stamped with the batch, the time and a copy of the reason, and reads as the derived statusunpublishedinstead ofpipeline_completein the dashboard and inlist_articles. The next successful publish clears the stamp. - The destination's answer decides. The worker still fetches each URL after an unpublish, but only records the result: a page that still answers 200 from the destination's one-hour page cache is
stale_cacheand is checked again 65 minutes later. Only a page that is still live then (mismatch) turns the item into a failure. - Per-locale is opt-in and gated. Without
localesan unpublish removes every locale of the article. Withlocales: ["ko"]only those locales come down, the article stays published, and later publishes leave the hidden locales out. Power Author refuses per-locale calls unless the destination advertisescapabilities.perLocaleUnpublishonGET /api/career-advice/unpublish: a destination without it ignoreslocalesand takes down every locale. - Filters combine with AND.
modifierIdsmatches the article's VARYING modifier (exact match; passincludeDescendants: trueto cascade to L2/L3 children),combinationIdsrestricts to article types. At least one of the two is required. - Dry run by default. Both tools return counts only (articles by combination, by locale, total live URLs) unless you pass
dryRun: false. - Real runs are queued, not inline. A role-level prune is thousands of articles and tens of thousands of URLs, far more than one MCP request can process.
dryRun: falseenqueues a publish run (modeunpublishorpublish) that the background worker processes in batches of 25 with automatic retry of transient failures. The response carries arunId. - Idempotent and safe to re-issue. Only
publishedarticles are selected for unpublish (onlypipeline_completeones for republish); articles already in an active run are listed underskipped. Re-running the same call after a partial run only touches what is still live. - Every run is recorded. MCP-created runs store who (user or API key), when, the filter, and the dry-run summary on
publish_runs.source;list_publish_runsshows them so a later session can see what was pruned and why.
Operations
- Preview a prune:
unpublish_articleswith{ modifierIds: [123], includeDescendants: true }(dry run). Returnsarticles,byCombination,byLocaleandliveUrls; changes nothing. - Run it: the same call with
dryRun: falseandbatch: { name, reason }. Returns{ runId, queued, skipped, failed, batch }. - Track progress:
get_publish_runwith{ runId }— status, item breakdown,done, the recordedsource, URLverificationcounts, and up to 200 failed items with their error. - Bring a role back:
republish_articleswith the same filter anddryRun: false. Articles return at the same URLs. Articles under archived modifiers are skipped unlessforce: true. - Bring a batch back:
republish_articles({ batchId: 12, dryRun: false })restores exactly what batch 12 took down and is still down.list_prune_batchesandget_prune_batchshow each batch's runs and, by combination and by modifier, what is still down versus republished. - Retire a role:
archive_modifierswith{ modifierIds: [123], reason: "…" }. Generation skips archived modifiers, so a new combination creates no articles for them; publishing refuses their articles unlessforce: true;list_modifiershides them unlessarchived: "any". Descendants are included by default. Archiving does not unpublish unless you passunpublish: true;unarchive_modifiersreverses it. - Retire a locale:
archive_project_locale({ locale: "ko", reason: "…", dryRun: false })stops generation, translation and publishing of the locale and keeps its translations;unarchive_project_localereverses it andlist_project_localesshows each locale's state and counts. Archiving does not unpublish:unpublish_articles({ combinationIds: [7], locales: ["ko"], batch: { … }, dryRun: false })takes existing pages down where the destination supports it (get_locale_publish_settings). Only an archived locale's translations can be deleted:delete_project_locale_translations({ locale: "ko", dryRun: false, confirm: "ko" }), after a dry run that counts the rows in every table. Each change is recorded as a prune batch. - Verify from either side:
list_modifiersnow returnsarticleCounts: { published, unpublished }per modifier;list_articlesacceptsmodifierId,includeDescendantsandlocalefilters and returnsmodifierId,sentAt,localesandarchivedLocalesper article. - Deleting a modifier afterwards:
delete_modifieralso defaults to a dry run now, and its preview says how many affected articles are live. It never unpublishes — published articles stay on the site with their modifier link removed — so rununpublish_articlesfirst and wait for that run to finish.
Field reference
publishAsHub— boolean toggle oncombinations; OFF by default.slug— public hub URL segment oncombinations(the path on the destination is/career-advice/{slug}on Specific Resume). Distinct fromslugTemplate, which is the per-article URL slug template (e.g.{{role}}-resume-examples).hubNavStyle— one of"grouped"(default; section per IMMEDIATE parent of each article's varying-tag),"grouped_by_top"(section per TOP-LEVEL/L1 ancestor — recommended for 3+ level role trees), or"flat"(single grid, no sections). See the decision aid above and the dedicated Public Hub Pages page.title,description,metaDescriptionper locale oncombination_translations(keyed by(combinationId, languageId)).modifier_translations.descriptionper locale (keyed by(modifierId, languageId)) — the per-section intro paragraph rendered above each L1 bucket on grouped hub pages. There is nodescriptioncolumn onmodifiersitself.
Caveats & errors
- Toggling
publishAsHubfrom true to false fires an unpublish call to the destination site BEFORE persisting. If that call fails (network, 5xx, etc.), the toggle change is rolled back andupdate_combinationreturns an error so PA's view of the combination matches what the destination is actually serving. - REST and MCP share the same upsert helpers under the hood, so dashboard edits and MCP edits cannot drift apart.
- Source-language descriptions are typically the only ones authored by hand — let the workspace Translate Taxonomy run handle the rest.