Adding this site to Claude.ai, ChatGPT, Claude Desktop, Claude Code or another MCP client, step by step, and how a client connects and authenticates.
The operator, adding this site to an AI client once the assistant has an account (see Onboard an AI agent for the account and its permissions); and an AI agent, or whoever builds a client, that needs to know how a client reaches the site. This is the one page with the steps for each client, the Endpoints, the two Connection modes and Authentication. It is public, so a client can read it before it holds a credential.
This site exposes an MCP connector so an AI assistant can maintain it through tools (list, read, write, move, delete pages; set permissions; activate themes/layouts). It speaks standard MCP + OAuth, so it works with any MCP-capable client - Claude.ai, ChatGPT, Claude Desktop, Claude Code, and others.
For the full tool catalogue, the capability model and the error reference, see AI connector - tools reference (signed in). This page covers connecting.
The endpoint is the same for everyone:
https://YOUR-SITE/cgi-bin/lazysite-mcp.pl
The web apps (Claude.ai, ChatGPT) are added by URL and sign in with a one-time
connect code; the developer tools (Claude Desktop, Claude Code, a script) set
a bearer header. How each credential works, and that both are held to the same
grant, is in Authentication. Whichever
you use, have the assistant call whoami first to confirm the grant.
WWW-Authenticate, which is exactly
the ask that setting waits for. "Always required" also works.GET with 405, so SSE (legacy) will not work.
Claude.ai selects SSE automatically for a URL ending in /sse; this one does
not, so the default is already right - just do not change it.whoami.If Authentication shows "None - Detected". Claude.ai probed the server and
found no OAuth, which means oauth_enabled is off on this instance. Turn it on
in the manager: Config → Services → OAuth authorization server. Every OAuth
endpoint returns 404 until you do, so the connect-code flow cannot start.
You can also skip OAuth entirely: leave Authentication on None, and add
Authorization: Bearer <partner-id>:<token> under Additional request
headers using a token from Generate agent brief. That path needs no
oauth_enabled and no connect code - it is the static-bearer method below,
driven from the web app.
whoami, then list_files.Note: on Plus/Pro, ChatGPT can call read-only tools only (whoami, list_files, read_file). Business/Enterprise get the write tools (write_file, activate_theme, …) with a per-call approval card. ChatGPT is also noticeably slower than Claude per tool call.
The most common cause is not the server - it is that the assistant's tool list
is fixed when a chat opens. A connector you finished authorising during a
conversation will not surface until you start a new chat. So: complete the
sign-in, confirm the Users panel shows connected, then open a fresh chat and
ask for whoami.
If a tool call is refused, the error's reason names the cause precisely:
sign-in-incomplete (the connect code was never pasted - redo the sign-in with a
fresh code), token-expired (reconnect, or let the client refresh), or
token-invalid (the token was revoked or the site secret was rotated - reconnect).
lzs_ token directly).Authorization: Bearer <partner-id>:<lzs_ token> (Desktop/Code support custom
headers). A script can also use the control API + WebDAV directly (API mode -
see Connection modes).Point it at the endpoint. If it supports OAuth, it will discover
/.well-known/oauth-protected-resource from the 401 WWW-Authenticate challenge
and run the connect-code flow. If it supports a static header, use the
partner-id:lzs_ bearer.
invalid_client: remove and re-add the
connector so the app does a fresh registration + sign-in.whoami works but a write is refused: the grant (capabilities + per-file
ACL) is authoritative - the partner lacks that capability or write access.https://YOUR-SITE/cgi-bin/lazysite-mcp.pl - a single MCP endpoint,
Streamable HTTP / JSON-RPC, exposing the maintenance operations as tools (see
Tools). initialize and tools/list are
open (discovery); a tools/call requires authentication. An unauthenticated tool call returns
HTTP 401 with a WWW-Authenticate challenge so an OAuth client starts the
sign-in flow.
https://YOUR-SITE/dav/ - content, assets, layout/theme files, and
nav.conf.
https://YOUR-SITE/cgi-bin/lazysite-auth.pl?action=exchange and
?action=rotate.
https://YOUR-SITE/cgi-bin/lazysite-manager-api.pl - token-authenticated (the
same Basic auth as WebDAV). Carries the operations that are not file-shaped.
Each is gated by the matching capability from your grant; the actions are in
AI briefing - publishing.
Two ways to drive a site - pick by what you are. The credential, capabilities and per-file ACLs are identical in both; only the transport differs.
lzs_ token).
whoami, list / read / write /
move / delete files, set_permissions, activate_theme, activate_layout -
as MCP tools. Best for an MCP-capable conversational agent (e.g. a Claude.ai
custom connector, or Claude Desktop/Code): add …/cgi-bin/lazysite-mcp.pl as
a remote connector with bearer auth <partner-id>:<lzs_ token> (the same
credential, colon-joined). It writes one file per call, so for a large initial
build prefer API mode + WebDAV; for ongoing maintenance either works.
PUT/mount over WebDAV - best for a full
build or bulk upload), or the MCP endpoint as a remote (HTTP) MCP server for
guided, tool-shaped maintenance. A scripted or non-AI client uses API
mode. Whichever you pick, call whoami first to confirm your grant.
Two credential shapes, held to the same capabilities and per-file ACLs:
lzs_ access token. The operator's Generate credential on the Users page
gives one directly; Generate agent brief gives a pairing key you exchange for
one (below). Over MCP send it as Authorization: Bearer <partner-id>:<lzs_ token>;
over WebDAV and the control API as HTTP Basic auth (below).
whoami returns an auth block - { method: "oauth"|"bearer", expires_at } - so
the agent can see how the session is authenticated and when it lapses.
You hold a single-use, short-lived pairing key (prefix lzp_). You exchange it
once for a working access token (prefix lzs_).
POST the pairing key to the exchange endpoint. The JSON response carries
the token and its expiry as an epoch timestamp.
claude-example), and the wrong username returns 401.
401, rotate if you still hold a recently valid token;
otherwise the operator must re-issue the pairing.
# Exchange the pairing key for an access token
curl -s -X POST "https://YOUR-SITE/cgi-bin/lazysite-auth.pl?action=exchange" \
--data "username=PARTNER&pairing_key=lzp_..."
# -> { "ok": true, "token": "lzs_...", "expires_at": 1750000000 }
# Rotate before expiry (current token as Basic auth, no body)
curl -s -X POST -u "PARTNER:lzs_..." \
"https://YOUR-SITE/cgi-bin/lazysite-auth.pl?action=rotate"
Read expires_at from the exchange and rotate responses so you rotate on
schedule rather than waiting for a 401. Both endpoints are HTTPS-only and
rate-limited.
A cheap, side-effect-free probe that the token is live:
PROPFIND /dav/ Depth: 0 Authorization: Basic base64(PARTNER:lzs_...)
-> 207 = authenticated; 401 = wrong username or token.
For your full grant - capabilities, groups, scope, and the extensions, layouts, and themes the site offers (with active flags) - introspect over the control API rather than assuming from the bootstrap:
GET /cgi-bin/lazysite-manager-api.pl?action=whoami
Authorization: Basic base64(PARTNER:lzs_...)
-> { partner, capabilities, groups, scope, layouts, themes, extensions, site_capabilities }