Skip to main content

Model Context Protocol (MCP)

AuthProxy embeds an MCP server so that AI assistants (Claude, ChatGPT, custom agents) can authenticate, discover capabilities and call platform tools through a single JSON-RPC 2.0 endpoint.

The MCP gateway sits in front of TrexWallet, Chat, Payments, Markets and any custom Core tools — every call goes through the same authentication, rate-limit and audit pipeline as the regular REST API.

Endpoint: POST /mcp Wire format: JSON-RPC 2.0 Protocol version reported by initialize: 2024-11-05

MCP is not a browser session

MCP login produces a programmatic session that intentionally differs from the browser cookie session. It does not set a browser-compatible sid cookie, and modules may refuse side-effecting browser-only flows on Mcp* sessions. For PWA debugging or reproducing user-reported bugs use the challenge-response browser flow (/auth/v1/login_options + /auth/v1/login).

Quick start

  1. Generate an Ed25519 key pair for your AI agent.
  2. Register the public key for a CRM user via POST /auth/v1/keys/register_key (or the admin UI at /ProxyAdmin/UserKeys).
  3. Set the McpAccess flag on the key via POST /auth/v1/keys/update_key ({ "keyId": "...", "mcpAccess": true }) or the admin UI.
  4. Call POST /auth/v1/mcp_login to obtain a session.
  5. Call POST /mcp with Authorization: Bearer <sessionId>.

For unauthenticated public flows (anonymous chat invites, payment-order pages) skip steps 1–4 and use Capability mode.

Authentication

mcp_login uses HTTP headers only — there is no JSON body. The login level is selected by the combination of headers you send.

Login typeRequired headersAccess
McpBasicX-PublicKeyLimited read on a curated tool list. Convenient bootstrap, no proof-of-possession.
McpVerifiedX-PublicKey + X-Timestamp + X-SignatureFull personal access. Ed25519 signature proves possession of the private key.
McpAppX-PublicKey + X-App-IdDelegated app access scoped to a single registered application.

Detailed semantics, threat model and tool eligibility per type: see MCP Access Model.

Headers

HeaderFormatWhen
X-PublicKeybase64url, 32 bytes (Ed25519 public key)Always
X-TimestampUnix seconds (integer as string)McpVerified
X-Signaturebase64url Ed25519 signature, 64 bytesMcpVerified
X-App-IdTimeTick of a registered user_appMcpApp

What you sign for McpVerified

The signature is over the 8-byte little-endian representation of the Unix timestamp (int64), not the decimal string. Sign exactly the same 8 bytes that AuthProxy will reconstruct from X-Timestamp.

Validation rules:

  • Timestamp must be within ±5 minutes of server UTC time.
  • The public key must be present in the user_key table for the corresponding user.
  • The key must have the McpAccess flag set.
  • The user must not be blocked.

Login example — Node.js

import { ed25519 } from '@noble/curves/ed25519';

const privateKey = /* Uint8Array(32) */;
const publicKey = ed25519.getPublicKey(privateKey);

const timestamp = BigInt(Math.floor(Date.now() / 1000));
const tsBytes = new Uint8Array(8);
new DataView(tsBytes.buffer).setBigInt64(0, timestamp, true); // little-endian

const signature = ed25519.sign(tsBytes, privateKey);

const toBase64Url = (bytes) =>
Buffer.from(bytes).toString('base64')
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

const res = await fetch('https://authproxy.example.com/auth/v1/mcp_login', {
method: 'POST',
headers: {
'X-PublicKey': toBase64Url(publicKey),
'X-Timestamp': timestamp.toString(),
'X-Signature': toBase64Url(signature),
},
});

const { result } = await res.json();
// result.sessionId, result.loginType, result.expiresUtc, result.appId

Login example — Python

import base64, struct, time, requests
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

def b64url(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b'=').decode()

private_key = Ed25519PrivateKey.from_private_bytes(secret_32_bytes)
public_key = private_key.public_key().public_bytes_raw()

ts = int(time.time())
ts_bytes = struct.pack('<q', ts) # 8-byte little-endian int64
sig = private_key.sign(ts_bytes)

res = requests.post(
'https://authproxy.example.com/auth/v1/mcp_login',
headers={
'X-PublicKey': b64url(public_key),
'X-Timestamp': str(ts),
'X-Signature': b64url(sig),
},
)
result = res.json()['result']
session_id = result['sessionId']

Login example — McpBasic (read-only bootstrap)

curl -X POST https://authproxy.example.com/auth/v1/mcp_login \
-H "X-PublicKey: <base64url-public-key>"

Login example — McpApp (delegated app)

curl -X POST https://authproxy.example.com/auth/v1/mcp_login \
-H "X-PublicKey: <base64url-public-key>" \
-H "X-App-Id: <user_app timetick>"

