Use
API reference
Zircon’s MCP connector, OAuth discovery and existing REST endpoints.
Discovery
POST /mcporPOST /mcp/-
Streamable HTTP MCP endpoint. It responds to JSON-RPC initialization,
server/discover, tool and event methods with JSON. An unauthenticated request returns an OAuth discovery challenge. GET /.well-known/oauth-protected-resource- Resource metadata for the MCP connector.
GET /.well-known/oauth-protected-resource/mcp-
The same resource metadata at the path used when ChatGPT probes
/mcp/. GET /.well-known/oauth-authorization-server- Authorization server metadata, including PKCE S256 and registration.
POST /oauth/register- Dynamic client registration for exact ChatGPT connector callback URIs. Supports public and confidential clients.
GET /openapi.json- Legacy GPT Action schema.
GET /healthz-
{"ok": true}when the service is up. GET /privacy- The privacy page to use as the Action’s privacy URL.
GET /admin/events-
Optional owner diagnostics when enabled. Requires an allowlisted
signed-in GitHub session or
Authorization: Bearer <ADMIN_TOKEN>. Returns recent IRC activity and MCP/connection metadata; acceptslimit(1–200) and ISO 8601since.
OAuth
Zircon is the authorization server for the MCP connector. Registered
MCP clients must use the authorization code grant with S256 PKCE and
include resource=https://zircon.chooser.us in authorization
and token requests. Access tokens are bound to that resource. Refresh
tokens rotate on each use. The legacy GPT Action client remains
confidential and accepts optional PKCE for compatibility.
| endpoint | purpose |
|---|---|
GET /oauth/authorize |
starts sign-in; response_type=code,
client_id, exact redirect_uri,
state, scope |
POST /oauth/token |
grant_type=authorization_code or
refresh_token |
POST /oauth/revoke |
revokes an access or refresh token |
Scopes are irc:read and irc:write. Refresh
tokens rotate on each use. Each dynamically registered redirect URI must
exactly match one submitted at registration. The older GPT Action client
uses the optional oauthRedirectUris list.
MCP tools
| tool | scope | result |
|---|---|---|
see_account_information |
irc:read |
Start or resume an agent session; see nick, server, channels, presence, last captured activity, unread and last acknowledged entries, and mention subscription state |
go_online |
irc:write |
Connect this human account’s ZNC network and establish Zircon’s ZNC session for history capture and events |
read_history |
irc:read |
Read recent, last_hour or
unread activity in one channel; unread requires the agent’s
session_id |
ack_messages |
irc:read |
Advance only the specified agent session’s channel cursor after processing an unread batch |
search_history |
irc:read |
Case-insensitive exact phrase search, newest first, with channel, UTC time and opaque pagination |
send_message |
irc:write |
Queue one message to an enabled channel; returns a stable
messageId for retries |
get_message_status |
irc:read |
Inspect a queued send by message_id: pending, queued,
echoed or failed |
go_offline |
irc:write |
Disconnect this human account’s upstream IRC network and local ZNC session, affecting all its agents |
One human account owns one ZNC network connection. Every agent using
that account shares its nick, channels and online state. Calling
go_offline therefore disconnects the account for all
agents. Zircon retains history while offline but captures no new IRC
activity or mention events. see_account_information reports
the local ZNC session, upstream status, joined channels and
onlineSince; upstreamConnected: null means the
status query could not determine a state. The returned
settingsUrl is where the human can change the approved
network, nick and channels.
For independent unread processing, call
see_account_information without session_id
once per agent and retain its sessionId in that chat. Pass
it as session_id to read_history(mode=unread)
and ack_messages. Calling
see_account_information with the existing ID resumes the
same mailbox and shows each channel’s unread count and last acknowledged
entry. Agent sessions are bound to the signed-in user and OAuth client;
one agent cannot acknowledge another session’s batch. Sessions survive
restarts and expire after 30 days of inactivity. A first call without an
ID creates a new mailbox starting at the earliest retained activity, so
an agent can recover by opening a new session if it loses its ID.
Activity includes messages, actions, joins, parts, kicks, topics and
modes. Each retained entry has a stable entryId, network
and channel context, the sender’s nick, event time, observation time,
and a timestamp source (server, observed or
local). An entry linked to a Zircon send also has its
messageId; other entries have messageId: null.
read_history(mode=unread) marks entries that mention the
current nick and returns the same batch until acknowledged.
recent and last_hour return newest first and
do not change unread state. last_hour uses the IRC event
timestamp. Retention limits still apply to unacknowledged data.
send_message returns queued after
writing to the local ZNC socket. Zircon records one local channel entry
immediately and links its stable entryId to the outgoing
messageId. If a matching ZNC echo or replay arrives, it
updates that entry with the server timestamp and
get_message_status reports echoed. An echo
shows that the line came back through ZNC; it cannot prove that other
people received or read it. If no echo arrives, the status stays
queued and the entry’s timestampSource
stays local. A failed write can be retried with the
same idempotency key. Old duplicate entries created before this change
remain until normal retention pruning.
lastReceived identifies the newest captured entry in an
enabled channel. ZNC’s finite buffer means Zircon cannot certify
complete capture across disconnects; compare
lastDisconnectedAt, onlineSince and the
retained entries when investigating a gap.
MCP Events
Zircon advertises MCP 2.0 (2026-07-28) through
server/discover and supports events/list,
events/subscribe and events/unsubscribe at the
authenticated /mcp endpoint. The first event is
message.mention: a channel message containing the user’s
current nick as a whole token, case-insensitively. It has no alternate
nick or alias matching. Subscription filters are network,
channel, sender and keyword; all
supplied filters must match. Omit channel to follow every
channel the user enables now or later. A channel-specific subscription
stays pinned to its original channel and pauses when that channel is
disabled; it does not move to a new channel. A channel must be enabled
when subscribing to it. An event includes network, channel, sender,
text, kind, observation time and the retained entryId,
which can be matched to history or unread results. IRC messages sent
under the user’s own nick do not emit events.
Subscriptions survive restarts. Zircon verifies the HTTPS callback
before activation, signs each delivery using Standard Webhooks, and
retries transient failures from a bounded background queue. New mentions
wake the delivery worker immediately; a five-second poll covers retries
and serves as a fallback. Each retry keeps its event ID and gets a new
signature and timestamp; delivery order is not guaranteed. Replayed
lines are processed if Zircon has not recorded the same fingerprint
before, so a reconnect can produce a late mention event. Unread batches
remain unacknowledged until the agent calls ack_messages;
event delivery does not advance that cursor. Users can view and revoke
subscriptions at /settings. events/list only
advertises the event; ChatGPT must also call
events/subscribe for callbacks to begin. Event delivery
requires Zircon to stay online; ordinary tools work independently of
Events. OpenAI’s
Events guide describes which ChatGPT surfaces can receive
events.
see_account_information.mentionEvents reports
not_subscribed until the calling OAuth client has an active
subscription. It then distinguishes an idle subscription, a queued
delivery, an accepted callback and a failed callback, and shows each
subscription’s filters, paused state and expiry. Event subscriptions are
tied to the OAuth client and ChatGPT callback URL, independently of the
agent’s unread mailbox session. Zircon does not receive ChatGPT’s
callback URL from an ordinary tool; the ChatGPT event host must invoke
the separate MCP events/subscribe method. To move a
channel-specific subscription, ChatGPT unsubscribes with its original
filters and callback URL, then subscribes with the new filters.
Unsubscribe works even after the old channel is disabled. An accepted
callback means ChatGPT received the webhook, while its chat response
happens later. The account result omits callback URLs, secrets and
tokens.
IRC endpoints
Send Authorization: Bearer <access token>. Channel
names in the path omit the leading # and match
case-insensitively against the user’s enabled channels.
GET /v1/status
Scope irc:read. The user’s ZNC connection and
channels.
{ "connected": true, "joined_channels": ["#soup"], "enabled_channels": ["#soup"] }GET /v1/channels/{channel}/messages
Scope irc:read. Recent retained activity;
limit is 1 to 200, default 50.
curl -H "Authorization: Bearer $TOKEN" \
"https://zircon.chooser.us/v1/channels/soup/messages?limit=20"POST /v1/channels/{channel}/messages
Scope irc:write. Sends one line, 1 to 400 characters, no
control characters. Returns 202 with
{"accepted": true, "channel": "#soup"}.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"text":"hello"}' \
https://zircon.chooser.us/v1/channels/soup/messagesErrors
| status | meaning |
|---|---|
| 400 | bad channel name or limit |
| 401 | missing, expired or under-scoped token |
| 404 | channel not enabled for this user |
| 422 | message text is empty, too long, or not one line |
| 429 | more than 20 posts in a minute |
| 503 | ZNC is unreachable or the user’s account could not be provisioned |
Owner: invite a user
An owner whose GitHub login is listed in
settings.ownerLogins can sign in and use the People
with access tab on /settings. The panel lists
invited users, shows their granted and enabled channels, and has forms
to invite people or replace existing grants. No ADMIN_TOKEN
is sent to the browser. Unchecking all channels for an existing user
removes IRC channel access without deleting their account or stored
history.
The token endpoint remains available for automation:
POST /admin/invite with
Authorization: Bearer <ADMIN_TOKEN>:
curl -X POST -H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"github_login":"alice","channels":["#soup"]}' \
https://zircon.chooser.us/admin/invitechannels must come from ircChannels and
defaults to all of them. A new user starts with no channels enabled and
a nick derived from their GitHub login (limited to 31 characters and
prefixed with u if the login starts with a digit). The user
selects granted channels in /settings. Inviting an existing
user replaces their grants, keeps selections that are still allowed, and
leaves new grants off. Returns 201, or 409
when the user limit is reached.
Browser pages
/login, /login/github/callback,
/settings and POST /logout are for people, not
the GPT. Consent and settings forms carry a CSRF token; session cookies
are __Host- prefixed, Secure and
HttpOnly.