Fuul MCP Server
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| FUUL_MCP_DEBUG | No | Set to 1 or true for debug logging | false |
| FUUL_API_BASE_URL | No | API origin (no trailing slash). Use https://api.stg.fuul.xyz for staging. | https://api.fuul.xyz |
| FUUL_OAUTH_CLIENT_ID | No | OAuth client ID | fuul-agent |
| FUUL_OAUTH_REDIRECT_URI | No | OAuth callback URL | http://127.0.0.1:8765/callback |
| FUUL_MCP_PROJECT_API_KEY | No | Project API key (Bearer) for public API tools: managed affiliates and Events | |
| FUUL_MCP_TOOL_TIMEOUT_MS | No | Per-tool timeout in milliseconds | 90000 |
Capabilities
Features and capabilities supported by this server
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| pingA | Health check: returns "pong" if the MCP process is running. No API calls. Example: invoke with empty input {}. |
| whoamiA | Returns the current Fuul dashboard user as JSON from GET /api/v1/auth/user. Requires prior CLI login (tokens in ~/.fuul/tokens.json). Example: {} after |
| list_chainsA | Lists supported blockchain chains from GET /public-api/v1/metadata/chains. Uses server metadata (not a hardcoded catalog); responses are cached with ETag/Cache-Control. Each chain includes snake_case fields such as chain_id, is_testnet, optional svm_network and webapp_capabilities, and can_be_used_for_payouts (boolean: true where Fuul reward/payout infra is deployed). Params: none (pass {}). Pagination: not exposed by this tool until the API adds cursor/limit. |
| list_price_referencesA | Lists currencies usable as price references for token-holder triggers: GET /api/v1/currencies?price_reference=true&page_size=100. Optional chain_identifier (e.g. "ethereum") filters to that chain. Each result includes identifier (use as volume_currency_expression), name, decimals, chain_identifier. REQUIRED before create_trigger when the held token may not be a known asset (not on CoinGecko/CMC). Workflow: (1) Call with the trigger chain. (2) If token_address is in results[] (EVM: compare identifier case-insensitively), set volume_currency_expression = token_address. (3) If NOT listed, ask the user: stablecoin or variable-price? How many decimals (6 or 18)? Pick a reference from results with matching decimals. Examples: DAI 0x6b175474e89094c44da98b954eedeac495271d0f on Ethereum is listed — use same address. Unknown 18-decimal stablecoin 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD on Ethereum is NOT listed — use DAI as volume_currency_expression, not the token address. Wrong reference → trigger creates (201) but never prices volume correctly. Params: {} or {"chain_identifier":"ethereum"}. |
| resolve_token_holder_price_referenceA | Resolves context.volume_currency_expression for token-holder triggers (check + assign). Step 1: pass token_address and chain_identifier (or chain_id). If the token is already in Fuul price references → status listed_use_same_address, use token_address as volume_currency_expression. Step 2: if status needs_user_input, ask: stablecoin or variable? decimals 6 or 18? Then call again with token_kind and decimals. Step 3: status resolved returns assigned volume_currency_expression (e.g. stablecoin + 18 decimals on Ethereum → DAI). Use that in create_trigger. create_trigger rejects unlisted tokens when volume_currency_expression equals token_address. Example listed: {"token_address":"0x6b175474e89094c44da98b954eedeac495271d0f","chain_identifier":"ethereum"}. Example assign: {"token_address":"0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD","chain_identifier":"ethereum","token_kind":"stablecoin","decimals":18}. |
| list_trigger_typesA | Lists trigger type metadata from GET /public-api/v1/metadata/trigger-types (cached), enriched for create_trigger. Each trigger_types[] row includes: context_json_schema (field definitions), create_payload_layout (flat_dto | context_only | context_and_root_fields), create_payload_notes, and create_payload_example when available. Top-level create_trigger_payload_guide explains the three layouts (same as fuul-webapp encode.ts). Always call this before create_trigger. Params: {}. |
| list_payout_schemasA | Lists payout schema metadata from GET /public-api/v1/metadata/payout-schemas (cached), enriched for create_incentive. Includes enums, payout_term_dto.schemes (per PayoutScheme), plus reward_types[] with create_payload_example for: fixed-reward, variable-reward, proportional-pool, leaderboard. Top-level create_incentive_payload_guide documents body shape and webapp encode.ts mappers. Call before create_incentive. Params: {}. |
| list_projectsA | Lists dashboard projects for the current user: GET /api/v1/projects with optional ?page= (1-based) and ?query=. Example: {"page":1} or {"query":"acme"}. |
| get_projectA | Loads one project (draft + published trigger mapping). Calls GET /api/v1/projects/:projectId and GET /api/v1/projects/:projectId/customizations in parallel. Replaces triggers[] with scoped rows: ref, signature, draft_trigger_id, published_trigger_id, draft, published. Stable key across versions is ref (not UUID). After publish, draft and published rows get different UUIDs for the same ref. conversions[] are draft incentives with nested triggers merged the same way; published_conversion_id is null until a future published-incentives API. Example: {"project_id":"550e8400-e29b-41d4-a716-446655440000"}. |
| list_incentivesA | Lists draft incentives with published trigger IDs merged by ref. Calls GET /api/v1/projects/:projectId/incentives and GET .../customizations. Each item: slug, draft_conversion_id, published_conversion_id (null for now), draft, published (null), triggers[] (scoped merge). Example: {"project_id":""}. |
| get_incentiveA | Gets one draft incentive with scoped triggers (same merge as list_incentives). conversion_id is the draft conversion UUID from incentives API. Use draft_trigger_id from triggers[] for PATCH update_trigger; published_trigger_id for live/prod comparisons (e.g. SQL on project.metadata_id). Example: {"project_id":"","conversion_id":""}. |
| get_triggerA | Gets one trigger row by UUID: GET /api/v1/projects/:projectId/triggers/:triggerId. Returns whichever row that UUID points to (draft or published copy). Does not resolve project.metadata_id. Prefer get_project or get_incentive triggers[] for draft_trigger_id vs published_trigger_id by ref. Example: {"project_id":"","trigger_id":""}. |
| create_triggerA | Creates a draft trigger: POST /api/v1/projects/:projectId/triggers. Body matches CreateTriggerDto (fuul-webapp triggersService.create / encodeByTriggerType). REQUIRED: call list_trigger_types first; use trigger_types[].id as trigger.type and follow create_payload_layout for that id. Layouts: (1) flat_dto — types custom/classic: put context_json_schema fields at trigger ROOT (signature, event_type, expressions, payable, end_user_identifier_property, contract_ids), NOT nested only in context. (2) context_only — token-holder, liquidity-pool-v2: fields only under trigger.context. (3) context_and_root_fields — most presets: fields under trigger.context plus end_user_identifier_property at root when needed. Use create_payload_example from list_trigger_types when present. Call list_chains for chain_id. dry_run then confirmed. Token-holder price reference (CRITICAL): context.volume_currency_expression prices held balance. Before create_trigger for token-holder / liquidity-pool-v2 / balancer / solana-token-holder / fogo-token-holder: call list_price_references for the chain. If token_address is in that list → volume_currency_expression = token_address. If NOT listed → ask user stablecoin vs variable-price and decimals (6/18), then set volume_currency_expression to a listed reference with matching decimals (e.g. 18-decimal stablecoin on Ethereum → DAI 0x6b175474e89094c44da98b954eedeac495271d0f). Using an unlisted token address causes HTTP 201 but broken volume at runtime. Token-holder example (known asset): {"name":"Hold DAI","description":"...","type":"token-holder","context":{"token_address":"0x6b175474e89094c44da98b954eedeac495271d0f","chain_id":1,"volume_currency_expression":"0x6b175474e89094c44da98b954eedeac495271d0f"}}. Token-holder example (unknown 18d stablecoin): token 0xa3931d71877C0E7a3148CB7Eb4463524FEc27fbD → volume_currency_expression 0x6b175474e89094c44da98b954eedeac495271d0f (DAI). Custom off-chain: {"name":"...","description":"...","type":"custom","signature":"event_name","event_type":"off-chain-event","end_user_identifier_property":"address","payable":true,...expressions at root}. On successful execution (not dry_run), the response includes _publish_metadata_reminder: publish project metadata from the dashboard (Project → Incentives or Triggers → Publish now). The MCP cannot publish for you. |
| delete_triggerA | Deletes a draft trigger: DELETE /api/v1/projects/:projectId/triggers/:triggerId. Pass draft_trigger_id or a published_trigger_id (post-publish remap). Requires dry_run then confirmed. Never call without explicit user approval. Before delete: ensure no incentives still link this trigger (delete_incentive first). Replace-trigger flow (token/chain change; after telling the user update_trigger cannot change token_address/chain_id): (1) get_project or list_incentives — list every incentive/conversion whose triggers[] includes this draft_trigger_id; (2) delete_conversion for each draft_conversion_id (removes conversion + trigger links); (3) delete_trigger; (4) create_trigger with the new context; (5) create_incentive if the program must be recreated. Do not call delete_trigger until step 2 is done for all linked conversions. If delete still returns HTTP 422, report remaining links and stop — do not retry delete_trigger blindly. Before executing (including dry_run), this tool refreshes project metadata (same as get_project) and resolves trigger_id / conversion_id / trigger_ids[] to the current draft UUIDs. If you pass a published_trigger_id from before a dashboard publish, it is remapped to the current draft_trigger_id for the same ref. Responses include _draft_id_resolution when an ID was remapped. Unknown stale UUIDs fail with an explicit error. On successful execution (not dry_run), the response includes _publish_metadata_reminder: publish project metadata from the dashboard (Project → Incentives or Triggers → Publish now). The MCP cannot publish for you. |
| create_incentiveA | Creates a draft incentive (conversion): POST /api/v1/projects/:projectId/incentives. Body: name, trigger_ids[] (draft or published trigger UUIDs — resolved to current draft), payout_terms[] (PayoutTermDto, min 1 each). REQUIRED: list_payout_schemas first — pick reward_types[].id (fixed-reward | variable-reward | proportional-pool | leaderboard) and use create_payload_example. Schemes on wire: pay-per-attribution (fixed/variable), pool, rank. type: point | onchain-currency. payee_type: affiliate | end-user | both. Fixed: calculation_strategy fixed, referrer_amount/referral_amount. Variable: calculation_strategy variable, trigger_amount_source, base_currency, *_amount_percentage. Pool: scheme pool, amount_source, pool_amount, pool_duration, pool_calculation_day_cron. Leaderboard: scheme rank, rank_scheme_config.ranks, pool window fields. MCP normalizes variable terms (referral_amount → referral_amount_percentage). dry_run then confirmed. Before executing (including dry_run), this tool refreshes project metadata (same as get_project) and resolves trigger_id / conversion_id / trigger_ids[] to the current draft UUIDs. If you pass a published_trigger_id from before a dashboard publish, it is remapped to the current draft_trigger_id for the same ref. Responses include _draft_id_resolution when an ID was remapped. Unknown stale UUIDs fail with an explicit error. On successful execution (not dry_run), the response includes _publish_metadata_reminder: publish project metadata from the dashboard (Project → Incentives or Triggers → Publish now). The MCP cannot publish for you. |
| delete_incentiveA | Deletes a draft incentive (conversion): DELETE /api/v1/projects/:projectId/incentives/:conversionId. Same API as delete_conversion. Use draft_conversion_id from list_incentives or get_project conversions[]. dry_run then confirmed. When replacing a trigger, use delete_conversion (step 2 of the replace flow) before delete_trigger. Before executing (including dry_run), this tool refreshes project metadata (same as get_project) and resolves trigger_id / conversion_id / trigger_ids[] to the current draft UUIDs. If you pass a published_trigger_id from before a dashboard publish, it is remapped to the current draft_trigger_id for the same ref. Responses include _draft_id_resolution when an ID was remapped. Unknown stale UUIDs fail with an explicit error. On successful execution (not dry_run), the response includes _publish_metadata_reminder: publish project metadata from the dashboard (Project → Incentives or Triggers → Publish now). The MCP cannot publish for you. |
| get_affiliate_portal_statsB | Project affiliate stats for one user identifier: GET /api/v1/projects/:projectId/affiliate-portal/stats. Response includes active referred-user counts by multilevel depth ( |
| get_project_affiliate_total_statsA | Aggregated project-wide affiliate totals: GET /api/v1/projects/:projectId/affiliate-portal/total-stats. Optional filters: statuses, regions, audiences, tiers, dateRange, dateFrom, dateTo (see fuul-server GetTotalStatsDto). Example: {"project_id":""} or {"project_id":"","dateRange":"30d"}. If the API returns HTTP 429, wait Retry-After seconds (if present) before retrying. |
| get_project_affiliates_breakdownA | Affiliate breakdown for a project (by audience, tier, region, or status): GET /api/v1/projects/:projectId/affiliate-portal/global-breakdown. groupBy is required (audience | tier | region | status). Optional sortBy, sortOrder, date filters, and dimension filters. Example: {"project_id":"","groupBy":"region","dateRange":"30d"}. If the API returns HTTP 429, wait Retry-After seconds (if present) before retrying. |
| get_project_affiliate_publicA | Loads one managed project affiliate (full detail: tiers, protections, audiences, tax): GET /api/v1/project-affiliates/:projectAffiliateId. Auth: project API key only (pass |
| create_project_affiliate_publicA | Creates a managed project affiliate: POST /api/v1/project-affiliates. Body matches server public DTO (user_identifier, user_identifier_type, optional alias, region, status, note, audiences, tier_protection, approve_project_tier_ids + reviewed_by_user_id when approving tiers). Auth: project API key only (pass |
| update_project_affiliate_publicA | Updates a managed project affiliate: PATCH /api/v1/project-affiliates/:projectAffiliateId. Partial body (alias, region, status, note, audiences, tier_protection including null to clear, approve_project_tier_ids with reviewed_by_user_id). Auth: project API key only (pass |
| send_eventA | Send one conversion event: POST /api/v1/events. Triggers real-time reward attribution. Required: name (trigger name), user_identifier, user_identifier_type, dedup_id. Optional: args, timestamp (ms). Duplicate dedup_id returns HTTP 409. After send, use check_event_status with verbose=true (dedup_id + event_name) to verify the pipeline. dry_run then confirmed. Auth: project API key only (pass args shape for value/revenue: For non-tracking events (swaps, deposits), args can include Example with value/revenue: {"name":"swap","user_identifier":"0x...","user_identifier_type":"evm_address","dedup_id":"swap-123","args":{"value":{"amount":"1000000","currency":{"name":"USDC"}},"revenue":{"amount":"3000","currency":{"name":"USDC"}}},"dry_run":true}. Simple dry_run: {"name":"trade","user_identifier":"0x...","user_identifier_type":"evm_address","dedup_id":"uuid-here","dry_run":true}. |
| send_batch_eventsA | Send up to 100 conversion events: POST /api/v1/events/batch. For backfills and bulk ingestion; processing is atomic (all succeed or all fail). Duplicate dedup_id values are silently ignored; response includes ingested_events count. Auth: project API key only (pass args shape for value/revenue: Same as send_event — each event in the batch can include Example dry_run: {"events":[{"name":"swap","user_identifier":"0x...","user_identifier_type":"evm_address","dedup_id":"id-1","args":{"value":{"amount":"5000000000","currency":{"identifier":"0xa0b...","identifier_type":"evm_contract","chain_identifier":"evm:1"}}}}],"dry_run":true}. |
| check_event_statusA | Check event ingestion and downstream pipeline. Default (verbose omitted or false): GET /api/v1/events/status with user_identifier, user_identifier_type, event_name → {"created":true|false}. verbose=true: GET /api/v1/events/pipeline — returns event, trigger_executions (status, status_details), attributions, payouts, movements. Requires event_id OR dedup_id + event_name (same dedup_id/name as send_event). Poll every 2–5s after send_event until attributions/payouts appear. 404 → {"created":false}. Auth: project API key only (pass |
| get_user_referrerA | Read a user referrer from user_referrers: GET /api/v1/user/referrer?user_identifier=&user_identifier_type=. Returns referrer_identifier, referrer_code, referrer_codes, referrer_name, referrer_user_rebate_rate (null referrer fields when unassigned). Unlike GET /referral_codes/status, this reflects PUT /user-referrers assignments even without referral_code_uses. Auth: project API key only (pass |
| update_user_referrerA | Admin override: create or overwrite user_referrers via PUT /api/v1/user-referrers (idempotent upsert). Sets source=project_imported; does NOT create referral_code_uses or increment actual_uses (status may stay referred:false). For real code redemption use use_referral_code instead. Requires service_role project API key. Auth: project API key only (pass |
| delete_user_referrerA | Remove admin-imported user_referrers row: DELETE /api/v1/user-referrers?user_identifier=&user_identifier_type=. Clears PUT/update_user_referrer assignments (including rows with referral_code_id set but no referral_code_uses). Does NOT touch referral_code_uses or actual_uses. If the user has referral code redemptions in this project, API returns 422 — use remove_user_from_referral_code instead. Maps 422 "User referrer relationship not found" to {already_removed:true} for idempotent retries. Requires service_role project API key. Auth: project API key only (pass |
| remove_user_from_referral_codeA | Remove a user from a referral code: DELETE /api/v1/referral_codes/:code/referrals. Atomically deletes user_referrers + referral_code_uses and decrements actual_uses. Not idempotent on the API; this tool maps known 422 cases to {"already_removed":true,"reason":"..."} for safe retries. Requires service_role project API key (no wallet signature). Auth: project API key only (pass |
| use_referral_codeA | Redeem a referral code for a user: PATCH /api/v1/referral_codes/:code/use. Creates referral_code_uses, increments actual_uses, sets user_referrers with source=code_redemption (GET /referral_codes/status → referred:true). Referrer is the code owner (do not pass referrer_identifier). Requires user has no existing referrer unless service_role idempotent replay of the same code. No wallet signature with service_role. Auth: project API key only (pass |
| swap_user_referral_codeA | Move a user between referral codes with full redemption semantics: DELETE from from_referral_code then PATCH /api/v1/referral_codes/:to_referral_code/use (not atomic). Step 1 tolerates already-removed 422s (e.g. user never had a prior code). Step 2 assigns to the owner of to_referral_code — there are no to_referrer_* params. If PATCH /use fails after DELETE, response has partial:true with use_error; complete with use_referral_code or update_user_referrer. Legacy PUT-only users (user_referrers without referral_code_use) may block step 2 with "User already has a referrer" — clear with delete_user_referrer first when appropriate. For first assign with no prior code, use use_referral_code directly. Requires service_role project API key. Auth: project API key only (pass |
| list_payouts_pending_approvalA | Lists payouts pending approval: GET /api/v1/projects/:projectId/payouts/pending-approval. Optional page, page_size. Example: {"project_id":"","page":1,"page_size":50}. |
| list_rewards_payoutsA | Lists rewards payouts history: GET /api/v1/projects/:projectId/payouts/rewards-payouts. Optional page, page_size, status, from_date, to_date. Example: {"project_id":"","page":1}. |
| approve_payoutsA | Approves payouts: PATCH /api/v1/projects/:projectId/payouts/approve. Body: payout_ids OR date filters (server validates mutual exclusivity). dry_run then confirmed. Example dry_run: {"project_id":"","payout_ids":[""],"dry_run":true}. |
| reject_payoutsC | Rejects payouts: PATCH /api/v1/projects/:projectId/payouts/reject. Same body rules as approve_payouts. |
| update_payout_termA | Updates one payout term on a draft conversion: PATCH /api/v1/projects/:projectId/conversions/:conversionId/payout_terms/:payoutTermId. Body is a single PayoutTermDto (use get_incentive, edit fields such as referral_amount / referrer_amount, send as payout_term). For variable rewards, the server expects referral_amount_percentage / referrer_amount_percentage; this tool maps GET aliases automatically (same as the dashboard). Per-unit rewards: edit referral_amount and referrer_amount; do not send zero percentages. dry_run shows the normalized body sent to the API. Example dry_run: {"project_id":"","conversion_id":"","payout_term_id":"","payout_term":{...},"dry_run":true}. Before executing (including dry_run), this tool refreshes project metadata (same as get_project) and resolves trigger_id / conversion_id / trigger_ids[] to the current draft UUIDs. If you pass a published_trigger_id from before a dashboard publish, it is remapped to the current draft_trigger_id for the same ref. Responses include _draft_id_resolution when an ID was remapped. Unknown stale UUIDs fail with an explicit error. On successful execution (not dry_run), the response includes _publish_metadata_reminder: publish project metadata from the dashboard (Project → Incentives or Triggers → Publish now). The MCP cannot publish for you. |
| update_audienceA | Updates an audience (user list): PATCH /api/v1/projects/:projectId/audiences/:audienceId. Body matches CreateOrUpdateAudienceDto: name (required), optional conditions[] (signature + parameters), condition_match_mode "any"|"all" (required if conditions non-empty), contractId. dry_run then confirmed. Example dry_run: {"project_id":"","audience_id":"","name":"VIP","dry_run":true}. |
| update_triggerA | Updates a trigger: PATCH /api/v1/projects/:projectId/triggers/:triggerId. Partial body matching UpdateTriggerDto (name, description, event_type, expressions, payable, ref, contract_ids as single-element array, etc.). Does NOT update context fields such as token_address or chain_id — those are immutable after create. If the user asks to change token_address, chain_id, or the tracked token/contract: do NOT call PATCH. First inform the user clearly: "This cannot be updated in place; you must delete the trigger and create a new one with the new token/chain." Then, only with explicit user approval, run the replace flow: Replace-trigger flow (token/chain change; after telling the user update_trigger cannot change token_address/chain_id): (1) get_project or list_incentives — list every incentive/conversion whose triggers[] includes this draft_trigger_id; (2) delete_conversion for each draft_conversion_id (removes conversion + trigger links); (3) delete_trigger; (4) create_trigger with the new context; (5) create_incentive if the program must be recreated. Do not call delete_trigger until step 2 is done for all linked conversions. Never skip step 1 — always list linked conversions before delete_trigger. At least one patch field required for allowed fields only. dry_run then confirmed. Prefer get_project for current draft_trigger_id by ref. Before executing (including dry_run), this tool refreshes project metadata (same as get_project) and resolves trigger_id / conversion_id / trigger_ids[] to the current draft UUIDs. If you pass a published_trigger_id from before a dashboard publish, it is remapped to the current draft_trigger_id for the same ref. Responses include _draft_id_resolution when an ID was remapped. Unknown stale UUIDs fail with an explicit error. On successful execution (not dry_run), the response includes _publish_metadata_reminder: publish project metadata from the dashboard (Project → Incentives or Triggers → Publish now). The MCP cannot publish for you. |
| update_project_tierA | Updates a project affiliate tier: PATCH /api/v1/projects/:projectId/tiers/:tierId. Optional fields: name, description, rank, audience_id (null clears audience). At least one field required. dry_run then confirmed. Example: {"project_id":"","tier_id":"","rank":2,"dry_run":true}. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
Latest Blog Posts
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kuyen-labs/mcp_server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server