talk_to_department({ domain: "content" }) from an MCP client, and the Content plays in the Claude Code plugin and the VS Code extension. All three run the same department, so a draft is grounded the same way whichever one asked for it.
Until September 2026 that grounding was doctrine the plugin taught and nothing enforced: the department could write a blog post without opening the account’s avatars, name no journey stage, invent an internal link, and publish a body with two H1s and no meta description. This page describes what the department now does before, during and after a draft, and the contract each behaviour rests on.
Foundation first
Before any outline, draft or rewrite, the department reads the account’s foundation from its hydrated data files, and falls back to the matching list tool when a file is missing:
Two of those rows changed in this release. The brand file used to omit the copy guardrails, so the department could not see
ai_forbidden_phrases, copy_dos or copy_donts even when the brand guide had them; it now carries them, along with ai_preferred_phrases, ai_brand_adjectives, ai_prompt_rules and copy_reading_level. The content file used to carry a slug and no address; every item now carries url (see Internal links from real URLs).
With the foundation read, four rules shape the draft:
- No usable avatar, no draft. An avatar needs a name,
pain_points,motivationsandbuying_behavior.objections. When none has them, the department says which fields are missing, points at the Customer Avatars page (/dashboard/marketing/customer-avatar) to fill them in, and offers to draft for the closest active avatar only when one is usable. Placeholder names, empty pain points and a “small business owners” persona with nothing specific behind it are reported, not used. - The avatar’s fields are the outline, not the industry. Pain points and frustrations become the angle and the H2s, motivations become the payoff, objections become the sections that answer them, and the typical quote can open the piece.
- The stage decides the job. An awareness-stage reader gets the problem named and an explanation worth saving; a consideration-stage reader gets the comparison and the proof; a decision-stage reader gets the offer, the objection handling and the booking call to action.
- The grid item supplies the promise. The draft writes the before the customer described and the after they got. It never invents a before the customer did not say.
content_create or content_update as target_keyword. It is stored on the item as settings.target_keyword, which is the same value the marketing editor’s Target keyword field on the SEO tab reads and writes, the pre-publish check checks placement against, and publishing enrols in rank tracking.
Brand voice is enforced, not assumed
A draft is checked against the brand guide before it is persisted:brand_is words present, brand_is_not words absent, copy_dos followed, copy_donts avoided, copy_reading_level respected. Any phrase from ai_forbidden_phrases in the title, excerpt, meta or body is a hard fail, and so is any phrase on the department’s own banned list:
elevate, unlock, unleash, seamless, game-changer, game-changing, cutting-edge, best-in-class, world-class, in today’s fast-paced world, in today’s digital age, ever-evolving, navigate the landscape, delve, dive in, let’s dive in, leverage, empower, supercharge, revolutionize, robust, holistic, synergy, at the end of the day, excited to announce, thrilled to share, your journey, it’s not just X it’s Y, here’s the thing, ready to level up, pro tip, discover how, look no further, in conclusionThe department never persists a draft under a hard fail. It rewrites once; if the rewrite still fails it says exactly what fails and stops. The same forbidden phrases are an error in the pre-publish check, so a phrase that slips past the department is still caught in the editor and on the API. Long-form drafts also run through a four-rule rubric before
content_create or content_update, and a draft that fails is rewritten once, then reported:
- Specific claims with a source. Every number, result or quote points at a grid item, a knowledge-base document, a tool result or a URL the department fetched. No source, no claim.
- No filler openers. The first sentence carries the persona’s pain or the payoff; no rhetorical question the next line answers.
- One idea per paragraph, one job per section, under a heading that says what the section proves.
- A call to action matched to the stage. Awareness earns a save or a subscribe, consideration earns a comparison or a checklist, decision asks for the booking or the purchase with the brand’s
cta_primary.
Internal links from real URLs
The department links at least two existing published pieces from every draft, by their live URL, with anchor text that names what the reader gets there. When fewer than two published pieces exist it writesLinks: 0 or Links: 1 in the header and says so rather than inventing links to make the count.
The URLs come from data, never from guesswork. Three reads return them:
content_listandcontent_getnow carryurlon every row: the live URL of a published, site-linked item, ornull.content_getaddsurl_source, which names the rule that produced the address:published_route(stamped by publish-to-site from the collection’s route pattern and the entry’s real fields),route_pattern(the collection’s pattern resolved with the entry slug alone) orpage_list(the route the project’s own page list gives the entry’s content file). Anullmeans the row cannot resolve its route.data/content_items.jsoncarries the sameurlper item, pluswebsite_project_id,cms_collection_id,cms_entry_slugandpublished_route, so the department can link without a tool call.GET /api/olympus/marketing/content/site-links?project_id=<uuid>(thecontent_site_linksMCP tool) lists every published post and page on one website project with its URL: the list a writer picks link targets from.
type is post for content items and page for page and landing_page items and for routes from the page list or the crawled index (source is content_item, page_list or crawl). limit defaults to and caps at 500; capped says when the cap cut the list. A missing or non-UUID project_id is 400 project_id_required; a project outside the account is 404 project_not_found. Every omission is explained in notes rather than hidden: a project with no production host returns an empty list and says to deploy it or add a domain, and a published item whose route cannot be derived is counted in posts.without_url with the fix named (set routePattern on the collection, or republish the item).
A URL is derived only for a row whose status is
published, on a project with a production host (the active primary production domain, else the project’s custom domain, else its deployed production tier), from a route that can be resolved. Anything else is null. A null is honest; a guessed URL is a broken link on a live page.The pre-publish check
Beforecontent_publish_to_site (and before manage_cms_write_entry on a site-only entry) the department states the pre-publish list in its reply, with a pass or fail per item, and fixes every fail first:
meta_titleis 60 characters or fewer and carries the keywordmeta_descriptionis 160 characters or fewer- the body has at most one H1 and the title or H1 carries the target keyword
- the keyword appears in the title, the slug and the first 100 words
- the hero image has alt text (
featured_image_alt) and every inline image has alt text - at least two internal links to published pieces, none invented
- no phrase from
ai_forbidden_phrasesor the banned list
warnings[] and a structured seo_check, so a department that skipped the list is still told. The check never blocks a publish. What it checks, how it scores, and every surface it runs on are on Pre-Publish SEO Check.
From draft to the live site
A blog or article meant for the customer’s site is one content item with its CMS link on the row, not two separate writes:1
Create the item with its destination
content_create(..., website_project_id, cms_collection_id, target_keyword). The CMS link travels with the item and the keyword is what closes the measurement loop at publish time. Every content_create and content_update parameter the routes read is declared on the tools, so featured_image_alt, meta_keywords, canonical_url, settings and the CMS link no longer fall off between the department and the row.2
Draft and refine
content_update with the grounded draft, the meta fields and the featured image.3
Publish to the site
content_publish_to_site(content_id) maps the fields to the collection schema, validates, saves the versioned entry file, resolves the live route and enrols the target keyword in rank tracking. On a Hiveku-hosted site the entry lands in the working tree and goes live on the next deploy; on a Webflow-backed project the same call stages the item in Webflow and it goes live with an item publish or the next site publish (see Webflow as the Content Source).4
Or schedule it
A linked item can be timed instead: the schedule lands on the CMS scheduler that runs, and an item not linked to a collection cannot be scheduled. See Scheduling and the Content Calendar.
Resuming a department turn
talk_to_department waits about 110 seconds for a reply, while a department turn may legitimately run for up to 1200 seconds. A long drafting turn therefore times out at the bridge while the department is still working. The timed-out result is an error, but it now carries session_id, turn_id and whatever had already arrived (response, tool_calls, data_updates), and error states the exact next step. Two follow-ups exist.
Poll the turn. GET /api/olympus/marketing/ai/turns/:turnId (the department_turn_get MCP tool) reads the department’s turn log under the same API key, scoped to the key’s account, and folds it into the shape a finished talk_to_department returns:
status is running, completed, errored or cancelled. stale is true when a running turn has produced no event for five minutes, which is a dead worker rather than a slow one. Pass since_seq=<last_seq> to read only what arrived since the last poll, and events=1 to include the raw { seq, event_type, event_data } rows. A turn keeps its events for 30 days; an older finished turn still answers with its status and timestamps, but response is empty and events_available is false, because the transcript itself is the chat session, not this route. A turn id outside the account is a 404.
Resume the conversation. Pass the session_id back into talk_to_department and the department continues with its full prior context. When the timeout happened before any response headers arrived, turn_id is null and the error says so: a turn may or may not have started upstream, so re-read the records the ask would touch, wait 30 seconds, then retry or break the request into a smaller ask. A session_id the caller passed in is still returned on that path, so a resumed conversation never looks as lost as a fresh one.
Related pages
- Pre-Publish SEO Check — every check, its level and the field it names, and the surfaces it runs on
- Scheduling and the Content Calendar — the scheduler that executes, and the month view of what will publish
- Use the Marketing Content Review Workflow — the dashboard side: drafts, reviews, approvals and the editor
- Customer Journey, Before/After Grid and Brand Guide — the foundation the department reads first
- Webflow as the Content Source — the same publish path on a Webflow-hosted site