Octopus.do

Read and edit octopus.do visual sitemaps: pages, blocks and review notes, as the signed-in user.

Documentation

Once connected, your agent can:

  • See your work — list your projects and workspaces, and read the full structure of a sitemap (pages, blocks, colors, tags, symbols).
  • Build and edit sitemaps — create and update pages, add content blocks with wireframes, restructure the page tree, apply several changes at once as a single atomic update.
  • Read and leave review notes — read the comments on pages and blocks and the threads pinned to the canvas, add notes and replies, and resolve or reopen a thread. A note left by an agent is authored by your account and looks exactly like one you typed, and posting it notifies nobody.
  • Manage projects — create, duplicate, archive, move projects; transfer ownership; update project settings like theme and layout. Projects cannot be deleted over MCP — archiving is as far as an agent goes, and archives are reversible.

Every action runs as you — the agent only ever sees and changes what your Octopus.do account has access to.

Connecting

Endpoint

https://mcp.octopus.do/mcp

The general flow is the same in every client:

  1. Add Octopus as a remote MCP server / connector using the endpoint above.
  2. Choose OAuth as the authorization type — most clients detect this automatically.
  3. Sign in (or register) with your Octopus.do account when the browser opens.
  4. Ask your agent to do something — e.g. “show my Octopus projects” or “add a pricing page to my sitemap.”

No API keys to copy or manage — authorization happens through your normal Octopus.do login. Client-specific instructions below.

Claude (web and desktop)

  1. Go to Settings → Connectors → Add custom connector.
  2. Enter https://mcp.octopus.do/mcp as the URL and click Add.
  3. Click Connect and sign in with your Octopus.do account.

Claude Code

Run in your terminal:

claude mcp add --transport http octopus https://mcp.octopus.do/mcp

Then run /mcp inside Claude Code to complete the sign-in.

Cursor

Add to ~/.cursor/mcp.json (or .cursor/mcp.json in your project):

{
  "mcpServers": {
    "octopus": {
      "url": "https://mcp.octopus.do/mcp"
    }
  }
}

Cursor will prompt you to authenticate the first time the server is used.

VS Code (GitHub Copilot)

Run MCP: Add Server from the Command Palette and choose HTTP, or add to .vscode/mcp.json:

{
  "servers": {
    "octopus": {
      "type": "http",
      "url": "https://mcp.octopus.do/mcp"
    }
  }
}

ChatGPT

Octopus.do is published in the ChatGPT app directory: search for Octopus.do under Settings → Apps & Connectors and press Connect. Requires ChatGPT Plus or higher. Full walkthrough: Octopus.do for ChatGPT.

To add the MCP endpoint by hand instead — for a dev environment, or a workspace where the app isn't available — turn on developer mode (Settings → Apps & Connectors → Advanced settings) and:

  1. Go to Settings → Apps & Connectors → Create (or Add connector).
  2. Set the MCP server URL to https://mcp.octopus.do/mcp and authentication to OAuth.
  3. Complete the Octopus.do sign-in when prompted.

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "octopus": {
      "serverUrl": "https://mcp.octopus.do/mcp"
    }
  }
}

Grok

  1. Open grok.com → Connectors, click New Connector, then Custom.
  2. Enter https://mcp.octopus.do/mcp as the MCP server URL.
  3. Complete the Octopus.do sign-in when prompted.

Custom connectors are a paid-plan feature in Grok. On Business and Enterprise plans a team admin has to provision the connector in the xAI cloud console before members can add it.

Gemini

  1. Open gemini.google.com → Settings & help → Connected Apps.
  2. Under Custom apps for Spark, add https://mcp.octopus.do/mcp as the MCP server URL.
  3. Complete the Octopus.do sign-in when prompted.

Custom apps require Gemini Spark on a personal Google Account — Workspace accounts cannot add them — and Google currently limits the feature to users in the United States, in English, from the Gemini web app. Once connected, it works in the mobile apps as well.

Other MCP clients

Any client that supports remote MCP servers (Streamable HTTP) with OAuth works: point it at https://mcp.octopus.do/mcp, pick OAuth, and sign in with your Octopus.do account.

Authentication

Octopus MCP uses OAuth 2.1, the standard MCP clients use for secure, per-user authorization. Your agent requests access on your behalf, you approve it once during sign-in, and access can be revoked from your Octopus.do account at any time. The MCP server itself never sees or stores your password.

