Recently, I've noticed that my colleagues go to Confluence and use the old-school way to search for info. So I decided to share the way I search Confluence now. A full example with instructions can be found here: https://github.com/stjam/mcp-confluence
Below is a working setup from an empty folder to ✓ Connected, with a breakdown of the two errors you're guaranteed to hit.
What you'll need
- Docker
- Claude Code (CLI)
- Self-hosted Confluence (Server / Data Center) and access to it
- A Personal Access Token from your Confluence
We'll use the ready-made mcp-atlassian MCP server by sooperset — it serves both Jira and Confluence from a single container. This guide is about Confluence, but Jira connects through the same server by analogy.
Step 1. Get a Personal Access Token
In Confluence: profile → Personal Access Tokens → create a token. Copy the value immediately — it won't be shown again.
Test the token directly, before any Docker or MCP — this separates access problems from configuration problems:
curl -H "Authorization: Bearer YOUR_CONFLUENCE_TOKEN" \
https://confluence.example.com/rest/api/user/current
If you get 200 and your profile back — the token works, move on. If you get 401/403 — the problem is with the token or its permissions, and that's what you fix here, not in MCP.
Step 2. Prepare the .env
Create an .env file next to your run script:
CONFLUENCE_URL=https://confluence.example.com
CONFLUENCE_PERSONAL_TOKEN=YOUR_CONFLUENCE_TOKEN
# THE KEY LINE — nothing works without it (breakdown below)
ALLOW_GLOBAL_CRED_FALLBACK=true
Two important points that cause most 401s:
- Use
CONFLUENCE_PERSONAL_TOKEN, not the CONFLUENCE_USERNAME + CONFLUENCE_API_TOKEN pair. The latter is the cloud scheme; on self-hosted it gives you 401.
- Check your base URL for a context path. If your Confluence opens at an address like
https://confluence.example.com/wiki or /confluence, that prefix must be in CONFLUENCE_URL, or every request goes to 404. Open any page in your browser and look at what comes after the domain.
Step 3. Start the container
docker run -d \
--name mcp-atlassian \
--env-file .env \
-p 8080:8080 \
ghcr.io/sooperset/mcp-atlassian:latest \
--transport streamable-http \
--host 0.0.0.0 \
--port 8080
Note: the flags --transport streamable-http --host 0.0.0.0 --port 8080 come after the image name — they're arguments to the server, not to Docker. They're easy to lose, and if you do, the server starts in stdio mode and never listens on the HTTP port at all. The symptom: Failed to connect, with an empty curl against the port.
Step 4. Connect to Claude Code
claude mcp add --transport http confluence http://localhost:8080/mcp
Syntax: claude mcp add --transport http <name> <url>. All flags go before the server name. The command saves the config without validating it — meaning it succeeds even if the server is down; you only see the real status when it tries to connect.
Check it:
claude mcp list
What you want to see:
confluence: http://localhost:8080/mcp (HTTP) - ✓ Connected
Now — the two errors you'll almost certainly hit between this step and that coveted checkmark.
Error #1: Not Acceptable: Client must accept text/event-stream
If you decide to poke the endpoint by hand with a plain curl, you'll get:
{"jsonrpc":"2.0","id":"server-error","error":{"code":-32600,
"message":"Not Acceptable: Client must accept text/event-stream"}}
This is not a failure. It's a sign that the server is alive and responding over the MCP protocol (streamable-http), while you hit it like plain HTTP. The server rightly demands that the client accept text/event-stream. To test by hand:
curl -i http://localhost:8080/mcp \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
But if the server responds and claude mcp list still says Failed to connect — the culprit is almost always the next error.
Error #2: the big one — rejecting unauthenticated MCP request
Check the container logs (this is the first thing to do for any problem):
docker logs mcp-atlassian
And you see:
WARNING - mcp-atlassian.server.main - UserTokenMiddleware:
rejecting unauthenticated MCP request
(no user identity and ALLOW_GLOBAL_CRED_FALLBACK is off)
There it is, the root of all evil. Recent versions of mcp-atlassian default to multi-user mode: the server expects each individual request to bring its own authentication (per-request, via a header from the client). Claude, meanwhile, connects without a user token, and the fallback to the global credentials from your .env is off by default. Result: the server kills the request before it ever gets to Confluence, and the client sees Failed to connect.
The fix is exactly one variable, which we already put in .env back in Step 2:
ALLOW_GLOBAL_CRED_FALLBACK=true
Recreate the container and check the logs:
docker rm -f mcp-atlassian
docker run -d --name mcp-atlassian --env-file .env -p 8080:8080 \
ghcr.io/sooperset/mcp-atlassian:latest \
--transport streamable-http --host 0.0.0.0 --port 8080
docker logs mcp-atlassian
The rejecting unauthenticated warning should be gone, replaced by a normal initialization with your Confluence credentials. After that, claude mcp list will finally show ✓ Connected.
Bonus: fix the context bloat
By default mcp-atlassian loads all toolsets — that's around 40 tools that eat 20–30k of context just at startup, before your first task. If all you need is search and read over Confluence, set the toolset explicitly via the TOOLSETS variable and skip dragging in the whole Jira arsenal. The server even warns you about this in the logs on startup.
A more general solution, if you run many MCP servers, is mcp-optimizer: a separate MCP that hands the agent the tool descriptions for a given server only when the agent asks for something matching the task. It saves a noticeable amount of context on tool-heavy servers.
Troubleshooting checklist
If something doesn't work, go strictly in order — that way you're not fixing things at random:
docker logs mcp-atlassian — 90% of your answers are here.
docker ps — is the container Up, not Restarting/Exited?
curl with the Bearer token against /rest/api/user/current — is the token even working?
ALLOW_GLOBAL_CRED_FALLBACK=true — is it in .env?
CONFLUENCE_PERSONAL_TOKEN (not the cloud pair) and base URL with the context path?
- Transport flags — do they come after the image name in
docker run?
Wrapping up
The whole trick to self-hosted Confluence + Claude comes down to one variable, ALLOW_GLOBAL_CRED_FALLBACK=true, which isn't documented anywhere obvious. Everything else is standard Docker + PAT. Once it's set up, you get an internal wiki you can query in plain language right from your terminal — and that's a lot nicer than clicking through Confluence search by hand.