Skip to main content
Version: Latest

Confluence (user passthrough)

Proxy for the Atlassian Confluence Cloud REST API via the managed gateway.

What this connector does

Gives agents gated, per-user access to Confluence for:

  • Searching content with CQL.
  • Reading pages (with body), their children, labels, attachments, and version history.
  • Listing spaces and the pages within them.
  • Reading, posting, editing, and deleting page comments — both footer and inline.
  • Creating, updating, moving, and deleting pages.
  • Creating and reading blog posts.
  • Creating, reading, and deleting folders.
  • Adding and removing labels.

The full CRUD lifecycle is covered: reads are readOnlyHint (allow-listable), the three irreversible deletes (delete_page, delete_folder, delete_comment) carry anthropic/requiresUserInteraction so a blanket approval cannot cover them, and destructive writes (update_page, move_page, remove_label) are marked destructiveHint. See the connector-metadata-review skill for the reasoning.

All operations are gated by PBAC policy. Every call flows: agent → gateway → /introspect (with this connector's resource_type) → OPA decision → Confluence API → response.

This connector ships in idp_passthrough mode only: the gateway forwards each calling user's own Atlassian OAuth token (captured when they log in to PolicyArc through the Atlassian IdP), as Authorization: Bearer. Confluence enforces its own space and page permissions, and its audit shows the real person. There is no shared service-account mode — Atlassian Cloud exposes no service identity at the OAuth layer (see the Atlassian products page).

API surface

Content and spaces use the Confluence v2 API (/wiki/api/v2/*), Atlassian's current REST API. Two operations fall back to the v1 endpoints because v2 has no equivalent: CQL search (/wiki/rest/api/search) and label add/remove and page move (/wiki/rest/api/content/{id}/...).

Because the v2 API is used, the connector requests granular Atlassian OAuth scopes — not the classic read:confluence-content.all family, which the v2 API rejects with HTTP 401 "scope does not match". Grant these on the Atlassian 3LO app's Confluence API permission:

  • Pages: read:page:confluence, write:page:confluence, delete:page:confluence
  • Blog posts: read:blogpost:confluence, write:blogpost:confluence
  • Folders: read:folder:confluence, write:folder:confluence, delete:folder:confluence
  • Comments: read:comment:confluence, write:comment:confluence, delete:comment:confluence
  • Labels: read:label:confluence, write:label:confluence
  • Attachments: read:attachment:confluence; plus the classic write:confluence-file for add_attachment — the v1 upload endpoint rejects the granular write:attachment:confluence with 401 "scope does not match". This one classic scope coexists fine with the granular set on a single token.
  • Spaces: read:space:confluence
  • Plus search:confluence, read:me, and offline_access.

The internal PBAC scopes an agent's token must carry are coarser: confluence:content:read / :write / :delete, confluence:space:read, and confluence:comments:read / :write. Destructive page and folder deletes sit on the dedicated confluence:content:delete scope so policy can withhold deletion without withholding writes.

Two Confluence quirks the tools document inline:

  • Body representation. Page and comment bodies use the storage representation (Confluence XHTML), e.g. {"representation": "storage", "value": "<p>Hello</p>"} — not plain text.
  • Optimistic locking on update. update_page requires the next version number. Call get_page first, read version.number, then pass version: {"number": N+1}.

Prerequisites

  • A Confluence Cloud account with access to the spaces/pages you want to reach.
  • An Atlassian IdP registered in PolicyArc (provider key atlassian). Pick Atlassian in the IdP setup wizard — the bundled preset fills in the endpoints and the required audience=api.atlassian.com authorize param. You supply the Client ID / secret from an Atlassian OAuth 2.0 (3LO) app (developer.atlassian.com) whose callback URL is the AS's /oauth2/callback, with the Confluence API permission granted for the connector's granular scopes (see above).
  • Your site's cloud ID — get it from https://<site>.atlassian.net/_edge/tenant_info.

See Atlassian products — multi-connector setup for the full 3LO walkthrough (shared with the Jira connector).

Install

Provide two setup fields: Atlassian site (the subdomain, e.g. acme) and Atlassian cloud ID (the UUID). Passthrough installs need no secret. On install, the gateway merges this connector's upstream_scopes into the Atlassian IdP row, so users re-consent to the union on their next login.

Verify

Request a token with a Confluence scope and call through the gateway as a logged-in user (passthrough needs a user session, so client_credentials will return a 502 "no stored IdP token"):

curl "https://<your-as>/gateway/confluence/wiki/api/v2/spaces" \
-H "Authorization: Bearer <user-token>"

A 200 with the spaces JSON confirms: token → introspect allowed → gateway proxied → Confluence responded as the user.

What can go wrong

SymptomCauseFix
502 "no stored IdP token"Caller never logged in through the atlassian IdP (or used client_credentials, which has no user context)Re-authenticate via Atlassian.
403 from ConfluenceThe user lacks permission on the space/page, or the token is missing a scopeCheck the user's Confluence permissions and that the Atlassian app granted the scope.
400 on create/updateBody sent as plain text instead of the storage representationWrap content as {"representation": "storage", "value": "<p>...</p>"}.
409 on updateStale version.numberRe-read with get_page and pass version.number + 1.
403 from the gatewayAgent lacks the route's PBAC scopeRequest a token with the matching confluence:* scope.
Stored IdP token missing required scopesConnector installed after the user logged inUser signs out and back in to re-consent.

Reference

Manifest reference

  • ID: identos.confluence
  • Version: 1.2.0
  • Resource type: urn:connector:identos:confluence
  • Capabilities: mcp

Supported auth modes

TypeDetails
idp_passthroughrequires IdP atlassian; setup fields: site, cloudid

Setup fields

IDLabelDefaultSecret?Notes
siteAtlassian sitenoplaceholder: acme / The subdomain of your Atlassian cloud URL (e.g. 'acme' for acme.atlassian.net). Used in resource IDs and to locate the cloud ID.
cloudidAtlassian cloud IDnoplaceholder: 11223344-5566-7788-9900-aabbccddeeff / UUID identifying your Atlassian site. Get from https://{site}.atlassian.net/_edge/tenant_info (the cloudId field). Required because Atlassian Cloud OAuth tokens route through https://api.atlassian.com/ex/confluence/\{cloudid\}/, not the site URL directly.

Scopes

Scope
confluence:content:read
confluence:content:write
confluence:content:delete
confluence:space:read
confluence:comments:read
confluence:comments:write

Routes

MethodPatternScopeResource template
GET/wiki/rest/api/searchconfluence:content:read
GET/wiki/api/v2/pages/{id}confluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pagesconfluence:content:read
GET/wiki/api/v2/pages/{id}/childrenconfluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pages/{id}/labelsconfluence:content:readconfluence://{{site}}/pages/{{id}}
POST/wiki/rest/api/content/{id}/labelconfluence:content:writeconfluence://{{site}}/pages/{{id}}
DELETE/wiki/rest/api/content/{id}/labelconfluence:content:writeconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pages/{id}/attachmentsconfluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/attachments/{id}confluence:content:readconfluence://{{site}}/attachments/{{id}}
POST/wiki/rest/api/content/{id}/child/attachmentconfluence:content:writeconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/spacesconfluence:space:read
GET/wiki/api/v2/spaces/{id}confluence:space:readconfluence://{{site}}/spaces/{{id}}
GET/wiki/api/v2/spaces/{id}/pagesconfluence:space:readconfluence://{{site}}/spaces/{{id}}
GET/wiki/api/v2/pages/{id}/footer-commentsconfluence:comments:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/pages/{id}/inline-commentsconfluence:comments:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/footer-comments/{id}confluence:comments:readconfluence://{{site}}/comments/{{id}}
GET/wiki/api/v2/pages/{id}/versionsconfluence:content:readconfluence://{{site}}/pages/{{id}}
GET/wiki/api/v2/folders/{id}confluence:content:readconfluence://{{site}}/folders/{{id}}
GET/wiki/api/v2/blogpostsconfluence:content:read
GET/wiki/api/v2/blogposts/{id}confluence:content:readconfluence://{{site}}/blogposts/{{id}}
POST/wiki/api/v2/pagesconfluence:content:write
POST/wiki/api/v2/blogpostsconfluence:content:write
POST/wiki/api/v2/foldersconfluence:content:write
PUT/wiki/api/v2/pages/{id}confluence:content:writeconfluence://{{site}}/pages/{{id}}
PUT/wiki/rest/api/content/{id}/move/{position}/{targetId}confluence:content:writeconfluence://{{site}}/pages/{{id}}
PUT/wiki/api/v2/footer-comments/{id}confluence:comments:writeconfluence://{{site}}/comments/{{id}}
POST/wiki/api/v2/footer-commentsconfluence:comments:write
POST/wiki/api/v2/inline-commentsconfluence:comments:write
DELETE/wiki/api/v2/pages/{id}confluence:content:deleteconfluence://{{site}}/pages/{{id}}
DELETE/wiki/api/v2/folders/{id}confluence:content:deleteconfluence://{{site}}/folders/{{id}}
DELETE/wiki/api/v2/footer-comments/{id}confluence:comments:writeconfluence://{{site}}/comments/{{id}}

MCP tools

NameScopeDescription
search_pagesconfluence:content:readSearch Confluence content with CQL (Confluence Query Language). Example cql: "type=page AND space=DEV AND text ~ "onboarding"". Returns {results, _links:{next}, ...}; pass the opaque cursor from _links.next (or start) to page. This is Confluence's v1 search endpoint — the v2 API has no CQL equivalent.
get_pageconfluence:content:readGet a single Confluence page by numeric id. By default the body is NOT included — pass body-format=storage (or atlas_doc_format) to get the content. The response carries version.number, which you MUST read and increment before calling update_page (optimistic locking).
list_pagesconfluence:content:readList pages, optionally filtered by space id, title, or status. Paginated via the opaque cursor from _links.next.
get_page_childrenconfluence:content:readList the direct child pages of a Confluence page (one level). Paginated via cursor.
list_page_labelsconfluence:content:readList the labels attached to a Confluence page.
add_labelconfluence:content:writeAdd a label to a Confluence page (or other content) by id. Sends a single label object as the body; Confluence also accepts an array but this tool adds one at a time. This is the v1 labels endpoint — v2 has no label-write route.
remove_labelconfluence:content:writeRemove a label from a Confluence page by id. The label is passed as the name query parameter (this form tolerates '/' in label names). Reversible by re-adding, so it is not marked always-ask.
list_page_attachmentsconfluence:content:readList attachments on a Confluence page (metadata only — title, mediaType, fileSize, downloadLink).
get_attachmentconfluence:content:readGet a single attachment's metadata by id (title, mediaType, fileSize, comment, version, and a downloadLink). Returns metadata, not the binary content.
add_attachmentconfluence:content:writeAttach a file to a Confluence page. Because MCP arguments are JSON, the file is supplied as base64 in file_content_base64 (plus a filename); the gateway assembles the multipart/form-data upload and adds Atlassian's required X-Atlassian-Token header. This is the v1 attachment endpoint (v2 has no attachment-create). Needs the classic write:confluence-file scope — the v1 endpoint rejects the granular write:attachment:confluence with 401 'scope does not match'.
list_spacesconfluence:space:readList Confluence spaces the caller can see. Use the returned numeric space id for list_pages/list_space_pages and the key (e.g. DEV) in CQL. Paginated via cursor.
get_spaceconfluence:space:readGet a single Confluence space by its numeric id.
list_space_pagesconfluence:space:readList pages in a Confluence space. Paginated via cursor.
list_page_commentsconfluence:comments:readList footer comments on a Confluence page. (Footer comments are the page-level comment thread; inline comments are list_inline_comments.)
list_inline_commentsconfluence:comments:readList root-level inline comments on a Confluence page (comments anchored to a text selection, distinct from footer comments). Paginated via cursor.
get_commentconfluence:comments:readGet a single footer comment by its id, including its body and version.number (needed to update_comment).
list_page_versionsconfluence:content:readList the version history of a Confluence page (each entry has number, authorId, createdAt, and the change message). Paginated via cursor.
get_folderconfluence:content:readGet a single Confluence folder by its numeric id (title, parentId, spaceId). Folders are a v2 content type that organise pages within a space.
list_blog_postsconfluence:content:readList blog posts, optionally filtered by space id, title, or status. Paginated via the opaque cursor from _links.next.
get_blog_postconfluence:content:readGet a single blog post by numeric id. By default the body is NOT included — pass body-format=storage (or atlas_doc_format) to get the content.
create_pageconfluence:content:writeCreate a Confluence page. The body uses the storage representation (Confluence XHTML), NOT plain text. Minimal valid shape: {"spaceId": "123456", "status": "current", "title": "My page", "body": {"representation": "storage", "value": "<p>Hello</p>"}}. Get spaceId from list_spaces. To nest under a parent, add "parentId": "<pageId>".
create_blog_postconfluence:content:writeCreate a Confluence blog post. Same body caveat as create_page — the body uses the storage representation (Confluence XHTML). Shape: {"spaceId": "123456", "status": "current", "title": "My post", "body": {"representation": "storage", "value": "<p>Hello</p>"}}.
create_folderconfluence:content:writeCreate a folder in a Confluence space to organise pages. Shape: {"spaceId": "123456", "title": "My folder"}. To nest under an existing folder or page, add "parentId". Folders are a v2 content type; this needs the write:folder:confluence Atlassian scope.
update_pageconfluence:content:writeUpdate a Confluence page, overwriting its body. OPTIMISTIC LOCKING: call get_page first, read version.number, and pass version:{number: N+1}. The body uses the storage representation (same caveat as create_page). Required fields: id, status, title, body, version. Example: {"id": "123", "status": "current", "title": "My page", "body": {"representation": "storage", "value": "<p>Updated</p>"}, "version": {"number": 4, "message": "edit via agent"}}.
move_pageconfluence:content:writeMove a Confluence page relative to another page. position = 'append' makes the page a child of targetId; 'before'/'after' place it as a sibling immediately before/after targetId. Nothing is deleted — the page is relocated — but it overwrites the page's parent, so it is marked a write. This is the v1 content-move endpoint (v2 has no move).
update_commentconfluence:comments:writeUpdate a footer comment, overwriting its body. OPTIMISTIC LOCKING like update_page: call get_comment first, read version.number, pass version:{number: N+1}. Body uses the storage representation. Shape: {"id": "123", "body": {"representation": "storage", "value": "<p>Edited.</p>"}, "version": {"number": 2}}.
add_commentconfluence:comments:writeAdd a footer comment to a Confluence page. Body uses the storage representation. Shape: {"pageId": "123", "body": {"representation": "storage", "value": "<p>Looks good.</p>"}}. To reply to an existing comment, add "parentCommentId".
add_inline_commentconfluence:comments:writeAdd an inline comment anchored to a text selection on a page. Shape: {"pageId": "123", "body": {"representation": "storage", "value": "<p>Question.</p>"}, "inlineCommentProperties": {"textSelection": "the exact highlighted text", "textSelectionMatchCount": 1, "textSelectionMatchIndex": 0}}. The textSelection must match text in the page body.
delete_pageconfluence:content:deleteDelete a Confluence page by id. By default this moves the page to the trash (restorable); it still removes the page from its space. Needs the delete:page:confluence Atlassian scope.
delete_folderconfluence:content:deleteDelete a Confluence folder by id. Moves the folder to the trash; its contents go with it, so this can remove far more than one page. Needs the delete:folder:confluence Atlassian scope.
delete_commentconfluence:comments:writeDelete a footer comment by id. This is permanent — comments are not trashed like pages. Kept on comments:write (not the content:delete scope) because it removes a single comment, not page content.