iblai-api-agent-mcp
Configure external MCP tool access for agents. The platform models MCP with
three objects, and a working integration always requires all three:
- MCP server — metadata for the external MCP endpoint (name, URL,
transport,
auth_type, auth_scope).
- MCP server connection — the credential binding (a static token, or a
reference to an OAuth connected service), at
platform, mentor (agent),
or user scope.
- Agent wiring — the agent's settings must have the
mcp-tool slug in
tool_slugs AND the server id in mcp_servers.
A missing step 3 is the most common failure mode: server and connection exist
but the agent never calls them. Always finish by re-reading the agent's
settings.
Auth & conventions
- Base URL:
https://api.iblai.app/dm — the /dm prefix is
required. MCP endpoints live under
/api/ai-mentor/orgs/{org}/users/{username}/... and OAuth connector
endpoints under /api/ai-account/..., appended to it. (… in the endpoint
lists below abbreviates https://api.iblai.app/dm/api/ai-mentor/orgs/{org}.)
- The backend also accepts an
agent-spelled twin of every agent route
(/api/ai-agent/..., agents/ for mentors/); the mentor spelling is
canonical and used here, matching the other skills.
- Header:
Authorization: Api-Token $IBLAI_API_KEY on every request.
- Path vars:
{org} = $IBLAI_ORG, {username} = $IBLAI_USERNAME,
{mentor} = the agent's unique id (UUID, e.g.
d17dc729-60fd-4363-81a0-f67d9318b03e).
- On the wire the agent noun is
mentor: body fields (mentor,
mentor_unique_id) and the scope enum value mentor refer to an agent.
- DELETE / destructive calls: confirm with the user first. Never echo
credentials or tokens back into chat, and never commit them to files.
- Not connected yet? Run
/iblai-api-login first to populate
IBLAI_ORG, IBLAI_USERNAME, and IBLAI_API_KEY.
Choosing the auth pattern
The auth_type × auth_scope decision on the server drives everything
downstream. auth_type = how credentials go on the wire (none | token | oauth2). auth_scope = whose credentials are used (platform | mentor | user). They are orthogonal.
| Pattern |
Server fields |
Connection setup |
End user prompted? |
| No auth |
auth_type=none |
connection with no credentials |
No |
| Shared key for the whole org |
auth_type=token, auth_scope=platform |
one platform-scoped connection holding the key |
No |
| Per-agent key |
auth_type=token, auth_scope=mentor |
one mentor-scoped connection per agent |
No |
| Pre-provisioned per-user |
auth_scope=user |
admin creates user-scoped connections up front |
No |
| In-chat OAuth (each user connects their own account) |
auth_type=oauth2 and auth_scope=user |
none up front — created automatically when the user completes OAuth mid-chat |
Yes |
Facts to keep straight:
auth_type="oauth2" + auth_scope="user" is the only combination that
triggers the in-chat OAuth prompt; oauth2 alone is not enough.
- Any
oauth2 connection (at any scope) requires a connected_service
id — an existing OAuth grant (see the connected-service lifecycle below).
- In-chat OAuth servers must also link an
oauth_service (an OAuth service
record id) on the server.
Runtime credential resolution
When an agent invokes an MCP server, credentials resolve in this order —
first match wins:
- User-scoped connection for (server, user)
- Agent-scoped (
mentor) connection for (server, agent)
- Platform-scoped connection for (server, org)
- Featured-server global fallback
- No connection → the call fails 401 — or the in-chat OAuth prompt
fires if the server is
auth_scope="user" + auth_type="oauth2"
OAuth-backed connections auto-refresh access tokens near expiry server-side;
no client action is needed.
OAuth connected services (lifecycle)
Terminology: an OAuth provider is the vendor (google, dropbox); an
OAuth service is one surface of it (drive, calendar); a connected
service is a user's persisted token grant for one service — unique on
(user, provider, org, service).
Prerequisite: the org must have a credential named auth_{provider}
(containing client_id, client_secret, redirect_uri) in the credential
store before any flow can start — 400 "No credentials found" on the start
call means it is missing (install it via the integration-credential
endpoints, see /iblai-api-integration).
Flow: discover enabled services → start (returns an auth_url; open
it in a new tab — providers block iframes; the flow's state entry expires
after 1 hour) → the vendor redirects the user's browser to the
callback, which exchanges the code and returns the connected service →
use its id as connected_service on an MCP connection. If a grant already
existed for the same (user, provider, org, service), it is updated in place.
Reads
- GET
…/users/{username}/mcp-servers/?include_global=true&mentor_unique_id={mentor}&is_featured={true|false}&search={q}&transport={…}&page={n}&page_size=12
— list MCP servers (paged; include_global=true surfaces org-wide
connectors; search and transport narrow results).
- GET
…/users/{username}/mcp-server-connections/ — list connections:
scope, is_active, server_name, platform_key, masked credentials
(e.g. sup****key), masked extra_headers, connected_service_summary
({id, provider, service, user, platform_key}).
- GET
…/users/{username}/mentors/{mentor}/settings/ — the agent's
active tool_slugs, mcp_servers (serialized server objects), and
can_use_tools.
- GET
https://api.iblai.app/dm/api/ai-account/orgs/{org}/oauth-services/
— enabled OAuth services: id, oauth_provider, name, display_name,
description, scope, image, created_at, updated_at.
- GET
https://api.iblai.app/dm/api/ai-account/orgs/{org}/oauth-services/{service_name}/scopes/
— the scopes a service requests.
- GET
https://api.iblai.app/dm/api/ai-account/connected-services/orgs/{org}/users/{username}/
— the user's connected services (token grants).
- GET
https://api.iblai.app/dm/api/ai-account/connected-services/orgs/{org}/users/{username}/{provider}/{service}/
— start an OAuth flow; returns { "auth_url": "..." }. The state entry it
primes expires after 1 hour.
- GET
https://api.iblai.app/dm/api/ai-account/connected-services/callback/?code=...&state=...
— the OAuth callback. Hit by the user's browser after provider consent —
relay the vendor's query params unmodified (never decode or alter state);
do not call it directly with fabricated values. Success returns the
connected service (id, provider, service, expires_at, scope
(raw scope string), scope_names (canonical ids, e.g. ["drive"]),
scopes (full scope strings), token_type, service_info
({id, name, display_name, logo}), username) — the same
ConnectedServiceSerializer the
connected-services list read returns.
Writes
MCP servers
- POST
…/users/{username}/mcp-servers/ — register a server (JSON, or
multipart/form-data with image):{
"name": "Google Drive MCP",
"url": "https://drive-mcp.example.com",
"transport": "sse|websocket|streamable_http",
"auth_type": "none|token|oauth2",
"auth_scope": "platform|mentor|user",
"description": "string",
"mentor": "uuid|null",
"credentials": "string",
"extra_headers": { "x-custom": "value" },
"oauth_service": "number|null",
"is_enabled": true,
"is_featured": false,
"clean_output": true,
"image": "File"
}
name, url, transport, auth_type are required; auth_scope
defaults to platform.
- Server-level
credentials must be the full authorization value
(<scheme> <credentials>); it takes priority over extra_headers.
is_featured=true makes the server available to other orgs to
create their own connections against (multi-organization sharing); the owning
org keeps control of the metadata.
is_enabled=false is a hard off-switch — disabled servers are skipped
at runtime.
oauth_service links the OAuth service and is required for in-chat
OAuth servers.
clean_output (default true) strips HTML from server responses;
disable it for documentation servers whose formatting must survive.
- Capture the returned
id — the connection and the agent wiring both
need it.
- PATCH | PUT
…/users/{username}/mcp-servers/{id}/ — edit a server (e.g. flip an
existing server to in-chat OAuth with
{"auth_scope": "user", "auth_type": "oauth2", "oauth_service": 12}).
- DELETE
…/users/{username}/mcp-servers/{id}/ — delete a server. Destructive — confirm
with the user first.
MCP server connections
POST …/users/{username}/mcp-server-connections/ — create a
connection. Common fields: server (id, required), scope
(platform|mentor|user, default user), auth_type (none|token|oauth2),
credentials, authorization_scheme, extra_headers,
connected_service (id), mentor (agent unique id).
Platform scope (token):
{ "server": 9, "scope": "platform", "auth_type": "token",
"credentials": "super-secret-api-key", "authorization_scheme": "Bearer",
"extra_headers": { "x-mcp-client": "agent-ui" } }
Mentor (agent) scope (token): add "mentor": "<agent unique id>" and
"scope": "mentor" — different agents can present different credentials
to the same server (e.g. read/write vs read-only keys).
User scope (OAuth2) — finalizes an OAuth connection after the
connected-service flow:
{ "server": 9, "scope": "user", "auth_type": "oauth2", "connected_service": 77 }
Validation rules the API enforces (per-field error messages):
platform and user are read-only: the org comes from the request
context ("Connections must be created for the current platform context." when they clash) and the user from the caller — do not send
them in the body.
scope=platform — mentor is forbidden; the connection's org must
match the server's org unless the server is featured.
scope=mentor — mentor is required and must belong to the same org
as the connection.
scope=user — mentor is forbidden; requires the calling user or a
connected_service.
auth_type=oauth2 — always requires connected_service
("OAuth2 connections require a connected service."), at every scope,
and the connected service must belong to the same org.
auth_type=token — requires credentials
("Token based connections must include credentials.").
Credential handling:
authorization_scheme becomes the header prefix
(Authorization: Bearer <credentials>); omit it to send the raw value.
extra_headers is merged into every outbound request; explicit
credentials override clashing headers.
credentials and extra_headers are masked on read — when
PATCHing, only send credentials if actually rotating the secret;
never send a masked value back.
PATCH …/users/{username}/mcp-server-connections/{id}/ — update; prefer
{"is_active": false} over DELETE if the credential may return.
DELETE …/users/{username}/mcp-server-connections/{id}/ — delete a connection.
Destructive — confirm with the user first.
Agent wiring
- PATCH | PUT
…/users/{username}/mentors/{mentor}/settings/ — enable /
disable connectors on the agent:{ "tool_slugs": ["ai-index", "mcp-tool"], "mcp_servers": [3, 9],
"can_use_tools": true }
Critical semantics — these lists are replaced, not merged. [] clears
everything; omitting a field leaves it untouched. Blindly sending
{"tool_slugs": ["mcp-tool"]} silently strips every other tool the agent
had. Safe procedure: GET the current settings, merge locally (keep
existing tool_slugs, ensure mcp-tool is present; keep existing
mcp_servers, append the new server id), then write back the full lists.
OAuth connected services
- DELETE
https://api.iblai.app/dm/api/ai-account/connected-services/orgs/{org}/users/{username}/{id}/
— revoke a user's OAuth grant / disconnect the service (204).
Destructive — confirm with the user first.
In-chat OAuth (per-user consent at chat time)
For servers with auth_type="oauth2" + auth_scope="user", the platform
prompts each user inside the chat stream the first time the agent needs the
tool. Events arrive as JSON on the existing chat WebSocket/SSE
connection — parse and switch on type; never close or refresh the
connection while waiting, resolution arrives on the same socket.
Trigger conditions (all must hold): server auth_type="oauth2", server
auth_scope="user", no valid connection for the current user + server, and
the chat user is authenticated (non-anonymous).
Admin setup checklist (before any prompt can fire): the OAuth provider
and OAuth service records exist, the auth_{provider} credential is in the
org's credential store, the MCP server is registered with
auth_type="oauth2", auth_scope="user", is_enabled=true, and a linked
oauth_service, and the server is attached to the agent (tool_slugs +
mcp_servers).
Handshake: the user sends a message → the backend fails to resolve a
user connection → it emits oauth_required (with auth_url) and polls
every 10s → the client opens auth_url; the user consents; the backend
callback creates the connected service + connection automatically (the
client does not process the callback) → the backend emits
oauth_connection_resolved and resumes the turn. On timeout (default 300s)
an error (status 400) terminates the turn — on WebSocket transports the
connection then closes. A user who finishes OAuth after the timeout
succeeds automatically on their next message, so offer a retry.
Event reference:
Event type |
Key fields |
Client action |
oauth_required |
server_name, server_id, auth_url, message |
show a prompt naming the server; open auth_url in a new tab; show a waiting indicator |
oauth_connection_resolved |
server_name, server_id, message |
dismiss the prompt; the chat resumes automatically |
mcp_tools_retrieved |
session_id, mentor_id |
informational: tool fetch succeeded on retry (3 attempts, backoff 1s/2s/4s) — log or ignore |
warning |
message, developer_error, code: 503 |
non-OAuth tool failure; the chat continues without MCP tools — surface message, log developer_error, never show it to end users |
error |
error, status_code: 400 (no type field — detect by the top-level error key) |
OAuth timeout / URL build failure / missing connected service; the turn terminates — offer retry |
Constants: max wait 300s (MCP_OAUTH_MAX_WAIT_SECONDS), poll interval 10s
(MCP_OAUTH_POLL_INTERVAL_SECONDS). Each poll checks for a connection with a
valid connected service for user + server (or a connected service matching
provider + user + org) — first match resolves.
Example
Register a platform-token server, bind the shared key, and enable it on an
agent (settings read-merge-write elided):
BASE="https://api.iblai.app/dm/api/ai-mentor/orgs/$IBLAI_ORG/users/$IBLAI_USERNAME"
AUTH="Authorization: Api-Token $IBLAI_API_KEY"
curl -s -X POST "$BASE/mcp-servers/" -H "$AUTH" -H "Content-Type: application/json" \
-d '{"name":"Workflow MCP","url":"https://wf.example.com/mcp","transport":"streamable_http",
"auth_type":"token","auth_scope":"platform","is_enabled":true}'
# → capture "id": 9
curl -s -X POST "$BASE/mcp-server-connections/" -H "$AUTH" -H "Content-Type: application/json" \
-d "{\"server\":9,\"scope\":\"platform\",\"auth_type\":\"token\",
\"credentials\":\"$MCP_KEY\",\"authorization_scheme\":\"Bearer\"}"
Notes
- Troubleshooting quick map:
400 OAuth2 connections require a connected service. — complete the
connected-service flow first; pass the resulting id.
400 cross-org server on a platform connection — use a server owned by
this org, or mark the source server is_featured=true.
400 No credentials found on OAuth start — install the
auth_{provider} credential (client_id, client_secret, redirect_uri).
- Agent never calls the server —
mcp-tool missing from tool_slugs or
the server id missing from mcp_servers; these lists are replaced, not
merged, so a careless settings write may have stripped them.
- No OAuth prompt on a per-user server — needs both
auth_scope="user" and auth_type="oauth2", plus a linked
oauth_service, plus an authenticated (non-anonymous) chat session.
- Prompt fires every message even after auth — the connected service
belongs to a different user or org than the chat user.
- Connection unexpectedly falls back to platform creds — check the user
connection's
is_active and that the connected service's user matches
the chat user.
oauth-services returns [] — no enabled OAuth service records; seed
the provider + service.
- Callback
Invalid state — the start/callback round-trip spanned browser
contexts or exceeded the 1-hour window; redo the flow in one session.
- Callback
Could not exchange auth token — the provider rejected the
code; verify the redirect URI matches the provider console and restart.
- Tool call fails silently — a
warning (503) event was ignored; surface
it and verify the MCP server is reachable from the platform.
- Scope enum is
platform | mentor | user on both MCPServer.auth_scope
and connection scope — mentor means agent-wide, platform means
org-wide. (There is no agent or tenant value on the wire.)
- The chat events above ride the runtime chat connection (see
/iblai-api-agent-chat for wiring live chat); everything else in this
skill is plain REST.
Reference material
references/ carries the additive material that doesn't fit the endpoint
sections above — the endpoint/method/body and event facts stay above:
references/concepts.md — backend model and the
design "why": the five Django models behind the wire objects, the module map,
runtime-resolution internals, the OAuth state/token design, and the
validation, access-control, and extensibility rules.
references/integration.md — client / front-end
notes for the REST setup screens: driving the connection form off auth_type,
scope-aware fields, sourcing the OAuth2 connected-service picker, rendering
inline per-field validation errors, and the browser OAuth start/callback
strategy.
references/in-chat-events.md — the fuller
per-frame event reference: field types, the three error variants with their
exact messages, and the client-handling gotchas.
references/mcp-servers-catalog.md — the
open-source iblai-mcp repo of ready-made MCP servers. This skill wires
external MCP servers onto agents; that repo is a source of servers to wire.
1---2name: iblai-api-agent-mcp3description: MCP connectors for ibl.ai agents, end to end — register MCP servers (featured cross-organization sharing, enable/disable, OAuth service linkage), manage credential connections with the auth_type × scope matrix (org / agent / per-user), wire servers onto an agent, run the OAuth connected-service lifecycle, handle per-user in-chat OAuth (the oauth_required / oauth_connection_resolved chat events), and debug MCP auth failures (401s, missing OAuth prompts, agents ignoring servers). Use when wiring an agent to external MCP servers and tools, or designing/troubleshooting MCP auth.4---56# iblai-api-agent-mcp78Configure external MCP tool access for agents. The platform models MCP with9three objects, and a working integration always requires all three:10111. **MCP server** — metadata for the external MCP endpoint (name, URL,12 transport, `auth_type`, `auth_scope`).132. **MCP server connection** — the credential binding (a static token, or a14 reference to an OAuth connected service), at `platform`, `mentor` (agent),15 or `user` scope.163. **Agent wiring** — the agent's settings must have the `mcp-tool` slug in17 `tool_slugs` AND the server id in `mcp_servers`.1819A missing step 3 is the most common failure mode: server and connection exist20but the agent never calls them. Always finish by re-reading the agent's21settings.2223## Auth & conventions2425- **Base URL:** `https://api.iblai.app/dm` — the **`/dm` prefix is26 required**. MCP endpoints live under27 `/api/ai-mentor/orgs/{org}/users/{username}/...` and OAuth connector28 endpoints under `/api/ai-account/...`, appended to it. (`…` in the endpoint29 lists below abbreviates `https://api.iblai.app/dm/api/ai-mentor/orgs/{org}`.)30- The backend also accepts an `agent`-spelled twin of every agent route31 (`/api/ai-agent/...`, `agents/` for `mentors/`); the `mentor` spelling is32 canonical and used here, matching the other skills.33- **Header:** `Authorization: Api-Token $IBLAI_API_KEY` on every request.34- **Path vars:** `{org}` = `$IBLAI_ORG`, `{username}` = `$IBLAI_USERNAME`,35 `{mentor}` = the agent's unique id (UUID, e.g.36 `d17dc729-60fd-4363-81a0-f67d9318b03e`).37- **On the wire the agent noun is `mentor`**: body fields (`mentor`,38 `mentor_unique_id`) and the scope enum value `mentor` refer to an agent.39- DELETE / destructive calls: confirm with the user first. Never echo40 credentials or tokens back into chat, and never commit them to files.41- Not connected yet? Run **`/iblai-api-login`** first to populate42 `IBLAI_ORG`, `IBLAI_USERNAME`, and `IBLAI_API_KEY`.4344## Choosing the auth pattern4546The `auth_type` × `auth_scope` decision on the **server** drives everything47downstream. `auth_type` = *how* credentials go on the wire (`none | token |48oauth2`). `auth_scope` = *whose* credentials are used (`platform | mentor |49user`). They are orthogonal.5051| Pattern | Server fields | Connection setup | End user prompted? |52|---|---|---|---|53| No auth | `auth_type=none` | connection with no credentials | No |54| Shared key for the whole org | `auth_type=token`, `auth_scope=platform` | one platform-scoped connection holding the key | No |55| Per-agent key | `auth_type=token`, `auth_scope=mentor` | one `mentor`-scoped connection per agent | No |56| Pre-provisioned per-user | `auth_scope=user` | admin creates user-scoped connections up front | No |57| **In-chat OAuth** (each user connects their own account) | `auth_type=oauth2` **and** `auth_scope=user` | none up front — created automatically when the user completes OAuth mid-chat | **Yes** |5859Facts to keep straight:6061- `auth_type="oauth2"` + `auth_scope="user"` is the **only** combination that62 triggers the in-chat OAuth prompt; `oauth2` alone is not enough.63- Any `oauth2` connection (at any scope) **requires** a `connected_service`64 id — an existing OAuth grant (see the connected-service lifecycle below).65- In-chat OAuth servers must also link an `oauth_service` (an OAuth service66 record id) on the server.6768## Runtime credential resolution6970When an agent invokes an MCP server, credentials resolve in this order —71first match wins:72731. User-scoped connection for (server, user)742. Agent-scoped (`mentor`) connection for (server, agent)753. Platform-scoped connection for (server, org)764. Featured-server global fallback775. No connection → the call fails 401 — **or** the in-chat OAuth prompt78 fires if the server is `auth_scope="user"` + `auth_type="oauth2"`7980OAuth-backed connections auto-refresh access tokens near expiry server-side;81no client action is needed.8283## OAuth connected services (lifecycle)8485Terminology: an **OAuth provider** is the vendor (`google`, `dropbox`); an86**OAuth service** is one surface of it (`drive`, `calendar`); a **connected87service** is a user's persisted token grant for one service — unique on88(user, provider, org, service).8990Prerequisite: the org must have a credential named `auth_{provider}`91(containing `client_id`, `client_secret`, `redirect_uri`) in the credential92store before any flow can start — `400 "No credentials found"` on the start93call means it is missing (install it via the integration-credential94endpoints, see `/iblai-api-integration`).9596Flow: **discover** enabled services → **start** (returns an `auth_url`; open97it in a new tab — providers block iframes; the flow's state entry expires98after **1 hour**) → the vendor redirects the user's browser to the99**callback**, which exchanges the code and returns the connected service →100use its `id` as `connected_service` on an MCP connection. If a grant already101existed for the same (user, provider, org, service), it is updated in place.102103## Reads104105- **GET** `…/users/{username}/mcp-servers/?include_global=true&mentor_unique_id={mentor}&is_featured={true|false}&search={q}&transport={…}&page={n}&page_size=12`106 — list MCP servers (paged; `include_global=true` surfaces org-wide107 connectors; `search` and `transport` narrow results).108- **GET** `…/users/{username}/mcp-server-connections/` — list connections:109 scope, `is_active`, `server_name`, `platform_key`, masked `credentials`110 (e.g. `sup****key`), masked `extra_headers`, `connected_service_summary`111 (`{id, provider, service, user, platform_key}`).112- **GET** `…/users/{username}/mentors/{mentor}/settings/` — the agent's113 active `tool_slugs`, `mcp_servers` (serialized server objects), and114 `can_use_tools`.115- **GET** `https://api.iblai.app/dm/api/ai-account/orgs/{org}/oauth-services/`116 — enabled OAuth services: `id`, `oauth_provider`, `name`, `display_name`,117 `description`, `scope`, `image`, `created_at`, `updated_at`.118- **GET** `https://api.iblai.app/dm/api/ai-account/orgs/{org}/oauth-services/{service_name}/scopes/`119 — the scopes a service requests.120- **GET** `https://api.iblai.app/dm/api/ai-account/connected-services/orgs/{org}/users/{username}/`121 — the user's connected services (token grants).122- **GET** `https://api.iblai.app/dm/api/ai-account/connected-services/orgs/{org}/users/{username}/{provider}/{service}/`123 — start an OAuth flow; returns `{ "auth_url": "..." }`. The state entry it124 primes expires after 1 hour.125- **GET** `https://api.iblai.app/dm/api/ai-account/connected-services/callback/?code=...&state=...`126 — the OAuth callback. Hit by the user's browser after provider consent —127 relay the vendor's query params unmodified (never decode or alter `state`);128 do not call it directly with fabricated values. Success returns the129 connected service (`id`, `provider`, `service`, `expires_at`, `scope`130 (raw scope string), `scope_names` (canonical ids, e.g. `["drive"]`),131 `scopes` (full scope strings), `token_type`, `service_info`132 (`{id, name, display_name, logo}`), `username`) — the same133 `ConnectedServiceSerializer` the134 connected-services list read returns.135136## Writes137138### MCP servers139140- **POST** `…/users/{username}/mcp-servers/` — register a server (JSON, or141 `multipart/form-data` with `image`):142 ```json143 {144 "name": "Google Drive MCP",145 "url": "https://drive-mcp.example.com",146 "transport": "sse|websocket|streamable_http",147 "auth_type": "none|token|oauth2",148 "auth_scope": "platform|mentor|user",149 "description": "string",150 "mentor": "uuid|null",151 "credentials": "string",152 "extra_headers": { "x-custom": "value" },153 "oauth_service": "number|null",154 "is_enabled": true,155 "is_featured": false,156 "clean_output": true,157 "image": "File"158 }159 ```160 - `name`, `url`, `transport`, `auth_type` are required; `auth_scope`161 defaults to `platform`.162 - Server-level `credentials` must be the **full authorization value**163 (`<scheme> <credentials>`); it takes priority over `extra_headers`.164 - `is_featured=true` makes the server available to **other orgs** to165 create their own connections against (multi-organization sharing); the owning166 org keeps control of the metadata.167 - `is_enabled=false` is a hard off-switch — disabled servers are skipped168 at runtime.169 - `oauth_service` links the OAuth service and is required for in-chat170 OAuth servers.171 - `clean_output` (default true) strips HTML from server responses;172 disable it for documentation servers whose formatting must survive.173 - Capture the returned `id` — the connection and the agent wiring both174 need it.175- **PATCH | PUT** `…/users/{username}/mcp-servers/{id}/` — edit a server (e.g. flip an176 existing server to in-chat OAuth with177 `{"auth_scope": "user", "auth_type": "oauth2", "oauth_service": 12}`).178- **DELETE** `…/users/{username}/mcp-servers/{id}/` — delete a server. Destructive — confirm179 with the user first.180181### MCP server connections182183- **POST** `…/users/{username}/mcp-server-connections/` — create a184 connection. Common fields: `server` (id, required), `scope`185 (`platform|mentor|user`, default `user`), `auth_type` (`none|token|oauth2`),186 `credentials`, `authorization_scheme`, `extra_headers`,187 `connected_service` (id), `mentor` (agent unique id).188189 **Platform scope (token):**190 ```json191 { "server": 9, "scope": "platform", "auth_type": "token",192 "credentials": "super-secret-api-key", "authorization_scheme": "Bearer",193 "extra_headers": { "x-mcp-client": "agent-ui" } }194 ```195 **Mentor (agent) scope (token):** add `"mentor": "<agent unique id>"` and196 `"scope": "mentor"` — different agents can present different credentials197 to the same server (e.g. read/write vs read-only keys).198199 **User scope (OAuth2)** — finalizes an OAuth connection after the200 connected-service flow:201 ```json202 { "server": 9, "scope": "user", "auth_type": "oauth2", "connected_service": 77 }203 ```204205 Validation rules the API enforces (per-field error messages):206 - `platform` and `user` are **read-only**: the org comes from the request207 context (`"Connections must be created for the current platform208 context."` when they clash) and the user from the caller — do not send209 them in the body.210 - `scope=platform` — `mentor` is forbidden; the connection's org must211 match the server's org **unless the server is featured**.212 - `scope=mentor` — `mentor` is required and must belong to the same org213 as the connection.214 - `scope=user` — `mentor` is forbidden; requires the calling user or a215 `connected_service`.216 - `auth_type=oauth2` — always requires `connected_service`217 (`"OAuth2 connections require a connected service."`), at every scope,218 and the connected service must belong to the same org.219 - `auth_type=token` — requires `credentials`220 (`"Token based connections must include credentials."`).221222 Credential handling:223 - `authorization_scheme` becomes the header prefix224 (`Authorization: Bearer <credentials>`); omit it to send the raw value.225 - `extra_headers` is merged into every outbound request; explicit226 credentials override clashing headers.227 - `credentials` and `extra_headers` are **masked on read** — when228 PATCHing, only send `credentials` if actually rotating the secret;229 never send a masked value back.230- **PATCH** `…/users/{username}/mcp-server-connections/{id}/` — update; prefer231 `{"is_active": false}` over DELETE if the credential may return.232- **DELETE** `…/users/{username}/mcp-server-connections/{id}/` — delete a connection.233 Destructive — confirm with the user first.234235### Agent wiring236237- **PATCH | PUT** `…/users/{username}/mentors/{mentor}/settings/` — enable /238 disable connectors on the agent:239 ```json240 { "tool_slugs": ["ai-index", "mcp-tool"], "mcp_servers": [3, 9],241 "can_use_tools": true }242 ```243 **Critical semantics — these lists are replaced, not merged.** `[]` clears244 everything; omitting a field leaves it untouched. Blindly sending245 `{"tool_slugs": ["mcp-tool"]}` silently strips every other tool the agent246 had. Safe procedure: **GET** the current settings, merge locally (keep247 existing `tool_slugs`, ensure `mcp-tool` is present; keep existing248 `mcp_servers`, append the new server id), then write back the full lists.249250### OAuth connected services251252- **DELETE** `https://api.iblai.app/dm/api/ai-account/connected-services/orgs/{org}/users/{username}/{id}/`253 — revoke a user's OAuth grant / disconnect the service (`204`).254 Destructive — confirm with the user first.255256## In-chat OAuth (per-user consent at chat time)257258For servers with `auth_type="oauth2"` + `auth_scope="user"`, the platform259prompts each user inside the chat stream the first time the agent needs the260tool. Events arrive as JSON on the **existing** chat WebSocket/SSE261connection — parse and switch on `type`; never close or refresh the262connection while waiting, resolution arrives on the same socket.263264**Trigger conditions** (all must hold): server `auth_type="oauth2"`, server265`auth_scope="user"`, no valid connection for the current user + server, and266the chat user is authenticated (non-anonymous).267268**Admin setup checklist** (before any prompt can fire): the OAuth provider269and OAuth service records exist, the `auth_{provider}` credential is in the270org's credential store, the MCP server is registered with271`auth_type="oauth2"`, `auth_scope="user"`, `is_enabled=true`, and a linked272`oauth_service`, and the server is attached to the agent (`tool_slugs` +273`mcp_servers`).274275**Handshake:** the user sends a message → the backend fails to resolve a276user connection → it emits `oauth_required` (with `auth_url`) and polls277every 10s → the client opens `auth_url`; the user consents; the **backend**278callback creates the connected service + connection automatically (the279client does not process the callback) → the backend emits280`oauth_connection_resolved` and resumes the turn. On timeout (default 300s)281an `error` (status 400) terminates the turn — on WebSocket transports the282connection then closes. A user who finishes OAuth *after* the timeout283succeeds automatically on their **next message**, so offer a retry.284285**Event reference:**286287| Event `type` | Key fields | Client action |288|---|---|---|289| `oauth_required` | `server_name`, `server_id`, `auth_url`, `message` | show a prompt naming the server; open `auth_url` in a new tab; show a waiting indicator |290| `oauth_connection_resolved` | `server_name`, `server_id`, `message` | dismiss the prompt; the chat resumes automatically |291| `mcp_tools_retrieved` | `session_id`, `mentor_id` | informational: tool fetch succeeded on retry (3 attempts, backoff 1s/2s/4s) — log or ignore |292| `warning` | `message`, `developer_error`, `code: 503` | non-OAuth tool failure; the chat continues **without** MCP tools — surface `message`, log `developer_error`, never show it to end users |293| `error` | `error`, `status_code: 400` (**no `type` field** — detect by the top-level `error` key) | OAuth timeout / URL build failure / missing connected service; the turn terminates — offer retry |294295Constants: max wait 300s (`MCP_OAUTH_MAX_WAIT_SECONDS`), poll interval 10s296(`MCP_OAUTH_POLL_INTERVAL_SECONDS`). Each poll checks for a connection with a297valid connected service for user + server (or a connected service matching298provider + user + org) — first match resolves.299300## Example301302Register a platform-token server, bind the shared key, and enable it on an303agent (settings read-merge-write elided):304305```bash306BASE="https://api.iblai.app/dm/api/ai-mentor/orgs/$IBLAI_ORG/users/$IBLAI_USERNAME"307AUTH="Authorization: Api-Token $IBLAI_API_KEY"308309curl -s -X POST "$BASE/mcp-servers/" -H "$AUTH" -H "Content-Type: application/json" \310 -d '{"name":"Workflow MCP","url":"https://wf.example.com/mcp","transport":"streamable_http",311 "auth_type":"token","auth_scope":"platform","is_enabled":true}'312# → capture "id": 9313314curl -s -X POST "$BASE/mcp-server-connections/" -H "$AUTH" -H "Content-Type: application/json" \315 -d "{\"server\":9,\"scope\":\"platform\",\"auth_type\":\"token\",316 \"credentials\":\"$MCP_KEY\",\"authorization_scheme\":\"Bearer\"}"317```318319## Notes320321- **Troubleshooting quick map:**322 - `400 OAuth2 connections require a connected service.` — complete the323 connected-service flow first; pass the resulting id.324 - `400` cross-org server on a platform connection — use a server owned by325 this org, or mark the source server `is_featured=true`.326 - `400 No credentials found` on OAuth start — install the327 `auth_{provider}` credential (client_id, client_secret, redirect_uri).328 - Agent never calls the server — `mcp-tool` missing from `tool_slugs` or329 the server id missing from `mcp_servers`; these lists are replaced, not330 merged, so a careless settings write may have stripped them.331 - No OAuth prompt on a per-user server — needs **both**332 `auth_scope="user"` and `auth_type="oauth2"`, plus a linked333 `oauth_service`, plus an authenticated (non-anonymous) chat session.334 - Prompt fires every message even after auth — the connected service335 belongs to a different user or org than the chat user.336 - Connection unexpectedly falls back to platform creds — check the user337 connection's `is_active` and that the connected service's user matches338 the chat user.339 - `oauth-services` returns `[]` — no enabled OAuth service records; seed340 the provider + service.341 - Callback `Invalid state` — the start/callback round-trip spanned browser342 contexts or exceeded the 1-hour window; redo the flow in one session.343 - Callback `Could not exchange auth token` — the provider rejected the344 code; verify the redirect URI matches the provider console and restart.345 - Tool call fails silently — a `warning` (503) event was ignored; surface346 it and verify the MCP server is reachable from the platform.347- **Scope enum is `platform | mentor | user`** on both `MCPServer.auth_scope`348 and connection `scope` — `mentor` means agent-wide, `platform` means349 org-wide. (There is no `agent` or `tenant` value on the wire.)350- The chat events above ride the runtime chat connection (see351 `/iblai-api-agent-chat` for wiring live chat); everything else in this352 skill is plain REST.353354## Reference material355356`references/` carries the additive material that doesn't fit the endpoint357sections above — the endpoint/method/body and event facts stay above:358359- [`references/concepts.md`](references/concepts.md) — backend model and the360 design "why": the five Django models behind the wire objects, the module map,361 runtime-resolution internals, the OAuth state/token design, and the362 validation, access-control, and extensibility rules.363- [`references/integration.md`](references/integration.md) — client / front-end364 notes for the REST setup screens: driving the connection form off `auth_type`,365 scope-aware fields, sourcing the OAuth2 connected-service picker, rendering366 inline per-field validation errors, and the browser OAuth start/callback367 strategy.368- [`references/in-chat-events.md`](references/in-chat-events.md) — the fuller369 per-frame event reference: field types, the three `error` variants with their370 exact messages, and the client-handling gotchas.371- [`references/mcp-servers-catalog.md`](references/mcp-servers-catalog.md) — the372 open-source `iblai-mcp` repo of ready-made MCP servers. This skill wires373 *external* MCP servers onto agents; that repo is a source of servers to wire.