For the complete documentation index, see llms.txt. This page is also available as Markdown.

Self-Hosted MCP Server Integration Specification

Wrap your own system as an MCP Server for MaiAgent Agents to call:supported transports and URL requirements, authentication (static headers, contact credentials, OAuth dynamic registration), allowed t

This page is for engineers who want to wrap their own system as an MCP Server for MaiAgent Agents to call. It explains how MaiAgent connects to your Server, which headers it sends, how OAuth works, and how the platform handles timeouts, retries, and errors when retrieving the tool list and calling tools.

Call Flow

MaiAgent is the MCP Client. The Agent only decides which tool to call and which arguments to use; the platform makes the actual connection.

Transports and URL Requirements

Item
Specification

Supported transports

Streamable HTTP and SSE. stdio is not supported (the platform does not launch local commands such as npx or node).

Transport detection

Detected automatically from the URL: SSE is used if the path ends in /sse, contains /sse/, or the query string includes transport=sse. All other URLs use Streamable HTTP.

URL scheme

Must be https://. http:// is accepted only when the target resolves to an internal network allowed by the platform (the internal CIDR allowlist in the deployment configuration).

Network location

The domain must resolve. Private, loopback, and other internal addresses are rejected unless they fall within the internal CIDR allowlist. Link-local addresses (such as 169.254.x.x) are always rejected, even if allowlisted. In SaaS environments, your Server must be accessible from the public internet.

TLS

Strict certificate verification is used by default. If a connection fails because the certificate chain uses an older root certificate without a Subject Key Identifier, only MaiAgent can enable the relaxed verification option in system administration, individually for each tool or domain. Contact MaiAgent.

