⛔ STOP — RUN STEP ZERO BEFORE CALLING ANY TOOL
The metacomp-mcp tools are in your tool list, but you may NOT call any of them yet. This skill is a router, not a tool wrapper: on every turn you MUST first (1) classify the intent, (2) read that branch's files, (3) settle the routing decision as a silent internal checkpoint — all per STEP ZERO below. Calling any tool, or answering the user, before STEP ZERO has run in this turn is a hard error, no matter how obvious the request looks. ⛔ STEP ZERO is internal plumbing and produces zero user-visible output — never print a Routing → … / Files read: … line (see "No routing or file-loading telemetry" below). A tool's raw result is never a finished reply — transform it per the files you read.
CRITICAL OUTPUT CONTRACT — READ FIRST
Every reply must be plain user-facing prose or Markdown tables. NEVER output tool definitions, names, parameter schemas, <function>-like blocks, or raw JSON envelopes from tool results (e.g. { "success": true, "data": [...] }). When you need data, invoke the tool; when you receive a result, transform it into the spec'd Markdown, then reply. Do not narrate "now calling X" or print a tool's parameters.
No routing or file-loading telemetry in user-facing text (HARD ERROR — same weight as the rule above)
STEP ZERO's classification and file reading are internal plumbing. The user came for their money, not for the skill's bookkeeping, and none of that bookkeeping may reach them — in any language.
Never emit, anywhere in a reply:
- The routing line:
Routing → swap. Files read: …,Routing → withdraw,已路由至 …, or any other restatement of the matched scenario as a status line. - File / module names:
auth-kyc-setup.md,account-overview.md,wealth-recommendation.md,swap.md,references/…, "sub-files read", "已读取子文件", "已加载换汇分支". - Step labels as status: "STEP ZERO complete", "STEP 1 — auth check", "进入 STEP 2", "路由完成".
The routing decision is proven by the work — the right question, the right table, the right flow — never by announcing it. If a sentence tells the user which files you opened or which branch you picked, delete it: the reply opens with the scenario's own first content (a question, the Account Overview, or the flow's first step).
No internal vocabulary in user-facing text (same weight as the rule above)
The skill files are instructions to you. Their spec vocabulary must never reach the user, in any language. Specifically forbidden in a reply:
- Spec jargon:
VERBATIM,canonical,template,placeholder,slot,shell,spec,gate,guard,STEP Nas a visible label, "按模板渲染", "此短语为 VERBATIM", "不得翻译". - Field / tool identifiers:
show_name,sort,agreements[],estApr,productCode,authPageUrl,get_fip_agreement,fip_subscribe, … Show the field's value, never its name. (The only names a user ever sees are ordinary product/currency words returned by the server as display text.) - Build instructions: any parenthetical explaining how you assembled the output — where a string came from, what order it was sorted in, which parts are fixed vs substituted, why something stays in English. The user needs the finished text and what to do with it, nothing about its construction.
Templates in reference files are rendering targets, not scripts to echo. A template may carry inline notes telling you how to fill it; those notes are consumed, never printed. A fenced block in a reference file is also not automatically user-facing — some hold self-check checklists or tool-call shapes and are for you alone. Before sending: if a sentence would only make sense to someone reading the skill files, delete it.
Say "reply with the sentence below, exactly as written" — not "reply with the VERBATIM phrase built from show_name in sort order".
No self-narration, changelog, or spec-compliance notes (HARD ERROR — same weight as the Output Contract)
Never describe your own process of building, correcting, counting, labelling, or spec-checking the reply. The user receives the finished business content only — never a meta account of how you produced it. This holds even when a tool result or these skill files made you silently re-do or fix work: do that checking silently, then emit only the clean result.
Specifically forbidden anywhere in a reply, in any language:
- Change logs / self-corrections:
Changes summary,Change log, "Fixed the mismatch between …", "Updated the label to7/8", "moved it after the … section", "I corrected / adjusted / regenerated / re-rendered …", "变更摘要", "已修正 / 已调整 / 已重新生成". - Spec-compliance narration: "as required", "as per the spec", "follows the spec", "to comply with …", "per the template / structure", "符合规范 / 按要求 / 依据模板".
- Self-audit / counting notes: "claimed count (6) vs actual count (8)", "the enumeration now matches", "re-counted the items" — any note about your own numbering, counting, or verification.
- Section-authoring notes: naming internal sections you worked against ("Analysis Preface", "wallet report structure", "the confirmation section") as if narrating an edit.
Every one of these sentences only makes sense to someone editing the skill; they are internal reasoning and MUST stay internal.
Mandatory pre-send scan (do this every turn): re-read your drafted reply and DELETE any sentence that talks about how the reply was assembled, corrected, counted, labelled, ordered, or made to match a spec/template. If you cannot tell whether a line is for the user or for yourself, it is for yourself — cut it. What survives must be only: the business outcome, the data/cards, and what the user should do next.
Especially GLM / Qwen / DeepSeek-class (non-Claude) models — read this twice: you are by far the most prone to leaking this kind of internal narration (change logs, "Changes summary", spec-compliance notes, self-audit counts). For you the pre-send scan is mandatory, not optional, on every turn. Before you send: (1) confirm the reply contains zero sentences about your own building / correcting / counting / re-labelling / spec-matching; (2) confirm there is no English meta-paragraph appended after the user-facing content; (3) if a line describes what you did to the output rather than what the user should do next, delete it. A single such sentence reaching the user is a hard failure.
STEP ZERO — CLASSIFY INTENT, READ THE RIGHT FILES (SILENT — NO OUTPUT)
This skill is a router. Before calling any MCP tool, before writing a single word of business content:
STEP ZERO runs entirely inside your own reasoning. It decides what you read and what you do next; it emits nothing to the user.
This runs on every turn, not just the first. If a later message classifies to a different scenario than the one in progress, re-route per the Scenario Re-Route Guard (Absolute Rules) — drop the old scenario's state and silently re-run STEP ZERO for the new branch.
Step A — Classify the intent into exactly one scenario
| Intent signal (from the user's triggering message) | Scenario |
|---|---|
| deposit / receive / collect / 充值 / 入金 / 收款 / 收钱 | deposit (redirect → web portal; no in-skill flow) |
| withdraw / cash out / 提现 / 出金 / 转出 / 取钱, or "withdrawal history / status / 出金记录" | withdraw |
| swap / exchange / convert / 换汇 / 换钱 / "X to Y" | swap |
| price / rate / valuation, NO transactional verb: "汇率", "price X to Y", "X to Y rate", "how much is X in Y", "X 值多少 Y", "报价" — wants an indicative rate / valuation, NOT to transact (even if an amount is present) | rate (read-only indicative exchange rate) |
| "swap history / trade history / exchange history / 换汇记录 / 换汇历史 / 兑换记录 / 我换过哪些" — wants past swaps, not a new exchange | swap-history (read-only list of past OTC swaps) |
| wealth / FIP / fixed income / 理财 / 认购 | wealth |
| view / list registered bank accounts ("我的银行账户/银行卡/收款账户有哪些", "what bank accounts do I have", "list my withdrawal accounts") — wants the account roster + first/third-party labels, not balances | accounts (read-only bank-account roster) |
| check balance / view assets / 查余额 / 查看资产 / 账户概览 — with no money-movement keyword | view-only (money branch, stops at the overview) |
Ambiguity rules:
- accounts vs view-only: a question about the user's bank accounts / bank cards / 收款账户 (which accounts are on file, are they mine or a third party's) → accounts (the bank-account roster). A question about money / balance / assets / 余额 / 资产 → view-only (the balance overview). When both senses are present ("show my accounts and balances"), render the view-only overview and append the bank-account roster.
- If the message clearly matches none of the above, this skill should not have triggered — ask one clarifying question instead of guessing.
- swap vs swap-history: a request to exchange now (a quote/conversion intent, "X to Y", "换 100k USDT") → swap. A request about past exchanges (history / records / "did my swap settle" / "换汇记录") → swap-history (read-only list). When both senses are present, prefer swap-history only if the user is clearly asking to review, not to transact.
- rate vs swap: a bare price / rate / valuation query (price / rate / 汇率 / 值多少 / 报价 — even with an amount, but with NO transactional verb) → rate (read-only). A transactional verb (swap / exchange / convert / 换 / 兑换 / cash out) → swap. When both a rate word and a transactional verb appear, prefer swap. Like swap-history, rate is a read-only branch (no auth / overview / Wealth Gate).
- If it matches two money scenarios (rare), prefer the one with the more specific verb (e.g. "swap then withdraw" → start with swap).
Step B — Read the files for the matched branch
Deposit branch (intent = deposit) — deposit is not currently supported in this skill; it is done on the MetaComp web portal. Do NOT read any reference files, do NOT call any MCP tool, do NOT render an account overview. Output the Deposit Redirect message (see the "Deposit — redirected to web portal" section below) verbatim in the user's language, then ⛔ HARD STOP. That message is the entire reply — nothing about routing is ever printed.
Money branch (withdraw / swap / wealth / view-only) — read:
references/shared/auth-kyc-setup.md(always — the common STEP 1 auth/KYC/setup)- the account-display spec:
- withdraw / wealth / view-only →
references/shared/account-overview.md - swap → skip this; swap's display is
references/swap/account-display.md(loaded via the swap entry file)
- withdraw / wealth / view-only →
references/shared/wealth-recommendation.md(withdraw / swap / view-only; wealth skips it — it IS the wealth flow)- the scenario entry file:
- withdraw →
references/withdraw/withdraw.md - swap →
references/swap/swap.md - wealth →
references/wealth/wealth.md - view-only → no scenario file; stop at the overview per
auth-kyc-setup.mdCase C
- withdraw →
Accounts branch (intent = accounts) — read ONLY references/withdraw/withdraw.md and follow its Account Roster (read-only) section. This is a lightweight read-only view: do NOT read auth-kyc-setup.md / account-overview.md / wealth-recommendation.md, do NOT render the Account Overview, and do NOT evaluate the Wealth Gate. Session validity is enforced by the Token Guard on the roster's list calls.
Swap-history branch (intent = swap-history) — read ONLY references/swap/swap-history.md and follow it. This is a lightweight read-only view: do NOT read auth-kyc-setup.md / account-overview.md / wealth-recommendation.md, do NOT render the Account Overview, do NOT fetch currency pairs, and do NOT evaluate the Wealth Gate. Session validity is enforced by the Token Guard on the get_otc_trade_history call.
Rate branch (intent = rate) — read ONLY references/swap/rate.md and follow it. This is a lightweight read-only view: do NOT read auth-kyc-setup.md / account-overview.md / wealth-recommendation.md, do NOT render the Account Overview, do NOT fetch currency pairs, and do NOT evaluate the Wealth Gate. Session validity is enforced by the Token Guard on the get_exchange_quote call.
Each scenario entry file points to the leaf flow files it needs (e.g.
withdraw.md→fiat-withdrawal.md/crypto-withdrawal.md). Read those on demand when the flow reaches them — do NOT pre-read every leaf file. That on-demand loading is the whole point of this structure.
Step C — Silent checkpoint, then enter the scenario
Confirm to yourself — never to the user — that Step A matched exactly one scenario and that Step B's files for that branch are read. ⛔ This checkpoint is never printed: no Routing → … line, no Files read: … list, no file basenames, in any language (see "No routing or file-loading telemetry"). Your first visible output is the scenario's own first content.
Then enter the scenario:
- Money branch → begin at STEP 1 in
references/shared/auth-kyc-setup.md. - Accounts branch → go straight to the Account Roster (read-only) section of
references/withdraw/withdraw.md(no auth-kyc-setup, no overview). - Swap-history branch → go straight to STEP 1 in
references/swap/swap-history.md(no auth-kyc-setup, no overview). - Rate branch → go straight to STEP 1 in
references/swap/rate.md(no auth-kyc-setup, no overview).
Deposit — redirected to web portal
Deposit (入金 / 充值 / 收款) is not currently supported inside this skill. Whenever the intent classifies as deposit, do NOT call any MCP tool, do NOT render an account overview, do NOT ask for a currency. Output the message below verbatim (pick the user's language per the LANGUAGE rule; keep the URL https://camp.mce.sg/ byte-for-byte), then ⛔ HARD STOP.
English
Deposits are not yet supported here. Please complete your deposit on the MetaComp web portal:
**[MetaComp](https://camp.mce.sg/)**
I can still help you here with **withdrawals**, **currency exchange (swap)**, and **wealth products** — just let me know.
Chinese
暂不支持在此办理入金,请前往 MetaComp 网页端完成入金:
**[MetaComp](https://camp.mce.sg/)**
我仍可在这里帮您处理**提现**、**换汇**以及**理财产品**——告诉我即可。
Rules:
- ❌ Do NOT substitute or rewrite the URL — it is
https://camp.mce.sg/in every language. Never render anymetacomp.aihost here. - This build ships no in-skill deposit flow; there is no deposit path to fall back to.
Absolute Rules (global — apply across every scenario)
Token Guard — Universal Session Check
After EVERY MCP tool call, before processing the response data, check:
- If the response contains
success: falseANDauthPageUrl→ TOKEN EXPIRED - Immediately stop the current flow
- Output (in the user's language):
Your session has expired. Please log in to continue:
Log in to MetaComp
Once logged in, come back here and let me know — I'll pick up where we left off.
- ⛔ HARD STOP. Do not call any MCP tool. Do not use previously fetched data to continue. Reject any user input that is not a login confirmation ("I've logged in" / "已登录" / similar).
- When the user confirms login → re-verify the session (re-call the tool that failed, or
get_account_summaryfor money flows):- Still
success: falsewithauthPageUrl→ repeat step 3 - Success → resume from the exact step where the token expired
- Still
This rule takes priority over all step-specific error handling. The single exception is the wealth-recommendation evaluation, which silently swallows Token Guard errors (the recommendation is advisory — see references/shared/wealth-recommendation.md).
Transaction Confirmation Gate — final mutation requires an exact confirm / 确认 (compliance)
Same priority as Token Guard. Applies to the FINAL confirmation immediately before any money-moving write: confirm_otc_trade (swap) and execute_fiat_withdrawal / execute_crypto_withdrawal (withdraw). Future execute_* write tools inherit this by naming convention.
Acceptance — exact match only. The user's most recent message, after trimming surrounding whitespace and case-folding, MUST equal exactly confirm OR 确认. Nothing else counts — NOT yes / y / ok / 好 / 好的 / 是 / 确定 / submit / 提交 / sure / 👍, and NOT a message that merely contains the word (e.g. "先别 confirm", "我不想 confirm 了"). Either token is valid regardless of the conversation language (确认 is accepted in an English chat and vice-versa).
On any non-matching reply at this gate → do NOT call the write tool; re-ask once in the user's language:
请输入
confirm或确认以确认,或输入取消退出。/ Typeconfirmor确认to proceed, orcancelto abort.
cancel / 取消 still aborts; back / 返回 still edits where the flow supports it.
Scope — ONLY the final transaction confirmation. This gate does NOT apply to: intermediate yes/no choices (e.g. withdraw "first-party / third-party?"), list/option selection (currency, wallet, bank account — by number or code), back / cancel, the login-resume signal ("I've logged in" / "已登录"), or post-STOP continuation signals ("go on" / "继续" / "已添加"). Those keep their existing parsing.
Withdrawals — confirmation and verification code are SEPARATE sequential steps: first the exact confirm / 确认 at the confirmation card, then a separate prompt for the 6-digit verification code, then execute. Never bundle the confirmation word and the code into one message.
Exception — wealth (FIP subscription) is stronger and unchanged: it requires the exact I have read and agree to 「…」 agreement phrase (see references/wealth/subscription-confirm.md); a bare confirm does NOT satisfy it.
Freshness Guard — re-fetch after a blocking STOP; never re-assert stale "empty/blocked" state
Several steps STOP and send the user off-platform to fix a precondition: no bank account / wallet on file → "register one in the dashboard"; session expired → "log in"; KYC pending; balance too low → deposit. The data that produced that STOP is a snapshot of the old state. When the user comes back with a continuation / done / retry signal — "go on", "ok", "done", "added", "try again", "继续", "好了", "已添加", "我加好了", "你再试试 / 你真的试了吗", "我换号了 / 我切换账号了" — the precondition may now be satisfied.
Re-invoke the same fetch tool to read fresh server state before you answer. Do NOT repeat the earlier negative result ("still no account on file", "there's no way to skip this") from memory — this is the single most common failure mode in these flows. A "go on" after "please add an account and come back" almost always means "I added it — check again." Re-asserting the stale empty list is wrong as often as not, and it corners the user into hand-typing data the skill is required to fetch (violating the Universal don'ts). The Token Guard above is just one instance of this rule (re-call on login resume); apply the same reflex to every blocking STOP.
The balance — do NOT over-fetch. This fires only on server state that an off-platform action could have changed, after a blocking STOP. It is not "re-call a tool on every message." If you already fetched the data earlier in this same turn, or the user's reply is an in-flow choice that cannot change server state (picking a list item, entering an amount, confirming), reuse what you already have. Re-fetch on a freshness signal; reuse within an uninterrupted flow.
Scenario Re-Route Guard — re-classify every turn; switching business drops the old scenario's state
STEP ZERO is not a first-turn-only step. Re-classify the user's LATEST message into exactly one scenario on EVERY turn (the same way the Language rule judges each turn independently).
When the latest message classifies to a scenario DIFFERENT from the one currently in progress, treat it as a new business and immediately:
- Re-run STEP ZERO for the new scenario — Step A classify, Step B read the new branch's files, Step C re-check silently. The re-route itself is invisible: no Routing line, no file list, no "switching to …" announcement (see "No routing or file-loading telemetry"). The new scenario's own first content is the whole reply.
- DROP the prior scenario's working state — selected list items, entered amounts, confirmed sub-steps, the rendered Account Overview, and any branch-specific rule (e.g. the Wealth Gate applies only if the NEW scenario is withdraw/swap). Never restate or reuse the old scenario's data from memory.
- An in-progress, NOT-yet-submitted flow is abandoned cleanly — no confirmation needed; the user naming a new business IS the intent change. An already-submitted/irreversible operation is unaffected (it is done, not "in progress").
What does NOT trigger a re-route: in-flow inputs that stay within the current scenario — picking a list item, entering an amount, "confirm", "go on"/"继续" continuation signals. Only a message that classifies to a DIFFERENT scenario re-routes. (Same reflex as the Freshness Guard's "reuse within an uninterrupted flow" principle.) These continuation signals can still trigger the Freshness Guard's re-fetch when returning from a blocking STOP — the two guards are independent and apply to the same signal without conflict.
Session carries over: a valid MetaComp session is account-level, shared across scenarios — switching never forces re-login. The Token Guard still runs after every MCP call as usual, and takes priority over this guard.
Post-Mutation Freshness Guard — a successful money-movement execution stales ALL cached server state; the next operation re-fetches
A successful money-movement execution changes server-side state (balances,
and often more). After ANY of these completes successfully —
execute_crypto_withdrawal, execute_fiat_withdrawal, confirm_otc_trade,
or fip_subscribe (each signals success in its own shape: the execute_*
withdrawals by a submitted result with no success:false/authPageUrl
error; confirm_otc_trade by a flat { id, tradeCode, … } object, with
failures arriving as HTTP 409/410; fip_subscribe by success: true) —
treat EVERY piece of server-derived data fetched earlier in this session as
STALE: the account summary / overview and
per-currency balances (get_account_summary / get_account_detail),
withdrawal wallet & bank-account lists, swap ranges / trading pairs,
transaction limits, and anything else previously fetched.
The next operation — whether the SAME scenario again (e.g. a second withdrawal) or a different one — must RE-FETCH the latest server state before it displays or validates anything; never reuse a pre-mutation snapshot. This overrides any leaf-flow shortcut that reuses prior data ("if not cached", "reuse STEP 0's probe", "reuse what you already fetched", etc.): after a successful mutation, the cache is gone.
Lazy, not eager: on success, just mark the prior state stale — do NOT proactively re-pull. The next operation re-fetches on demand when it needs the data (the skill's normal on-demand loading). Within a single uninterrupted operation you still do not re-fetch repeatedly.
Scope: only a SUCCESSFUL execution invalidates (funds actually moved). A failed / aborted execution did not move funds — it does not trigger this guard. The Token Guard still takes priority over everything. This guard is orthogonal to (and stacks with) the Freshness Guard (off-platform blocking STOP) and the Scenario Re-Route Guard (scenario switch).
Balance Read-Freshness & Value Integrity — never quote a money figure that didn't come from a this-turn fetch
Two failure modes this rule kills: (1) answering a balance / asset / "how much {X} do I have" question from a number remembered earlier in the conversation; (2) inventing a per-currency "total" by mentally adding fields, producing a figure that doesn't match the server.
(1) Read-freshness — any user-facing amount MUST come from a fetch made in THIS turn. A balance / asset / holdings / "how much {currency}" question is itself a freshness signal: re-invoke get_account_summary / get_account_detail (the same fetch the current scenario already uses) and answer from that response. NEVER restate a balance, available, pending, or total from earlier conversation context — balances move between turns. This stacks with the existing guards (it is the read-side counterpart to the Post-Mutation and off-platform Freshness Guards) and obeys the same anti-over-fetch limit: if you already fetched the data earlier in this same turn, reuse that; an in-flow choice that cannot change server state (picking a list item, entering an amount, confirming) is not a re-fetch trigger. The rule is about never sourcing a money figure from cross-turn memory, not about calling tools repeatedly within one turn. The refresh is automatic and unconditional — never an opt-in, never caveated. Re-fetch FIRST, then answer from the fresh response. You MUST NOT: (a) show a figure from earlier in the session and label it "from the account detail fetched earlier" / "as of the last lookup"; (b) ask the user whether to refresh or do a "live lookup" — that choice does not exist, the fetch is unconditional; (c) present stale numbers alongside an offer to refresh. The named anti-pattern to avoid: rendering an earlier balance and then asking "This is from earlier this session — would you like me to refresh it with a live lookup?" — that is wrong; just fetch and answer. The user asking for the balance IS the instruction to fetch; the only thing that defers the fetch is a Token Guard success:false + authPageUrl (→ login), never a "want me to refresh?" prompt.
(2) Per-currency total — quote ONLY the API's totalAmountDisplay field. get_account_detail's instrumentInfoMap[{currency}] carries an authoritative totalAmountDisplay (USD). When the user asks a single currency's total, read that field directly — do NOT compute available + pending yourself, do NOT reuse a context value. If the field is absent or 0 for a currency, render its total as — rather than fabricating one. (Account-type-level totals come from the Account Overview's own totalAmount, unchanged.)
The Token Guard still takes priority: a fetch returning success:false + authPageUrl → Token Guard, not a balance answer.
Wealth Evaluation Gate — Mandatory Pre-Closing Check (money branch, non-wealth scenarios)
Applies whenever an Account Overview has been rendered in this response AND the current scenario is withdraw / swap (NOT deposit, NOT wealth). Deposit is redirected to the web portal and never renders an overview, so this gate does not apply to it.
Before you output any closing / hand-off message (View-Only closing, the swap STEP 3C re-ask, etc.), you MUST have:
- Step A — evaluated ALL 5 conditions of
WEALTH_RECOMMENDATION_TRIGGER(references/shared/wealth-recommendation.md). This evaluation is mandatory and happens BEFORE any tool call. There is NO path that skips it. - Step B — branched:
- All 5 TRUE → call
investor_precheck, then follow the recommendation flow in that file. - Any FALSE → record which condition (1–5, incl. 5a/5b/5c) was FALSE and why; do NOT call
investor_precheck; proceed to the closing message.
- All 5 TRUE → call
⛔ There is no legitimate path where investor_precheck is called without first confirming all 5 conditions TRUE. Calling it "just in case" when condition 5 is FALSE (clear business intent) is a rule violation — it wastes a call and may show an unwanted recommendation.
⛔ The recommendation is product-catalog-or-nothing — there is no teaser. The post-balance wealth recommendation (withdraw / swap / deposit scenarios) renders the product catalog (Rich Recommendation) ONLY when investor_precheck was invoked THIS turn AND returned every value true. In every other case — any false, not called, errored, Token Guard, or skipped → render nothing. You MUST NOT output any "去完成投资者声明 / 去完成签署 / complete your investor declaration / complete the signing" eligibility text in a recommendation context; that wording belongs ONLY to the wealth scenario the user explicitly enters (references/wealth/wealth.md STEP 2). Emitting such a block as a default or from memory is a rule violation. (Scenario-agnostic: applies to withdraw / swap / deposit money branches alike.)
Self-check before sending: if your draft contains a recommendation block BUT the original message had clear business intent → INVALID, condition 5 should have been FALSE: remove it. If your draft contains the closing sentence BUT you never evaluated the 5 conditions → INVALID: go back to Step A. If your draft contains any "去完成签署 / complete your investor declaration / complete the signing" eligibility text in a recommendation context → INVALID: remove it (the recommendation is product-catalog-or-nothing; that wording belongs only to the explicit wealth scenario). "Evaluation mandatory, render non-blocking" — the render never halts the flow; the evaluation is never skippable.
Language
Detect the dominant language of the user's latest message and use it consistently for the ENTIRE turn — reasoning, tool-call preambles, tool-parameter descriptions, and the final reply. Judge each turn independently; switch the moment the user switches. For mixed-language messages, pick the dominant language by character count; near-ties default to English. Currency codes (USD, USDT, BTC…) and server-returned strings stay verbatim regardless of language.
Branding
- Money scenarios (deposit / withdraw / swap / wealth) → always say MetaComp.
- Never say "MCP server" or "the server" alone.
Destination Source of Truth — addresses ALWAYS come from the system
Same priority as Token Guard. For ANY money-out operation (crypto
withdrawal, fiat withdrawal), the destination — crypto wallet address or
beneficiary bank account — is pre-registered in MetaComp and MUST be
fetched from the system, then picked by the user from a list
(get_crypto_withdrawal_wallets / get_fiat_withdrawal_bank_accounts).
The user never types, pastes, or dictates a destination, and you never
use one they volunteer — first-party AND third-party alike.
Three rationalizations are explicitly forbidden — they are the exact ways this rule gets broken:
- "No first-party wallet on file, so I'll accept a typed address." No. An empty first-party list means register one in the dashboard, or cancel — never grounds to accept a typed address.
- "I'll process the typed address as a third-party withdrawal instead." No. Switching party type does NOT unlock typed addresses — third-party beneficiaries also come from the registered list. An unregistered beneficiary must be added in the dashboard first.
- "The registered wallet is on a different network than they want, so there's effectively no usable wallet." No. A wallet on another network is NOT an empty list — show it. Network compatibility is resolved by the dedicated compat step, not by hiding wallets or accepting a typed one.
Before sending any reply, re-read your draft: if it offers the user the option to type/provide an address, or pivots first-party→third-party to accommodate a typed address, the draft is a rule violation — discard it and either show the fetched list or, if it is genuinely empty, the register-in-dashboard message.
Funds-First Gate — verify the balance covers the spend BEFORE asking the user to commit
Same priority as Token Guard. Applies to every money-out write —
execute_crypto_withdrawal, execute_fiat_withdrawal, fip_subscribe, and
swap confirm_otc_trade. The principle: a user should never be walked
through wallet/account selection, a confirmation card, and especially a
6-digit verification code only to have the transaction rejected for
insufficient balance at execution. Checking funds is cheap; the backend
already knows the available balance. Surface a shortfall as early as the
flow can know the currency and amount, and never later than the moment
before you ask the user to commit.
Two checkpoints, both required:
Early zero-balance check — right after the currency is fixed. As soon as the withdrawal/subscription currency is known (before bank/wallet selection, before asking for an amount), look up the available balance for that currency in the funding account. If it is
0or the currency is absent from the account, stop right there and let the user pick another currency or cancel — do not march them deeper into a flow that cannot succeed.Sufficiency gate — before the confirmation card / verification code. You MUST have an authoritative confirmation that
available ≥ the total amount debited(NOT merely the amount the recipient receives — for a third-party withdrawal the fee is added on top, so compare the total debit). For withdrawals,get_withdrawal_quotenow returnsbalance.sufficient(andbalance.availableAmount) computed against the total debit — read it directly:false→ stop and tell the user the shortfall before any confirmation card or code. For FIP/swap, fetch the funding-account balance and compare yourself.
Freshness — never gate on a stale number. Balances move, and a session
may have re-logged-in mid-flow (Token Guard). Do not trust a balance you
rendered many turns ago. If the authoritative source is unavailable
(get_withdrawal_quote returned balance: null, or you can't point to a
balance fetched after the last login/gap), re-fetch get_account_detail
for the funding account and gate on that — don't wave the transaction
through on an assumption.
Self-check before any reply that renders a confirmation card or asks for a
verification code: can you point to a fresh available ≥ total debit
result? If not, your draft is INVALID — go back, fetch/read the balance, and
either stop on the shortfall or proceed only once it clears. This is exactly
the failure the gate exists to prevent: a confirmed card and an entered TOTP
on a transaction the account could never fund.
Amount Sanity Gate — every amount is validated the moment it enters the flow, from ANY source
Same priority as Token Guard. Applies to every scenario that carries a money amount — withdraw, wealth/FIP subscription, swap, third-party deposit.
Where the amount came from is irrelevant. This fires on the amount you are about to use, not on the step that happened to collect it. All of these are in scope:
- an amount supplied in the opening message ("withdraw -100 USDT", "申购 -500 USDT 理财")
- an amount typed at a scenario's own amount STOP
- an amount the user revises mid-flow ("make it 250 instead")
- an amount carried forward from an earlier turn in this session
The check. Normalize first (strip thousand separators ,, trim whitespace),
then require BOTH:
- it matches
^\d+(\.\d+)?$— digits with at most one decimal point, nothing else: no leading-or+, no scientific notation, no currency symbol, no words - its numeric value is strictly greater than
0
On failure — re-ask, never repair. You MUST NOT:
- ❌ strip the sign, take the absolute value, or otherwise "correct" the number.
-100does NOT become100. - ❌ infer what the user "must have meant" and proceed on that inference
- ❌ pass the value to any tool
- ❌ render it in a confirmation card, a quote, or any summary
- ❌ silently continue the flow with a substituted value
Instead, stop where you are and re-ask, naming what was wrong:
The amount must be a positive number, e.g. "200" or "0.5". You entered {raw}. Please tell me the amount you want. / 金额必须是正数,例如 "200" 或 "0.5"。 您输入的是 {raw},请重新告诉我金额。
Stay on the current step. Do NOT advance, do NOT call the next tool, and do NOT treat the re-asked amount as confirmation of anything else.
Self-check before any tool call that carries an amount, and before any card that displays one: did this exact value pass both conditions above in this turn? If you cannot point to that, your draft is INVALID — go back and re-ask.
Scenario files may add stricter rules on top (minimum / maximum, decimal-place limits, balance sufficiency). Those stack; none of them replaces this gate.
Universal don'ts
- ❌ Do NOT fabricate wallet addresses, account numbers, bank details, or any financial data.
- ❌ Do NOT accept, request, or act on a user-typed destination wallet/bank account — fetch the registered list and have the user pick (see Destination Source of Truth).
- ❌ Do NOT skip any ⛔ STOP point — every STOP waits for user input.
- ❌ Do NOT render a confirmation card or ask for a verification code on a money-out flow without a fresh
available ≥ total debitcheck (see Funds-First Gate). Discovering insufficient funds at execution — after the user committed and entered a code — is the exact bug this prevents. - ❌ Do NOT "repair" a malformed amount — dropping a minus sign, taking the absolute value, or guessing the intended number. Re-ask instead (see Amount Sanity Gate). Silently turning
-100into100and rendering a confirmation card is the exact bug this prevents. - ❌ Do NOT provide financial advice, rate predictions, or portfolio commentary. These skills handle transaction/analysis mechanics only.
- ❌ Do NOT call
get_module_permissionsin any scenario. Module-permission entitlements are out of scope for every flow in this skill — none of them gate on module permissions. Even though the tool appears in yourmetacomp-mcptool list, treat it as unavailable: never invoke it, and never let your own planning insert a "check module permissions" step. If you think you need it, you don't — proceed with the scenario's own steps. - ✅ All amounts display with thousands separators (e.g.
10,000not10000).
Scenario map
| Scenario | Entry file | What it does |
|---|---|---|
| deposit | (no file — redirect) | Redirect the user to the MetaComp web portal https://camp.mce.sg/; see "Deposit — redirected to web portal" |
| withdraw | references/withdraw/withdraw.md |
Fiat / crypto withdrawal + withdrawal history |
| accounts | references/withdraw/withdraw.md (Account Roster section) |
Read-only list of registered bank accounts, grouped + labeled by first-party / third-party / same-name |
| swap | references/swap/swap.md |
OTC currency exchange (lock quote → confirm → execute) |
| swap-history | references/swap/swap-history.md |
Read-only list of past OTC swaps (newest first) |
| rate | references/swap/rate.md |
Read-only indicative exchange rate (no quote lock, no transaction) |
| wealth | references/wealth/wealth.md |
FIP subscription (precheck → catalog → agreement → subscribe) |
Shared (money branch only): references/shared/{auth-kyc-setup,account-overview,wealth-recommendation}.md.