Available tools

Your agent picks these tools on its own — you don’t call them directly. The reference below is useful when you want to know exactly what the agent can (and can’t) do, or to phrase a request precisely.

Most write tools accept an optional idempotency_key (string): a unique key that makes retries safe — a repeated request with the same key is applied only once. It’s omitted from the tables below.

Destructive actions (like deleting a page, a block or a comment) are flagged to your AI client, which will typically confirm with you before running them.

Reading

get_me

Returns your account name. No parameters.

list_projects

Lists the projects you can access, across every workspace (personal and teams), most recently updated first. Each item includes the workspace it belongs to. One page at a time: total is how many matched and has_more whether any were left out. To find one project by name use q rather than paging — it searches every project on the account, not just the current page.

FieldRequiredTypeDescription
qNostringOnly projects whose title contains this, case-insensitively
workspace_uuidNostringOnly this workspace. Use the literal "my" for your personal workspace
limitNonumberHow many to return, 1–200 (default 50)
offsetNonumberHow many to skip; for paging

list_workspaces

Lists your workspaces (personal + teams) with their folders and the 10 most recently updated projects in each — project_count gives the real total, and list_projects searches and pages through the rest. No parameters.

get_workspace

Returns one workspace: info, folders, and projects.

FieldRequiredTypeDescription
uuidYesstringWorkspace uuid. Use the literal "my" for your personal workspace.

get_project

Returns the state of one project: the entities (tabs, sections, nodes = pages, blocks, colors, tags, symbols, arrows, …) and the parent/child tree linking them. A whole sitemap is a large read, so start with view: "outline" and narrow further with scope_id when you only need one branch. A page’s notes come back in camelCase (seoTitle, pageIntent) although writes name them snake_case — see Notes field names.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid. uuid is accepted as a synonym, so a call written either way works
viewNoenumoutline (default) — the page tree with titles, urls and ids, an order of magnitude cheaper; full — page content as well
scope_idNostringRead one branch instead of the project: a tab, section or page id. The response carries the chain of ancestors, so the branch keeps its context
excludeNoarrayContent groups to drop — blocks, content, notes, styling, tags, estimates, overlays, meta
formatNoenumnested (default) — the tree; plain — flat collections plus the relations graph

list_comments

Reads the review notes on a project. There are two kinds and one call returns both: flat comments left on pages and on content blocks (thread replies are comments too), and canvas threads — the pins on the sitemap canvas, which carry a position and an open/resolved state. The result has a comments array and a threads array; whichever was not asked for comes back empty. Threads default to the open ones, so the default answer is what is still outstanding rather than everything ever written.

No access shows up differently depending on what was asked for. A default call reads both halves, and the threads half is refused outright, so the whole call fails and says so. A comments-only call (a kind of page, block or reply) gets an empty list instead — the underlying endpoint answers 200 with nothing rather than a 403, and no caller can tell that from a project that simply has no notes.

Both halves name what they point at: a comment’s target and a thread’s anchor carry a title beside the id, so a note can be read without pulling the whole project down to look the id up. For a page or a tab that is its title; for a block it is the block’s name. title is null in four ordinary cases, none of them an error: the pin is an orphan (below); the note is a reply, whose target is a thread and threads have no title of their own; the page is simply untitled; or the project tree could not be read, which costs the titles and nothing else — a comments-only call on a project you cannot read still answers with an empty list rather than an error, exactly as before.

An anchor.type of null on a thread means the page or tab it was pinned to has been deleted and the pin outlived it — orphaned pins are normal. content comes back exactly as it was written: a Plate/Slate document serialized to a JSON string, or plain text. author is an opaque user uuid — never a name, an email or an avatar — and null when the note was left by an anonymous commenter.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
kindNoenumpage | block | reply | thread. Omit for comments of every kind and threads together; thread returns threads only
target_idNostringOnly notes on this id — a page id, a block id, or a thread id, which returns that thread's replies
resolvedNobooleanThreads only. Omit for the open threads, true for the resolved ones
include_repliesNobooleanEmbed each thread's replies in the thread (default false)

Editing

apply_changes

Applies several editing operations to a project as one atomic change — the preferred way to build or restructure a sitemap. Operations run in order and each sees the effect of the previous ones. Give a created entity a ref and reference it in later operations via parent_ref / node_ref instead of an id.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
operationsYesarrayAt least one operation (see below)
dry_runNobooleanValidate and preview only — nothing is changed
idempotency_keyNostringRepeating a call with the same key applies it once

