xiaowu89

image-audit

Detects adult, political and violent content in images via automated batch auditing. Compresses large images, calls MCP audit service, and outputs results as a table. Use when auditing images, checking image content, scanning photos for inappropriate material, or when the user says audit images, review pictures, check content, or image moderation.

xiaowu89 12 Updated 1mo ago
GitHub

Install

npx skillscat add xiaowu89/skill-function/dist-qoder

Install via the SkillsCat registry.

SKILL.md

Image Content Moderation

Audit images for adult, political, and violent content using the nx-mcp-audit MCP service.

配置

在项目根目录创建 .mcp.json

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

查找顺序:项目根目录 → 用户家目录。提取 urlMCP_URLenv.NX_API_KEYAPI_KEY

Skill 直连 MCP 端点,无需重启 Claude Code

No API Key? Contact WeChat zhjian_2026 to get one.

审核流程

步骤 1:检查配置(缺少则立即停止)

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • 找到 → 记录 urlNX_API_KEY,继续步骤 2
  • 找不到 → 询问用户是否已有 API Key:
    • 有 Key:帮用户创建 ~/.mcp.json(用户家目录),全局和项目安装都通用
    • 没有 Key:告知联系微信 zhjian_2026 获取,等用户拿到后回来配置

⚠️ 配置缺失时不要安装 sharp 或继续后续步骤,先解决配置再往下走。

步骤 2:安装 sharp + 执行审核(一次 Bash 调用,纯内存,零文件)

替换 PIC_DIRMCP_URLAPI_KEY 后,heredoc 直接通过 stdin 喂给 node,不写任何文件

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='<目标图片目录绝对路径>';
const SINGLE_FILE='<单张图片路径,为空则审核整个目录>';
const MCP_URL='<从.mcp.json读取的url>';
const API_KEY='<从.mcp.json读取的NX_API_KEY>';
(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(`共 ${imgs.length} 张,总 ${(origTotal/1024).toFixed(0)}KB`);
const records=[],data_urls=[];let compTotal=0;console.time('压缩');
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('压缩');
console.log(`payload: ${(compTotal/1024).toFixed(0)}KB`);
console.time('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('MCP审核');
let itemIdx=0,pass=0,block=0,fail=0;
console.log('\n'+'='.repeat(85));console.log(`${'文件'.padEnd(50)} ${'原始'.padStart(6)} ${'结果'.padStart(6)} ${'引擎'.padStart(6)} 说明`);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='⚠️ 错误';fail++}else if(safe===true){st='✅ 通过';pass++}else if(safe===false){st='⛔ 违规';block++}else{st='❌ 失败';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)} ${'❌ 失败'.padStart(6)} ${'—'.padStart(6)} 压缩失败`);fail++}}
const total=records.length;console.log(`\n📊 ${total} 张 | ✅ ${pass} 通过 | ⛔ ${block} 违规 | ⚠️ ${fail} 错误/失败 | v${items[0]?.auditVersion||'?'}`);
})();
AUDITEOF

建议

  • 通过:可正常使用
  • 违规:建议删除或人工复核
  • 失败:重试一次

返回字段速查

字段 类型 说明
safe boolean true=通过,false=违规
source string 审核引擎(wechat / api
errcode number 错误码,0=正常
errmsg string 错误信息,"ok"=正常
message string 审核结果描述
auditVersion string 服务版本号
summary object {total, pass, block, error}

常见错误速查

错误现象 原因 正确做法
-32602 Invalid request parameters 未发送 notifications/initialized 必须三步:init → notified → call
406 Not Acceptable 缺少 Accept 同时声明 application/jsontext/event-stream
"请提供 urls 或 files 参数" 用了不存在的 imagePath 改用 files(base64 dataUrl 数组)
"invalid api key" API Key 错误或过期(errcode=-1) 检查 .mcp.json 中的 Key 是否正确
"未配置 API Key" 没传 apiKey 必须传,工具定义说可选是误导
413 Payload Too Large payload 超限 压缩后通常 < 200KB,不触发;未压缩大图需分批
Cannot find module 'sharp' 未全局安装或缺少 NODE_PATH NODE_PATH=$(npm root -g) node ...

禁止事项

  • ❌ 不要跳过压缩(即使图片很小)
  • ❌ 不要使用 imagePath 参数(不存在)
  • ❌ 不要省略 apiKey 参数
  • ❌ 不要省略 notifications/initialized 步骤
  • ❌ 不要写任何临时文件(heredoc 直接喂 stdin,纯内存执行)
  • ❌ 不要把流程拆成多次 Bash 调用(一次 node << 'AUDITEOF' 搞定)
  • ❌ 不要使用反斜杠路径(d:\path),bash heredoc 会被转义,一律用正斜杠(d:/path