Skip to content

The missing /mcp that sent me down an OAuth rabbit hole

Two real problems, several misleading errors, and a missing URL path while reconnecting my self-hosted Obsidian MCP server to ChatGPT.

  • self-hosting
  • obsidian
  • mcp
  • troubleshooting

My Obsidian connection in ChatGPT stopped working. What followed involved Docker permissions, OAuth registrations, reverse proxy logs, and MCP protocol versions.

The setting that finally restored tool discovery? I had forgotten /mcp at the end of the server URL.

Yes. That was it.

There was also a real permissions problem, so the investigation wasn’t imaginary. But we (we as in me and Codex) spent far too long exploring complicated explanations before checking the exact URL saved in ChatGPT.

The setup

I use Obsidian with the Self-hosted LiveSync plugin, which syncs my vault to CouchDB on my home server. Alongside it, I run obsidian-sync-mcp, which gives MCP clients access to my notes. Caddy handles HTTPS and reverse proxying, with Cloudflare in front.

ChatGPT connects through OAuth and should then discover tools such as read_note, list_notes, and search_notes.

The important distinction is between the public hostname:

https://notes.example.com

and the actual MCP endpoint:

https://notes.example.com/mcp

Keep that distinction in mind. Apparently, I didn’t.

First problem: the container couldn’t access its own files

The first error was “Unknown client.” Deleting and recreating the ChatGPT connection brought back the password prompt, but the connection still didn’t finish properly.

I asked Codex to inspect the deployment and check whether something had changed upstream. The project’s v0.10.0 changelog documented a switch from running the Docker image as root to the unprivileged node user, UID 1000. Because my deployment followed the latest image, the running user had changed while the existing files kept their previous ownership.

The logs showed permission errors against the search index and OAuth state:

EACCES: permission denied, open '/data/.../search-index.json'
EACCES: permission denied, open '/data/.../auth-tokens.json'

This mattered because the server stores OAuth registrations and tokens on disk. If it cannot read or update them, an existing client can become unknown and a new connection may not survive a restart.

After confirming the container’s user, we repaired ownership inside its mounted MCP data directory:

docker exec obsidian-sync-mcp id
docker exec --user 0 obsidian-sync-mcp chown -R 1000:1000 /data

That UID matched this particular image, and the ownership change did not touch CouchDB’s storage.

Authorization, token persistence, and a diagnostic tools/list request now worked. One real problem was fixed.

ChatGPT still had no tools.

Authentication worked, but discovery didn’t

Different attempts produced “OAuth state not found,” “Authentication succeeded, action discovery failed,” or an app that appeared connected but exposed no actions.

That distinction was important: password approval, token exchange, and tool discovery are separate steps. A connected label did not prove that ChatGPT had reached the MCP endpoint.

We found another convincing clue. A modern server/discover request returned HTTP 400, while the older initialize followed by tools/list flow succeeded. It looked like a protocol compatibility problem.

Temporary Caddy logging showed otherwise. ChatGPT tried modern discovery, received the error, and then successfully fell back to the older initialization flow. No protocol patch was needed.

The diagnostic tests had succeeded because they targeted /mcp. We still hadn’t checked whether ChatGPT was configured with that same URL.

Finally checking the exact URL

The question that should have come much earlier was: what exact MCP Server URL was saved in ChatGPT?

My answer was the hostname without /mcp.

That explained why authentication could appear to work while tool discovery failed. The OAuth routes existed directly under the hostname:

/.well-known/oauth-authorization-server
/oauth/authorize
/oauth/token

But the MCP server itself lived at /mcp. The root path returned 404. Re-entering the password could never correct the endpoint URL.

The fix

The direct fix was to configure the connector with the complete URL, including /mcp.

I also added a Caddy alias so the previously saved root URL would continue to work:

notes.example.com {
    rewrite / /mcp
    reverse_proxy 127.0.0.1:8787
}

The rewrite matches only /; the OAuth routes and existing /mcp endpoint keep their paths. It forwards internally rather than relying on the client to follow a redirect.

After that, both URLs passed initialization and tool-list checks. More importantly, the trace showed ChatGPT itself completing initialization and fetching the tools with HTTP 200.

The tools were back, and the temporary logging could be removed.

What I’ll check next time

Two things had failed: the upgraded container couldn’t access its persisted data, and the recreated connector pointed to the wrong endpoint. Fixing the first did not fix the second.

My next checklist is much shorter:

  1. Copy the exact URL from the client’s settings, including its path.
  2. Test that URL—not the URL I remember configuring.
  3. Separate password approval, token exchange, tool discovery, and execution.
  4. Check upgrade notes and mounted-file permissions when a container changes.
  5. Observe what the real client sends before blaming protocol compatibility.

Working with an AI assistant didn’t remove the need for those checks. We found genuine errors and useful evidence, but we also followed plausible explanations too far. A test passing against the correct endpoint could not tell us that the actual client was configured with the wrong one.

Next time, before another round of OAuth debugging, I’m checking the last four characters of the URL.