Each operation:

FieldRequiredTypeDescription
operationYesstringOperation name, e.g. "nodes.create"
refNostringLocal name for the created entity, referenced by later operations via *_ref fields
dataNoobjectOperation payload — same fields as the corresponding standalone tool

There are two limits on a batch, and only one of them is ours. The API caps a call at 100 operations. Separately, the AI client that hosts the connection caps how large a single tool call’s arguments may be, and past roughly 15–20 KB of JSON some clients truncate them. A truncated call does not arrive as a smaller batch — it arrives as broken JSON, and the error says the arguments could not be parsed as JSON. That is not a syntax mistake in what you sent and retrying it unchanged will not help: split the work into several smaller batches by size, not only by operation count. There is no byte limit on the Octopus side, so the exact ceiling depends on the client.

A successful response may also carry warnings — advisory notes such as a page whose url does not sit under its parent's path. Nothing was blocked and no retry is needed; they are there to be read, not acted on automatically.

Operation names follow group.action:

GroupActions
nodescreate, update, move, delete, clone, collapse, replace_url_prefix
sectionscreate, update, delete, clone, collapse, move_up, move_down
tabscreate, update, delete, clone
blockscreate, update, replace_text, move, delete, clone
tagscreate, update, delete, assign_node, unassign_node
colorscreate, update, delete
symbolscreate, update, delete
sticky_notescreate, update, delete, clone
arrowscreate, update, delete
settingsupdate

Both halves of the name are snake_case — tags.assign_node, sections.move_up, nodes.replace_url_prefix. The REST endpoint for the same operation spells its path with hyphens (/tags/assign-node); the two forms are not interchangeable, and an operation named in path form is rejected.

Two of them edit many entities from one operation, which is worth knowing before writing eighty of something. nodes.replace_url_prefix {from, to, scope_id?} rewrites the start of every matching page url; blocks.replace_text {from, to, scope_id?} replaces a literal string inside block content without resending the content — the way to correct one word across a whole site. Both match literally and case-sensitively rather than by pattern, both take a scope_id (a tab, section or page — and for blocks.replace_text, a single block) that defaults to the whole project, and both fail if nothing matched instead of answering success: a rewrite that hit nothing is something to know about, not to move on from. They answer with count and a list of what they changed, capped at 50 entries and saying so when it truncates. blocks.replace_text skips symbol instances, whose content belongs to the symbol, and says how many it skipped.

The REST API has a few operations the MCP server deliberately does not offer — estimates (money), tab reordering, symbol linking and external links. They stay available over the Public API.

create_node

Creates a page (node) in the sitemap.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
parent_idYesstringParent id — a tab, section, or another node (ids come from get_project)
titleNostringPage title
color_idNostringProject color id; defaults to the project default
urlNostringPage URL/slug shown on the node
variantNoenumdefault | frame | ghost | stack
after_idNostringPlace it directly after this sibling — name a neighbour instead of counting positions
before_idNostringPlace it directly before this sibling
indexNonumberAbsolute position among the parent’s children; omit to append

update_node

Updates page fields.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
idYesstringNode id
titleNostringNew title
color_idNostringProject color id
urlNostring | nullPage URL/slug; null clears it
variantNoenumdefault | frame | ghost | stack
notesNoobjectSEO/notes fields to merge: note, keywords, page_intent, seo_title, seo_description, seo_h1, seo_slug, seo_url. A field left out keeps its current value. Written in snake_case, read back in camelCase — see the table below

Notes fields are written in snake_case and read back in camelCase. The names on the left are what update_node and the nodes.update batch operation accept; the names on the right are what get_project returns inside a page’s notes:

WrittenRead back
notenote
keywordskeywords
page_intentpageIntent
seo_titleseoTitle
seo_descriptionseoDescription
seo_h1seoH1
seo_slugseoSlug
seo_urlseoUrl

Each pair is one field under two spellings, not two fields. A write that succeeded followed by a read that shows no seo_title is this mapping and not lost data — look for seoTitle. Both spellings stay as they are: normalising either side would break the clients already reading the other.

move_node

