---
name: eden-mcp
description: >-
  Drive the Eden Canvas MCP server (boards, notes, social intelligence, and the
  post scheduler) correctly from any MCP client — Claude Code, Codex, Cursor,
  ChatGPT. Use when connecting to Eden over MCP, when an Eden tool call fails
  (especially "OAuth authorization required", "Unauthorized", an expired
  token, or a rejected platform / validation error), when scheduling or
  editing social posts through Eden, or when deciding which Eden tool answers a
  given request. Covers the auth model and recovery, the scheduling
  write flow, routines (recurring chat runs), the structured-error contract,
  and read-tool routing.
---

The Eden MCP exposes one user's Eden Canvas account: their **workspace** (boards, notes, saved cards), the **social-intelligence** layer behind Eden Discover (cross-platform creator analysis + indexed post search), and the **post scheduler** (draft → schedule → publish across X, Threads, LinkedIn, Substack, Instagram, TikTok, Facebook, YouTube).

Hosted endpoint: `https://mcp.eden.so/mcp` (Streamable HTTP, OAuth). Tools are namespaced `eden_*`.

## Read the reference before you act

This file is a router. For anything beyond a single obvious read, open the matching reference **first** — the failure modes here are non-obvious and an unguided agent burns turns guessing (or invents subprocess workarounds that corrupt data).

| Situation                                                                                                                     | Reference                                                       |
| ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| A tool call failed, especially auth / "OAuth authorization required" / expired session / 401                                  | <references/connection-and-auth.md> then <references/errors.md> |
| Creating, scheduling, editing, or publishing a social post; platform/media rules; threads; per-platform variants              | <references/scheduling.md>                                      |
| Any tool returned `{ "ok": false, "status": ... }` and you need to know whether to retry, fix input, or surface to the user   | <references/errors.md>                                          |
| "What's on my board", "find posts about X", "best posts by creator", "who do I follow", reading notes/cards                   | <references/workspace-and-social.md>                            |
| Running or managing the user's Custom AI (including `/` commands and attached knowledge)                                      | <references/custom-ai.md>                                       |
| Setting up something that should run on its own every day / week / month ("every Monday at 9am…"), or managing those routines | <references/routines.md>                                        |
| Discovering, applying, importing, or exporting the user's Eden Skills (reusable SKILL.md instruction files)                   | <references/skills-and-prompts.md>                              |
| Connecting social accounts, or reading the user's OWN analytics ("how did my posts do")                                       | <references/scheduling.md>                                      |
| You know the goal and want the exact tool sequence (research → draft → schedule, connect accounts, analytics, dashboards)     | <references/recipes.md>                                         |

## The one rule that prevents most damage

**Every Eden tool returns a structured result — read it before acting.** Success is `{ "ok": true, ... }`. Failure is a discriminated union `{ "ok": false, "status": "...", "message": "...", "errors"?: [...] }`. The `status` tells you exactly what to do: `auth-expired` → reconnect, `invalid` → fix the input (deterministic, do **not** retry the same payload), `conflict`/`forbidden`/`not-found` → surface to the user, `unreachable` → retry once. Full table in <references/errors.md>.

Never paper over a failed Eden call with a shell/subprocess workaround, an in-place hack, or a "fresh copy" that leaves a broken row behind. If a write won't go through, the structured error says why — act on it or tell the user.

## Confirm outward writes before firing

`eden_schedule_post`, `eden_publish_post_now`, and `eden_cancel_scheduled_post` are **real, often public actions** — they queue or push posts to live social accounts. Confirm the content, platforms, and time with the user before calling them, unless they've already told you to just do it. Drafts (`eden_schedule_post` with `draft: true`) and workspace writes (notes, boards) are reversible and safe to do as you go.

## Getting oriented (first calls)

Most tools are workspace-scoped. Resolve the workspace once and reuse it: an explicit `workspaceId` wins; otherwise the connection's default workspace is used (set by the user in Eden → Settings → Integrations). If a call returns `status: "missing-workspace"`, call `eden_list_workspaces` and pass an explicit `workspaceId`. A good first call to confirm the connection works is something cheap like `eden_following_overview` or `eden_list_workspaces`. In team workspaces, reads are scoped to what THIS user can see: other members' Private items and personal chats simply don't appear (a missing item usually means "private to a teammate", not "deleted"), and items the user creates through you start Private to them until moved to the Team section in Eden.

## Auth in one paragraph

