Trackily Docs

MCP Tokens

Un token MCP, c'est une chaîne tly_ap_<64 hex> qui autorise un client (Claude Desktop, Cursor, script…) à appeler les tools MCP de ton instance. Chaque token a son propre set de scopes, ses propres rate limits, et est tracé dans l'audit log à chaque appel.

Concept

Comparable à une "API key" classique, mais conçue spécifiquement pour les agents LLM :

  • Format : tly_ap_ + 64 caractères hex (32 bytes random)
  • Stockage : seulement le SHA-256 du token est stocké en DB (autopilot_tokens.token_hash). Le plaintext n'est montré qu'une seule fois à la création. Si tu le perds, tu dois rotater (= révoquer + recréer).
  • Identification rapide : les 12 premiers caractères (tly_ap_a1b2) sont stockés en clair comme token_prefix — affichés dans les listes et l'audit log pour distinguer les tokens sans révéler le reste.

Modèle de données

Schema autopilot_tokens :

Colonne Type Note
id SERIAL PK
name TEXT label humain ("Claude Desktop laptop")
token_prefix TEXT tly_ap_a1b2 (12 chars)
token_hash TEXT UNIQUE SHA-256 du plaintext
scopes JSONB array de scope strings
rate_limit_per_min INTEGER bucket default (par défaut 100/min)
ai_rate_limit_per_min INTEGER bucket ai pour les tools coûteux (par défaut 20/min)
daily_spend_cap_usd NUMERIC limite cumulée par jour sur les tools qui dépensent (AI generation, ad budgets…)
ip_whitelist JSONB array d'IPs autorisées ; vide = pas de restriction
expires_at TIMESTAMPTZ date d'expiration ; NULL = jamais
revoked_at TIMESTAMPTZ date de révocation ; NULL = actif
last_used_at TIMESTAMPTZ mis à jour à chaque call
use_count INTEGER compteur cumulé
created_by INTEGER FK users qui a créé le token
created_at TIMESTAMPTZ
updated_at TIMESTAMPTZ

Créer un token

Via l'UI

Sidebar → MCP Autopilot → Tokens → New token. Tu remplis :

  • Name — libellé que tu reverras dans la liste et l'audit log
  • Scopes — soit un preset (read_only, operator, full, admin), soit tu coches les scopes individuels (cf. Scopes)
  • Rate limit / min — par défaut 100, monte à 300 si tu fais des batches intensifs
  • AI rate limit / min — par défaut 20, pour limiter les tools ai:generate qui coûtent cher
  • Daily spend cap (USD) — optionnel, plafond cumulé par jour pour les opérations facturables
  • IP whitelist — optionnel, array d'IPs autorisées (utile pour brancher Trackily depuis ton VPS de prod uniquement)
  • Expires at — optionnel, date limite (token automatiquement révoqué après)

Au submit, l'UI affiche le plaintext token UNE seule fois dans un modal. Copie-le tout de suite dans ton client (Claude Desktop config, etc.). Si tu fermes le modal sans l'avoir copié, tu dois rotater.

Via l'API admin

POST /admin/api/autopilot/tokens HTTP/1.1
Cookie: tly_admin_session=...

{
  "name": "Claude Desktop laptop",
  "scopes": ["campaigns:read", "campaigns:write", "landings:read", "email:read"],
  "rate_limit_per_min": 200,
  "ai_rate_limit_per_min": 30,
  "daily_spend_cap_usd": 5.00,
  "ip_whitelist": []
}

Response :

{
  "token": "tly_ap_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6a7b8c9d0e1f2",
  "record": {
    "id": 5,
    "name": "Claude Desktop laptop",
    "token_prefix": "tly_ap_a1b2",
    "scopes": ["campaigns:read", "campaigns:write", "landings:read", "email:read"],
    "rate_limit_per_min": 200,
    "ai_rate_limit_per_min": 30,
    "daily_spend_cap_usd": 5.00,
    "ip_whitelist": [],
    "expires_at": null,
    "created_at": "2026-05-18T14:22:11.421Z"
  }
}

Le champ token n'est présent que dans cette réponse. Toute query ultérieure (GET /admin/api/autopilot/tokens) retourne seulement le record sans le plaintext.

Lister les tokens

GET /admin/api/autopilot/tokens HTTP/1.1

Response (extrait) :

[
  {
    "id": 5,
    "name": "Claude Desktop laptop",
    "token_prefix": "tly_ap_a1b2",
    "scopes": ["campaigns:read", "campaigns:write", "landings:read", "email:read"],
    "rate_limit_per_min": 200,
    "ai_rate_limit_per_min": 30,
    "daily_spend_cap_usd": "5.00",
    "ip_whitelist": [],
    "expires_at": null,
    "revoked_at": null,
    "last_used_at": "2026-05-18T18:42:33.221Z",
    "use_count": 247,
    "created_at": "2026-05-18T14:22:11.421Z"
  }
]

Par défaut, les tokens révoqués sont exclus. Ajoute ?include_revoked=true pour les voir.

Révoquer un token

DELETE /admin/api/autopilot/tokens/5 HTTP/1.1

Le token reste en DB (audit trail conservé), mais revoked_at est setté → toute call ultérieure retourne 401 Unauthorized (code -32001).

Rotater un token

