Modération de contenu image
Auditez les images pour les contenus adultes, politiques et violents à l'aide du service MCP nx-mcp-audit.
Configuration
Créez un fichier .mcp.json à la racine du projet :
{
"mcpServers": {
"nx-mcp-audit": {
"type": "url",
"url": "https://mcp.api-inference.modelscope.net/da16b3f65bdb4e/mcp",
"env": {
"NX_API_KEY": "Votre clé API"
}
}
}
}
Ordre de recherche : racine du projet → répertoire personnel. Extraire url → MCP_URL, env.NX_API_KEY → API_KEY.
La compétence se connecte directement au point d'accès MCP, aucun redémarrage de Claude Code nécessaire.
Pas de clé API ? Contactez WeChat
zhjian_2026pour en obtenir une.
Processus d'audit
Étape 1 : Vérification de la configuration (arrêt immédiat si absente)
cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
- Trouvé → enregistrer
urletNX_API_KEY, passer à l'étape 2 - Pas trouvé → demander à l'utilisateur s'il possède déjà une clé API :
- A une clé : aider l'utilisateur à créer
~/.mcp.json(dans le répertoire personnel, valable globalement et pour le projet) - N'a pas de clé : informer qu'il faut contacter WeChat
zhjian_2026pour en obtenir une, attendre qu'il revienne avec la clé
- A une clé : aider l'utilisateur à créer
⚠️ En cas d'absence de configuration, n'installez pas sharp et ne poursuivez pas les étapes suivantes ; résolvez d'abord la configuration.
Étape 2 : Installation de sharp + exécution de l'audit (un seul appel bash, entièrement en mémoire, zéro fichier)
Après avoir remplacé PIC_DIR, MCP_URL, API_KEY, le heredoc est passé directement via stdin à node, sans écrire aucun fichier :
NODE_PATH=$(npm root -g) node -e "require('sharp')" 2>/dev/null || npm install -g sharp
NODE_PATH=$(npm root -g) node << 'AUDITEOF'
const fs=require('fs'),path=require('path'),sharp=require('sharp');
let PIC_DIR='<chemin absolu du répertoire d'images cible>';
const SINGLE_FILE='<chemin de l'image unique, laisser vide pour auditer tout le répertoire>';
const MCP_URL='<url lue depuis .mcp.json>';
const API_KEY='<NX_API_KEY lue depuis .mcp.json>';
(async()=>{
const exts=['.png','.jpg','.jpeg','.webp','.bmp','.tga'];
let imgs;
if(SINGLE_FILE){imgs=[path.basename(SINGLE_FILE)];PIC_DIR=path.dirname(SINGLE_FILE)}
else{imgs=fs.readdirSync(PIC_DIR).filter(f=>exts.includes(path.extname(f).toLowerCase())).sort()}
const origTotal=imgs.reduce((s,f)=>s+fs.statSync(path.join(PIC_DIR,f)).size,0);
console.log(`Total ${imgs.length} images, ${(origTotal/1024).toFixed(0)} Ko`);
const records=[],data_urls=[];let compTotal=0;console.time('Compression');
for(let i=0;i<imgs.length;i++){const f=imgs[i],fp=path.join(PIC_DIR,f),osz=fs.statSync(fp).size;
try{const buf=await sharp(fp,{limitInputPixels:false}).resize({width:500,height:500,fit:'inside',withoutEnlargement:true}).jpeg({quality:40}).toBuffer();
const url='data:image/jpeg;base64,'+buf.toString('base64');records.push({name:f,origKb:osz,compKb:buf.length,dataUrl:url});
data_urls.push(url);compTotal+=buf.length;console.log(` [${i+1}/${imgs.length}] ${f} ${(osz/1024).toFixed(0)}Ko→${(buf.length/1024).toFixed(0)}Ko`)}
catch(e){records.push({name:f,origKb:osz,compKb:0,dataUrl:null,error:e.message});console.log(` [${i+1}/${imgs.length}] ${f} ❌ ${e.message}`)}}
console.timeEnd('Compression');
console.log(`Payload : ${(compTotal/1024).toFixed(0)} Ko`);
console.time('Audit MCP');
const H={'Content-Type':'application/json','Accept':'application/json, text/event-stream','Authorization':`Bearer ${API_KEY}`};
const r1=await fetch(MCP_URL,{method:'POST',headers:H,body:JSON.stringify({jsonrpc:'2.0',id:'1',method:'initialize',params:{protocolVersion:'2025-06-18',capabilities:{},clientInfo:{name:'cc',version:'1'}}})});
const sid=r1.headers.get('Mcp-Session-Id');H['Mcp-Session-Id']=sid;console.log(`MCP : init→${sid}`);
await fetch(MCP_URL,{method:'POST',headers:H,body:JSON.stringify({jsonrpc:'2.0',method:'notifications/initialized'})});console.log('MCP : notifié→202');
let items=[];
if(data_urls.length>0){const r3=await fetch(MCP_URL,{method:'POST',headers:H,body:JSON.stringify({jsonrpc:'2.0',id:'3',method:'tools/call',params:{name:'nx_img_audit',arguments:{files:data_urls,apiKey:API_KEY}}})});
const raw=await r3.json();const inner=JSON.parse(raw.result.content[0].text);items=inner.items}
console.timeEnd('Audit MCP');
let itemIdx=0,pass=0,block=0,fail=0;
console.log('\n'+'='.repeat(85));console.log(`${'Fichier'.padEnd(50)} ${'Original'.padStart(6)} ${'Résultat'.padStart(6)} ${'Moteur'.padStart(6)} Description`);console.log('-'.repeat(85));
for(const r of records){const oszS=(r.origKb/1024).toFixed(0)+'Ko';
if(r.dataUrl){const item=items[itemIdx++],safe=item.safe,src=item.source||'-';const ec=item.errcode,em=item.errmsg||item.error||'';let st;
if(em==='invalid api key'||em==='Clé API non configurée'){st='⚠️ Erreur';fail++}else if(safe===true){st='✅ Passé';pass++}else if(safe===false){st='⛔ Bloqué';block++}else{st='❌ Échec';fail++}
console.log(`${r.name.padEnd(50)} ${oszS.padStart(6)} ${st.padStart(6)} ${src.padStart(6)} ${em.padStart(8)}`)}
else{console.log(`${r.name.padEnd(50)} ${oszS.padStart(6)} ${'❌ Échec'.padStart(6)} ${'—'.padStart(6)} Échec de compression`);fail++}}
const total=records.length;console.log(`\n📊 ${total} img | ✅ ${pass} passés | ⛔ ${block} bloqués | ⚠️ ${fail} erreurs/échecs | v${items[0]?.auditVersion||'?'}`);
})();
AUDITEOF
Suggestions
- ✅ Passé : peut être utilisé normalement
- ⛔ Bloqué : supprimer ou réviser manuellement
- ❌ Échec : réessayer une fois
Référence rapide des champs retournés
| Champ | Type | Description |
|---|---|---|
safe | boolean | true=passé, false=bloqué |
source | string | Moteur d'audit (wechat / api) |
errcode | number | Code d'erreur, 0=normal |
errmsg | string | Message d'erreur, "ok"=normal |
message | string | Description du résultat d'audit |
auditVersion | string | Version du service |
summary | object | {total, pass, block, error} |
Référence rapide des erreurs courantes
| Erreur observée | Cause | Solution correcte |
|---|---|---|
-32602 Invalid request parameters | notifications/initialized non envoyé | Procédure en trois étapes : init → notified → call |
406 Not Acceptable | En-tête Accept manquant | Déclarer à la fois application/json et text/event-stream |
"Veuillez fournir les paramètres urls ou files" | Utilisation du paramètre imagePath inexistant | Utiliser files (tableau de dataUrl base64) |
"invalid api key" | Clé API erronée ou expirée (errcode=-1) | Vérifier la clé dans .mcp.json |
"Clé API non configurée" | Paramètre apiKey non fourni | Obligatoire, la définition indique "optionnel" par erreur |
413 Payload Too Large | Payload trop volumineux | Après compression, généralement < 200 Ko ; sinon, diviser les images |
Cannot find module 'sharp' | Installation globale absente ou NODE_PATH manquant | NODE_PATH=$(npm root -g) node ... |
Interdictions
- ❌ Ne pas sauter la compression (même pour de petites images)
- ❌ Ne pas utiliser le paramètre
imagePath(inexistant) - ❌ Ne pas omettre le paramètre
apiKey - ❌ Ne pas omettre l'étape
notifications/initialized - ❌ Ne pas écrire de fichiers temporaires (heredoc passé directement via stdin, exécution en mémoire)
- ❌ Ne pas diviser le processus en plusieurs appels bash (faire tout en un seul
node << 'AUDITEOF') - ❌ Ne pas utiliser de chemins avec des barres obliques inversées (
d:\chemin) ; le heredoc bash serait échappé ; utilisez toujours des barres obliques (d:/chemin)