Automates drafting and publishing articles to Substack and Medium with Apify and Notion integration
Resources
2Install
npx skillscat add oyi77/1ai-skills/content-publisher Install via the SkillsCat registry.
SKILL.md
Content Publisher Agent
Automates drafting and publishing articles to Substack and Medium with workflow automation.
Required Tools
MCP Servers
Apify MCP (Publishing Automation)
{
"mcpServers": {
"apify": {
"command": "npx",
"args": ["-y", "@apify/mcp-server"],
"env": { "APIFY_API_TOKEN": "${APIFY_API_TOKEN}" }
},
"notion": {
"command": "npx",
"args": ["-y", "@makenotion/mcp-server"],
"env": { "NOTION_API_KEY": "${NOTION_API_KEY}" }
},
"slack": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-slack"],
"env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}" }
}
}
}Tool Permissions
| Tool | Capabilities |
|---|---|
Bash(apify:*) |
Execute browser automation for publishing |
MCP(apify:*) |
Run web scraping/automation actors |
MCP(notion:*) |
Store editorial calendar, track drafts |
MCP(slack:*) |
Send approval notifications |
Authentication
Setup
Apify Token
export APIFY_API_TOKEN="your-token"Notion Integration
- Create integration at https://www.notion.so/my-integrations
- Share database with integration
Slack (optional)
export SLACK_BOT_TOKEN="xoxb-your-token"
Capabilities
- Drafting: Turn ideas into structured Markdown drafts
- Publishing: Navigate to platform editors and publish
- Cross-Posting: Sync content between platforms
- Scheduling: Schedule posts for future publication
- Editorial Calendar: Track in Notion
Pseudo Code
Example 1: Draft Article
// 1. Analyze request
const topic = "The impact of Agentic AI on coding";
const audience = "developers";
const tone = "professional";
// 2. Research if needed
const context = await apify.actor("apify/firecrawl-scraper", {
urls: [`https://news.ycombinator.com/?q=${topic}`]
});
// 3. Generate content
const draft = await generateArticle({ topic, audience, tone, context });
// 4. Save to drafts folder
const filename = `memory/drafts/${dateSlug(topic)}.md`;
await fs.write(filename, draft);
// 5. Log to Notion editorial calendar
await notion.createPage("Editorial Calendar", {
title: draft.title,
status: "Draft",
scheduledDate: null,
platform: "both"
});Example 2: Publish to Platform
// 1. Read draft
const content = await fs.read("memory/drafts/agentic-ai.md");
// 2. Navigate to platform
await browser.goto("https://medium.com/new-post");
// 3. Fill content
await browser.fill(".title-input", content.title);
await browser.fill(".body-editor", content.body);
// 4. Add tags
for (const tag of content.tags) {
await browser.click(".tag-input");
await browser.type(tag);
}
// 5. Publish or save draft
if (mode === "live") {
await browser.click(".publish-button");
await slack.notify("#content", `Published: ${content.title}`);
} else {
await browser.click(".save-draft");
}Example 3: Cross-Post to Both Platforms
// 1. Read content
const content = await fs.read(draftPath);
// 2. Convert for each platform
const mediumContent = convertToMedium(content);
const substackContent = convertToSubstack(content);
// 3. Publish to Medium
await publishToMedium(mediumContent);
// 4. Publish to Substack
await publishToSubstack(substackContent);
// 5. Update Notion
await notion.updatePage(draftId, { status: "Published" });CLI Reference
| Command | Description |
|---|---|
draft "topic" |
Generate article draft |
publish <file> <platform> |
Publish to platform |
publish <file> <platform> live |
Publish live |
schedule <file> <platform> <datetime> |
Schedule post |
Error Handling
| Error Code | Meaning | Fix |
|---|---|---|
AUTH_001 |
Not logged in | Check credentials, re-login |
PUBLISH_001 |
Platform changed UI | Update selectors |
RATE_001 |
Rate limited | Wait and retry |
VALIDATE_001 |
Content validation failed | Check format |
Common Patterns
Pattern: Dry-Run Publishing
async function publishWithDryRun(content, platform, dryRun = true) {
// Validate content
if (!content.title || !content.body) {
throw new Error("Missing required fields");
}
// Preview
console.log("=== PREVIEW ===");
console.log(`Title: ${content.title}`);
console.log(`Platform: ${platform}`);
if (dryRun) {
console.log("DRY RUN - No actual publishing");
return { status: "preview" };
}
// Actually publish
return await platform.publish(content);
}Skill v2.0 - Content Publisher with MCP