Communitygithub.com

xiaowu89/skill-function

Skill de moderação de conteúdo de imagem com IA — detecta conteúdo adulto, político e violento em imagens

O que é skill-function?

Este skill de moderação de conteúdo de imagem permite auditar imagens em busca de conteúdo adulto, político e violento utilizando o serviço MCP nx-mcp-audit. Ele é projetado para desenvolvedores que utilizam agentes de IA como Claude Code, Cursor ou Codex, e suporta a verificação de múltiplos formatos de imagem (PNG, JPG, WebP, BMP, TGA). O skill funciona em duas etapas principais: primeiro, ele instala globalmente a biblioteca sharp se necessário e, em seguida, executa um script Node.js que redimensiona e comprime as imagens para JPEG (qualidade 40%, máximo 500px) antes de enviá-las em formato base64 para o endpoint MCP. O resultado é um relatório detalhado com status de aprovação, violação ou erro para cada imagem. Uma característica importante é que todo o processo ocorre em memória, sem criar arquivos temporários, e a configuração é feita através de um arquivo `.mcp.json` no diretório do projeto ou do usuário.

Funciona com✓Claude Code~Codex CLI~Cursor
npx skills add xiaowu89/skill-function

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Moderação de Conteúdo de Imagem

Audite imagens para conteúdo adulto, político e violento usando o serviço MCP nx-mcp-audit.

Configuração

Crie o arquivo .mcp.json na raiz do projeto:

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

Ordem de busca: raiz do projeto → diretório home do usuário. Extraia url → MCP_URL, env.NX_API_KEY → API_KEY.

Este skill se conecta diretamente ao endpoint MCP, não sendo necessário reiniciar o Claude Code.

Sem chave de API? Entre em contato pelo WeChat zhjian_2026 para obter uma.

Fluxo de Auditoria

Passo 1: Verificar configuração (pare imediatamente se estiver faltando)

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • Encontrado → registre url e NX_API_KEY, vá para o passo 2
  • Não encontrado → pergunte ao usuário se ele já possui uma chave de API:
    • Com chave: ajude o usuário a criar ~/.mcp.json (diretório home), que funciona tanto para instalação global quanto por projeto
    • Sem chave: informe para contatar o WeChat zhjian_2026 para obter uma e aguarde o usuário retornar para configurar

⚠️ Quando a configuração estiver faltando, não instale o sharp nem prossiga com os próximos passos. Resolva a configuração primeiro.

Passo 2: Instalar sharp + executar auditoria (uma única chamada Bash, totalmente em memória, zero arquivos)

Substitua PIC_DIR, MCP_URL e API_KEY e, em seguida, o heredoc alimenta o node diretamente via stdin, sem escrever arquivo algum:

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='<caminho absoluto do diretório de imagens alvo>';
const SINGLE_FILE='<caminho da imagem única, vazio para auditar todo o diretório>';
const MCP_URL='<url lida do .mcp.json>';
const API_KEY='<NX_API_KEY lido do .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 de ${imgs.length} imagens, ${(origTotal/1024).toFixed(0)}KB`);
const records=[],data_urls=[];let compTotal=0;console.time('Compressão');
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('Compressão');
console.log(`payload: ${(compTotal/1024).toFixed(0)}KB`);
console.time('Auditoria 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: notificado→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('Auditoria MCP');
let itemIdx=0,pass=0,block=0,fail=0;
console.log('\n'+'='.repeat(85));console.log(`${'Arquivo'.padEnd(50)} ${'Original'.padStart(6)} ${'Resultado'.padStart(6)} ${'Motor'.padStart(6)}  Descrição`);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='⚠️ Erro';fail++}else if(safe===true){st='✅ Aprovado';pass++}else if(safe===false){st='⛔ Violação';block++}else{st='❌ Falha';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)} ${'❌ Falha'.padStart(6)} ${'—'.padStart(6)} Falha na compressão`);fail++}}
const total=records.length;console.log(`\n📊 ${total} imagens | ✅ ${pass} aprovados | ⛔ ${block} violações | ⚠️ ${fail} erros/falhas | v${items[0]?.auditVersion||'?'}`);
})();
AUDITEOF

Recomendações

  • ✅ Aprovado: pode ser usado normalmente
  • ⛔ Violação: recomenda-se excluir ou revisar manualmente
  • ❌ Falha: tente novamente

Referência Rápida de Campos

CampoTipoDescrição
safebooleantrue=aprovado, false=violação
sourcestringMotor de auditoria (wechat / api)
errcodenumberCódigo de erro, 0=normal
errmsgstringMensagem de erro, "ok"=normal
messagestringDescrição do resultado da auditoria
auditVersionstringVersão do serviço
summaryobject{total, pass, block, error}

Referência Rápida de Erros Comuns

ErroCausaSolução Correta
-32602 Invalid request parametersNão enviou notifications/initializedDevem ser três passos: init → notified → call
406 Not AcceptableFaltando cabeçalho AcceptDeclare tanto application/json quanto text/event-stream
"请提供 urls 或 files 参数"Usou imagePath inexistenteUse files (array de dataUrl base64)
"invalid api key"Chave de API errada ou expirada (errcode=-1)Verifique se a chave no .mcp.json está correta
"未配置 API Key"Não passou apiKeyObrigatório passar, a definição da ferramenta dizendo opcional é enganosa
413 Payload Too LargePayload excedeu o limiteApós compressão geralmente fica < 200KB, não ocorre; imagens grandes sem compressão precisam ser divididas em lotes
Cannot find module 'sharp'Não instalado globalmente ou faltando NODE_PATHUse NODE_PATH=$(npm root -g) node ...

Proibições

  • ❌ Não pule a compressão (mesmo que as imagens sejam muito pequenas)
  • ❌ Não use o parâmetro imagePath (não existe)
  • ❌ Não omita o parâmetro apiKey
  • ❌ Não pule o passo notifications/initialized
  • ❌ Não escreva nenhum arquivo temporário (heredoc alimenta stdin diretamente, execução puramente em memória)
  • ❌ Não divida o fluxo em múltiplas chamadas Bash (resolva tudo em um único node << 'AUDITEOF')
  • ❌ Não use caminhos com barra invertida (d:\path), pois o heredoc do Bash sofre escape; use sempre barras normais (d:/path)

Habilidades Relacionadas