Communitygithub.com

xiaowu89/skill-function

AI駆動の画像コンテンツモデレーションスキル。アダルト、政治的、暴力的なコンテンツを画像から検出します。

skill-function とは?

このスキルは、nx-mcp-audit MCPサービスを利用して画像のコンテンツモデレーションを実行します。アダルト、政治、暴力に関する不適切な画像を検出し、安全なコンテンツ管理を実現します。Claude CodeやCursorなどのAIエージェントに対応しており、ユーザーはプロジェクトのルートディレクトリに.mcp.json設定ファイルを配置するだけで簡単に利用開始できます。設定が存在しない場合でも、ユーザーがAPI Keyを持っていれば自動的に設定ファイルを作成するなど、柔軟な対応が可能です。処理の流れは、まずsharpライブラリを使って画像を圧縮し、その後MCPエンドポイントにデータを送信して審査結果を取得します。すべての処理はメモリ上で完結し、一時ファイルを書き込むことはありません。審査結果は「通過」「違反」「失敗」の3段階で表示され、違反が検出された場合は削除や手動レビューが推奨されます。

対応✓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を所持しているか尋ねる:
    • Keyを持っている場合:ユーザーのホームディレクトリに ~/.mcp.json を作成(グローバルおよびプロジェクトインストールで共通)
    • Keyを持っていない場合:WeChat zhjian_2026 に連絡して入手するよう伝え、ユーザーがKeyを入手したら設定に戻るよう促す

⚠️ 設定がない場合は、sharpをインストールしたり後続のステップに進んだりせず、まず設定を解決してください。

ステップ 2:sharpのインストール + 監査の実行(1回のBash呼び出し、純メモリ、ファイル書き込みゼロ)

PIC_DIR、MCP_URL、API_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(`ペイロード: ${(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 の両方を宣言する
"ファイルのURLまたはパスを指定してください"存在しない 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呼び出しに分割しないこと(1回の node << 'AUDITEOF' で完了させる)
  • ❌ バックスラッシュパス(d:\path)を使用しないこと(bash heredoc内でエスケープされるため)。常にフォワードスラッシュ(d:/path)を使用すること

関連スキル