You help an Immersive Commons operator's agent triage the pending-membership queue. The MCP server at https://www.immersivecommons.com/api/mcp exposes three operator verbs that are wired to the same helpers (applyTier / denyTierRequest) as the in-browser admin page at /floor10/admin/members. Behaviour is identical across transports; this skill teaches you the workflow, not the boundary.
Pre-flight (every session)
Token check. You need an IC agent token with
admin:tier_reviewin its scope set. Only operator-tier IC members can mint this scope —lib/capabilities.ts::SCOPES_BY_TIER.operatoris the gate, enforced server-side at mint time. Smoke probe:curl -H "Authorization: Bearer $FLOOR10_AGENT_TOKEN" \ -X POST -H "content-type: application/json" \ -H "Accept: application/json, text/event-stream" \ -d '{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"ic_admin_list_pending_tier_requests","arguments":{}}}' \ https://www.immersivecommons.com/api/mcp200 + result with
pending: [](or non-empty) = good. 401 = no/bad token.token missing required scope: admin:tier_review= your token is non-operator (or your tier was demoted between mint and now — the live-tier check fires every call).REQUIRED Accept header. MCP streamable-HTTP transport gates on
Accept: application/json, text/event-stream. A bareAccept: application/json(or missing header) returns JSON-RPC-32000 "Not Acceptable"even for tools/list that doesn't actually stream. Every curl / fetch / HTTP probe against/api/mcpMUST include both content types.Identity check. Tools refuse if the calling operator has been demoted in Clerk since the token was minted (Layer-3 freshness gate; ~60s cached). If you see
tier check failed, the human's tier is no longer operator — escalate to a human-in-the-loop fix; do not retry.
The three verbs
1. ic_admin_list_pending_tier_requests
Read-only. Returns { pending: PendingTierRequest[], audit: TierAuditEntry[] }. Newest first. The audit tail is the last 25 approve/deny/auto-promote decisions across the queue (anyone, not just yours) — use it to understand recent context before you decide.
{ "name": "ic_admin_list_pending_tier_requests", "arguments": {} }
Pending record shape:
{
"user_id": "user_2x...", // Clerk userId
"email": "jane@example.com",
"display_name": "Jane Founder",
"current_tier": "public",
"requested_tier": "ic-member",
"note": "I host VCN, attended every Lab night",
"submitted_at": "2026-05-19T22:14:08.214Z"
}
2. ic_admin_approve_tier_request
Two-step. Default is a dry-run preview; confirm: true is required to mutate.
Step 1 — preview:
{
"name": "ic_admin_approve_tier_request",
"arguments": { "user_id": "user_2x..." }
}
Returns { ok: true, dry_run: true, from, to, action: "approve", was_pending, requested_tier, override_used } or { ok: false, error_kind: "no_pending" } if there's nothing to approve and you didn't pass a tier override.
Step 2 — commit:
{
"name": "ic_admin_approve_tier_request",
"arguments": {
"user_id": "user_2x...",
"reason": "Hosts VCN, verified attendance via graph + Telegram",
"confirm": true
}
}
Override: if you want to approve a member to a different tier than they requested (e.g. they asked for ic-member but you decide ai-floor), pass tier:
{
"name": "ic_admin_approve_tier_request",
"arguments": {
"user_id": "user_2x...",
"tier": "ai-floor",
"reason": "Active in the AI floor channel but not yet at the IC-member bar",
"confirm": true
}
}
tier: "operator" is approvable here even though it isn't self-requestable. Use this for the rare promotion-from-trust path (Ray bringing in a deputy). Treat it as a separate review — see the hard rules below.
3. ic_admin_deny_tier_request
Same two-step shape. The user's tier is unchanged; only their pending request is cleared. The reason is recorded in the audit log AND surfaced to the requester on their /membership page so they understand why.
{
"name": "ic_admin_deny_tier_request",
"arguments": {
"user_id": "user_2x...",
"reason": "Haven't seen you on the floor yet — come hang at a VCN night and re-apply.",
"confirm": true
}
}
The judgement layer (read this before deciding)
Approving someone unlocks scope vocabulary they couldn't have before. Map each ring to what they get and what bar to apply:
| Tier | Unlocks | Bar |
|---|---|---|
ft-member |
Frontier Tower presence; events:read_upcoming + RSVP | Verified FT member (paid floor or guest pass on file). |
ai-floor |
+ directory:search, resources:read | Active in the AI Floor channel; recognized by 2+ existing ai-floor members; has shown up to at least one event. |
ic-member |
+ resources:book, events:submit_recap, leaderboard:manage, github:link, headsets:lend, headsets:report_damage | Has hosted or co-hosted IC programming, OR has been an active member for 60+ days with consistent floor presence. |
operator |
+ every admin scope (tier_review, highlights_review, manifest_edit, roster_sync, headsets_review) | Ray's explicit trust circle. Defaults to "no" unless Ray has personally said yes. |
Evidence before decision. Cross-reference each candidate against:
- The
audittail — has this user requested before, been denied, been demoted? - Their
note— is it specific (event names, dates, hosted-by) or generic (gen-ai vibes)? - Their email domain — known sponsor / partner orgs (anthropic.com, openai.com, deepmind...) get scrutiny upward (don't auto-approve based on prestige, but do flag as worth knowing).
- The display_name — does it match someone on the Telegram floor channel? On Luma guest lists? (The agent that mints this token typically has membership:read; cross-link if you have it.)
Don't infer evidence. If the note is "I'm interested in AI," the human did not give you a reason — surface the gap, don't manufacture justification.
Hard rules
- No batch approvals. Each
confirm:truecall is one human-deliberated decision. If the agent encounters 10 pending requests, run 10 separate decision loops — list → preview → ask human → confirm. Never loop blindly. - Self-promotion is a foot-gun. The MCP doesn't structurally prevent you from approving the operator themselves to a different tier (or worse, demoting themselves via an override-down approve). Refuse the call. The operator's own membership belongs in Ray's hands, not their agent's.
tier: "operator"is a Ray-level decision. Surface the request to the human operator + Ray (if they're not the same person) explicitly. Don't approve operator promotions on a same-session agent decision unless Ray has confirmed in a durable channel (telegram message, commit message, written approval).- Honor the dry-run. If the operator hasn't explicitly said "do it," return the preview and stop. Don't shortcut to
confirm:truebecause the preview looked fine. - Audit the audit. Before each approve, scan the recent audit tail for a pattern: was this user denied recently? Was their tier rolled back? Surface the prior decision to the human; don't approve over a recent denial without explanation.
- Never invent reasons. The
reasonfield becomes part of the user's/membershippage on a deny, and part of the audit log on either decision. Use only what the human told you, or quote the evidence (event attendance, graph link, ticket from sponsor). - Rate limit awareness. 20 mutations per token per UTC day. If you're approaching the cap on a legitimate burndown day, stop and tell the human; do not try to rotate to a different token to evade the bound.
What gets logged
Every call writes to two places:
- MCP activity log (per-user, readable via
ic_activity_get_recent) — every tool call, including dry-runs. - Tier audit log (
KVKeys.tierAudit, capped at 500) — every approve/deny mutation withby_clerk_user_idattribution to the operator's Clerk identity (NOT the agent's token).
If you see a decision in the audit log that you don't recognize, it's coming from another operator (or from a different agent under the same operator). Investigate before assuming compromise.
Failure modes & recovery
no_pendingon approve withouttier. The user has nothing pending (cleared by another operator, or they cancelled). Use thetieroverride only if the human explicitly wants to grant a ring without a fresh request.tier check failed: tier <X> does not authorize scope admin:tier_review. The operator was demoted between mint and call. Stop; escalate to a human-in-the-loop.admin rate limit exceeded. You've hit 20 mutations in the UTC day. Stop. Resumes at 00:00 UTC.could not resolve user. Theuser_iddoesn't exist in Clerk. Double-check the id from thependingarray.
Discovery surfaces
- Agent card: https://www.immersivecommons.com/.well-known/agent-card.json
- Aiia manifest: https://www.immersivecommons.com/.well-known/ai-agent.json
- MCP discovery: https://www.immersivecommons.com/.well-known/mcp.json
- llms.txt URL map: https://www.immersivecommons.com/llms.txt
- Human-facing admin page: https://www.immersivecommons.com/floor10/admin/members
Related skills
ic-onboarding— RFC 8628 device-code signup; how a fresh agent gets authorized.ic-headsets— Same operator scope pattern (admin:headsets_review) for the PICO lending program.ic-events— Sister surface; same auth model.