The user_app must have the McpEnabled flag, and the calling key must belong to the app owner or be the explicit pubkey configured for that app.

Login response

{
"result": {
"sessionId": "abc123…",
"loginType": "McpVerified",
"expiresUtc": "2026-05-13T15:00:00Z",
"appId": null
}
}

Use sessionId as a Bearer token on every subsequent /mcp call:

Authorization: Bearer abc123…

Errors are returned in the standard ApiResponse envelope ({ "error": { "code": …, "message": … } }). Common cases:

HTTPerror.messageReason
400Invalid input formatMalformed key, signature, timestamp or app id
401Session not foundPublic key not found in the runtime key cache (only UserKey rows are eligible)
401Account not foundKey resolved but the owning apguser row is missing
401Access deniedKey lacks the McpAccess flag, user blocked, or app not approved / key not authorised for app
401Challenge expiredTimestamp outside ±5 minute window
401Invalid signatureSignature does not verify against X-PublicKey
429Access rate limitPer-key quota exhausted (see Rate limits)

JSON-RPC over /mcp

After login, send JSON-RPC 2.0 requests to POST /mcp with the Bearer token. The server supports the standard MCP method set: initialize, notifications/initialized, tools/list, tools/call, resources/list, resources/read.

initialize

Always call initialize first. It does not require authentication, lets the client negotiate the protocol version, and returns server capabilities.

curl -X POST https://authproxy.example.com/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": { "name": "my-agent", "version": "1.0.0" }
}
}'

Response includes a Mcp-Session-Id response header (used by transports that want it) and a JSON body:

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": { "tools": {}, "resources": {} },
"serverInfo": { "name": "ItBuild AuthProxy MCP", "version": "1.0.0" }
}
}

After initialize, send the standard notifications/initialized notification — the server replies with HTTP 200 and no body.

tools/list

Returns the catalogue of tools available to the current authentication context. Without authentication you receive the full advertised catalogue (so the client can plan a login flow); with authentication the list is filtered to what the session is actually allowed to call.

curl -X POST https://authproxy.example.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sessionId>" \
-d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }'

Each tool has an MCP-standard shape:

{
"name": "wallet_get_balance",
"description": "Get wallet balance (free, locked in/out, orders) by currency or all",
"inputSchema": {
"type": "object",
"properties": {
"currency": { "type": "string", "description": "Currency code …" }
}
}
}

tools/call

curl -X POST https://authproxy.example.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sessionId>" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "wallet_get_balance",
"arguments": { "currency": "USDT" }
}
}'

Successful response:

{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "{\"result\":[{\"currency\":\"USDT\",\"freeBalance\":1000.5}]}"
}
]
}
}

The tool result is the raw backend JSON wrapped in MCP's content[] array. The platform never re-shapes the underlying response — clients see exactly what the REST endpoint returned.

When the underlying REST call returns an ApiResponse error, MCP marks the result with isError: true:

{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{ "type": "text", "text": "{\"error\":{\"code\":-11001,\"message\":\"Gate error\"}}" }
],
"isError": true
}
}

JSON-RPC level errors (auth, unknown tool, validation) are returned via the standard error field:

error.codeMeaning
-32601Method not found
-32602Invalid params (missing tool name, unknown tool, invalid capability code)
-32001Unauthorized / forbidden for this session
-32002Resource URI not found
-32000Tool execution error / capability module unavailable

resources/list and resources/read

Resources expose read-only views of platform state and use stable itbuild://… URIs.

curl -X POST https://authproxy.example.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sessionId>" \
-d '{ "jsonrpc": "2.0", "id": 4, "method": "resources/list" }'

Read a resource:

curl -X POST https://authproxy.example.com/mcp \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sessionId>" \
-d '{
"jsonrpc": "2.0",
"id": 5,
"method": "resources/read",
"params": { "uri": "itbuild://wallet/info" }
}'

The response carries contents[].text and mimeType. Resources also honour the isError: true flag for backend errors.

Tool catalogue (built-in)

AuthProxy ships built-in tools for the platform modules. The exact list visible to a session depends on its login type and the access model.

