Author content with your own agent
Zynth Auth's CMS is built for one loop: an AI agent drafts under its own identity, a person approves the exact bytes, and the published page proves both. This guide connects any MCP-capable model — Claude, GPT, Gemini, or your own harness — to that loop.
Availability: the content tools ride the MCP tool gateway, which is enabled per installation, and the deployment must have its content store configured. While either is off, the surfaces below return 404.
The design in one paragraph
The agent can draft, revise, ask for review, and read its feedback. It cannot publish — there is no publish tool on the agent surface: not disabled, absent. Publication spends a human approval that is fingerprinted to the content hash, so editing after approval invalidates it, and the public page renders its provenance (which agent, which model, which hash, who approved) in prose and in its Article JSON-LD. If a person never says yes, nothing ever goes public.
1. Register the agent and delegate the scope (admin)
The author is an ordinary Zynth agent principal with one delegated permission:
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \
-d '{"name": "content-author", "description": "Drafts blog posts over MCP"}' \
https://auth.example.com/api/v1/agents
Delegate content:draft:write to it (the console's Agents page, or the delegation API). That
scope covers every content tool; nothing else is needed — and deliberately nothing else works.
2. Point your model at the gateway
The content tools live on the internal zynth-content server, called through the gateway with
the agent's own token:
POST https://auth.example.com/api/v1/mcp/call/zynth-content
Authorization: Bearer <agent token>
Content-Type: application/json
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "create_draft", "arguments": { ... }}}
tools/list on the same endpoint returns the five tools:
| Tool | What it does |
|---|---|
create_draft | Start a post: title, description, kind, tags, markdown. |
update_draft | Revise it — each revision is a new immutable, content-addressed version. |
request_review | Park the ask with your reviewers, bound to the current bytes. |
get_review_feedback | Read every human verdict on this post — a denial's reason is the next revision's brief. |
list_my_drafts | Where the agent's posts sit. |
Rate limits are deliberate (20/min, 200/day): room to iterate, a wall for a runaway loop — plus a detection rule watching draft volume per agent.
What a draft may contain
Markdown (CommonMark). Every rule below is enforced at ingest and comes back as a
structured reason (code, detail, line) your agent can act on — nothing is silently
rewritten, so what you sent is what a reviewer reads and what the page serves:
| Field | Rule | Refusal code |
|---|---|---|
markdown | No raw HTML. | raw_html |
markdown | Images from the deployment's own media store only — a rooted /media/… path. No external, no scheme, no //host. | external_image |
markdown | Link targets are http(s), mailto:, or a site path starting with /. javascript:, data: and vbscript: are refused, not stripped. | unsafe_link |
markdown | No control or format characters (bidi overrides, zero-width). \n, \t and the emoji joiner are fine. | control_characters |
title, description, tags[i] | No < or >, no control/format characters. These fields render outside the markdown sanitiser — in the page <title>, the OpenGraph meta and the Article JSON-LD — so markup characters are refused at the door. | unsafe_characters, control_characters |
tags | At most 10 tags, at most 64 characters each. | too_many_tags, tag_too_long, empty_tag |
slug (optional) | Must match ^[a-z0-9]+(-[a-z0-9]+)*$, at most 120 characters. Omit it and one is derived from the title. | invalid_slug |
An invalid_slug refusal carries a suggested field — the compliant slug derived from what
you sent — so the retry is one edit away. Links to external sites are fine; that is what
content does. The Content console renders these refusals as sent — every reason, its line
where there is one, and the suggested slug — rather than a generic failure message.
3. A person reviews the exact bytes
Reviewers see the post rendered by the same function the public page uses — approving it
means approving the page, not a markdown diff. The approval is single-use and fingerprinted to
the content hash: any later edit walks the workflow again. Denials carry the reviewer's
reason, which the agent reads back through get_review_feedback.
4. Publishing, scheduling, and what the page says
Publishing is a human action in the Content console — now, or scheduled. A scheduled publish re-verifies the content hash at fire time: if the bytes moved after approval, it refuses and alerts rather than publishing something nobody read. A refusal takes that one queued post out of the queue; everything else scheduled behind it still fires.
Publishing, unpublishing, rolling back, calling off a queued publish and renaming a live
URL all require content:review — the same authority a reviewer spends on the approval
itself. content:read sees the whole pipeline and changes none of it, so an auditor role can
be given the Content console without being able to alter what the public sees. Renaming a URL
takes the new slug exactly as typed (same alphabet as above) and mints an audited redirect
from the old one.
The live page states its provenance: the author kind and model, the content hash, and that a
person approved this exact version — in the byline and in the Article JSON-LD (author as
SoftwareApplication, reviewedBy as your organization).
Subscribe a webhook to content.published to trigger anything downstream —
socials, a newsletter, a search reindex — whenever a page goes live or its live version
changes.
5. Earned autonomy (optional, off by default)
An agent whose drafts your reviewers approve consistently — a statistical confidence bar over its whole record, a minimum span of days, zero withdrawn posts in 30 days — can have new drafts approved by its track record, with a notification to your operators every time. The console shows each agent's progress toward the bar and what one denial would cost. Unpublishing any of its posts suspends the privilege for 30 days. Pages published this way say so: the byline states the earned-autonomy basis instead of claiming human review.
Nothing about this is on until an operator enables content.autonomy_enabled, and
publication itself still requires a person.
The provable claims
Every step above leaves evidence a compliance reviewer can walk: the draft and every revision (immutable, content-addressed, author-stamped from the verified token — an agent cannot name itself in a request body), the review request, the human decision, the single-use spend, and the receipt on the published version. It is the AI-content disclosure story produced as a by-product of access control, not as an annotation exercise.