Moves a page with its whole subtree to another parent.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
idYesstringNode id to move
parent_idYesstringNew parent (tab, section, or node) — not the node itself or its subtree
after_idNostringPlace it directly after this sibling — name a neighbour instead of counting positions
before_idNostringPlace it directly before this sibling
indexNonumberPosition among the new parent’s children

delete_node

Deletes a page with its whole subtree and all blocks inside.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
idYesstringNode id

create_block

Creates a content block inside a page.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
node_idYesstringPage (node) id the block is created in
titleNostringBlock title
contentNostringBlock text content
wireframesNostring[]Wireframe names to render, e.g. ["header"], ["text"], ["footer"]
color_idNostringProject color id
indexNonumberPosition within the page; omit to append

update_block

Updates block fields.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
idYesstringBlock id
titleNostringNew title
contentNostringBlock text content
color_idNostringProject color id
wireframesNostring[]Wireframe names to render
collapsedNobooleanCollapse/expand the block
completedNobooleanMark the block done/not done

move_block

Moves a block to another page.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
idYesstringBlock id to move
node_idYesstringDestination page (node) id
indexNonumberPosition within the destination page

delete_block

Deletes a block from its page.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
idYesstringBlock id

manage_comment

Adds, deletes, resolves and reopens comments and canvas threads. A note added this way is authored by the account the connection is signed in as, with no marker of any kind — in the editor it is indistinguishable from one that person typed — and it notifies nobody, because no mentions are sent.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
actionYesenumadd | delete | resolve | reopen
targetYes (add)objectadd: what the note hangs off — { "type": "page" | "block" | "tab" | "thread", "id": "…" }
contentYes (add)stringadd: the note body; must not be empty. Send plain text — it is wrapped into an editor document, one paragraph per line, and renders the way a person typing it would expect. Do not build that document yourself; one you already have is stored untouched
positionNoobjectadd: the pin position, { "x": 0, "y": 0 } — supplying it asks for a pin rather than a plain note. See the table below
idYes (delete, resolve, reopen)stringThe comment id or thread id to act on

What add creates depends on the target type and on whether a position is given:

Target typepositionWhat is created
pageomittedA note on the page
pagegivenA canvas pin on that page — a thread
blockomittedA note on the content block
blockgivenRefused — a block can never hold a canvas pin. Drop position, or pin to the page instead
tabeitherA canvas pin on the empty canvas of that tab — a thread
threadomittedA reply on that thread
threadgivenRefused — a reply has no position of its own

Every id is checked against the project before anything is written — on add, on delete, and on resolve and reopen alike — so an id the project does not contain fails rather than creating a note attached to nothing or closing a thread somewhere else. An id of the wrong kind, a block id given as a page for instance, is refused with the id's real kind named. Neither is worth a retry: the fix is to re-read the ids.

Only canvas threads have a resolved state, so resolve and reopen take a thread id — a page or block comment cannot be resolved at all. delete takes either a comment id or a thread id and works out which it is, so the caller does not have to know; deleting a thread removes the pin but leaves its replies behind, since they are comments in their own right.

The optional idempotency_key makes a retry safe — the same key sent twice adds one note, not two. It does not make add idempotent: two add calls with different keys add two notes. On delete the key is accepted and ignored: there is nothing to protect, because deleting the same id twice removes one note and then reports it gone.

Project management

create_project

Creates a new project in the target workspace. May fail when your plan’s project limit is reached.

FieldRequiredTypeDescription
workspace_idYesstringDestination workspace — a team workspace uuid, or the literal "my" for your personal space. Required; an empty or omitted value is rejected with an error.
folder_idNostringFolder id within the team workspace

manage_project

Project lifecycle actions: duplicate, archive/unarchive, move, transfer.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
actionYesenumduplicate | archive | unarchive | move | transfer
workspace_idYes (move)stringmove: destination workspace — a team workspace uuid, or the literal "my" for your personal space. Required for move; an empty value is rejected with an error.
folder_idNostringmove: destination folder id
emailNostringtransfer: recipient email — must be a registered, non-free-plan user

update_project_settings

Updates project-level settings.

FieldRequiredTypeDescription
project_uuidYesstringProject uuid
titleNostringProject title
themeNoenumblueprint | bold | dark | light
treeNoenumTree layout: map | matrix
frameNoenumNode frame style: mobile | neutral | web
mobileNobooleanMobile mode
image_modeNobooleanShow images on nodes
legend_positionNoenumbottom | none | top
default_color_idNostringDefault color for new pages

Learn more