# Ressources

> Ajouter, tester et partager les serveurs MCP de l’organisation, configurer son stockage de fichiers et partager bases de connaissances et agents par l’API.

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://chat.realmoon.ai/api/v1/orgs/{org_id}`. 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](https://realmoon.ai/documentation/organisations/organisations-gerees/) (sinon `409 org_not_managed`).

## Le bloc de partage

Les serveurs MCP et les partages utilisent le même objet `sharing` :

```json
{ "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 not_found` (« 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 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: true`), et que leurs règles l’autorisent. Une organisation a 50 serveurs au plus. Le parcours : créer, tester puis activer.

### L’objet serveur MCP

```json
{
  "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: "changed"`) 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 MCP

`GET /resources/mcp` · `resources:read`

Paramètres : `page`, `limit`. Tri par nom. La réponse ajoute `max_servers` (50).

### Ajouter un serveur MCP

`POST /resources/mcp` · `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 <jeton>`. |
| `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>": "ask"}`, avec `ask`, `auto` ou `never`, 200 entrées au plus. |
| `sharing` | objet | non | [Bloc de partage](https://realmoon.ai/documentation/api-organisations/ressources/#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é.

```bash title="cURL"
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}
  }'
```

```python title="Python"
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()
```

```javascript title="JavaScript"
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 MCP

`GET /resources/mcp/{server_id}` · `resources:read`

Rend l’objet serveur. `404 not_found` pour un serveur inconnu.

### Modifier un serveur MCP

`PATCH /resources/mcp/{server_id}` · `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_name` ou le jeton remet `status` à `untested` : le serveur n’est plus proposé jusqu’au prochain test réussi. Changer l’adresse, `auth` ou `transport` supprime aussi les connexions personnelles des membres.
- `sharing: null` retire le partage. Pour un serveur OAuth, retirer le partage d’un membre supprime sa connexion.
- `enabled: true` demande une organisation gérée et un dernier test réussi, sinon `409 test_required` (« Testez le serveur avant de l’activer. »). `enabled: false` est toujours permis.

### Supprimer un serveur MCP

`DELETE /resources/mcp/{server_id}` · `resources:write`

Supprime le serveur, son journal d’appels, les connexions des membres et son étiquette. Réponse : `{"success": true}`.

### Tester un serveur MCP

`POST /resources/mcp/{server_id}/test` · `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 rate_limited`).

| 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 OAuth

`POST /resources/mcp/{server_id}/oauth/start` · `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 appels

`GET /resources/mcp/{server_id}/calls` · `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. |

```json
{
  "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’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 stockage

Sans stockage, la réponse est `{"configured": false}`. Sinon :

```json
{
  "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 stockage

`GET /resources/storage` · `resources:read`

### Configurer le stockage

`PUT /resources/storage` · `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: true` et `disabled_by_change`. Pour l’activer : testez-le, puis renvoyez la même configuration avec `enabled: true`.

| 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: true` sans test réussi. |
| 409 | `org_not_managed` | Création dans une organisation non gérée. |

### Tester le stockage

`POST /resources/storage/test` · `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 org_storage_test_failed` 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 stockage

`DELETE /resources/storage` · `resources:write`

Retire la configuration, sans supprimer aucun objet du bucket. Refusé tant que des fichiers y sont (`409 storage_in_use` : désactivez-le plutôt) ou que des objets attendent d’être supprimés (`409 storage_pending_deletes`, avec `pending` : testez-le d’abord pour qu’ils partent). Réponse : `{"success": true}`.

## 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 partage

```json
{
  "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 partages

`GET /resources/shares` · `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 partager

`GET /resources/shares/candidates` · `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 partage

`GET /resources/shares/{resource_type}/{resource_id}` · `resources:read`

`PUT /resources/shares/{resource_type}/{resource_id}` · `resources:write` · organisation gérée

| Champ | Type | Obligatoire | Description |
| --- | --- | --- | --- |
| `sharing` | objet | non | [Bloc de partage](https://realmoon.ai/documentation/api-organisations/ressources/#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/shares/{resource_type}/{resource_id}` · `resources:write`

Retire le partage par l’organisation. Réponse : `{"success": true}` ; `404` s’il n’y en a pas.

### Lister les destinataires possibles

`GET /resources/audience` · `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.

## Voir aussi

- [Groupes](https://realmoon.ai/documentation/api-organisations/groupes/), [Rôles](https://realmoon.ai/documentation/api-organisations/roles/), [Étiquettes](https://realmoon.ai/documentation/api-organisations/etiquettes/)
- [Organisations gérées](https://realmoon.ai/documentation/organisations/organisations-gerees/)

---

Documentation Moon AI › API des organisations › Ressources. Page : https://realmoon.ai/documentation/api-organisations/ressources/. Modifiée le 5 novembre 2026.

© 2026 Stellarr Studio SAS. Tous droits réservés. Moon AI, Moon Blur et Stellarr Studio sont des marques déposées à l’INPI.
