Trois sortes de ressources se gèrent ici : les serveurs MCP proposés aux membres dans le chat, le stockage propre de l’organisation pour les fichiers envoyés, et le partage des bases de connaissances et des agents avec des groupes, des rôles ou des étiquettes.
Toutes les routes sont sous https://. Portée : resources:read pour lire, resources:write pour écrire. Les permissions demandées sont celles du créateur de la clé : gérer les connecteurs (serveurs MCP), gérer le stockage, gérer la bibliothèque (bases de connaissances) ou publier au catalogue (agents). Les créations, l’activation et les partages demandent une organisation gérée (sinon 409 ).
Le bloc de partageLien vers la section Le bloc de partage
Les serveurs MCP et les partages utilisent le même objet sharing :
{ "all": false, "group_ids": ["<id de groupe>"], "role_keys": ["admin", "custom:<id>"], "label_ids": ["<id d’étiquette>"] }| Champ | Type | Description |
|---|---|---|
all | booléen | true : tout le workspace. |
group_ids | liste | Groupes de l’organisation, 200 au plus. |
role_keys | liste | Rôles fournis (owner, admin, manager, billing, member) ou créés (custom:<id>), 50 au plus. |
label_ids | liste | Étiquettes de membres (pas de sensibilité), 200 au plus. |
Un bloc absent ou null vaut « partagé avec personne ». Un groupe, un rôle ou une étiquette inconnu répond 404 (« Groupe introuvable. », « Rôle introuvable. », « Étiquette introuvable. »). Les erreurs de ces routes peuvent porter un champ field (le champ en cause) en plus de fields.
Serveurs MCPLien vers la section Serveurs MCP
Un serveur MCP de l’organisation est proposé dans le chat aux membres avec qui il est partagé, quand il est activé et que son dernier test a réussi (offered: ), et que leurs règles l’autorisent. Une organisation a 50 serveurs au plus. Le parcours : créer, tester puis activer.
L’objet serveur MCPLien vers la section L’objet serveur MCP
{
"id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f6a1b2c",
"name": "CRM interne",
"url": "https://mcp.example-crm.com/mcp?…",
"transport": "streamable_http",
"header_name": "Authorization",
"auth": "token",
"token_configured": true,
"token_last4": "9xQa",
"readable": true,
"oauth_client_configured": false,
"members_connected": null,
"my_oauth_status": null,
"my_oauth_host": null,
"tools": [
{ "name": "search_contacts", "description": "Recherche des contacts par nom.", "read_only": true, "hash": "3f5a9c…" }
],
"tools_pending": [],
"tools_allowed": ["search_contacts"],
"approval": { "search_contacts": "auto" },
"sharing": { "all": false, "group_ids": ["2d4c6e8a-1b3d-4f5a-9c7e-0a2b4c6d8e1f"], "role_keys": ["admin"], "label_ids": [] },
"sensitivity_label_id": null,
"enabled": true,
"status": "ok",
"last_error": null,
"tested_at": 1793440000,
"offered": true,
"created_at": 1793436400,
"updated_at": 1793440000
}| Champ | Description |
|---|---|
url | L’adresse, masquée : la requête devient ?… et les longs segments du chemin …. L’adresse complète n’est jamais rendue. |
transport | auto, streamable_http ou sse ; après un test réussi, le transport détecté. |
auth | token (jeton fixe), none ou oauth (chaque membre se connecte avec son compte). |
token_configured, token_last4 | Un jeton est enregistré ; ses 4 derniers caractères (jeton de 8 caractères ou plus). Le jeton n’est jamais rendu. |
readable | Les informations de connexion enregistrées sont lisibles. |
members_connected | Serveur OAuth : nombre de membres connectés ; null sinon. |
my_oauth_status | Serveur OAuth : none, ok ou expired pour la connexion de l’appelant (pour une clé, son créateur). |
tools | Outils approuvés au dernier test (40 au plus) : name, description, read_only, hash. |
tools_pending | Outils nouveaux (kind: "new") ou modifiés (kind: ) depuis, mis de côté jusqu’au prochain test. |
tools_allowed | Outils proposés ; null : tous. |
approval | Par outil : ask (demander), auto (sans demander) ou never (jamais). |
sensitivity_label_id | Étiquette de sensibilité : seuls les rôles de sharing.role_keys (à défaut propriétaire et administrateurs) utilisent alors le serveur. |
status | untested, ok ou failed ; last_error donne le message du dernier échec. |
offered | enabled et status à ok. |
Lister les serveurs MCPLien vers la section Lister les serveurs MCP
GET / · resources:read
Paramètres : page, limit. Tri par nom. La réponse ajoute max_servers (50).
Ajouter un serveur MCPLien vers la section Ajouter un serveur MCP
POST / · resources:write · organisation gérée
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
name | chaîne | oui | 1 à 80 caractères, unique dans l’organisation (sans tenir compte de la casse). |
url | chaîne | oui | Adresse http(s) sans identifiants, 8 à 2 048 caractères. Elle doit être publique : c’est vérifié au test. |
transport | chaîne | non | auto (par défaut), streamable_http ou sse. |
auth | chaîne | non | token (par défaut), none ou oauth. |
header_name | chaîne | non | En-tête du jeton, Authorization par défaut (lettres, chiffres et tirets, 64 au plus). Sur Authorization, un jeton sans espace est envoyé en Bearer . |
token | chaîne | si auth vaut token | Le jeton d’accès, 8 192 caractères au plus. Ignoré pour none et oauth. |
tools_allowed | liste | non | Outils proposés (200 noms au plus) ; absent : tous. |
approval | objet | non | {"<outil>": , avec ask, auto ou never, 200 entrées au plus. |
sharing | objet | non | Bloc de partage ; absent : personne. |
sensitivity_label_id | chaîne | non | Une étiquette de sensibilité de l’organisation. |
Le serveur est créé désactivé et non testé.
curl https://chat.realmoon.ai/api/v1/orgs/$ORG_ID/resources/mcp \
-H "Authorization: Bearer $MOONAI_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "CRM interne",
"url": "https://mcp.example-crm.com/mcp",
"auth": "token",
"token": "<jeton du serveur>",
"sharing": {"all": true}
}'import os
import requests
BASE = f"https://chat.realmoon.ai/api/v1/orgs/{os.environ['ORG_ID']}"
HEADERS = {"Authorization": f"Bearer {os.environ['MOONAI_ADMIN_KEY']}"}
server = requests.post(
f"{BASE}/resources/mcp",
headers=HEADERS,
json={
"name": "CRM interne",
"url": "https://mcp.example-crm.com/mcp",
"auth": "token",
"token": os.environ["CRM_MCP_TOKEN"],
"sharing": {"all": True},
},
timeout=30,
).json()
test = requests.post(f"{BASE}/resources/mcp/{server['id']}/test", headers=HEADERS, timeout=60)
test.raise_for_status()
requests.patch(f"{BASE}/resources/mcp/{server['id']}", headers=HEADERS, json={"enabled": True}, timeout=30).raise_for_status()const BASE = `https://chat.realmoon.ai/api/v1/orgs/${process.env.ORG_ID}`;
const HEADERS = { Authorization: `Bearer ${process.env.MOONAI_ADMIN_KEY}`, 'Content-Type': 'application/json' };
const server = await (await fetch(`${BASE}/resources/mcp`, {
method: 'POST',
headers: HEADERS,
body: JSON.stringify({
name: 'CRM interne',
url: 'https://mcp.example-crm.com/mcp',
auth: 'token',
token: process.env.CRM_MCP_TOKEN,
sharing: { all: true },
}),
})).json();
const test = await fetch(`${BASE}/resources/mcp/${server.id}/test`, { method: 'POST', headers: HEADERS });
if (!test.ok) throw new Error(await test.text());
await fetch(`${BASE}/resources/mcp/${server.id}`, { method: 'PATCH', headers: HEADERS, body: JSON.stringify({ enabled: true }) });| Statut | code | Cause |
|---|---|---|
| 400 | invalid_request | Nom vide, adresse invalide, en-tête refusé, jeton manquant, nom d’outil invalide, plus de 50 serveurs, étiquette qui n’est pas de sensibilité. |
| 409 | invalid_request | « Un serveur porte déjà ce nom. » |
| 409 | org_not_managed | L’organisation n’est pas gérée. |
Lire un serveur MCPLien vers la section Lire un serveur MCP
GET / · resources:read
Rend l’objet serveur. 404 pour un serveur inconnu.
Modifier un serveur MCPLien vers la section Modifier un serveur MCP
PATCH / · resources:write
Mêmes champs qu’à la création, tous facultatifs, plus enabled (booléen). Seuls les champs envoyés changent.
token: une chaîne remplace le jeton,""le supprime.- Changer l’adresse,
auth,transport,header_nameou le jeton remetstatusàuntested: le serveur n’est plus proposé jusqu’au prochain test réussi. Changer l’adresse,authoutransportsupprime aussi les connexions personnelles des membres. sharing:retire le partage. Pour un serveur OAuth, retirer le partage d’un membre supprime sa connexion.null enabled:demande une organisation gérée et un dernier test réussi, sinontrue 409(« Testez le serveur avant de l’activer. »).test_required enabled:est toujours permis.false
Supprimer un serveur MCPLien vers la section Supprimer un serveur MCP
DELETE / · resources:write
Supprime le serveur, son journal d’appels, les connexions des membres et son étiquette. Réponse : {"success": .
Tester un serveur MCPLien vers la section Tester un serveur MCP
POST / · resources:write · organisation gérée
Se connecte au serveur, lit ses outils, les approuve et rend l’objet serveur avec status: "ok". Le test n’active pas le serveur. Un serveur OAuth est testé avec la connexion de l’appelant (pour une clé, celle de son créateur).
Chaque test compte dans la limite de 20 tests en 10 minutes par personne, partagée avec les tests du stockage et des webhooks (429 ).
| Statut | code | Cause |
|---|---|---|
| 400 | org_mcp_test_failed | Échec de la connexion ; message dit pourquoi (adresse non publique, accès refusé, aucun serveur à cette adresse, délai dépassé…). Le serveur passe en failed. |
| 400 | oauth_sign_in_required | Serveur OAuth : l’appelant n’a pas de connexion valide à ce serveur. |
| 400 | oauth_required | Serveur auth: "none" qui demande une connexion : passez auth à oauth. Porte host et server_host. |
Connecter un compte à un serveur OAuthLien vers la section Connecter un compte à un serveur OAuth
POST / · resources:write
Démarre la connexion OAuth personnelle de l’appelant et rend authorize_url, host et server_host. Le parcours se termine dans le navigateur qui l’a lancé : utilisez « Tester avec mon compte » dans l’administration plutôt que cette route depuis un serveur. Corps facultatif : client_id (512 caractères) et client_secret (1 024), pour un fournisseur qui demande un identifiant client.
Lire le journal des appelsLien vers la section Lire le journal des appels
GET / · resources:read
Qui a appelé quel outil, quand, et avec quel résultat ; jamais les arguments ni le contenu. Le créateur de la clé doit pouvoir gérer les connecteurs ou lire le journal d’audit (la clé doit alors avoir audit:read).
| Paramètre | Type | Description |
|---|---|---|
user_id | chaîne | Seulement les appels de ce membre. |
page, limit | entier | Pagination. Les plus récents d’abord. |
{
"items": [
{
"id": "c0a1e2f3-4b5c-4d6e-8f70-91a2b3c4d5e6",
"server_id": "6f1c2a9e-3b7d-4e21-9a0c-5d8e7f6a1b2c",
"user_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"user_name": "Camille Martin",
"tool": "search_contacts",
"outcome": "ok",
"approved": false,
"duration_ms": 842,
"created_at": 1793441200
}
],
"total": 1,
"page": 1,
"limit": 50
}outcome vaut ok, error ou refused ; approved est vrai pour un appel fait après validation du membre.
Stockage de l’organisationLien vers la section Stockage de l’organisation
Les fichiers envoyés par les membres peuvent être gardés dans un stockage de l’organisation : Amazon S3 ou compatible, Azure Blob Storage ou Google Cloud Storage. Les identifiants sont chiffrés et jamais rendus.
L’objet stockageLien vers la section L’objet stockage
Sans stockage, la réponse est {"configured": . Sinon :
{
"configured": true,
"provider": "s3",
"config": { "prefix": "moonai", "bucket": "acme-moonai-files", "region": "eu-west-3", "endpoint_url": null, "addressing_style": "auto" },
"credentials": { "configured": true, "hint": "7QXZ" },
"enabled": true,
"status": "ok",
"last_error": null,
"tested_at": 1793440000,
"active": true,
"files": 1240,
"bytes": 5368709120,
"pending_deletes": 0,
"last_ok_at": 1793448000,
"last_failure_at": null,
"last_failure_code": null,
"updated_at": 1793440000
}| Champ | Description |
|---|---|
credentials.hint | Pour reconnaître les identifiants : 4 derniers caractères de la clé d’accès (S3) ou de la clé du compte (Azure), adresse du compte de service (Google). |
status | untested, ok ou failed ; last_error donne le code du dernier test raté. |
active | enabled et status à ok : les nouveaux fichiers y vont. |
files, bytes | Fichiers gardés dans ce stockage et leur taille totale. |
pending_deletes | Objets en attente de suppression. |
Lire le stockageLien vers la section Lire le stockage
GET / · resources:read
Configurer le stockageLien vers la section Configurer le stockage
PUT / · resources:write · organisation gérée pour une création
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
provider | chaîne | oui | s3, azure ou gcs. |
config | objet | oui | Selon le fournisseur (ci-dessous). |
credentials | objet | à la création, pour changer de fournisseur ou de cible | Absent : les identifiants enregistrés sont gardés. |
enabled | booléen | non | false par défaut : un PUT sans enabled désactive le stockage. |
| Fournisseur | config | credentials |
|---|---|---|
s3 | bucket, region, endpoint_url (https://, publique ; absent pour AWS), addressing_style (auto, path ou virtual), prefix | access_key_id, secret_access_key |
azure | account_url (https://), container, prefix | account_key |
gcs | bucket, prefix | service_account : la clé JSON d’un compte de service Google, en objet ou en texte |
prefix est un dossier dans le bucket (lettres, chiffres, ., _, -, /, 200 caractères au plus).
Un changement de fournisseur, de configuration ou d’identifiants remet le stockage en untested et le désactive : la réponse porte alors test_required: et disabled_by_change. Pour l’activer : testez-le, puis renvoyez la même configuration avec enabled: .
| Statut | code | Cause |
|---|---|---|
| 400 | invalid_request | Configuration invalide ; field nomme le champ (bucket, prefix, endpoint_url, credentials…). |
| 409 | storage_in_use | Changer de bucket, de conteneur ou d’adresse alors que des fichiers y sont ; files donne leur nombre. Changez seulement les identifiants, ou désactivez ce stockage. |
| 409 | test_required | enabled: sans test réussi. |
| 409 | org_not_managed | Création dans une organisation non gérée. |
Tester le stockageLien vers la section Tester le stockage
POST / · resources:write
Écrit, relit et supprime un petit fichier, puis rend l’objet stockage. Le test n’active pas le stockage. Un échec répond 400 avec reason :
reason | Cause |
|---|---|
config | La configuration est invalide. |
denied | Le service refuse l’accès : vérifiez les identifiants et leurs droits (écriture, lecture, suppression). |
not_found | Bucket ou conteneur introuvable. |
unreachable | Le service ne répond pas. |
mismatch | Le fichier relu ne correspond pas au fichier écrit. |
error | Autre échec. |
Un échec retire le stockage des nouveaux envois ; les fichiers déjà gardés restent lisibles.
Supprimer le stockageLien vers la section Supprimer le stockage
DELETE / · resources:write
Retire la configuration, sans supprimer aucun objet du bucket. Refusé tant que des fichiers y sont (409 : désactivez-le plutôt) ou que des objets attendent d’être supprimés (409 , avec pending : testez-le d’abord pour qu’ils partent). Réponse : {"success": .
PartagesLien vers la section Partages
Une base de connaissances (knowledge) ou un agent (agent) de l’organisation se partage avec des groupes, des rôles ou des étiquettes, et peut recevoir une étiquette de sensibilité. Un resource_type inconnu répond 404.
L’objet partageLien vers la section L’objet partage
{
"resource_type": "knowledge",
"resource_id": "4f2a8c1e-6b3d-4e5f-9a7b-0c1d2e3f4a5b",
"name": "Procédures RH",
"owner_id": "91b3d5f7-2a4c-4e6f-8b0d-1e3f5a7c9b2d",
"sharing": { "all": false, "group_ids": ["2d4c6e8a-1b3d-4f5a-9c7e-0a2b4c6d8e1f"], "role_keys": [], "label_ids": [] },
"sensitivity_label_id": null,
"updated_at": 1793452000
}Lister les partagesLien vers la section Lister les partages
GET / · resources:read
Paramètres : resource_type (knowledge ou agent, facultatif), page, limit. Les plus récemment modifiés d’abord. Chaque élément porte resource_type, resource_id, name (null si la ressource n’existe plus), sharing, sensitivity_label_id et updated_at.
Lister ce qu’on peut partagerLien vers la section Lister ce qu’on peut partager
GET / · resources:read
Paramètres : resource_type (knowledge par défaut, ou agent), q (recherche dans le nom, 128 caractères), page, limit. Chaque élément : id, name, owner_id, owner_name. Les agents listés sont ceux que leur propriétaire a partagés avec le workspace.
Lire, définir et retirer un partageLien vers la section Lire, définir et retirer un partage
GET / · resources:read
PUT / · resources:write · organisation gérée
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
sharing | objet | non | Bloc de partage ; absent : personne. Remplace le partage entier. |
sensitivity_label_id | chaîne | non | Étiquette de sensibilité ; absente ou null : retirée. |
Pour une base de connaissances, ses accès sont réécrits : all: false retire la lecture par tout le workspace, même ouverte par son propriétaire ; les partages à des personnes restent. Avec une étiquette de sensibilité, seuls les rôles de role_keys (à défaut propriétaire et administrateurs) la lisent et la modifient.
Un agent se partage avec le workspace depuis sa page « Agents » ; ici, on ne choisit que les rôles qui peuvent utiliser un agent confidentiel.
| Statut | code | Cause |
|---|---|---|
| 400 | agent_share_all_unsupported | all: true sur un agent. |
| 400 | invalid_request | Groupes ou étiquettes sur un agent. |
| 400 | role_share_needs_sensitivity | Rôles sans étiquette de sensibilité. |
DELETE / · resources:write
Retire le partage par l’organisation. Réponse : {"success": ; 404 s’il n’y en a pas.
Lister les destinataires possiblesLien vers la section Lister les destinataires possibles
GET / · resources:read
| Paramètre | Type | Description |
|---|---|---|
kind | chaîne | groups (par défaut), labels (étiquettes de membres), sensitivity (étiquettes de sensibilité) ou roles. |
q | chaîne | Recherche dans le nom (ou la clé et la valeur d’une étiquette), 128 caractères. |
page, limit | entier | Pagination. |
Pour roles, items donne les rôles créés (role_key custom:<id>, name) et, en première page, builtin donne les cinq rôles fournis avec leur nom selon le type d’organisation.