Interested in an on-premise deployment or AI transformation? Calculate your AI costs. Call/text πŸ“ž (571) 293-0242

In-Chat MCP Events

Handle the real-time WebSocket and SSE events the mentor runtime emits when an MCP server needs attention during a live chat β€” per-user OAuth prompts, tool-retrieval recoveries, and graceful failures.


Overview

Most MCP authentication happens well before a learner opens a chat: an administrator provisions a server and a connection, and every request uses those credentials. Some integrations, however, need to authenticate each learner individually. For these, the platform pauses the active chat and asks the user to authenticate in a side window, then resumes the conversation automatically once credentials arrive.

This doc describes the messages the backend emits on the existing chat connection during that handshake (and other MCP lifecycle events), so that your client can react correctly.

The in-chat OAuth flow only triggers when all of the following are true:

  • The MCP server has auth_type = "oauth2".
  • The MCP server has auth_scope = "user".
  • No valid MCPServerConnection exists for the current user + server.
  • The chat session is an authenticated (non-anonymous) user.

For a full walkthrough of server configuration and connection provisioning, see MCP Server Connections.


Prerequisites

Server Configuration

RequirementDetail
MCP ServerRegistered with is_enabled=true and attached to the mentor
auth_typeMust be "oauth2"
auth_scopeMust be "user" for the in-chat flow
oauth_serviceMust link to a valid OauthService β†’ OauthProvider
Client credentialsauth_{provider} credentials must exist in the credential store with client_id, client_secret, and a redirect_uri
Active sessionThe chat session must belong to an authenticated user

Auth scope matrix

The auth_scope field on the MCP server controls whether the in-chat prompt fires at all.

ValueBehavior
platform (default)Uses shared, pre-provisioned platform credentials. No prompt.
mentorUses mentor-scoped credentials. No prompt.
userEach user authenticates individually. The in-chat flow runs when no user-scoped connection exists.

Admin setup checklist

Before a frontend ever receives an oauth_required event, a tenant admin must have:

  1. Created an OauthProvider (e.g., google) with a valid auth_url and token_url.

  2. Created an OauthService (e.g., drive) linked to the provider with the required scope.

  3. Stored client credentials in the credential store:

    Key:    auth_google
    Tenant: main
    Value:  {
      "client_id":     "your-client-id",
      "client_secret": "your-client-secret",
      "redirect_uri":  "https://your-app.com/api/ai-mentor/orgs/main/users/oauth/callback/"
    }
  4. Registered an MCPServer with auth_type="oauth2", auth_scope="user", is_enabled=true, and a linked oauth_service.

  5. Attached the server to the target mentor via PATCH /mentors/{id}/settings/ with "mcp_servers": [...] and "tools": ["mcp-tool"].

auth_scope is set at server-creation time or updated later:

PATCH /api/ai-mentor/orgs/{org}/users/{user_id}/mcp-servers/{server_id}/
Content-Type: application/json

{
  "auth_scope": "user"
}

Valid values: "platform" (default), "mentor", "user".


Sequence Diagram

Frontend (WS / SSE)         Backend                       OAuth Provider
      β”‚                        β”‚                                 β”‚
      │── user sends message ─▢│                                 β”‚
      β”‚                        │── resolve server headers ─┐     β”‚
      β”‚                        β”‚   (no user connection)    β”‚     β”‚
      β”‚                        β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
      β”‚                        β”‚                                 β”‚
      │◀── oauth_required ─────│                                 β”‚
      β”‚   {auth_url, server_*} β”‚                                 β”‚
      β”‚                        │── begin polling (every 10s) ──▢ β”‚
      β”‚                        β”‚                                 β”‚
      │── open auth_url ──────▢│────── redirect to provider ───▢ β”‚
      β”‚                        β”‚                                 β”‚
      β”‚                        β”‚     [user consents]             β”‚
      β”‚                        β”‚                                 β”‚
      β”‚                        │◀──── callback with code ────────│
      β”‚                        │───── exchange for tokens ─────▢ β”‚
      β”‚                        │◀──── access / refresh tokens ───│
      β”‚                        β”‚                                 β”‚
      β”‚                        │── create ConnectedService ─┐    β”‚
      β”‚                        β”‚   + MCPServerConnection    β”‚    β”‚
      β”‚                        β”‚β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
      β”‚                        β”‚                                 β”‚
      │◀── oauth_connection_*──│                                 β”‚
      β”‚   resolved             β”‚                                 β”‚
      β”‚                        │─── resume chat processing ───▢  β”‚
      │◀── normal chat reply ──│                                 β”‚

