My Multi-Platform Publishing Data Model
The reality is that "one post" is actually several different formats. X needs threads, LinkedIn takes PDFs, and Instagram requires carousels. Even two accounts on the same platform often need different copy. Instead of stacking endless exceptions into my code, I had to rethink the data model from scratch.
Separating Intent from Rendition
I shifted the architecture to separate the "Publication" (the intent) from the "Rendition" (the actual output).
The Publication holds the core idea and source text, while the Rendition is specific to the account and platform. Here is the simplified Go structure:
type Publication struct {
ID string
WorkspaceID string
Title string
ContentProfile string
SourceText string
SourceURL string
Status string
ScheduledAt time.Time
ActualRunAt time.Time
}And the corresponding rendition:
type Rendition struct {
ID string
PublicationID string
SocialAccountID string
Platform string
Profile string
Body string
Title string
Description string
SettingsJSON string
Status string
ExternalID string
ExternalURL string
ErrorMessage string
}Linking renditions to specific accounts rather than just platforms was a critical move. My personal LinkedIn and a company page are both "LinkedIn," but they rarely share the exact same copy. This also solves the media problem; a rendition can have its own alt text or thumbnail timestamps, which a flat list of attachments couldn't handle.
Validating the Output, Not the Source
A common trap in this AI workflow is validating the source text and assuming the final output is safe. That's a mistake. If an LLM agent modifies the rendition to fit a platform's style, it might accidentally exceed a character limit or use an unsupported image format.
I implemented content profiles (like short_text, thread, or carousel) to define the general shape. The provider adapter then performs a final check on the specific rendition before it ever hits the API.
Integrating LLM Agents via MCP
To make this work with AI agents, I needed a way to let them prepare drafts without giving them full administrative access to provider credentials. I implemented two MCP scopes:
mcp:read: Allows the agent to inspect workspace data.mcp:full: Allows creating, editing, and publishing.
This is enforced server-side. If a client only has read access, the mutation executor is stripped during tool discovery, ensuring the agent can't accidentally trigger a post without permission. This setup makes the transition from "AI-generated draft" to "human-approved post" much cleaner.