iblai-api-agent-mcp
Mirrored from
iblai/vibe·skills/agents/iblai-api-agent-mcp/SKILL.md. This page is generated — edit it in the repository, not here.
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), oruserscope. - Agent wiring — the agent's settings must have the
mcpslug intool_slugsAND the server id inmcp_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.
Three smaller records hang off a server: the per-agent sign-in scope
(mentor-auth-scope/), the user's choices (preference/,
session-preference/; refused when user_can_disable is false), and the
organization's own OAuth app (credentials/).
Auth & conventions
- Base URL:
https://api.iblai.app/dm— the/dmprefix 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 abbreviateshttps://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/formentors/);mentoris canonical here. - Header:
Authorization: Api-Token $IBLAI_API_KEYon 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 valuementorrefer to an agent. - DELETE / destructive calls: confirm with the user first. Never echo credentials or tokens into chat or files.
- Not connected yet? Run
/iblai-api-loginfirst to populateIBLAI_ORG,IBLAI_USERNAME, andIBLAI_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;oauth2alone is not enough.- Any
oauth2connection (at any scope) requires aconnected_serviceid — 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 server-side.
OAuth connected services (lifecycle)
Terminology: an OAuth provider is the vendor (google); an OAuth
service is one surface of it (drive); a connected service is a
user's 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/?mentor_unique_id={mentor}&include_global=true&is_featured={true|false}&is_enabled=true®istry={true|false}&auth_type={none|token|oauth2}&auth_scope={platform|mentor|user}&search={q}&transport={…}&session_id={chat}&page={n}&page_size=12— list MCP servers (paged). Without params: every server the caller may see;mentor_unique_idalone: that agent's own servers; withinclude_global=true: the organization-wide ones too (the OS lists a tab that way,is_featured=truefor Featured,falsefor Directory).is_enabled,registry,auth_type,auth_scope,searchandtransportnarrow it. Each row carriesis_enabled,user_can_disable,user_enabled(whether chat uses it for the caller; withsession_id, after that chat's choice),mentor_auth_scope(only withmentor_unique_id),effective_auth_scope(on every row; the server's own scope when no agent is in context),oauth_service_data, andregistry(the MCP directory entry a global server comes from,nullotherwise;registry.source == "registry"marks one the organization cannot edit). - GET
…/users/{username}/mcp-server-connections/— list connections: scope,is_active,server_name,platform_key, the owninguser(username) withuser_emailanduser_full_name, maskedcredentials(e.g.sup****key), maskedextra_headers,connected_service_summary({id, provider, service, user, platform_key}). A server row likewise carriesowner(username) plusowner_emailandowner_full_name; all six are read-only andnullwhen the row has no user. - GET
…/users/{username}/mentors/{mentor}/settings/— the agent's activetool_slugs,mcp_servers(serialized server objects), andcan_use_tools. - GET
…/users/{username}/mcp-servers/{id}/mentor-auth-scope/?mentor={mentor}— the agent's overrideauth_scope(nullwhen unset),server_auth_scope,effective_auth_scope. - GET
…/users/{username}/mcp-servers/{id}/preference/— the caller's lasting choice:is_enabled(null= never chosen),user_can_disable,effective_is_enabled. - GET
…/users/{username}/mcp-servers/{id}/session-preference/?session_id={chat}— the caller's choice for one chat:is_enabled,user_is_enabled(the lasting choice),user_can_disable,effective_is_enabled. - GET
…/users/{username}/mcp-servers/{id}/credentials/— the organization's own app or key for the server, masked. Organization admins or the Platform API Token only; others get 403 "MCP server credentials are limited to platform admins." (the same onPUT,DELETEandverify/):configured,shared_client_allowed(a client shared by ibl.ai already signs users in),source(dcr | manual | legacy_backfill),client_id,client_secret(masked;""public),redirect_uri,scopes,token_endpoint_auth_method,authorization_params,token_params,authorization_url/token_url(""= discovered); for a token serverapi_key(masked),authorization_scheme,header_name. - 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 alterstate); 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,user_email,user_full_name) — the sameConnectedServiceSerializerthe connected-services list read returns.
Writes
MCP servers
- POST
…/users/{username}/mcp-servers/— register a server (JSON, ormultipart/form-datawithimage):{ "name": "Google Drive MCP", "url": "https://drive-mcp.example.com", "transport": "sse|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, "user_can_disable": true, "is_featured": false, "clean_output": true, "image": "File" }name,url,transport,auth_typeare required;auth_scopedefaults toplatform.transportacceptssseandstreamable_httponly:websocket, still offered by older UIs, answers 400.- Server-level
credentialsmust be the full authorization value (<scheme> <credentials>); it takes priority overextra_headers. is_featured=truemakes 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=falseis a hard off-switch — disabled servers are skipped at runtime, whatever an agent or user chose.user_can_disable(default true) lets users turn the server off for themselves or for one chat (the preference endpoints below).falsemakes it mandatory wherever it is enabled: the chat shows a locked switch and the preference writes are refused.oauth_servicelinks 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. Featured servers of other organizations and directory servers (registryset) are not the caller's to edit or delete (PATCH/DELETEanswer 404,user_can_disableincluded — only the owning organization can make one mandatory); their per-agent sign-in scope (below) is the one thing another organization sets.
Per-agent sign-in scope
- PUT
…/users/{username}/mcp-servers/{id}/mentor-auth-scope/— one agent's override of the server'sauth_scope. Organization admins (or the Platform API Token) only: anyone else gets 403 "A mentor's MCP connection mode is set by platform admins."; 404 "This mentor does not use this MCP server." until the server is in the agent'smcp_servers:{ "mentor": "<agent unique id>", "auth_scope": "mentor" }auth_scope: nullrestores the server's own. Returnsauth_scope,server_auth_scope,effective_auth_scope(the list read reports them withmentor_unique_id). An override tomentororplatformneeds a connection at that scope before the server works for the agent.
User and per-chat preferences
A user's own on/off for a server. user_can_disable: false forces the
server on and makes both writes fail; the chat's choice wins over the
lasting one.
- PUT
…/users/{username}/mcp-servers/{id}/preference/—{ "is_enabled": false }: the caller's lasting choice (same fields back). - PUT
…/users/{username}/mcp-servers/{id}/session-preference/—{ "session_id": "<chat session>", "is_enabled": false }: for one chat only, applied from the next message (same fields back as its GET). The chat composer's MCP panel writes this.
For one chat turn: is_enabled → the agent's mcp_servers → the user's
lasting choice → the chat's choice (null falls through).
The organization's own OAuth app (credentials/)
When a service neither registers a sign-in app by itself nor is covered by
the client ibl.ai shares (oauth-find fails at client_registration or
authorization_server_metadata), or the organization already has an app
(tenant_client_configured), store the organization's own app:
- PUT
…/users/{username}/mcp-servers/{id}/credentials/(OAuth server):{ "client_id", "client_secret", "redirect_uri", "scopes": [...], "token_endpoint_auth_method", "authorization_params": {}, "token_params": {}, "authorization_url", "token_url" }.client_idandredirect_uriare required;client_secretis required on the first save, omitted to keep the stored one,""for a public client.token_endpoint_auth_method:client_secret_post(default) |client_secret_basic|none. The two URLs only for a server that publishes no sign-in settings (both or neither;""returns to the discovered ones).redirect_uriis the page the app returns users to, registered word for word at the service. Token server:{ "api_key", "authorization_scheme", "header_name" }. - DELETE
…/users/{username}/mcp-servers/{id}/credentials/— remove the app; everyone who signed in through it signs in again. Destructive — confirm with the user first.
Finding a service's sign-in settings (oauth-find)
- POST
…/users/{username}/mcp-servers/oauth-find/—{ "url": "https://api.example.com/mcp", "name": "Example", "callback_url": "https://app.example.com/google-oauth-callback/" }discovers the server's OAuth settings, registers a client where the service allows it, and returns (or reuses) the OAuth service record (id,oauth_provider,name,display_name, …) for the server'soauth_service. Withclient_id(thencallback_urlis required too;client_secret,""public; optionalscopes,token_endpoint_auth_method, and bothhttps://URLs only when the server publishes none) it uses and stores the organization's own app instead. A400carrieserrorand adiagnostic:succeeded,failed_step(mcp_server_url|authorization_server_metadata|client_registration|provider_name_conflict|tenant_client_configured|manual_client|internal_error),hint,timed_out,discovered(endpoints,scopes_supported, auth methods) andattempts[];manual_clientmeans the app sent was refused (hintnames the field).
Verify a server
- POST
…/users/{username}/mcp-servers/{id}/verify/— checks the organization's setup without writing credentials: the discovery diagnostic, an authorize-URL preview and the server'stools/list; the caller's expired OAuth token is refreshed, as chat would.
MCP server connections
POST
…/users/{username}/mcp-server-connections/— create a connection. Common fields:server(id, required),scope(platform|mentor|user, defaultuser),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):
platformanduserare 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—mentoris forbidden; the connection's org must match the server's org unless the server is featured.scope=mentor—mentoris required and must belong to the same org as the connection.scope=user—mentoris forbidden; requires the calling user or aconnected_service.auth_type=oauth2— always requiresconnected_service("OAuth2 connections require a connected service."), at every scope, and the connected service must belong to the same org.auth_type=token— requirescredentials("Token based connections must include credentials.").
Credential handling:
authorization_schemebecomes the header prefix (Authorization: Bearer <credentials>); omit it to send the raw value.extra_headersis merged into every outbound request; explicit credentials override clashing headers.credentialsandextra_headersare masked on read — when PATCHing, only sendcredentialsif 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:
Critical semantics — these lists are replaced, not merged.{ "tool_slugs": ["ai-index", "mcp"], "mcp_servers": [3, 9], "can_use_tools": true }[]clears everything; omitting a field leaves it untouched. Blindly sending{"tool_slugs": ["mcp"]}silently strips every other tool the agent had. Safe procedure: GET the current settings, merge locally (keep existingtool_slugs, ensuremcpis present; keep existingmcp_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",
effective scope user, no valid connection for the current user + server,
an authenticated (non-anonymous) chat user. Setup before any prompt can
fire: the OAuth provider and service records exist (or oauth-find
created them), the server is is_enabled=true with a linked
oauth_service, and it 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. After 300s without a
sign-in it emits oauth_authentication_timeout and the turn ends. 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_id, server_name, auth_url, message | The turn pauses (up to 300s; polled every 10s). Show a card keyed by server_id: Sign in opens auth_url (the backend creates the connection itself), and Skip in this conversation (not for mandatory servers) sets the session preference off. |
oauth_connection_resolved | server_id, server_name, message | Dismiss the card; the reply follows. |
oauth_authentication_timeout | message, status_code: 401 | The turn ends. Offer Sign in and Retry; a late sign-in works on the next message. |
mcp_tools_retrieved | session_id, mentor_id | Informational: the tools loaded. |
warning | message, code: 503, optional developer_error | One server failed to connect, authorize or list its tools within 15s. It is skipped alone, and message names it; the others still work. Show message, and only log developer_error. |
(no type) {error, status_code: 400} | The turn ends. Could not build OAuth URL… means the server isn't set up (offer turn off for this conversation). A connection with no connected service means reconnect. |
Fuller per-frame notes: references/in-chat-events.md.
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.400cross-org server on a platform connection — use a server owned by this org, or mark the source serveris_featured=true.400 No credentials foundon OAuth start — install theauth_{provider}credential (client_id, client_secret, redirect_uri).- Agent never calls the server —
mcpmissing fromtool_slugsor the server id missing frommcp_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"andauth_type="oauth2", plus a linkedoauth_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_activeand that the connected service's user matches the chat user. - Callback
Invalid state— the round-trip spanned browser contexts or the 1-hour window; redo it in one session.Could not exchange auth token— the provider rejected the code; check the redirect URI. - Tool call fails silently — a
warning(503) frame was ignored; surface it;verify/shows what the platform sees. preference/orsession-preference/refused — the server hasuser_can_disable: false(fixed by the platform for some featured and directory servers).- A directory or another organization's featured server refuses
PATCH/DELETE— not the caller's record; setmentor-auth-scope/for the agent instead. oauth-findfails atclient_registrationorauthorization_server_metadata— store the organization's own app (credentials/, or resendoauth-findwithclient_id).- A
mentor- orplatform-scope sign-in never prompts in chat — an administrator makes that connection up front; onlyuserscope asks each person.
- Scope enum is
platform | mentor | useron bothMCPServer.auth_scopeand connectionscope—mentormeans agent-wide,platformmeans org-wide. (There is noagentortenantvalue on the wire.) - The chat events above ride the runtime chat connection (see
/iblai-api-agent-chatfor 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 offauth_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 threeerrorvariants with their exact messages, and the client-handling gotchas.- ibl.ai's own MCP servers (
@iblai/mcp, the hostediblai-agent-chat) are not ones this skill wires: docs/mcp-servers.md.