If the learner does not finish within the timeout window:

      β”‚                        │── polling exhausted (5 min) ──▢ β”‚
      │◀── error (400) ────────│                                 β”‚
      β”‚   "Timed out waiting…" β”‚                                 β”‚

Event Reference

Every event arrives as a JSON string on the existing chat WebSocket/SSE connection. Parse it with JSON.parse() and switch on the type field.

oauth_required

Sent when: The backend is about to call an MCP server for which the current user has no connection. Arrives before polling starts.

Client action: Show the user the auth_url (link or popup) so they can complete authentication with the external provider.

{
  "type": "oauth_required",
  "server_name": "Google Drive MCP",
  "server_id": 42,
  "auth_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&response_type=code&scope=...&state=...",
  "message": "Authentication required for MCP server 'Google Drive MCP'. Please complete the OAuth flow to continue."
}
FieldTypeDescription
typestringAlways "oauth_required".
server_namestringHuman-readable name of the MCP server.
server_idintegerDatabase ID of the MCP server.
auth_urlstringThe full OAuth authorization URL.
messagestringUser-safe explanation.

Client guidance

  • Render a prominent UI element (banner, modal, or inline card) naming the server.
  • Open auth_url in a new tab or popup β€” OAuth providers typically block framing.
  • Show a "waiting for authentication" indicator while the backend polls.
  • Do not close or refresh the chat connection; the resolution event will arrive on the same socket.

oauth_connection_resolved

Sent when: The backend's polling loop detects that the OAuth flow succeeded and a valid MCPServerConnection now exists for the user.

Client action: Dismiss the OAuth prompt UI. The chat will resume automatically.

{
  "type": "oauth_connection_resolved",
  "server_name": "Google Drive MCP",
  "server_id": 42,
  "message": "OAuth connection resolved for MCP server 'Google Drive MCP'. Continuing with chat."
}
FieldTypeDescription
typestringAlways "oauth_connection_resolved".
server_namestringName of the authenticated server.
server_idintegerDatabase ID of the authenticated server.
messagestringFriendly confirmation message.

Client guidance

  • Hide the OAuth prompt.
  • Optionally show a success toast (e.g., "Connected to Google Drive MCP").
  • The chat response arrives shortly after this event.

mcp_tools_retrieved

Sent when: An initial MCP tool fetch failed but a retry succeeded. The backend retries up to 3 times with exponential backoff (1s, 2s, 4s).

Client action: None required. This is purely informational.

{
  "type": "mcp_tools_retrieved",
  "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "mentor_id": "123"
}
FieldTypeDescription
typestringAlways "mcp_tools_retrieved".
session_idstringCurrent chat session UUID.
mentor_idstringMentor ID for the session.

Client guidance

  • Log for debugging or ignore.
  • Optionally surface a subtle indicator that tools recovered.

warning

Sent when: The backend fails to load MCP tools for a reason unrelated to OAuth β€” the server is unreachable, configuration is invalid, or retries are exhausted. The chat continues without MCP tools (graceful degradation).

Client action: Inform the user that some tools may be unavailable, but keep the chat going.

{
  "type": "warning",
  "message": "MCP tools temporarily unavailable for this session. Continuing without them.",
  "developer_error": "ConnectionError: MCP server unreachable",
  "code": 503
}
FieldTypeDescription
typestringAlways "warning".
messagestringUser-safe message for display.
developer_errorstringTechnical detail for logging. Do not show to end users.
codeintegerHTTP-equivalent status (503 = Service Unavailable).

Client guidance

  • Show a non-blocking banner or toast using message.
  • Log developer_error for diagnostics.
  • The chat reply still arrives, but without MCP-powered capabilities.
  • The user can retry the message once the external service recovers.

error (ChatValidationError)

Sent when: A validation failure terminates the chat turn. In the MCP context, this happens in three situations:

  1. OAuth timeout β€” the user did not finish authentication within MCP_OAUTH_MAX_WAIT_SECONDS (default 5 minutes).
  2. OAuth URL build failure β€” the backend could not construct the authorization URL (missing credentials, unknown provider, etc.).
  3. Missing connected service β€” an OAuth2 connection record exists but has no linked ConnectedService with valid tokens.

Client action: Show the error to the user and offer a retry.

{
  "error": "Timed out waiting for OAuth authentication for MCP server 'Google Drive MCP' after 300s. Retry message after completing the OAuth flow.",
  "status_code": 400
}
FieldTypeDescription
errorstringUser-facing error description.
status_codeintegerAlways 400 for ChatValidationError.

Example payloads

OAuth URL build failure

{
  "error": "Could not build OAuth URL for MCP server 'Google Drive MCP'.",
  "status_code": 400
}

