Connect an AI agent over MCP
Let Claude Code, Codex, Cursor or another MCP client read your community and draft changes, within a policy your admins control.
CEEJAY runs an MCP (Model Context Protocol) server. An AI agent connects to it and calls tools: it reads what the dashboard shows, such as analytics, members, tickets and signals, and, where the community allows it, makes a few changes in the dashboard.
Agents change the dashboard only. Messages, polls and giveaways are saved as drafts for your team to send, and nothing is posted in Discord. No tool deletes members, segments, reports or any other item, although one can remove a tag from a member.
This page is for the developer connecting the agent. Your community's team sets the rules on Settings → MCP server, described below.
The endpoint
https://ceejay.communitystudios.xyz/api/mcpThat is the URL on the shared CEEJAY service. On a dedicated instance it is your dashboard's URL followed by /api/mcp. Settings → MCP server → Set up shows your URL with a copy button.
- It takes
POSTonly. The messages are JSON-RPC 2.0, and every request is handled on its own: there is no session to maintain, and a token you revoke stops working on its next call. - Send
Content-Type: application/jsonandAccept: application/json, text/event-stream. Without both inAcceptthe endpoint returns406. The response to a call can be a single server-sent event (event: message, thendata:and the JSON), so use an MCP client library. - Request bodies can be up to 1 MB.
- A caller that sends an
Originheader, which browsers do and agents do not, must be your own dashboard. Anything else gets403Origin not allowed. - If the URL returns
404, MCP is not switched on for your instance. The Set up tab says so too.
Sign in, or use a token
Every call acts as a person, and an agent never sees more than that person can.
Sign in. Where your instance has agent sign-in switched on, the Set up tab shows commands that carry no token. The first time the agent connects, your browser opens. You choose which communities it can reach and what it may do, and from then on it acts as you. A client that connects without a credential gets 401 with a WWW-Authenticate header that points at /.well-known/oauth-protected-resource, and MCP clients use that to start the sign-in.
The Connected tab lists every agent that has signed in, with the permissions it was given and when it was approved and last used. The delete icon disconnects the agent, and it loses access immediately. A sign-in can cover several communities, each with its own policy.
Access token. For scripts and CI jobs that cannot open a browser, and where sign-in is not switched on:
Open Settings → MCP server → Tokens and press Create token.
Under Token name, give the token a name, such as Claude Code on my laptop.
Under Permissions, tick the ones to allow. Each says what it allows. Write permissions only appear when the community lets agents make changes, and a permission the community does not allow is greyed out.
Choose when it Expires: 30 days, 90 days (the default), 1 year or No expiry. Press Create token and copy the token. It is shown once.
A token starts with ceejay_mcp_, followed by 64 hexadecimal characters. It belongs to one community and acts as the person who made it. Their access is checked on every call, so if they lose access to the community the token stops working. The delete icon on the Tokens tab revokes it, effective immediately. It is not an API key: tokens and API keys are not interchangeable.
Connect a client
The Set up tab shows these with your URL filled in.
Claude Code. Run this, then type /mcp inside Claude Code, pick ceejay and sign in.
claude mcp add --transport http ceejay https://ceejay.communitystudios.xyz/api/mcpClaude app. Open Settings, Connectors, Add custom connector. Paste the URL, then press Connect.
Codex. Run this, then codex mcp login ceejay to sign in.
codex mcp add ceejay --url https://ceejay.communitystudios.xyz/api/mcpCursor. Add this to .cursor/mcp.json. Cursor asks you to sign in when it first connects.
{ "mcpServers": { "ceejay": { "url": "https://ceejay.communitystudios.xyz/api/mcp" } } }To see the tools without a client, list them:
curl https://ceejay.communitystudios.xyz/api/mcp \
-H "Authorization: Bearer $CEEJAY_MCP_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'A call is the same with "method":"tools/call" and "params":{"name":"ceejay_get_pulse","arguments":{"range":"1w"}}. A tool returns readable text and, for most tools, a structured copy of the result.
What an agent may do
The community's policy decides, on Settings → MCP server → Policy. Everyone with access to the page can read it, and only server admins can change it.
| Setting | What it does |
|---|---|
| Allow AI apps | The master switch. Off, and agents can neither read nor change anything, even ones already connected. A connection is refused with 403 agents_disabled. |
| What agents can read | One tick per read permission. An agent cannot read anything that is not ticked. |
| Let agents make dashboard changes and drafts | Off, and no change tool is available. On, the write permissions under it decide which ones are. |
| Requests per minute | The most requests each connected agent can make in a minute, up to 120. |
A community that has never saved a policy has agents allowed, every read permission on, changes off and 120 requests a minute.
Every call has to pass three checks: the community's policy, the permissions the token or sign-in was given, and the access of the person behind it. A token or sign-in can never widen the policy. A change to the policy takes effect within 30 seconds. An agent only sees the tools it could use.
Permissions
A write permission includes the matching read permission.
| Permission | In the dashboard | Allows |
|---|---|---|
community:read | Community overview | The server overview, health, signals that need attention, channels and roles |
analytics:read | Analytics | Engagement, growth, retention, activity, timing, channels, keywords and invites |
members:read | Members and segments | Search members, open profiles and journeys, see who they talk with, read segments, tags and admin notes. Never email addresses |
tickets:read | Support tickets | List tickets: status, who opened and claimed them, and the subject. No tool returns a transcript |
engagement:read | Polls, giveaways and events | List polls, giveaways, forms, suggestions, calendar events and campaigns, with their status |
automations:read | Automations | List automations, their triggers and whether they are running |
reports:read | Reports | List saved reports |
knowledge:read | Knowledge base | Search the knowledge base and answered questions |
signals:write | Update signals | Acknowledge, resolve or dismiss signals |
members:write | Tag members and write notes | Add or remove member tags and add admin notes |
messages:write | Draft messages | Save messages as drafts in Scheduled Messages |
engagement:write | Draft polls and giveaways, add events | Save polls and giveaways as drafts, and add events to the calendar |
Tools
Every tool starts with ceejay_. Several arguments are shared between tools:
server(every tool exceptceejay_list_serversandceejay_describe_metrics): the community, by name or Discord server ID. Leave it out to use the first community the connection was given.ceejay_list_serverslists them.range(ceejay_get_pulseand the analytics tools):1d(24 hours, the default),1w,1m,3m,6m,1yorall.24h,7d,30dand90dare accepted as well.customneedsstartandendasYYYY-MM-DD.limit(tools that list things): how many rows to return. The maximum and default are in the tables.
Figures come with their change against the previous period. Text written by members, such as ticket subjects or example messages, is labelled as untrusted data in the result.
Reading
| Tool | Permission | What it returns | Other arguments |
|---|---|---|---|
ceejay_list_servers | none | The communities this connection can reach, which one is the default, and what agents may do in each | |
ceejay_get_pulse | community:read | The headline numbers with their change, and the signals asking for attention. The place to start | range, start, end |
ceejay_list_signals | community:read | Signals CEEJAY has raised, by severity | severity (critical, warning, info, all), status (active by default, acknowledged, resolved, dismissed), limit (1 to 50, default 20) |
ceejay_describe_metrics | community:read | What CEEJAY means by a term on the dashboard | term; leave out for the whole glossary |
ceejay_get_engagement | analytics:read | Messages, active users, the share of members who posted, and who and where | range, start, end, channels, roles (comma-separated Discord IDs) |
ceejay_get_member_growth | analytics:read | Joins, leaves and net change, the biggest spikes and where joins came from | range, start, end |
ceejay_get_retention | analytics:read | Whether new members settle in and stay: roles, doing anything, posting and voice in their first week, still here after 30 days, leaving on day one, being active in weeks one and four, and weekly cohorts | range, start, end |
ceejay_get_activity | analytics:read | Games, music and tools members use, and time in voice | range, start, end, category (all, games, music, development) |
ceejay_get_activity_timing | analytics:read | The busiest days and hours for messages | range, start, end, tz (an IANA zone such as Europe/London, UTC by default) |
ceejay_get_channel_analytics | analytics:read | Messages and unique members per channel. Channels excluded from analytics do not appear | range, start, end |
ceejay_get_community_health | analytics:read | Daily, weekly and monthly active members, lifecycle, conversations and regular posters whose activity has halved | range, start, end |
ceejay_get_voice_of_community | analytics:read | The health score and what drives it, sentiment, topics and example messages | range, start, end |
ceejay_get_keywords | analytics:read | Mentions of the terms the community tracks, against the previous period, with sentiment | range, start, end |
ceejay_get_invites | analytics:read | Who brings members in, how many stayed, and which sources joins came from | range, start, end |
ceejay_search_members | members:read | Members by name, segment or what they did in the community's apps. Returns names, join date, last activity, messages, XP, segment and tags. Never email addresses | query, segment (from ceejay_list_segments), activity, sortBy, sortOrder, page, limit (1 to 100, default 50) |
ceejay_get_member | members:read | One member in full: activity, XP and level, roles, tags, segments, admin notes and recent activity in the apps | userId (required) |
ceejay_list_segments | members:read | The audience segments, how many members are in each and how that changed over 7 days | |
ceejay_get_connections | members:read | How members and topics connect, from the knowledge graph over the last 90 days | view (required: member, topic, circles, connectors, topic_links, knowledge_gaps), member, topic, limit (1 to 50, default 10) |
ceejay_list_tickets | tickets:read | Tickets with status, who opened and claimed them, and the subject | status (open by default, claimed, pending, resolved, closed, all), limit (1 to 200, default 50) |
ceejay_list_automations | automations:read | Each automation's trigger, what it does, whether it is on and when it last ran | |
ceejay_list_engagement_items | engagement:read | Polls, giveaways, forms, suggestions and upcoming events, with their status | kind (all, polls, giveaways, forms, suggestions, events), limit (1 to 100, default 25) |
ceejay_list_campaigns | engagement:read | The community's campaigns: status, dates, goal, items and members reached | status (all, planned, live, ended, archived), limit (1 to 200, default 50) |
ceejay_get_campaign | engagement:read | One campaign with its results, as totals | campaignId (required) |
ceejay_list_reports | reports:read | Saved reports and whether each is a draft, published or scheduled. Archived reports are left out | |
ceejay_search_knowledge | knowledge:read | Knowledge base documents and answered questions that match | question (required) |
activity on ceejay_search_members takes an object with a kind. { "kind": "did", "app": "polls", "days": 30 } finds members who did something in an app: action narrows it to one action, days (1 to 365) limits it to that many days back and is all time when left out, and min (1 to 1,000, default 1) asks for at least that many times. { "kind": "campaign", "campaignId": 7 } finds members who took part in a campaign. { "kind": "none", "days": 30 } finds members who did nothing in any app for that many days. The apps and their actions are listed in the tool's own description.
The campaign tools say so when campaigns are not switched on for the community.
Changing
These tools only appear when the community has switched on Let agents make dashboard changes and drafts and ticked the permission.
| Tool | Permission | What it does |
|---|---|---|
ceejay_update_signal | signals:write | Sets a signal to acknowledged, resolved or dismissed. Arguments signalId and status, both required |
ceejay_tag_member | members:write | Adds or removes tags on one member. The tags must already exist, and are given by name: userId, addTags, removeTags |
ceejay_bulk_tag_members | members:write | The same for up to 50 members at once: userIds, addTags, removeTags |
ceejay_add_member_note | members:write | Adds a line to the member's admin notes, such as [2026-10-03] (via AI agent) Asked about the Saturday tournament, under the notes already there: userId, note |
ceejay_draft_message | messages:write | Saves a message as a draft in Scheduled Messages: channelId, content (up to 2,000 characters), optional title (Drafted by an AI agent when left out) |
ceejay_create_event | engagement:write | Adds an event to the community calendar: title, eventDate (ISO 8601), optional description. Events show as markers on analytics charts |
ceejay_create_poll | engagement:write | Saves a poll as a draft: title, options (2 to 10), channelId |
ceejay_create_giveaway | engagement:write | Saves a giveaway as a draft: title, prizeDescription, channelId, optional durationMinutes (5 to 43,200, and 1,440 when left out) and maxWinners (1 to 100, and 1 when left out) |
Drafts and changes. Messages, polls and giveaways are saved as drafts: nothing is posted until a person opens the draft in the dashboard and sends or starts it. Signal updates, tags, notes and calendar events take effect as soon as the change is confirmed. A calendar event is a real event, not a draft. Everything a tool makes is attributed to the person the agent acts as.
Confirming. A change tool does nothing the first time. Called without confirm, it returns a preview of what it would do. With dry_run: true it returns the same preview and states that nothing was changed. Only confirm: true makes the change. The agent is meant to show you the preview and wait for your confirmation before it calls again.
There is no tool to run database queries or search message text, to post in Discord, to close tickets or switch automations on or off, or to delete members, segments, reports or other items.
Limits
- Each connection can make 120 reading calls and 20 changing calls a minute, and never more than the community's Requests per minute. A connection is one token, or one sign-in.
- Over the limit, the tool returns an error result instead of an HTTP error:
Rate limit reached (120 calls a minute for this connection). Try again in 30 seconds. ceejay_bulk_tag_memberstakes up to 50 members at once, and every tool'slimithas a maximum, shown above.
Activity log
Every tool call is logged. Settings → MCP server → Activity shows the 20 most recent: the tool, the agent's name, whether it succeeded (ok), failed (error) or was refused (denied), how long it took, and the error code. A filter picks All calls, Succeeded, Failed or Refused, and a second list shows the most used tools over the last 7 days.
The log also stores the client version, the token or sign-in it used, the user it acted as, the length of the result, and the caller's IP address. Of the arguments it stores a summary: identifiers, counts and ranges. Free text, such as a search or a note, is stored only as its length.
Connections refused before any tool runs, such as a bad token, are not logged.
Errors
A connection that is refused for its token, its sign-in or the community's policy gets an HTTP error with a body of { "error": "…", "code": "…" }.
| Status | code | Meaning |
|---|---|---|
401 | missing_token | No Authorization: Bearer header |
401 | invalid_token | The token is not valid |
401 | not_an_agent_token | The credential is neither an agent token nor an agent sign-in. An API key (ceejay_sk_…) or a dashboard session was sent by mistake. Create an agent token |
401 | revoked, expired | The token was revoked, or the token or sign-in has expired |
401 | unbound | The token is not linked to a user. Revoke it and create a new one |
401 | user_lost_access | The person behind the token or sign-in no longer has access to the community |
401 | grant_revoked, no_servers, oauth_unconfigured | A sign-in problem: the agent was disconnected, it was given no community, or sign-in is not switched on |
403 | agents_disabled | Allow AI apps is off in every community the connection was given |
Once connected, a tool that cannot run returns an error result: isError is true and the text says what to do. These are the codes you will see in the Activity tab:
| Code | Meaning |
|---|---|
agents_disabled | Agents are off in the community the call names |
writes_disabled | The community allows agents to read but not to change |
policy_forbids | The community does not allow that permission |
insufficient_scope | This token or sign-in was not given that permission |
server_not_in_credential | The server argument is not one of the connection's communities, or matches more than one |
rate_limited | Over the per-minute limit |
invalid_input | An argument is missing or wrong |
access_denied, not_found, upstream_error, rejected | The request was denied, the item was not found, the upstream call failed or the request was rejected |
activity_unavailable | ceejay_search_members was asked to filter by what members did in the apps, and that cannot be done here: the member history is not switched on, or the campaign is not one of the community's |
tool_failed, request_failed, unreachable, not_configured, unhandled | The tool failed for a reason on CEEJAY's side. The text of the result says what happened |
Related
- MCP server in the guide describes the same settings for your community's team.