Use Streamable HTTP and place the MCP endpoint on a dedicated path (for example, https://mcp.example.com/mcp). If your Server only supports SSE, use a URL ending in /sse so the platform connects over SSE.

Connection Validation During Tool Setup

When you create or update an MCP tool in the console or through the API, the platform first connects to your Server and runs tools/list:

  • If it cannot connect or retrieve the tool list, creation fails. The error message includes the connection error details.

  • If the request omits mcpAllowedTools or passes an empty array [], the platform stores all tool names returned by tools/list at that moment as the allowlist. Tools added to the Server later are not enabled automatically. Return to the tool settings, retrieve the list again, and select them.

  • Connection validation only sends headers in rawMcpHeader that do not contain {{variables}} (there is no conversation yet to supply variable values). If your Server requires a dynamic header on every request, make sure tools/list can also respond with only static headers.

  • If this URL matches an MCP Registry configured for OAuth (see OAuth 2.0), validation is skipped during creation or updates, and the allowlist remains empty until authorization completes and the tool list can be retrieved.

Authentication

The platform sends identity information to your Server through HTTP headers. The headers depend on the tool's authorization subject (API field authSubject):

authSubject

Console name

Identity used

Headers applied (later entries override earlier headers with the same name)

shared (default)

Organization-wide

One authorization for the entire organization

Tool-level OAuth token → rawMcpHeader → the contact's MCP credentials

contact

Individual contact authorization

The contact in the conversation

rawMcpHeader → the contact's MCP credentials → the contact's OAuth token

In contact mode, this tool is never provided to the Agent in conversations with anonymous contacts (those without a sourceId).

Static Headers (API Key)

The simplest approach is to put a JSON object in the tool's rawMcpHeader. It is sent with every connection.

  • rawMcpHeader is write-only. Reading the tool returns only the masked rawMcpHeaderMasked.

  • Values support the following {{variables}}, populated from the current conversation: contact_id, source_id, contact_name, conversation_id, inbox_id, organization_id, agent_slug, agent_version.

Contact-level MCP Credentials

To let the Agent call your Server with the end user's own permissions, write the user's token into that contact's MCP credentials:

  • Each contact can have at most one credential entry per MCP tool, containing a set of headers.

  • During a conversation, the platform looks up credentials by “current conversation's contact + this tool” and merges the headers into the request headers as-is, overriding rawMcpHeader when names match. Credential headers do not substitute {{variables}}.

  • Ways to write credentials:

    • When embedding Web Chat, call setup-contact-credentials and include toolId and headers in mcpCredentials (toolId must identify an MCP tool; otherwise, the response is 400).

    • From your backend, call the contact API's mcp-credentials endpoint to create, update, or delete credentials.

For full parameters, token updates, and revocation on logout for both approaches, see Contact Identity Synchronization and Token Updates. For manual console configuration, see Contact MCP Credential Settings in the user manual.

Conversation Identification Header

Whenever a conversation context is available, the platform adds X-Conversation-Id: <conversation ID> to every request. Stateful Servers can use it to retain state per conversation without relying on the model to pass a session token. If your authentication headers use the same name, the authentication header takes precedence.

OAuth 2.0

If your MCP Server is protected by OAuth, the platform automatically completes the following flow according to the MCP authorization specification:

1

Discover the Protected Resource

The platform first sends a GET to the MCP URL without a token. If the response is 401 with WWW-Authenticate: Bearer resource_metadata="...", it reads that URL (which must use https and have the same host as the MCP URL). Otherwise, it tries the default RFC 9728 locations: first https://{host}/.well-known/oauth-protected-resource{MCP path}, then https://{host}/.well-known/oauth-protected-resource.

It retrieves resource (the token audience) and authorization_servers (uses the first entry, which must use https). Discovery results are cached for 5 minutes.

2

Read Authorization Server Metadata

The platform reads /.well-known/oauth-authorization-server according to RFC 8414. If the authorization server URL includes a path, it tries three locations in order: well-known inserted between the host and path, well-known appended after the path, and the host root.

3

Dynamically Register the Client (RFC 7591)

If the metadata includes registration_endpoint, the platform sends:

If there is no registration_endpoint, configure oauthClientId in the MCP Registry in advance (also set oauthClientSecret for confidential clients). Otherwise, authorization fails.

4

Authorization Code + PKCE

The authorization request includes response_type=code, state, code_challenge (S256), and resource. The scope is selected in this order: scope configured in the MCP Registry → scopes_supported in protected resource metadata → scopes_supported in authorization server metadata. state is valid for 10 minutes.

When exchanging the code for a token, the platform uses grant_type=authorization_code with code_verifier and resource, including the client secret only if one exists.

5

Automatically Refresh the Token

Within 60 seconds before token expiry, the platform refreshes it using grant_type=refresh_token. If the response does not include a new refresh_token, the old one is retained.

Callback URL: If your authorization server does not support dynamic registration and requires redirect URIs to be registered in advance, register both URLs below. Otherwise, the corresponding flow fails at /authorize:

Flow
Callback URL

Member (organization-wide) authorization

MaiAgent console domain + /oauth/mcp/callback

Individual contact authorization

https://api.maiagent.ai/api/mcp/oauth/contacts/callback/ (SaaS; private deployments use that environment's API domain)

MCP Registry: If your self-hosted Server requires a fixed oauthClientId, a specific authorization server URL, or a resourceIdentifier, a member with connector management permissions can create an organization-private MCP Registry (/api/v1/mcp/private-registry/). authType can be oauth, service_account (key sent through headers), or none. The platform matches tools to a Registry when the MCP URL starts with the Registry's remoteMcpUrl. Only fields left blank use automatically discovered values.

Allowed Tools (mcpAllowedTools)

mcpAllowedTools is an array of Server tool names that determines which tools are provided to the Agent. At runtime, stored values are interpreted according to the authorization subject:

Value

shared

contact

null

All Server tools enabled

All Server tools enabled

[] (empty array)

No tools enabled

All Server tools enabled

["search_orders", ...]

Only listed tools enabled

Only listed tools enabled

In contact mode, an empty array enables all tools because administrators do not have the contact's token and cannot retrieve the tool list in advance. In shared mode with OAuth, the list is populated only after the administrator completes authorization and retrieves the tool list.

How the Tool List Reaches the Model

  • When it is retrieved: Each time the Agent's tools are assembled, the platform connects to your Server, runs tools/list, and filters the results by the allowlist.

  • Names: If the tool has a configured name, the model sees {name}__{your tool name}. Characters other than alphanumerics and underscores are replaced with underscores. If the name does not start with an English letter, t_ is prepended. Names longer than 64 characters are truncated and given a 6-character hash suffix.

  • Descriptions: The model sees the description you return in tools/list. Clearly explain the tool's purpose and when to use it.

  • Parameters: inputSchema is converted into the model's parameter definition. The platform automatically handles two common patterns: converting array-form property values to anyOf, and inlining local $ref references (recursive references are changed to accept any value). If the schema still cannot be converted, only that tool is skipped; other tools remain available.

  • Retrieval failure: If the connection fails, times out, or fails authentication, this MCP tool provides no tools to the model for the current reply. The conversation itself continues.

Tool Calls: Timeouts, Retries, and Errors

Sessions and Timeouts

  • Every tools/call opens a new MCP session (initialize followed by the call). Sessions are not reused across calls. To retain state across calls, use X-Conversation-Id or your own authenticated identity.

  • The MCP session read timeout is 30 seconds.

Automatic Retries for Transient Errors

For connection timeouts, interrupted or reset connections, disconnections before the Server responds, sessions closed mid-operation, and MCP error codes indicating a closed connection or 408, the platform retries up to 3 times at intervals of 1, 2, and 4 seconds (capped at 8 seconds). Error results returned by your Server and application-level errors are not retried.

Only tools that are safe to resend are retried, using this decision order:

  1. The tool's annotations include readOnlyHint: true or idempotentHint: true → retry.

  2. Either is explicitly false → do not retry.

  3. If neither is declared, check whether the first word of the tool name is get, list, read, fetch, search, find, check, query, describe, show, view, browse, or lookup (camelCase and - are split into words first).

Explicitly declare readOnlyHint / idempotentHint for every tool in tools/list. Mark tools with side effects (placing orders or submitting forms) as false to prevent resends after a disconnection.

Content Returned to the Model

Situation
Content seen by the model

Successful call

Text converted from CallToolResult (including the content, structuredContent, and isError fields). The model still sees this content when you return isError: true.

Exception during the call (retries exhausted or a non-transient error)

MCP tool execution error\n MCP Tool Execution Error: <error details>

Result too long

The limit is the model's context window × tool output ratio (default 0.8), estimated in tokens. When exceeded, the first and last halves are retained and the middle is omitted. A notice that the result is incomplete is prepended.

Every call writes a tool execution log (tool, actual MCP tool name, input arguments, output or error, duration, and retry count). Debugging works the same way as Logging and Debugging in the API Tool Engineering Guide.

Billing Categories

Each successful call counts as 1 MCP tool usage, categorized as a read or write operation by tool name. If the first word matches one of the read prefixes above, it is a read operation. Otherwise, a name containing a write keyword is a write operation; all remaining names count as read operations. If the organization has insufficient credits, the call is not sent to your Server.

MCP Apps (Interactive Cards)

MaiAgent supports the MCP Apps extension (SEP-1865). In addition to returning data, tools can attach an HTML interface, which appears as an interactive card in the conversation, such as a chart, form, or query result list. For the user experience and limitations, see MCP Interactive Cards (MCP Apps) in the user manual. To get started immediately, jump to the Minimal Runnable Example.

Server Side: Returning UI Resources

  1. Link the UI resource: Include ui.resourceUri in _meta in the tool definition in tools/list or the tools/call result. The value must start with ui://, for example {"_meta": {"ui": {"resourceUri": "ui://orders/list"}}}. If both locations specify it, the call result takes precedence. The flat form _meta["ui/resourceUri"] (the format generated by registerAppTool in @modelcontextprotocol/ext-apps) is also accepted. The tool definition's _meta is read each time the tool list is assembled.

  2. Provide resource content: After a successful tool call, the platform sends resources/read for this URI in the same MCP session and uses the first content entry's text as the card HTML. Use text/html;profile=mcp-app for mimeType.

  3. Return data: The card receives the current tool result: the first text block in content, plus structuredContent (an object). Use structuredContent as the card's data source. The platform does not pass tool input arguments to the card (arguments in ui/notifications/tool-input is an empty object).

Cards do not appear, or fall back to text, in the following cases:

Situation
Result

Tool result has isError: true

No card generated

No linked ui:// resource

No card generated; the conversation displays the tool result normally

resources/read fails or returns no content

The card displays the first text block in content instead

The card HTML is saved with the message when it is generated. Updating the UI on the server later does not change cards already generated in historical messages. Make sure new UI versions can handle data produced by older versions.

Frontend: How Cards Are Displayed

  • Location: After the Agent finishes its reply, the card appears below that reply. Within the same reply, only the latest card from the same source and UI resource is displayed.

  • Sandbox: Cards run in an iframe with sandbox="allow-scripts" (without allow-same-origin). They cannot read the host page's cookies or storage, or open new windows themselves.

  • Content Security Policy (CSP): By default, only inline scripts and styles are allowed. Images, media, and fonts must use data: / blob:. connect-src is 'none' (no outbound requests), and form submissions are blocked. Bundle JS, CSS, and images into one HTML file.

  • Additional allowed origins: If a card needs external resources, list their origins in cspAllowedOrigins in the organization-private MCP Registry (/api/v1/mcp/private-registry/). Only origins starting with https://, without paths or wildcards, are accepted, such as https://viewer.example.com. When creating or updating an MCP tool, a tool whose URL starts with the Registry's remoteMcpUrl is matched to that Registry.

  • Size and display mode: Cards start at a default height of 220px and adjust according to the height reported by ui/notifications/size-changed (up to 2000px). The maximum height in the message stream is min(62vh, 620px); taller cards scroll internally. Cards can request fullscreen mode. The platform adds an exit button in the upper-right corner, and users can also press Esc to return. After collapsing, the card retains its last reported height. Requests for pip use normal (inline) mode.

  • Hidden during streaming: Cards mount only after the Agent's reply stream ends. When the same reply is updated multiple times, the platform pushes new results in place using ui/notifications/tool-result without rebuilding the iframe.

Card–Host Handshake

Cards and the platform communicate through MCP Apps JSON-RPC via window.parent.postMessage (the host uses AppBridge from @modelcontextprotocol/ext-apps 1.2, protocol version 2026-01-26). The sequence is:

  1. The card sends ui/initialize (appInfo, appCapabilities, protocolVersion). The platform returns hostInfo, hostCapabilities (serverTools, message.text, openLinks), and hostContext (displayMode, availableDisplayModes: ["inline", "fullscreen"], theme: { primaryColor, mode }).

  2. The card sends ui/notifications/initialized.

  3. The platform pushes ui/notifications/tool-input (empty arguments) followed by ui/notifications/tool-result (the complete CallToolResult). Later, changes to display mode or theme trigger ui/notifications/host-context-changed. Before the card is unmounted, the platform sends a ui/resource-teardown request.

The platform also injects window.__MCP_INITIAL_RENDER_DATA__ (the same tool result) into the card HTML's <head> so the card can render before the handshake completes. The authoritative data still comes from the tool-result notification.

Host Functions Available to Cards

Method
Platform handling

tools/call

The platform makes the call on the card's behalf. The card cannot connect directly to your server or the MaiAgent backend. It can only call tools from the same MCP source that generated the card, enabled in this conversation and included in the allowlist. Authentication headers are the same as those used by the Agent. Each call times out after 30 seconds. The result returned to the card retains only the first text block in content, structuredContent, and isError. On failure, a message outside the card says “This action could not be completed. Please try again later.”

ui/message

content must be an array of content blocks. The platform extracts its text blocks, submits them to the conversation as a user message, and first collapses the card to inline so the Agent can respond

ui/open-link

The platform opens a new tab. Only https:, http:, tel:, and mailto: are accepted. Credentials in the URL are removed. If disallowed or blocked by the browser, it returns { "isError": true }

ui/request-display-mode

Switches between inline and fullscreen and returns the mode actually applied

ui/notifications/size-changed

Adjusts the card height using height (ignored in fullscreen mode)

ui/download-file, ui/update-model-context, resources/read

Unsupported; the platform returns -32601 Method not found

Scope

  • Cards appear in Web Chat and MaiGPT conversations. Other messaging platforms such as LINE and Messenger receive only the Agent's text reply.

  • Cards work for MCP tools attached to an Agent (Agent mode) and connectors authorized by members in MaiGPT. No additional switch is required on the MaiAgent side.

  • Whether Web Chat in an on-premises deployment displays cards depends on deployment settings. Contact your MaiAgent representative.

Minimal Runnable Example

A Node.js (18 or later) MCP Server: a tool for querying order status (returning mock data) links to a ui://orders/status card. The card can refresh the query and ask the AI questions. Put the three files in the same folder.

1

Create the Project

2

server.mjs: Tool and UI Resource

3

card.html: a single-file card

Use JSON-RPC directly through postMessage without importing any packages: perform the handshake, receive tool-result, report the height, refresh through tools/call, and ask AI through ui/message. Keep JS and CSS in the same file (the sandbox CSP does not allow external resources to load).

4

Start and verify

Open another terminal and send three requests in the order used by the platform (stateless mode does not require a session header):

Once all three return the correct responses, the platform can render the card.

Steps to connect to MaiAgent

  1. Make the Server publicly accessible over https:// (for local testing, use a tool such as cloudflared tunnel --url http://localhost:8787 or ngrok http 8787 to obtain a temporary URL). The MCP endpoint is https://<你的網域>/mcp.

  2. Add an MCP tool in the admin console by following Create an MCP tool in the user manual, and enter the endpoint from the previous step as the URL. This example requires no headers. After refreshing the list, "Allowed tools" should include get_order_status.

  3. Assign the tool to an Agent whose reply mode is Agent mode, then ask "What is the status of order A1001?" in Web Chat or MaiGPT. After the Agent finishes replying, the card appears below the reply. Click Refresh to use tools/call, or Ask AI about this order to use ui/message. For user-facing behavior, see MCP interactive cards (MCP Apps).

Common errors

Symptom
Cause

The Agent calls the tool, but no card appears

Both the tool definition in tools/list and the tools/call result lack _meta.ui.resourceUri (or its value does not start with ui://), the tool returns isError: true, or the channel, such as LINE, does not display cards

Only plain text appears where the card should be

resources/read fails or returns empty content, so the platform falls back to the first text block in content; check with the third curl command above

The card stays on "Loading…"

The card has not completed the handshake (ui/initialize lacks appInfo / appCapabilities / protocolVersion in params, or ui/notifications/initialized was not sent), so the platform does not push tool-result; external JS loaded by the card HTML may also have been blocked by CSP

The card is blank

The HTML depends on external CSS / JS / images blocked by the sandbox CSP. Bundle them into the same HTML file, or configure cspAllowedOrigins in the Registry

The card displays "This action could not be completed. Please try again later."

The platform rejects the card's tools/call, or the call fails: the tool name is not on the MCP tool's allowed list, the tool has been removed from the Agent, authorization has expired, the Server returns an error, or it does not respond within 30 seconds

"Ask AI" on the card does not respond

The content in ui/message is not an array, or it contains no block with type: "text"

Pre-launch checklist

Last updated

Was this helpful?