Missing connected service

{
  "error": "MCP connection for server 'Google Drive MCP' is configured for OAuth2 but has no connected service.",
  "status_code": 400
}

Client guidance

  • Display error directly; it is written for end users.
  • For timeouts, the prior oauth_required prompt still contained the auth_url. If the user finishes OAuth after the timeout, their next chat message will succeed automatically because the connection is now in place.
  • Offer a Retry button so the user can resend their message.
  • On WebSocket transports, the connection closes after this error.

Error Handling Summary

ScenarioEventstatus_codeChat continues?
OAuth prompt sentoauth_requiredβ€”Paused (backend polls)
User completes OAuthoauth_connection_resolvedβ€”Yes (resumes)
Tools retrieved after retrymcp_tools_retrievedβ€”Yes
Tools unavailable (non-OAuth)warning503Yes, without MCP tools
OAuth timeout (5 min)error400No β€” user must retry
OAuth URL build failureerror400No β€” user must retry
Missing connected serviceerror400No β€” user must retry

Timing & Polling Constants

ConstantValueDescription
MCP_OAUTH_MAX_WAIT_SECONDS300 (5 min)Maximum time the backend waits for the user to finish OAuth.
MCP_OAUTH_POLL_INTERVAL_SECONDS10Interval between DB polls for new credentials.
MCP retry attempts3Number of retries for transient MCP tool retrieval failures.
MCP retry backoff1s, 2s, 4sExponential backoff between retries.

Polling starts immediately after oauth_required is sent. Each poll checks for:

  1. An MCPServerConnection with a valid ConnectedService for the user + server, or
  2. A ConnectedService matching the OAuth provider + user + platform.

On the first match, the connection resolves and oauth_connection_resolved is emitted. On timeout, a ChatValidationError is raised.


Frontend Implementation Guide

Message handler pattern

// Inside your WebSocket/SSE message handler
function handleMessage(data) {
  const message = JSON.parse(data);

  switch (message.type) {
    case "oauth_required":
      showOAuthPrompt({
        serverName: message.server_name,
        serverId: message.server_id,
        authUrl: message.auth_url,
        displayMessage: message.message,
      });
      break;

    case "oauth_connection_resolved":
      dismissOAuthPrompt(message.server_id);
      showToast(`Connected to ${message.server_name}`);
      break;

    case "mcp_tools_retrieved":
      console.debug("MCP tools recovered", message);
      break;

    case "warning":
      showWarningBanner(message.message);
      console.warn("MCP warning:", message.developer_error);
      break;

    default:
      if (message.error && message.status_code) {
        handleError(message);
      }
      break;
  }
}

OAuth prompt UX tips

  • Show the MCP server name prominently so the user knows what they are granting access to.
  • Open auth_url in a new tab or popup, not an iframe β€” OAuth providers block framing.
  • Show a waiting state after the user clicks the link so the chat doesn't appear stuck.
  • Auto-dismiss on oauth_connection_resolved.
  • Show the timeout message if an error arrives while the prompt is visible, and offer a retry.
  • Let the user manually dismiss the prompt and retry their message on demand.

Handling the post-OAuth redirect

The OAuth callback URL redirects the browser back to the application; the backend handles the token exchange automatically. The frontend does not need to process the callback itself β€” it only needs to listen for oauth_connection_resolved on the open chat connection.

If the callback opens in a popup, you may auto-close it after the redirect completes. The main chat tab receives oauth_connection_resolved regardless of whether the popup is still open.


Troubleshooting

SymptomLikely CauseAction
No oauth_required event ever arrivesServer is not configured with auth_type="oauth2" + auth_scope="user"Update the server via PATCH /mcp-servers/{id}/ with both fields.
oauth_required fires every message even after the user authenticatesResolved connection belongs to a different user or platformCheck that the callback created a ConnectedService for the chat user and the correct tenant.
Timeout with no UI shownClient didn't handle oauth_required β€” event was ignoredEnsure the chat message handler dispatches on message.type, not just content.
error with "Could not build OAuth URL"Missing auth_{provider} credential in the credential storeConfigure the provider credential (client ID, secret, redirect URI) as a tenant admin.
Chat continues but tool call fails silentlywarning with 503 was emitted and ignoredSurface the warning in the UI, and verify the MCP server is reachable from the platform.

  • MCP Server Connections β€” register servers, create connections, and attach them to mentors.
  • OAuth Connectors β€” provider/service discovery and the standalone OAuth handshake used outside of chat.
  • Architecture β€” backend internals, data model, and the runtime resolution pipeline.

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