diff --git a/documentation/blog/2025-11-21-social-media-agent-automation/header-image.png b/documentation/blog/2025-11-21-social-media-agent-automation/header-image.png
new file mode 100644
index 000000000000..d711ffc6a212
Binary files /dev/null and b/documentation/blog/2025-11-21-social-media-agent-automation/header-image.png differ
diff --git a/documentation/blog/2025-11-21-social-media-agent-automation/index.md b/documentation/blog/2025-11-21-social-media-agent-automation/index.md
new file mode 100644
index 000000000000..320333c103d8
--- /dev/null
+++ b/documentation/blog/2025-11-21-social-media-agent-automation/index.md
@@ -0,0 +1,470 @@
+---
+title: Building a Social Media Agent
+description: I built a fully automated social media agent using MCP servers to fetch content and post through Sprout Social.
+authors:
+ - ebony
+---
+
+
+
+> Creating content is fun.
+> Promoting it (aka the most important part) drains my soul 😩
+
+
+When I posted that on LinkedIn the other night, I realized I'm definitely not the only one who feels this way. You spend hours making this masterpiece, and then you have to remember to promote it across multiple platforms every single time.
+
+It’s exhausting, so I decided to automate it.
+
+
+
+## The Game Plan
+
+Here's what we're building: two [MCP servers](https://modelcontextprotocol.io/docs/getting-started/intro) that work together to handle all our social media promotion automatically.
+
+**MCP Server #1: Content Fetcher**
+This one goes out and grabs all our content from:
+- YouTube videos
+- Blog posts
+- GitHub release notes
+
+Then it compares everything to a `last_seen.json` file to figure out what's actually new. If nothing is new it proceeds to check an `evergreen.json` file and randomly pick old content to socialize.
+
+**MCP Server #2: Sprout Social Integration**
+Once we have new content, this server takes over and:
+- Generates captions for each platform
+- Uploads media (videos, images, or just links)
+- Creates draft posts in Sprout Social
+
+The goal? Wake up to social posts ready to go, without lifting a finger. Well, almost, more on that later.
+
+## Building the Content Fetcher
+
+I used [Fast MCP](https://github.com/punkpeye/fastmcp) to spin up these TypeScript servers because, well, I'm a TypeScript girly. But you can use whatever SDK you vibe with.
+
+First thing I needed was our YouTube channel ID. Quick tip: go to your YouTube channel, click on videos, and look at the URL. Everything after `/channel/` is your channel ID. Easy.
+
+Click to see the code
+```typescript
+
+// fetch youtube function
+async function fetchYoutube(): PromiseClick to see the code
+```typescript
+server.addTool({
+ name: "createScheduledPost",
+ description:
+ "Create a DRAFT post in Sprout scheduled for the future. Uses SCHEDULED delivery.",
+ parameters: z.object({
+ text: z
+ .string()
+ .describe("Text of the post. This will be the copy for the social post."),
+ customer_profile_ids: z
+ .array(z.number())
+ .nonempty()
+ .describe(
+ "Array of Sprout customer_profile_ids to post to (e.g., LinkedIn, X, YouTube, Bluesky)."
+ ),
+ scheduled_times: z
+ .array(z.string())
+ .nonempty()
+ .describe(
+ "Array of ISO8601 UTC timestamps for scheduled send times (e.g. '2025-11-20T15:00:00Z')."
+ ),
+ media: z
+ .array(
+ z.object({
+ media_id: z
+ .string()
+ .describe("media_id returned from uploadMediaFromUrl."),
+ media_type: z
+ .enum(["PHOTO", "VIDEO"])
+ .describe("Type of media (PHOTO or VIDEO)."),
+ })
+ )
+ .optional()
+ .describe("Optional array of media to attach to the post."),
+ }),
+ execute: async ({ text, customer_profile_ids, scheduled_times, media }) => {
+ try {
+ const payload = buildPublishingPostPayload({
+ text,
+ customer_profile_ids,
+ is_draft: true,
+ scheduled_times,
+ media,
+ });
+
+ const data = await sproutPost("/publishing/posts", payload);
+
+ return JSON.stringify({
+ success: true,
+ request: payload,
+ response: data,
+ });
+ } catch (err: any) {
+ return JSON.stringify({
+ success: false,
+ error: err?.message || String(err),
+ });
+ }
+ },
+});
+```
+Click to see the full daily automation recipe
+```yaml
+version: "1.0.0"
+title: "Daily Social Promo Automation"
+description: "Fetches new goose content or posts evergreen, generates platform-specific captions, and creates Sprout drafts."
+
+instructions: |
+ You are Ebony's daily social media automation assistant.
+
+ ## YOUR WORKFLOW:
+
+ ### STEP 1: Fetch All Content
+ Call these MCP tools to gather everything:
+ - contentfetcher__fetchYoutube
+ - contentfetcher__fetchGooseBlog
+ - contentfetcher__fetchGithubReleases
+
+ Each returns a JSON array. Combine them into one array of items with:
+ { id, title, url, published_at, type }
+
+ ### STEP 2: Check What's New
+ For EACH item in your combined array:
+ - Call contentfetcher__isNewContent with { id, type }
+ - It returns { is_new: true/false }
+ - Build a list of items where is_new == true
+
+ ### STEP 3: Decide What to Post
+
+ **IF you found NEW content:**
+ - Pick the MOST RECENT new item (by published_at date)
+ - Use that item for posting
+
+ **IF NO new content exists:**
+ - Load the file /Users/ebonyl/.config/goose/evergreen.json
+ - Parse the JSON array
+ - Randomly select ONE item from the array
+ - Use that item for posting
+
+ ### STEP 4: Generate Platform-Specific Captions
+
+ For the selected item, create 3 captions following these rules:
+
+ #### EBONY'S TONE (ALL PLATFORMS):
+ - Confident, warm, developer-focused
+ - NO hype language (never: "revolutionary", "unlock", "cutting-edge", "game-changer", "transform")
+ - NO cringe marketing speak ("leverage", "synergy", "disrupt")
+ - Short, clear sentences
+ - 0-1 emoji maximum (✨ only, if any)
+ - Never more than 1 exclamation point per post
+ - Sound calm, resourceful, dev-first
+ - Highlight what developers will LEARN or BUILD, not hype
+ - Never use generic AI clichés ("fast-paced world", "stay ahead of the curve")
+ - NEVER use em dashes (—) at all
+ - Focus on practical value and real use cases
+ - Be conversational but professional
+
+ #### LINKEDIN RULES:
+ - NEVER post YouTube links (heavily penalized by LinkedIn algorithm)
+ - For videos: MUST use native video upload
+ - Tone: calm, clear, slightly longer is OK (but still concise)
+ - No more than 1 emoji
+ - NO hashtags
+ - Focus on professional learning value
+ - Can be 2-3 sentences
+
+ #### TWITTER/X RULES:
+ - NEVER post YouTube links (penalized)
+ - For videos: MUST use native video upload
+ - Short and punchy (under 280 chars ideal)
+ - No corporate tone
+ - 0-1 emoji max
+ - If thread needed: max 2 tweets
+ - Conversational but professional
+ - Get to the point fast
+
+ #### BLUESKY RULES:
+ - Links ARE allowed (YouTube links OK here)
+ - Most conversational and casual
+ - Emojis allowed if on-brand (still max 1)
+ - For videos: prefer native upload but link is acceptable
+ - Can be slightly more playful than other platforms
+ - Community-focused tone
+
+ #### MEDIA HANDLING BY CONTENT TYPE:
+
+ **If type == "video" (YouTube content):**
+
+ CRITICAL: YouTube URLs cannot be uploaded as native media to Sprout.
+ You MUST handle each platform differently:
+
+ - **LinkedIn:**
+ • DO NOT include YouTube URL in caption (penalized)
+ • DO NOT pass media_url (cannot upload YouTube natively)
+ • Caption should describe the video content
+ • Say something like "Watch the full video on YouTube" WITHOUT the link
+ • media_url: omit or empty string ""
+
+ - **Twitter:**
+ • DO NOT include YouTube URL in caption (penalized)
+ • DO NOT pass media_url (cannot upload YouTube natively)
+ • Caption should describe the video content
+ • Say something like "Full video on YouTube" WITHOUT the link
+ • media_url: omit or empty string ""
+
+ - **Bluesky:**
+ • Links ARE allowed here
+ • Include the YouTube URL directly in the caption text
+ • DO NOT pass media_url (cannot upload YouTube natively)
+ • Caption should include the YouTube link
+ • media_url: omit or empty string ""
+
+ **If type == "blog":**
+ - LinkedIn: include blog URL in caption text, no media_url
+ - Twitter: include blog URL in caption text, no media_url
+ - Bluesky: include blog URL in caption text, no media_url
+
+ **If type == "release":**
+ - LinkedIn: include release URL in caption text, no media_url
+ - Twitter: include release URL in caption text, no media_url
+ - Bluesky: include release URL in caption text, no media_url
+
+ **IMPORTANT:** The sproutsocialmedia__createPostFromContent tool will:
+ - Upload media natively IF you provide a direct media file URL (MP4, JPG, PNG, etc.)
+ - YouTube URLs are NOT direct media files and cannot be uploaded
+ - For YouTube videos, you must rely on caption text only (with link on Bluesky)
+
+ ### STEP 5: Get Sprout Profile IDs
+
+ Call sproutsocialmedia__getConfiguredProfiles to get the profile IDs.
+ This returns:
+ {
+ linkedin_company: "