POST /admin/api/autopilot/tokens/5/rotate :

  • Révoque le token courant
  • Crée un nouveau token avec les mêmes scopes / limits / IP whitelist
  • Retourne le nouveau plaintext

Utile quand tu suspectes une fuite (token committed par mégarde dans Git, partagé sur Slack, etc.) — tu fais une rotation, tu mets à jour les configs client, sans avoir à recréer tous les réglages.

Rate limiting

Chaque token a deux buckets :

  • default — utilisé par 99% des tools. Quota = rate_limit_per_min.
  • ai — utilisé par les tools coûteux (génération AI, scraping). Quota = ai_rate_limit_per_min.

Le bucket d'un tool est défini à l'enregistrement (registerTool({ ..., rateLimitBucket: 'ai' })). Tous les tools generate_* AI sont dans le bucket ai.

L'implémentation est une sliding window 60 secondes en mémoire process. Pas de Redis — Trackily est mono-process par instance. Si tu tournes plusieurs instances derrière un LB, chaque process a son propre compteur (donc le quota effectif est multiplié — à prévoir).

Quand le quota est dépassé, le serveur répond :

{
  "jsonrpc": "2.0",
  "id": 42,
  "error": {
    "code": -32003,
    "message": "Rate limit exceeded",
    "data": {
      "bucket": "default",
      "limit_per_min": 100,
      "retry_after_ms": 12450
    }
  }
}

Plus un header HTTP Retry-After: 13 (secondes).

Daily spend cap

Pour les tools qui dépensent de l'argent réel — appels AI (OpenAI / Anthropic), budgets ad réseaux (update_source_campaign_budget) — Trackily accumule le coût estimé dans une fenêtre glissante de 24h par token.

Si daily_spend_cap_usd est défini et que le call dépasserait le plafond, le tool refuse :

{
  "status": "rejected",
  "error": "Daily spend cap exceeded",
  "spent_24h_usd": "4.92",
  "cap_usd": "5.00"
}

Tu peux raise le cap depuis l'UI ou via PATCH sur la row.

IP whitelist

Si non-vide, seules les IP listées peuvent utiliser le token. Implémenté côté handler MCP avant même le dispatch du payload :

const whitelist = Array.isArray(tokenRow.ip_whitelist) ? tokenRow.ip_whitelist : [];
if (whitelist.length && !whitelist.includes(ip)) {
  // 403 Forbidden, audit_log entry 'auth_fail'
}

L'IP est lue depuis X-Forwarded-For (premier de la liste) ou req.socket.remoteAddress en fallback. Si tu es derrière un reverse proxy (Caddy, Cloudflare), assure-toi qu'il forwarde bien le X-Forwarded-For.

Audit log

Chaque appel MCP est loggé dans autopilot_audit_log :

Colonne Note
token_id FK vers autopilot_tokens.id
token_prefix redondant pour pouvoir lire l'audit sans join
event_type tool, resource, prompt, rpc, auth_fail, rate_limit
method méthode JSON-RPC (tools/call, etc.)
tool_name si event_type='tool'
resource_uri si event_type='resource'
prompt_name si event_type='prompt'
params_summary params sérialisés, tronqués à 4000 chars
result_status ok, error
result_summary résumé du résultat (bytes=…) ou message d'erreur
error_message si erreur
duration_ms temps total côté server
ip IP du caller
user_agent UA du caller
created_at

Une page admin (/admin#autopilot/audit) expose le log avec filtres (par token, par event_type, par status, par range de dates). Utile pour debugger pourquoi un agent a fait n'importe quoi à 3h du matin.

Bonnes pratiques

  • Un token par client — pas un token "global" partagé. Si Claude Desktop et un script Python utilisent tous les deux Trackily, donne-leur deux tokens. Tu pourras révoquer l'un sans toucher à l'autre.
  • Scope minimal — donne read_only par défaut, ajoute :write quand tu as confirmé que l'agent en a besoin. Le admin preset est à réserver à un token "break-glass" stocké en coffre.
  • IP whitelist en prod — si Trackily est joint depuis ton VPS de prod, set la whitelist à l'IP du VPS. Une exfiltration du token (via leak Git, mauvaise config) sera inutilisable.
  • Expires_at sur les tokens temporaires — si tu donnes l'accès à un freelancer pour 2 semaines, set expires_at = NOW() + INTERVAL '14 days'. Plus de cleanup à faire.
  • Daily spend cap obligatoire sur les tokens qui ont ai:generate ou sources:write — un LLM qui hallucine et boucle peut générer 100$ d'AI en 10 minutes ou +1000% un budget Kadam.
  • Rotation tous les 90 jours — minimum, ou immédiatement si suspicion de fuite.

Erreurs courantes

  • "Invalid or expired token" — soit typo dans le header, soit token révoqué, soit expires_at dépassé. Check dans /admin#autopilot/tokens.
  • "Missing scope for X" — le tool nécessite un scope absent. La response contient "data": { "required": ["scope:foo"] } pour savoir lequel ajouter.
  • "Rate limit exceeded" — soit ton client fait trop d'appels, soit le bucket ai est trop restrictif. Augmente côté token.
  • "IP not allowed" — tu as ajouté une whitelist mais ton IP a changé (DHCP, CDN…). Mets à jour la whitelist ou vide-la.

Voir aussi

  • Scopes — la liste des scopes à coller sur un token
  • Tier System — comportement des destructives
  • Concept — protocole JSON-RPC sous-jacent