Communitygithub.com

xiaowu89/skill-function

AI 기반 이미지 콘텐츠 심사 스킬 — 이미지에서 성인, 정치, 폭력 콘텐츠를 감지합니다.

skill-function란 무엇인가요?

이 스킬은 nx-mcp-audit MCP 서비스를 활용하여 이미지 내 성인, 정치, 폭력 콘텐츠를 자동으로 심사합니다. 이미지를 로컬에서 최대 500×500 픽셀, JPEG 품질 40%로 압축한 후 base64 data URL로 변환하여 MCP 엔드포인트에 전송하므로 개인정보 유출 위험이 낮고 처리 효율이 높습니다. 한 번의 Bash heredoc 호출로 전체 심사 과정을 완료하며, 별도의 임시 파일을 생성하지 않습니다. .mcp.json 설정 파일을 통해 API 키와 엔드포인트 URL을 안전하게 관리하고, Claude Code 환경에서 바로 실행 가능하며 추가 도구 설치가 필요 없습니다. 주요 사용 사례로는 디자이너가 업로드한 이미지의 안전성 검수, 게시판 이미지의 자동 검열, 사용자 프로필 사진의 정책 위반 여부 확인 등이 있습니다.

지원 대상✓Claude Code~Codex CLI~Cursor
npx skills add xiaowu89/skill-function

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

이미지 콘텐츠 심사

nx-mcp-audit MCP 서비스를 사용하여 이미지의 성인, 정치, 폭력 콘텐츠를 감사합니다.

설정

프로젝트 루트 디렉토리에 .mcp.json 파일을 생성합니다:

{
  "mcpServers": {
    "nx-mcp-audit": {
      "type": "url",
      "url": "https://mcp.api-inference.modelscope.net/da16b3f65bdb4e/mcp",
      "env": {
        "NX_API_KEY": "여기에 API Key를 입력"
      }
    }
  }
}

검색 순서: 프로젝트 루트 → 사용자 홈 디렉토리. url은 MCP_URL로, env.NX_API_KEY는 API_KEY로 추출됩니다.

스킬이 MCP 엔드포인트에 직접 연결되므로 Claude Code를 재시작할 필요가 없습니다.

API Key가 없으신가요? WeChat zhjian_2026으로 연락하여 키를 받으세요.

심사流程

단계 1: 설정 확인 (설정이 없으면 즉시 중단)

cat .mcp.json 2>/dev/null || cat ~/.mcp.json 2>/dev/null
  • 찾음 → url과 NX_API_KEY를 기록하고 단계 2로 진행
  • 못 찾음 → 사용자에게 API Key가 있는지 질문:
    • 키 있음: 사용자의 ~/.mcp.json (홈 디렉토리) 생성 도움, 전역 및 프로젝트 설치에 모두 적용
    • 키 없음: WeChat zhjian_2026으로 연락하여 키를 받도록 안내하고, 사용자가 키를 받은 후 설정하도록 기다림

⚠️ 설정이 없으면 sharp를 설치하거나 이후 단계를 진행하지 말고, 먼저 설정을 해결한 후에 진행하세요.

단계 2: sharp 설치 + 심사 실행 (한 번의 Bash 호출, 순수 메모리, 파일 생성 없음)

PIC_DIR, MCP_URL, API_KEY를 교체한 후, heredoc을 통해 node에 stdin으로 전달하며 파일을 전혀 쓰지 않습니다:

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

권장사항

  • ✅ 통과: 정상 사용 가능
  • ⛔ 위반: 삭제 또는 수동 재심사 권장
  • ❌ 실패: 한 번 재시도

반환 필드 참고

필드타입설명
safebooleantrue=통과, false=위반
sourcestring심사 엔진 (wechat / api)
errcodenumber에러 코드, 0=정상
errmsgstring에러 메시지, "ok"=정상
messagestring심사 결과 설명
auditVersionstring서비스 버전 번호
summaryobject{total, pass, block, error}

일반 오류 참고

오류 현상원인올바른 조치
-32602 Invalid request parametersnotifications/initialized를 보내지 않음반드시 3단계: init → notified → call
406 Not AcceptableAccept 헤더 누락application/json과 text/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페이로드 크기 초과압축 후 보통 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)를 사용하십시오.

관련 스킬