GroupExamples
AuthProxysessions, login_log, user_keys, get_usr_apps, get_app_info, list_app_access, revoke_app_access, close_sessions
Wallet (TrexWallet)wallet_get_info, wallet_get_balance, wallet_get_history_transactions, wallet_get_tx_info, wallet_list_addresses, wallet_internal_transfer, wallet_create_withdraw
Marketsmarket_get_rates, market_swap_info, market_get_trade_info, market_create_swap, get_candles, assets, tokens_networks
Trading roomsroom_list, room_create, room_close, room_join
Ordersmarket_get_orders, market_create_order
Payment orders (merchant)payorder_info, payorder_terminals, payorder_payments, payorder_payment, payorder_create
Payment gate (capability)payment_order_info, payment_terminals, payment_pay_card, payment_pay_p2p, payment_cancel_p2p, payment_pay_crypto, payment_confirm_crypto, payment_cancel_crypto
Chat (session mode)chat_list, chat_info, chat_members, chat_messages, chat_unread, chat_pinned, chat_files, chat_file_info, chat_read_text_file, chat_friends, chat_invites, chat_send_message, chat_mark_read, chat_create, chat_create_private, chat_edit_message, chat_soft_delete_message, chat_hard_delete_message, chat_archive_chat, chat_leave_chat, chat_hard_delete_chat, chat_add_member
Chat (anonymous capability)chat_anon_* family

The authoritative list (with parameters) is what tools/list returns at runtime — it always reflects the deployed version.

Custom tools (Core projects)

Customer Core projects extend the catalogue through appsettings.json:

{
"Config": {
"McpTools": {
"core_get_orders": {
"endpoint": "http://core:80/api/v1/orders/list",
"method": "GET",
"description": "List orders for the current user",
"appScope": false,
"restricted": ["McpBasic"],
"inputSchema": {
"type": "object",
"properties": {
"limit": { "type": "integer", "description": "Max rows (default 50)" }
}
}
}
},
"McpResources": {
"itbuild://core/profile": {
"endpoint": "http://core:80/api/v1/profile",
"name": "Customer profile",
"description": "Profile snapshot for the authenticated user",
"mimeType": "application/json"
}
}
}
}

Fields:

FieldPurpose
endpointBackend URL the gateway forwards the call to. AuthProxy injects X-Crm, X-Scopes, X-KeyType, X-Project and (for McpApp) X-MCP-App-Id automatically.
methodGET or POST. For GET all arguments become query parameters; for POST they form the JSON body.
descriptionFree-form description shown to MCP clients.
inputSchemaJSON Schema for arguments (rendered to clients via tools/list).
restrictedLogin types denied for this tool (e.g. ["McpBasic"]). See Access Model.
appScopetrue to allow McpApp sessions to call the tool. Default false.
queryParamsNames of arguments that should travel as query string instead of JSON body for POST requests.
bodyRootPropertyName of an argument whose value replaces the entire request body (useful when the backend expects a raw value).
capabilityModuleIf set ("chat" or "wallet") the tool is only callable in Capability mode.
capabilityCodeBodyPropertyWhen in capability mode, automatically inject the capability code into this body field.

Resources (McpResources) accept the same endpoint, description, mimeType, restricted, appScope, sessionScope, capabilityModule fields plus an optional fixed queryString.

Capability mode

For public, code-based flows (anonymous chat invites, public payment-order pages) AuthProxy supports sessionless MCP calls — there is no mcp_login.

Send the capability code with the request:

  • Query string: POST /mcp?code=<capability_code>
  • Header: X-MCP-Code: <capability_code>
  • JSON-RPC argument: params.arguments.code or params.code

For chat-family codes you must also identify the device:

  • Header: X-Device-Guid: <uuid>
  • Query string: device_guid=<uuid>
  • JSON-RPC argument: params.arguments.device_guid

Example — list anonymous chat tools and read a message:

curl -X POST "https://authproxy.example.com/mcp?code=<invite_code>" \
-H "Content-Type: application/json" \
-H "X-Device-Guid: <uuid>" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }'

Capability mode exposes only the tools/resources whose capabilityModule matches the resolved code family (chat for invite codes, wallet for payment-order codes). Session-scoped tools are not visible.

Rate limits

mcp_login and /mcp calls are rate-limited per public key. The active quota is configured in admin settings (McpApiRPSLimit).

When the quota is exhausted the server returns HTTP 429 with:

X-RateLimit: s=m;l=10;r=0;t=3
Retry-After: 3

X-RateLimit parts: s = scope (m for MCP), l = window limit, r = remaining, t = seconds until reset. Wait Retry-After seconds before retrying.

Security checklist for AI agent integrations

  1. Generate the Ed25519 key pair on the agent host. Never share the private key.
  2. Use McpAccess-flagged keys exclusively for MCP — keep them separate from browser/PWA keys.
  3. Prefer McpVerified over McpBasic for any flow that mutates state; McpBasic is intentionally limited.
  4. Use McpApp for third-party AI integrations so that the operator can revoke a single user_app without disturbing other agents.
  5. Treat sessionId like any other bearer token. Rotate by calling mcp_login again or close_sessions.
  6. Keep clocks synchronised — McpVerified rejects timestamps outside ±5 minutes.
  7. Pay attention to isError: true on tool responses; the JSON-RPC envelope is result even when the underlying call failed.