The connection is OAuth-bound: the client holds a bearer token, refreshes it periodically, and sends it on each request. Sessions live **in memory on the server**, so a server redeploy or a long idle gap can drop your session — the next call then fails at the transport layer ("OAuth authorization required" / "Unauthorized" / an expired-session 404) rather than as a clean tool result. This is almost always a **reconnect**, not a code or argument problem. See <references/connection-and-auth.md> for the exact recovery and how to tell it apart from a real permission error.

## Tool routing cheat-sheet

- **Scheduler:** `eden_list_schedules` (connected platforms, timezone, next slot) → `eden_schedule_post` (`draft: true` = save for review) | `eden_publish_post_now`; edit in place with `eden_update_scheduled_post` (time, body, `firstComment`, `autoRepost`); `eden_list_scheduled_posts`, `eden_cancel_scheduled_post`. Change the recurring queue times or timezone (not one post) with `eden_update_schedule`. Account setup: `eden_connect_social_accounts` (status / get-link / sync). Instagram Auto-DM: `eden_list_auto_dm_rules` → `eden_create_auto_dm_automation` (clarify-first; real DMs cost 1 credit each). **Details in <references/scheduling.md>.**
- **Analytics (the user's OWN numbers):** `eden_get_analytics` (digest: totals, deltas, outliers, topic/format winners, benchmarks) and `eden_list_analytics_posts` (per-post rows for charts/dashboards). The first digest call auto-starts tracking when accounts are connected (pass `brandId` for one brand's scope). Private warehouse, not public research. If the warehouse is unavailable (not enabled, upgrade required, first import running), fall back to `eden_resolve_creator` + `eden_analyze_creator` on the user's own connected handles — labeled as public estimates. **Details in <references/scheduling.md>.**
- **Thin topic research:** Standard global search covers standouts; try one short `depth: "deep"` query with `orderBy: "relevance"`, preserving dates/platform. Empty results stay empty. Then use available web/creator discovery, or ask for 1–2 reference creators/links, resolve them, and read their relevant posts. Errors are not zero matches; never claim a topic is absent. See <references/workspace-and-social.md>.
- **Creator discovery and analysis (ranked, full corpus):** `eden_search_creators` finds people with `kind: "topic"` + `query`, `kind: "similar-to-creators"` + exact `creatorRefs`, or `kind: "similar-to-list"` + `listId`; `eden_analyze_creator`, `eden_resolve_creator`, `eden_search_social_content`, `eden_following_overview`, and `eden_analyze_list` handle known creators, posts, following, and lists. Never rank over saved cards.
- **Ads research (the PAID-ads corpus — separate from organic posts):** `eden_search_ads` (real Meta Ad Library + TikTok Creative Center creatives; filter by `category`, creative `angle`, `platform`, `minRunDays`, sort `top`/`rising`/`newest`/`longest`) and `eden_get_brand_ads` (one brand's library by name, @handle, or URL — facebook.com / instagram.com / Ad Library URLs resolve most reliably; a website URL resolves only when a Meta page's name/handle IS the brand or its ads land on that domain, otherwise `status: "ambiguous"` with `candidates` — ask which brand they mean, never pick one). Never answer an ads question with `eden_search_social_content` or vice versa. Meta ads carry NO engagement metrics — `runDays` + `variationCount` are the proof (brands pay daily to keep an ad up); TikTok rows have real `ctr`/`likeCount`. An un-tracked brand is auto-added, starts syncing, and the tool already waits ~25s for the first ads (a `notice` marks a still-growing library — relay it): `status: "indexing"` means the wait came up dry — say you've started gathering their ads and to check back in minutes, NEVER "brand not found", and don't immediately re-call; `status: "unavailable"` means ads research isn't enabled for this account — say so and stop. `eden_get_brand_list_ads` reads the user's curated BRAND lists (advertiser collections — a different entity from creator lists, and the two can share a name: ads-context "my <name> list" resolves here first; omit `list` for the roster, pass a name for that list's brands + their top ads). Add brands TO a brand list with `eden_save_brands_to_list` (exact list name — a new name creates the list; `brands` accepts names, @handles, URLs, or `advertiserId`s; un-tracked brands join the ads index on the spot — relay the syncing `notice`; idempotent, repeats report `alreadyInList`). Save ads onto a board with `eden_save_ads_to_board` (each result's `id`, verbatim — never via `eden_save_links_to_board`, which makes a bare link card). Cite an ad in notes or table url cells with its Eden link `https://app.eden.so/ads/{platform}/{adId}` (ids exactly as returned) — it renders as a rich chip and opens inside Eden; never the raw Ad Library URL. **Details in <references/workspace-and-social.md>.**
- **Titles and headlines from what's working:** `eden_study_top_titles` (a creator or a niche; `platform` is `youtube` | `substack` | `twitter`, default `youtube`). It returns each winning headline's reusable SKELETON with slots — `After [studying N of a thing], here's how to [outcome]` — the verbatim source, and `doNotReuse` (the source's own numbers, names, story). **Reuse the frame, replace the substance.** Do NOT read a winner, restate the principle behind it ("uses credibility and specificity"), and write a fresh title from that principle — that discards the structure and produces titles nobody clicks. Prefer it over picking titles out of an `eden_search_social_content` result, which makes you throw the frame away. Pass `topic` to get each frame back already refilled. Only three sources have authored titles: **YouTube videos, Substack ARTICLES, and native X ARTICLES**. Short-form has no title — tweets, Substack notes, reels, TikToks, LinkedIn and Threads posts are out of scope, because their first line is body text, not packaging. The hook INSIDE a video is also a different artifact and lives in the transcript. When the user's own surface IS short-form ("titles for my tweets", "hooks for my reels"), it is not a titles question: pull the posts with `eden_search_social_content` / `eden_read_social_post` and study their openings directly, and say plainly that short-form carries no title rather than quietly studying a platform they did not ask about.
- **Workspace (what the user saved):** `eden_list_workspaces` → `eden_list_workspace_items` → `eden_read_board` / `eden_read_social_post` (pass a card's `url`) / `eden_get_note_markdown` / `eden_read_table` (rows + columns of a table item); write with `eden_create_note` (`presentation: "card"` makes a text Card — use for captures/ideas; the default makes a Document), `eden_update_note`, `eden_create_board`, `eden_save_posts_to_board` (indexed social posts), `eden_save_ads_to_board` (ads-corpus creatives), `eden_save_links_to_board` (new URLs), `eden_save_items_to_board` (items the workspace ALREADY has — the only correct way to put a highlight on a board). Sidebar FOLDERS organize boards only and can never hold items — search returns matching folders in `folders`; "in my <folder> folder" means a board inside it (pick one of its boards, or `eden_create_board` with `folder`). Write note bodies without blank lines between blocks (Eden's editor spaces blocks itself; the server strips blank lines on save, keeping those inside code fences). Note reads show embedded workspace items as `[Embedded <type>: "<title>" — itemId <id>]` marker lines — opaque tokens: carry them through verbatim when rewriting, never reword or delete them, and read the embedded item via its `itemId`.
- **Tables (database-style lists — trackers, content calendars, hook banks):** create with `eden_create_table` (typed columns + seed rows + view in one call — never all-text columns; a column you'll GROUP BY must be a select with options like "Week 1"…"Week 12", never a bare number — only select/rating/checkbox/date columns form groups, and a number/text groupBy target is auto-converted to a select built from its values); read with `eden_read_table`; edit with `eden_add_table_rows` / `eden_update_table_rows` / `eden_update_table` (which also owns SAVED VIEWS: filters and hidden columns by column name, `saveAsView` to name the setup, `applyView` to switch — all SHARED state; reads are never filtered and report the user's view — layout included — in `viewSummary`). Cells go by column NAME, options by option name; address a row by its `itemId`, exact title, or its `rev` stamp from a CURRENT read (record rows carry no item id, and a stale stamp matches nothing — re-read then); echo each row's `rev` from your read as `expectedRev` when updating, so a cell a person changed since then is refused (`conflicts`) rather than overwritten; fix `warnings` with a follow-up update, never by recreating (warnings are plumbing addressed to you, not the user: act on one silently and never quote, paraphrase, or narrate it in your reply). For every select/multiSelect option you create, pass `{ name, color }` with an intentional semantic color instead of relying on fallback rotation: workflow statuses use gray for backlog/not started, amber for drafting/review, blue for scheduled/in progress, green for approved/published/done, red for blocked; prefer X gray, LinkedIn blue, Instagram purple, YouTube red, and text gray, thread blue, carousel purple, video red, newsletter amber. Adapt to different labels and preserve existing concept colors when editing. A recurring to-do is ONE row with a `repeat` rule on a date column (`{"Due": "day"}`), never one row per occurrence — marking it done rolls the date forward instead of completing it. A to-do table wants `view.layout: "list"` grouped on its date column — one line per row, bucketed Overdue / Today / Tomorrow / This week. Name each row for THE THING ITSELF — the hook, the essay title, the task, whatever a person would say out loud to refer to it. If a value counts up ("Week 1", "Idea 3") or repeats across rows it is a COLUMN, never a name; a calendar row is named for what ships, not for the slot; and no column may restate the name (a longer version is fine, called "Script" or "Full text", never "Idea"). Rows you ADD to an existing table follow this even when the rows already there break it — a bad convention is not a pattern to copy, and never renaming their existing rows is not permission to add a matching bad name. A structured collection belongs in a table, not a note full of markdown tables — and a table ask gets a table ON ITS OWN, never a document wrapped around it: offer the doc in one line at the end whenever you produced strategy the table can't hold (strategy you wrote into the reply itself and not into the table is exactly that case, never a reason to skip the offer), and write one up front only when the user's own ask carried the thinking (say so in one line, then proceed). After building, one line naming what went in the Name column and how it is grouped; exactly one thing may ever follow that line — the doc offer above, which is required whenever you produced strategy the table can't hold, so the line ends the reply only when there is nothing to offer — and nothing else may follow it or join it, because the table already renders its columns, so never restate or explain the ones you added. A table can also live INSIDE a note: `eden_create_table` with `embedInNoteId` renders it as a live block in that note, and `replaceInNoteText` swaps the list it was built from for the table (don't follow it with an `eden_update_note` that strips the list). A url column pointing at an Eden post or ad uses the public link forms — post: `https://app.eden.so/posts/{platform}/{contentId}`, ad: `https://app.eden.so/ads/{platform}/{adId}`, ids EXACTLY as a tool returned them — which render as rich chips that open the post/ad details inside Eden (`eden://` links do not work in cells). **Details in <references/workspace-and-social.md>.**
- **Table option palette (current; supersedes the older color examples in the preceding paragraph):** the ten keys are `gray`, `amber`, `orange`, `red`, `pink`, `purple`, `indigo`, `blue`, `teal`, and `green`. Keep workflow statuses on the semantic core colors. For category columns, use the added orange/teal/indigo/pink swatches when they make values easier to distinguish; current defaults prefer Instagram pink and thread indigo.
- **Tags (the user's own labels):** `eden_list_tags` (the workspace's tag vocabulary) → `eden_update_item_tags` (add/remove by NAME on a library item; adds auto-create, so reuse existing names — matching is case-insensitive). Filter semantic search to tagged items with `eden_find_workspace_items`'s `userTags` param. User tags are NOT the AI-generated topic tags (`eden_find_workspace_items`'s `tags` param) — "tag this" always means user tags. Boards and chats can't be tagged. **Details in <references/workspace-and-social.md>.**
- **Full routing + the saved-vs-indexed distinction:** <references/workspace-and-social.md>.
- **Custom AI:** `eden_list_custom_ai` → `eden_get_custom_ai` to discover and apply configured assistants (also surfaced as `/` slash commands); manage editable assistants with `eden_create_custom_ai`, `eden_update_custom_ai`, and `eden_delete_custom_ai`; manage attached knowledge with `eden_manage_custom_ai_sources` (boards, notes, creator refs, social posts, and `brand` ad-library refs); load `eden_get_custom_ai_builder_guide` before authoring one from scratch. **Details in <references/custom-ai.md>.**
- **Routines (recurring chat runs, at most once a day):** `eden_list_routines` (what exists; find a `routineId`; avoid duplicates) → `eden_create_routine` (a self-contained prompt for an unattended run, `frequency` + `hour` + a required `timezone`, and `customAIId` so every run is a chat with that Custom AI) → `eden_update_routine` (edit, `enabled: false` to pause, `remove: true` to delete). Each run opens a fresh Eden chat and sends the prompt as if the user typed it; the last message is what gets emailed when `emailOnFinish` is on. Not post scheduling: "schedule this post" is `eden_schedule_post`. **Details in <references/routines.md>.**
- **Skills (the user's reusable SKILL.md instruction files):** `eden_list_skills` (their own + Eden's built-ins; each `description` says when to use it) → `eden_get_skill` (follow the returned `skillMd` as your instructions). Portable and byte-exact: `eden_export_skill` → `{ skillMd, slug }` verbatim; `eden_import_skill({ skillMarkdown })` stores the file text verbatim as a new skill (markdown-only over MCP). **Details in <references/skills-and-prompts.md>.**

For `eden_update_note`, a `LINEAR_NOTE_CONFLICT` means overlapping edits could not be merged safely. Preserve your draft and `currentContent`; ask the user which version to keep if they conflict. Never silently replace newer text by retrying with its hash. After an uncertain append result, re-read before retrying so you do not add the text twice.

### Purchased Custom AI access

For purchased or managed Custom AIs, read `references/custom-ai.md`: full published instructions and all owned bundled knowledge are available through the external-agent route. Custom AI list/get/builder-guide/knowledge search/read calls are zero credits; other tool prices are unchanged. Knowledge search uses `documentOffset` / `nextDocumentOffset` to page past 24 documents.
