Skip to main content
Glama
README.md
# Confluence MCP Server

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that connects AI agents to **Confluence Cloud**. Agents can **search, read, create, and update** Confluence content — pages, comments, links, images, and labels.

The agent authenticates via **Atlassian OAuth 2.0** and passes its Bearer token to the server on each request — **no credentials are stored on the server**, and every caller acts as their own Confluence user (respecting their existing permissions).

Runs as a portable streamable-HTTP server — deploy with **Docker** to Azure Container Apps, App Service, or any container host, and connect from **Microsoft Copilot Studio**, **VS Code (GitHub Copilot)**, or any MCP-compatible client.

> ℹ️ A **token mode** is also available for local development / single-user self-hosting — see [Local development](#local-development).

---

## What it does — the tools

| Tool | Description | Type |
|------|-------------|------|
| `search_pages` | Find pages by keyword (filters: space, created/modified dates, label) | Read |
| `get_page` | Read a single page's content | Read |
| `get_page_context` | One-shot: page + comments + links + images | Read |
| `search_page_content` | Find text inside one page, with context | Read |
| `get_comments` | List comments and replies (incl. their images) | Read |
| `search_comments` | Find comments matching a phrase (one page or site-wide) | Read |
| `extract_links` | List hyperlinks on a page and/or in its comments | Read |
| `get_images` | List images on a page and/or in its comments (source-tagged) | Read |
| `get_labels` | List a page's labels | Read |
| `list_spaces` | Discover the spaces you can access | Read |
| `get_child_pages` | Browse the pages nested under a page | Read |
| `add_comment` | Add a comment or reply to a page | Action |
| `append_to_page` | Append a paragraph to the end of a page | Action |
| `create_page` | Create a new page from Markdown content | Action |
| `update_page` | Edit a page: append / replace a section / replace body | Action |
| `add_labels` | Tag a page with labels | Action |

**Safety by design:** write tools support a `preview` (dry-run) mode; `update_page`'s `replace_section` is idempotent; `CONFLUENCE_READONLY=true` disables all writes; there is **no delete tool**.

> **Search scopes:** `search_pages` / `search_comments` use Confluence's CQL search, which requires the **classic** `search:confluence` scope. Every other tool uses the Confluence **v2** API (granular scopes). See [Connect to Copilot Studio](#connect-to-copilot-studio-oauth-20--recommended) for the full scope list.

---

## Architecture

```
┌────────────────────┐   Bearer   ┌─────────────────────┐   REST    ┌────────────────────┐
│  Agent (Copilot    │   token    │  Confluence MCP      │  (OAuth)  │  Atlassian /       │
│  Studio / VS Code) │ ─────────► │  Server (container)  │ ────────► │  Confluence Cloud  │
└────────────────────┘    MCP     └─────────────────────┘ ◄──────── └────────────────────┘
```

The agent obtains an OAuth token from Atlassian and includes it as `Authorization: Bearer <token>` on every MCP request. The server forwards it to the Confluence Cloud REST API via `https://api.atlassian.com/ex/confluence/{cloudId}/...`. Reads and writes use the Confluence **v2** API (`/wiki/api/v2`); full-text search uses the v1 CQL search endpoint (`/wiki/rest/api/search`), which has no v2 equivalent. It never stores credentials or acquires tokens itself.

---

## Quick start (local, token mode)

The fastest way to try it — run against your own Confluence with an API token.

```bash
git clone <your-repo-url>
cd confluence-mcp-server
python -m venv .venv
# Windows: .\.venv\Scripts\Activate.ps1   |   macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt

cp .env.example .env
# In .env set:
#   CONFLUENCE_AUTH_MODE=token
#   CONFLUENCE_BASE_URL=https://your-site.atlassian.net/wiki
#   CONFLUENCE_EMAIL=you@example.com
#   CONFLUENCE_API_TOKEN=...     (https://id.atlassian.com/manage-profile/security/api-tokens)

python -m confluence_mcp
```

The server starts on `http://localhost:8000`:
- Health: `GET /` (and `/health`)
- MCP endpoint: `POST /mcp` (streamable HTTP)

## Connect to Copilot Studio (OAuth 2.0 — recommended)

> Microsoft Power Platform / Copilot Studio drops extra query parameters from the OAuth
> authorization URL, but Atlassian **requires** `audience=api.atlassian.com`. This server
> ships a small **[`/oauth/authorize` proxy](#oauth-authorize-proxy)** that re-adds it —
> point Copilot Studio's Authorization URL at that proxy (below).

**1. Register an Atlassian OAuth 2.0 app** at <https://developer.atlassian.com/console/myapps/>:

- Under **Permissions → Confluence API → Configure → Edit Scopes**, enable **all** of the
  scopes below. They live in **two different sections** of the console — you must add from
  **both**. Atlassian lets one app hold classic + granular scopes together, but every
  requested scope must be enabled here, or consent fails with
  *"Something went wrong — information for the owner"*:

  | Scope | Section | Enables |
  |-------|---------|---------|
  | `read:space:confluence` | Granular | list spaces |
  | `read:page:confluence` | Granular | read pages, children, title lookup |
  | `write:page:confluence` | Granular | create / update pages |
  | `read:comment:confluence` | Granular | read comments |
  | `write:comment:confluence` | Granular | add comments |
  | `read:attachment:confluence` | Granular | attachments / images |
  | `read:label:confluence` | Granular | read labels |
  | `write:label:confluence` | Granular | add labels |
  | `search:confluence` | **Classic** | full-text CQL search |

- Under **Authorization → OAuth 2.0 (3LO)**, set the **Callback URL** your agent host uses.
  For Copilot Studio / Power Platform custom connectors this is
  `https://global.consent.azure-apim.net/redirect` (the connector also shows a
  generated Redirect URL on save — register whichever your connection actually uses).
- Note the **Client ID** and **Client Secret**.

**2. In Copilot Studio** → your agent → **Tools → Add a tool → New tool → Model Context
Protocol**, authentication **OAuth 2.0**:

| Field | Value |
|-------|-------|
| **Server URL** | `https://<your-host>/mcp` |
| **Authorization URL** | `https://<your-host>/oauth/authorize`  ←  the proxy, **no query string** |
| **Token URL** | `https://auth.atlassian.com/oauth/token` |
| **Refresh URL** | `https://auth.atlassian.com/oauth/token` |
| **Client ID / Secret** | from your Atlassian app |
| **Scopes** (space-separated) | `read:space:confluence read:page:confluence write:page:confluence read:comment:confluence write:comment:confluence read:attachment:confluence read:label:confluence write:label:confluence search:confluence offline_access` |

**3.** Save the connector, copy its generated **Redirect URL**, and confirm it is listed in
your Atlassian app's Callback URLs. Create the connection and complete the consent prompt.

> **Changing scopes later?** Delete and recreate the connection — an existing connection
> keeps its original token and scopes.

The agent now sends its Bearer token to the server on every call, all 16 tools appear, and
each user acts as **their own** Confluence identity.

## OAuth authorize proxy

Atlassian's OAuth 2.0 (3LO) requires `audience=api.atlassian.com` on the `/authorize`
request. Some clients — notably Microsoft Power Platform / Copilot Studio — do **not**
forward extra query parameters placed on the authorization URL, so Atlassian rejects the
request (`invalid_request — incorrect request parameters`).

The server exposes **`GET /oauth/authorize`** to fix this: it copies the incoming query
parameters, injects `audience=api.atlassian.com` (and `prompt=consent`), and 302-redirects
to `https://auth.atlassian.com/authorize`. Point your client's Authorization URL at
`https://<your-host>/oauth/authorize` (no query string needed).

Overridable via env: `ATLASSIAN_AUTHORIZE_URL`, `ATLASSIAN_AUDIENCE`.

## Connect to VS Code (GitHub Copilot)

Add an MCP server entry (e.g. in `.vscode/mcp.json`):

```json
{
  "servers": {
    "confluence": { "type": "http", "url": "http://localhost:8000/mcp" }
  }
}
```

For a remote OAuth-protected deployment, point `url` at `https://<your-host>/mcp`;
VS Code will run the OAuth flow. Then use Copilot Chat in **Agent mode**.

## Local development

Use **token mode** to run and test without OAuth:

```bash
# .env
CONFLUENCE_AUTH_MODE=token
CONFLUENCE_BASE_URL=https://your-site.atlassian.net/wiki
CONFLUENCE_EMAIL=you@example.com
CONFLUENCE_API_TOKEN=...

python -m confluence_mcp        # run the server
pytest                          # run unit tests
ruff check .                    # lint
```

In token mode the server uses one shared identity (your token) for all callers —
intended only for local development or single-user self-hosting.

## Configuration

| Variable | Required | Description |
|----------|----------|-------------|
| `CONFLUENCE_AUTH_MODE` | no | `oauth` (default) or `token`. |
| `HOST` / `PORT` | no | Bind address / port (default `0.0.0.0:8000`). |
| `CONFLUENCE_READONLY` | no | `true` disables all write tools. |
| `CONFLUENCE_DEFAULT_SPACE` | no | Default space key for `create_page`. |
| `CONFLUENCE_SITE_URL` | no | (oauth) Pin to one site if the token can access several. |
| `MCP_ALLOWED_HOSTS` | no | Comma-separated Host values to allow (turns on MCP DNS-rebinding protection). Default: **disabled** — appropriate behind an authenticating TLS gateway/ingress. |
| `ATLASSIAN_AUDIENCE` | no | Audience injected by the `/oauth/authorize` proxy (default `api.atlassian.com`). |
| `ATLASSIAN_AUTHORIZE_URL` | no | Upstream authorize URL for the proxy (default `https://auth.atlassian.com/authorize`). |
| `CONFLUENCE_BASE_URL` | token mode | Your Confluence base URL, ending in `/wiki`. |
| `CONFLUENCE_EMAIL` | token mode | Atlassian account email. |
| `CONFLUENCE_API_TOKEN` | token mode | Atlassian API token. |

See [`.env.example`](.env.example).

## Deploy

### Docker

```bash
docker build -t confluence-mcp-server .
docker run -p 8000:8000 --env-file .env confluence-mcp-server
```

### Azure Container Apps

```bash
az containerapp up \
  --name confluence-mcp \
  --resource-group my-rg \
  --location eastus \
  --ingress external --target-port 8000 \
  --source .
# then set app settings (env vars):
az containerapp update --name confluence-mcp --resource-group my-rg \
  --set-env-vars CONFLUENCE_AUTH_MODE=oauth
```

Your MCP endpoint is `https://<app-fqdn>/mcp`. The image also runs on any container
host (App Service for Containers, AWS ECS, Cloud Run, etc.).

## Security model

- **oauth mode (default):** no credentials are stored on the server; the agent
  supplies a Bearer token per request and every caller acts as their own Confluence
  user, respecting their existing permissions.
- **token mode:** a single static identity — keep the token secret; use only for
  local dev or single-user self-hosting.
- `CONFLUENCE_READONLY=true` disables all write tools. There is **no delete tool**.
- Write tools support `preview` (dry-run); `update_page`'s `replace_section` is idempotent.

## License

[MIT](LICENSE)

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md).