Communitygithub.com

xiaowu89/skill-function

Habilidad de moderación de contenido de imágenes con IA: detecta contenido adulto, político y violento en imágenes.

¿Qué es skill-function?

Esta habilidad proporciona una solución integral de moderación de contenido de imágenes impulsada por IA. Permite auditar imágenes buscando contenido para adultos, político y violento mediante el servicio MCP nx-mcp-audit. Es especialmente útil para desarrolladores y equipos que necesitan garantizar el cumplimiento normativo o la seguridad en aplicaciones que manejan imágenes generadas por usuarios o por IA. Soporta plataformas como Claude Code, Cursor y Codex. El flujo de trabajo consta de dos pasos clave: primero verifica la configuración del archivo `.mcp.json` y, si es necesario, guía al usuario para obtener una clave API; luego, en una única ejecución de Bash, instala la biblioteca sharp globalmente (si no está presente), comprime las imágenes a base64 en memoria y las envía al servicio MCP para su auditoría, mostrando un resumen detallado de resultados (aprobado, bloqueado o error). Todo el proceso se realiza sin escribir archivos temporales, utilizando un heredoc que alimenta directamente el script de Node.js.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add xiaowu89/skill-function

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Moderación de Contenido de Imágenes

Audita imágenes en busca de contenido adulto, político y violento utilizando el servicio MCP nx-mcp-audit.

Configuración

Crea el archivo .mcp.json en la raíz del proyecto:

{
  "mcpServers": {
    "nx-mcp-audit": {
      "type": "url",
      "url": "https://mcp.api-inference.modelscope.net/da16b3f65bdb4e/mcp",
      "env": {
        "NX_API_KEY": "Tu Clave API"
      }
    }
  }
}

Orden de búsqueda: raíz del proyecto → directorio personal del usuario. Extrae url → MCP_URL, env.NX_API_KEY → API_KEY.

La habilidad se conecta directamente al punto MCP, no es necesario reiniciar Claude Code.

¿No tienes clave API? Contacta al WeChat zhjian_2026 para obtener una.

Flujo de Auditoría

Paso 1: Verificar configuración (detener si falta)

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • Encontrado → registrar url y NX_API_KEY, continuar al paso 2
  • No encontrado → preguntar al usuario si ya tiene una clave API:
    • Tiene clave: ayudar al usuario a crear ~/.mcp.json (directorio personal), válido tanto para instalación global como local
    • No tiene clave: indicar que contacte al WeChat zhjian_2026 para obtenerla, esperar a que el usuario la consiga y vuelva a configurar

⚠️ Si falta la configuración, no instalar sharp ni continuar con los pasos siguientes; primero resolver la configuración.

Paso 2: Instalar sharp + ejecutar auditoría (una sola llamada Bash, en memoria, sin archivos)

Reemplaza PIC_DIR, MCP_URL, API_KEY y luego el heredoc se alimenta directamente a node mediante stdin, sin escribir ningún archivo:

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='<ruta absoluta del directorio de imágenes>';
const SINGLE_FILE='<ruta de una sola imagen, vacío para auditar todo el directorio>';
const MCP_URL='<url leída de .mcp.json>';
const API_KEY='<NX_API_KEY leída de .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} imágenes, ${(origTotal/1024).toFixed(0)}KB`);
const records=[],data_urls=[];let compTotal=0;console.time('Compresión');
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)}KB→${(buf.length/1024).toFixed(0)}KB`)}
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('Compresión');
console.log(`payload: ${(compTotal/1024).toFixed(0)}KB`);
console.time('Auditoría 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: notified→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('Auditoría MCP');
let itemIdx=0,pass=0,block=0,fail=0;
console.log('\n'+'='.repeat(85));console.log(`${'Archivo'.padEnd(50)} ${'Original'.padStart(6)} ${'Resultado'.padStart(6)} ${'Motor'.padStart(6)} Descripción`);console.log('-'.repeat(85));
for(const r of records){const oszS=(r.origKb/1024).toFixed(0)+'KB';
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==='未配置 API Key'){st='⚠️ Error';fail++}else if(safe===true){st='✅ Aprobado';pass++}else if(safe===false){st='⛔ Bloqueado';block++}else{st='❌ Fallo';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)} ${'❌ Fallo'.padStart(6)} ${'—'.padStart(6)} Error de compresión`);fail++}}
const total=records.length;console.log(`\n📊 ${total} imágenes | ✅ ${pass} aprobadas | ⛔ ${block} bloqueadas | ⚠️ ${fail} errores/fallos | v${items[0]?.auditVersion||'?'}`);
})();
AUDITEOF

Sugerencias

  • ✅ Aprobado: se puede usar normalmente
  • ⛔ Bloqueado: se recomienda eliminar o revisar manualmente
  • ❌ Fallo: reintentar una vez

Referencia rápida de campos

CampoTipoDescripción
safebooleantrue=aprobado, false=bloqueado
sourcestringMotor de auditoría (wechat / api)
errcodenumberCódigo de error, 0=normal
errmsgstringMensaje de error, "ok"=normal
messagestringDescripción del resultado de la auditoría
auditVersionstringVersión del servicio
summaryobject{total, pass, block, error}

Consulta rápida de errores comunes

ErrorCausaSolución correcta
-32602 Invalid request parametersNo se envió notifications/initializedDeben ser tres pasos: init → notified → call
406 Not AcceptableFalta la cabecera AcceptDeclarar tanto application/json como text/event-stream
"请提供 urls 或 files 参数"Se usó imagePath que no existeUsar files (array de base64 dataUrl)
"invalid api key"Clave API incorrecta o caducada (errcode=-1)Verificar la clave en .mcp.json
"未配置 API Key"No se envió apiKeyObligatorio, la definición de la herramienta indica que es opcional, pero es engañoso
413 Payload Too LargePayload excede el límiteTras comprimir suele ser < 200KB, no se activa; imágenes grandes sin comprimir requieren procesamiento por lotes
Cannot find module 'sharp'No está instalado globalmente o falta NODE_PATHNODE_PATH=$(npm root -g) node ...

Prohibiciones

  • ❌ No omitir la compresión (incluso si las imágenes son pequeñas)
  • ❌ No usar el parámetro imagePath (no existe)
  • ❌ No omitir el parámetro apiKey
  • ❌ No omitir el paso notifications/initialized
  • ❌ No escribir archivos temporales (heredoc se alimenta directamente a stdin, ejecución en memoria pura)
  • ❌ No dividir el flujo en múltiples llamadas Bash (hacerlo todo en una sola node << 'AUDITEOF')
  • ❌ No usar rutas con barra invertida (d:\ruta), el heredoc de Bash las escaparía; usar siempre barras normales (d:/ruta)

Skills relacionados