content-studio
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@content-studioRead my ideas and turn the first one into a community post."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
content-studio (nom provisoire)
Studio de contenu open source (AGPL-3.0). Une idée entre, elle ressort déclinée par canal, persona et direction artistique — écrite par TON agent IA (Claude Code ou autre) connecté en MCP. L'outil n'appelle jamais de modèle : ton abonnement IA travaille, l'outil orchestre.
Self-host
git clone <repo> && cd content-studio
BETTER_AUTH_SECRET=$(openssl rand -hex 32) docker compose up -d --build
# http://localhost:3003 → créer un compte (workspace + 3 canaux créés automatiquement)Related MCP server: creatorforge
Sécurité / déploiement
Par défaut, l'app n'écoute QUE sur 127.0.0.1 — npm run dev et npm run start
(exécution bare, hors Docker) bindent explicitement le loopback. Un déploiement via
docker compose reste identique à l'usage (le conteneur écoute en interne sur toutes ses
interfaces, requis par le NAT de publication de port de Docker) mais le port publié côté
hôte, docker-compose.yml, est lui aussi restreint à 127.0.0.1:3003:3003 — même schéma que
postgres juste au-dessus dans le même fichier. Dans les deux cas, l'app est injoignable
depuis le réseau/internet tant qu'aucune étape supplémentaire n'a été prise.
Exposer l'instance au-delà de ta propre machine = deux étapes, en conscience :
Un reverse-proxy (nginx, Caddy, Traefik…) devant
127.0.0.1:3003, avec TLS. Ne jamais remapper le port Docker sur0.0.0.0directement.DISABLE_SIGNUP=1(outrue) dans l'environnement, une fois ton compte owner créé. Signup reste ouvert par défaut (pratique en dev local mono-utilisateur) : combiné à un port joignable, un signup ouvert permet à n'importe qui de créer un workspace puis de configurerlaneCommand(Réglages → Lanes) — voir l'avertissement ci-dessous, c'est l'exécution de commande à distance. Le tout premier compte créé sur une base encore vide reste toujours autorisé, mêmeDISABLE_SIGNUP=1posé avant le premier démarrage (bootstrap : impossible sinon de créer le compte owner lui-même).
Rappel qui vaut pour toute la section Lanes ci-dessous : les lanes exécutent des commandes
sur l'hôte qui fait tourner le serveur. Sur cette version, self-host, l'accès à l'instance
(un compte + un token, ou une session) EST un accès shell à cette machine, au travers de
laneCommand. Ce n'est pas un bug à corriger plus tard, c'est l'architecture (cf. section
Lanes, « Self-host uniquement ») — raison de plus pour ne jamais exposer une instance en
signup ouvert.
Dev
docker compose up -d postgres
cp .env.example .env.local # éditer BETTER_AUTH_SECRET
npm i && npm run db:migrate && npm run devBrancher ton agent (MCP)
UI → Tokens MCP → Créer (le token n'est montré qu'une fois)
claude mcp add --transport http content-studio http://localhost:3003/api/mcp --header "Authorization: Bearer cs_…"Dans ta session : « lis mes idées et décline la première en post communauté »
Outils exposés : list_ideas, get_idea, list_channels, list_personas, get_art_direction, create_content_draft, get_content, update_content, register_asset (v1.1).
Pendant que tu édites dans l'UI, une écriture de l'agent devient une « proposition » avec diff à accepter — jamais d'écrasement silencieux.
Extension Chrome (clipper)
Une page ou une sélection de texte → une idée + une source dans l'inbox, en un clic. MV3, vanilla JS, zéro build.
Installation (mode développeur)
chrome://extensions→ activer Mode développeur (en haut à droite)Charger l'extension non empaquetée → sélectionner le dossier
extension/de ce repoÉpingler l'icône (facultatif) pour l'avoir dans la barre d'outils
Configuration
Cliquer sur l'icône de l'extension → popup
Renseigner l'URL de l'instance (ex.
http://localhost:3003) et le token MCP (cs_…, généré via UI → Réglages → Tokens MCP)Enregistrer — une permission d'accès à cette origine est demandée à ce moment précis (
chrome.permissions.request, cf. « Choix technique » ci-dessous). Accepter la demande.
Usage
Bouton popup « Clipper cette page » : envoie l'URL + le titre de l'onglet actif, et le texte sélectionné s'il y en a un (lu via
chrome.scripting.executeScript)Menu contextuel « Clipper la sélection » : sélectionner du texte sur n'importe quelle page → clic droit → « Clipper la sélection ». Un badge ✓ (succès) ou ✗ (échec) s'affiche 3s sur l'icône
Le popup affiche l'état : succès avec lien direct vers l'idée (
/ideas/<id>), ou erreur lisible (« Token invalide », « Instance injoignable », etc.)
Choix technique : permissions minimales, pas de host_permissions large
Le manifest ne déclare que activeTab, scripting, storage, contextMenus — jamais un
host_permissions couvrant tout le web. Le fetch cross-origin vers l'instance configurée
(depuis le popup et le service worker) est rendu possible par optional_host_permissions
(http://*/*, https://*/*) demandée à la volée uniquement au moment où l'utilisateur
enregistre l'URL de SON instance — jamais au chargement de l'extension.
Filet de secours côté serveur : si la permission est refusée (ou pour tout appel qui déclenche
quand même un preflight), src/app/api/clip/route.ts répond aux requêtes OPTIONS et pose
Access-Control-Allow-* sur ses réponses, mais seulement quand l'Origin commence par
chrome-extension:// — jamais un Access-Control-Allow-Origin: *, le reste de l'app reste
same-origin only.
Limite connue de la vérification automatisée
Chrome a retiré le flag --load-extension des builds Chrome (branded) sur canal stable — un
unpacked ne peut plus être chargé en ligne de commande pour un test scripté classique. La
commande CDP Extensions.loadUnpacked existe mais charge l'extension dans un contexte isolé
de l'automatisation, invisible depuis une fenêtre normale (chrome-extension://<id>/... répond
net::ERR_BLOCKED_BY_CLIENT hors de ce contexte) — piloter le vrai popup via CDP n'est donc pas
possible dans cet environnement. La vérification réelle effectuée : le POST exact de clip.js
(mêmes headers/body) rejoué en Node contre le serveur de dev réel avec un token réel a créé une
idée + source vérifiées en base ; l'installation manuelle (chrome://extensions → mode
développeur → charger l'extension non empaquetée) reste à faire une fois par l'utilisateur pour
valider le popup et le menu contextuel eux-mêmes.
Jauges (abonnements IA multi-comptes)
Le header affiche l'état des abonnements IA que TES agents consomment. L'outil n'appelle
jamais de modèle et ne connaît aucun provider : il interroge des endpoints que tu
configures toi-même (Réglages → Jauges), rien d'autre.
Contrat de payload
Chaque source répond en JSON, sur ce contrat (documenté ici, volontairement minimal) :
{
"accounts": [
{ "id": "compte-1", "usedPercent": 87, "resetAt": "2026-08-10T00:00:00Z", "available": true },
{ "id": "compte-2", "usedPercent": 12, "resetAt": "2026-08-12T00:00:00Z", "available": true }
],
"costMonthlyEur": 378
}accounts[](optionnel, 50 entrées max) — unid(obligatoire),usedPercent(0–100),resetAt(chaîne parseable en date, ex. ISO 8601 — un format non parseable n'affiche simplement pas de date, jamais d'erreur) etavailable(booléen), tous optionnels saufid.costMonthlyEur(optionnel, nombre) : coût mensuel de CETTE source.
Multi-comptes natif : chaque entrée de accounts[] devient un segment de jauge séparé —
un /health de bridge local qui expose plusieurs comptes en pool (ex. un pool round-robin de
clés) s'affiche tel quel, un segment par compte. Le chiffre affiché à côté de la barre est le
compte disponible le plus consommé (celui qui bloque en premier) ; un compte available: false s'affiche hachuré et sort du calcul.
La tuile Coût / mois du header additionne les costMonthlyEur de toutes les sources
kind: "cost" activées.
Un payload qui ne respecte pas ce contrat (mauvais type, usedPercent hors [0,100], plus de
50 comptes, champ requis absent…) est un échec de parsing : la source passe grise
(« injoignable », erreur visible au survol), jamais une erreur bloquante pour le reste du
cockpit. Champs inconnus dans le payload : ignorés, pas rejetés.
kind
quota: source de comptes/segments de jauge.cost: source de coût mensuel (n'affiche pas de segments, seulementcostMonthlyEur).
Polling
Le serveur interroge chaque source activée à l'affichage de la page, avec un cache de 5 minutes (timeout réseau : 4 s) — pas de cron, pas de polling en tâche de fond. Le header rafraîchit lui-même toutes les 5 minutes, plus un bouton manuel (↻).
localhost autorisé — la vraie protection SaaS est l'egress réseau
En self-host, les bridges locaux de l'utilisateur SONT le cas d'usage : localhost,
127.0.0.1 et [::1] sont explicitement autorisés comme cible de jauge. Le reste de
127.0.0.0/8, 10.*, 192.168.*, 169.254.*, 172.16.0.0/12 (172.16.* à 172.31.*),
100.64.0.0/10 (100.64.* à 100.127.*, plage CGNAT) et 0.0.0.0 écrits en clair dans l'URL
sont refusés à la création — mais cette liste ne couvre QUE les adresses IP littérales : elle
n'empêche pas un nom d'hôte qui résout vers une IP privée (DNS rebinding). Ce n'est pas la
protection réelle d'un déploiement SaaS multi-tenant — celle-là est le firewall d'egress
réseau du déploiement, pas cette validation applicative côté app. Aucune redirection (3xx)
n'est suivie non plus : un endpoint approuvé à la création qui répondrait plus tard par un 302
vers une cible interne, header custom embarqué, ne fait rien passer.
Lanes (conversations agent — self-host uniquement)
Un onglet de conversation avec TON CLI agent local, directement dans l'outil, à côté du
contenu (bouton « 💬 Chat » sur une page contenu, ou l'icône du header). @ dans l'input
référence une idée ou un contenu existant : la sélection insère son contexte (titre + corps,
tronqué à 2000 caractères) dans le message envoyé au CLI. Chaque révision écrite par l'agent
PENDANT qu'une lane est active porte lane:<id> — le panneau révisions du contenu affiche
alors « ouvrir la conversation » dessus.
Self-host uniquement
Contrainte architecturale, pas un choix arbitraire. En self-host, le serveur tourne sur TA
machine : il peut donc spawn ton CLI local (claude -p, codex exec…) — c'est ton abonnement
qui travaille, zéro inférence faite par l'outil lui-même. En SaaS, le serveur ne peut PAS faire
ça (spawn un process sur une machine qu'il ne possède pas) : les lanes y exigeraient un
compagnon local, hors périmètre de cette version.
laneCommand — configurable, jamais en dur
Réglages → Lanes (/settings/workspace) expose un seul champ, laneCommand, par défaut
claude -p --output-format stream-json --verbose. Chaque tour de conversation spawn CETTE
commande, avec le message utilisateur en dernier argument positionnel (précédé de --resume <session_id> dès la 2e conversation, si le CLI le supporte).
Avertissement : cette commande tourne sur TA machine. Le serveur ne fait que
l'orchestrer — il ne l'inspecte pas, ne la sandbox pas. Configurer laneCommand avec une
commande qu'on n'a pas écrite ou vérifiée soi-même revient à exécuter un process arbitraire
côté serveur, avec les droits du process serveur.
Le séparateur -- : un message n'est jamais un flag
Le message est toujours transmis après un -- littéral ([..., "--", message], ou
["--resume", id, "--", message] en reprise) — sans lui, un message commençant par - (ex.
--dangerously-skip-permissions) serait lu comme une OPTION par le parseur d'arguments du CLI
cible (commander.js et consorts), pas comme du texte. Le spawn passe aussi par sh -c '<laneCommand> "$@"' sh ...args : la chaîne interprétée par le shell est FIGÉE (la commande
des réglages) + "$@" littéral — le message arrive en argv séparé, jamais concaténé dans du
texte exécuté par le shell, donc incapable d'en ouvrir une nouvelle commande.
Timeout et cap
Timeout dur : 120 secondes. Au-delà, l'arbre de process entier est tué (SIGTERM puis SIGKILL après 5 s de grâce si le premier signal est ignoré), la lane passe en statut
error.Cap anti-DoS : 2 MiB, sur stdout ET stderr indépendamment. Un CLI qui inonde l'un ou l'autre flux est coupé — la LECTURE s'arrête au cap, pas seulement un kill demandé : même un process qui ignore SIGTERM pendant toute la fenêtre de grâce ne fait pas grossir le buffer au-delà.
Le verrou (une seule exécution à la fois par lane, 409 sur un 2e message concurrent) est TOUJOURS relâché en sortie, quel que soit le chemin (succès, erreur CLI, timeout, cap dépassé).
Tests
npm run test # vitest single-run, db content_studio_testLimites connues (v1)
Le Dockerfile self-host copie
.next+node_modulesen entier (pas deoutput: "standalone"dansnext.config.ts) : image plus grosse que nécessaire, assumé pour rester simple en v1.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceOpen-source MCP server that turns any CMS backend into an AI-agent-ready content management system. It provides tools for content CRUD, idea management, and publication tracking, with safety features requiring human approval for publishing.MIT
- FlicenseNot gradedqualityAmaintenanceMCP server for AI content generation, enabling agents to learn a voice, generate content ideas, write scripts, and produce multi-platform content deterministically.
- AlicenseAqualityBmaintenanceA content studio MCP server for coding agents that generates media, renders videos and carousels deterministically, and publishes with a draft-first outbox.14374MIT
- AlicenseAqualityBmaintenanceMCP server to manage social media accounts from AI assistants, enabling post creation, scheduling, publishing, and media uploads across multiple platforms.131661MIT
Related MCP Connectors
MCP server for QPost — lets AI agents publish video and image posts to YouTube, TikTok, Instagram.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
MCP server for generating rough-draft project plans from natural-language prompts.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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/MattiooFR/content-studio'
If you have feedback or need assistance with the MCP directory API, please join our Discord server