Last updated

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:

  1. MCP server — metadata for the external MCP endpoint (name, URL, transport, auth_type, auth_scope).
  2. MCP server connection — the credential binding (a static token, or a reference to an OAuth connected service), at platform, mentor (agent), or user scope.
  3. Agent wiring — the agent's settings must have the mcp 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.

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 /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/); mentor is canonical here.
  • 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 into chat or 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.

PatternServer fieldsConnection setupEnd user prompted?
No authauth_type=noneconnection with no credentialsNo
Shared key for the whole orgauth_type=token, auth_scope=platformone platform-scoped connection holding the keyNo
Per-agent keyauth_type=token, auth_scope=mentorone mentor-scoped connection per agentNo
Pre-provisioned per-userauth_scope=useradmin creates user-scoped connections up frontNo
In-chat OAuth (each user connects their own account)auth_type=oauth2 and auth_scope=usernone up front — created automatically when the user completes OAuth mid-chatYes

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:

  1. User-scoped connection for (server, user)
  2. Agent-scoped (mentor) connection for (server, agent)
  3. Platform-scoped connection for (server, org)
  4. Featured-server global fallback
  5. 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&registry={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_id alone: that agent's own servers; with include_global=true: the organization-wide ones too (the OS lists a tab that way, is_featured=true for Featured, false for Directory). is_enabled, registry, auth_type, auth_scope, search and transport narrow it. Each row carries is_enabled, user_can_disable, user_enabled (whether chat uses it for the caller; with session_id, after that chat's choice), mentor_auth_scope (only with mentor_unique_id), effective_auth_scope (on every row; the server's own scope when no agent is in context), oauth_service_data, and registry (the MCP directory entry a global server comes from, null otherwise; 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 owning user (username) with user_email and user_full_name, masked credentials (e.g. sup****key), masked extra_headers, connected_service_summary ({id, provider, service, user, platform_key}). A server row likewise carries owner (username) plus owner_email and owner_full_name; all six are read-only and null when the row has no user.
  • GET …/users/{username}/mentors/{mentor}/settings/ — the agent's active tool_slugs, mcp_servers (serialized server objects), and can_use_tools.
  • GET …/users/{username}/mcp-servers/{id}/mentor-auth-scope/?mentor={mentor} — the agent's override auth_scope (null when 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 on PUT, DELETE and verify/): 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 server api_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 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, user_email, user_full_name) — 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|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_type are required; auth_scope defaults to platform. transport accepts sse and streamable_http only: websocket, still offered by older UIs, answers 400.
    • 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, 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). false makes it mandatory wherever it is enabled: the chat shows a locked switch and the preference writes are refused.
    • 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. Featured servers of other organizations and directory servers (registry set) are not the caller's to edit or delete (PATCH / DELETE answer 404, user_can_disable included — 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's auth_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's mcp_servers:
    { "mentor": "<agent unique id>", "auth_scope": "mentor" }
    auth_scope: null restores the server's own. Returns auth_scope, server_auth_scope, effective_auth_scope (the list read reports them with mentor_unique_id). An override to mentor or platform needs 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_id and redirect_uri are required; client_secret is 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_uri is 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's oauth_service. With client_id (then callback_url is required too; client_secret, "" public; optional scopes, token_endpoint_auth_method, and both https:// URLs only when the server publishes none) it uses and stores the organization's own app instead. A 400 carries error and a diagnostic: 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) and attempts[]; manual_client means the app sent was refused (hint names 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's tools/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, 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"], "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"]} silently strips every other tool the agent had. Safe procedure: GET the current settings, merge locally (keep existing tool_slugs, ensure mcp 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.

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 typeKey fieldsClient action
oauth_requiredserver_id, server_name, auth_url, messageThe 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_resolvedserver_id, server_name, messageDismiss the card; the reply follows.
oauth_authentication_timeoutmessage, status_code: 401The turn ends. Offer Sign in and Retry; a late sign-in works on the next message.
mcp_tools_retrievedsession_id, mentor_idInformational: the tools loaded.
warningmessage, code: 503, optional developer_errorOne 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.
    • 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 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.
    • 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/ or session-preference/ refused — the server has user_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; set mentor-auth-scope/ for the agent instead.
    • oauth-find fails at client_registration or authorization_server_metadata — store the organization's own app (credentials/, or resend oauth-find with client_id).
    • A mentor- or platform-scope sign-in never prompts in chat — an administrator makes that connection up front; only user scope asks each person.
  • 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.
  • ibl.ai's own MCP servers (@iblai/mcp, the hosted iblai-agent-chat) are not ones this skill wires: docs/mcp-servers.md.

Copyright © ibl.ai | support@iblai.zendesk.com