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.
For MCP concepts and how it works, see MCP (Model Context Protocol). For hosted services such as Composio and Zapier, see Remote MCP Service Overview.
For step-by-step console instructions (where to add an MCP tool and what to enter in each field), see Create an MCP Tool in the user manual. This page does not repeat those steps.
To integrate a regular REST API instead of an MCP Server, see the API Tool Engineering Guide.
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
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.
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
mcpAllowedToolsor passes an empty array[], the platform stores all tool names returned bytools/listat 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
rawMcpHeaderthat do not contain{{variables}}(there is no conversation yet to supply variable values). If your Server requires a dynamic header on every request, make suretools/listcan 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.
rawMcpHeaderis write-only. Reading the tool returns only the maskedrawMcpHeaderMasked.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
headersinto the request headers as-is, overridingrawMcpHeaderwhen names match. Credential headers do not substitute{{variables}}.Ways to write credentials:
When embedding Web Chat, call
setup-contact-credentialsand includetoolIdandheadersinmcpCredentials(toolIdmust identify an MCP tool; otherwise, the response is 400).From your backend, call the contact API's
mcp-credentialsendpoint 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:
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.
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.
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.
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:
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.
When creating or updating tools through the API:
Regular MCP tools: Omitting
mcpAllowedToolsor passing[]replaces it with all tool names retrieved at that moment (see Connection Validation During Tool Setup). You therefore cannot use[]to disable all tools.Tools matching an OAuth MCP Registry:
[]is stored as-is. During updates, passing[]or omitting this field also clears the allowlist, leaving no tools enabled insharedmode. Always include the completemcpAllowedToolswhen updating these tools.
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
descriptionyou return intools/list. Clearly explain the tool's purpose and when to use it.Parameters:
inputSchemais converted into the model's parameter definition. The platform automatically handles two common patterns: converting array-form property values toanyOf, and inlining local$refreferences (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/callopens a new MCP session (initializefollowed by the call). Sessions are not reused across calls. To retain state across calls, useX-Conversation-Idor 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:
The tool's
annotationsincludereadOnlyHint: trueoridempotentHint: true→ retry.Either is explicitly
false→ do not retry.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, orlookup(camelCase and-are split into words first).
Content Returned to 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
Link the UI resource: Include
ui.resourceUriin_metain the tool definition intools/listor thetools/callresult. The value must start withui://, 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 byregisterAppToolin@modelcontextprotocol/ext-apps) is also accepted. The tool definition's_metais read each time the tool list is assembled.Provide resource content: After a successful tool call, the platform sends
resources/readfor this URI in the same MCP session and uses the first content entry'stextas the card HTML. Usetext/html;profile=mcp-appformimeType.Return data: The card receives the current tool result: the first text block in
content, plusstructuredContent(an object). UsestructuredContentas the card's data source. The platform does not pass tool input arguments to the card (argumentsinui/notifications/tool-inputis an empty object).
The platform's MCP client does not declare the io.modelcontextprotocol/ui extension capability during initialize. Do not use checks such as getUiCapability() to decide whether to attach _meta, or the platform will never receive the UI resource.
Cards do not appear, or fall back to text, in the following cases:
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"(withoutallow-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-srcis'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
cspAllowedOriginsin the organization-private MCP Registry (/api/v1/mcp/private-registry/). Only origins starting withhttps://, without paths or wildcards, are accepted, such ashttps://viewer.example.com. When creating or updating an MCP tool, a tool whose URL starts with the Registry'sremoteMcpUrlis matched to that Registry.Size and display mode: Cards start at a default height of 220px and adjust according to the
heightreported byui/notifications/size-changed(up to 2000px). The maximum height in the message stream ismin(62vh, 620px); taller cards scroll internally. Cards can requestfullscreenmode. 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 forpipuse 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-resultwithout 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:
The card sends
ui/initialize(appInfo,appCapabilities,protocolVersion). The platform returnshostInfo,hostCapabilities(serverTools,message.text,openLinks), andhostContext(displayMode,availableDisplayModes: ["inline", "fullscreen"],theme: { primaryColor, mode }).The card sends
ui/notifications/initialized.The platform pushes
ui/notifications/tool-input(emptyarguments) followed byui/notifications/tool-result(the completeCallToolResult). Later, changes to display mode or theme triggerui/notifications/host-context-changed. Before the card is unmounted, the platform sends aui/resource-teardownrequest.
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
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.
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).
Steps to connect to MaiAgent
Make the Server publicly accessible over
https://(for local testing, use a tool such ascloudflared tunnel --url http://localhost:8787orngrok http 8787to obtain a temporary URL). The MCP endpoint ishttps://<你的網域>/mcp.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.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 useui/message. For user-facing behavior, see MCP interactive cards (MCP Apps).
Common errors
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?