1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Agent SDK + MCPで業務エージェントを作る — 本番運用中のシェル実装から移植した最小構成

1
Posted at

はじめに

当社(合同会社ジョインクラス)では、営業リード収集・SNS監視・記事補充などの業務を、launchd の定期ジョブ17個から Claude を無人実行して回しています。自動化率はおよそ98%です。

この記事では、本番で動かしている claude -p のシェルラッパー を出発点にして、Claude Agent SDK(TypeScript)+ MCP で同じ業務エージェントを組む最小構成を紹介します。読み終わったら、その日のうちに「MCPツールを1つ持ち、予算上限つきで無人実行できるエージェント」を手元で動かせるはずです。

前提

  • Node.js 20 以上
  • Claude Code にログイン済み、または ANTHROPIC_API_KEY を設定済み
  • macOS / Linux(定期実行は launchd か cron を使う)

Step 0: いま本番で動いているシェル版

まずは出発点です。当社の全ジョブは、次の共通ラッパーを通して claude を呼んでいます(実ファイル .company/scripts/lib/claude-run.sh から抜粋)。

lib/claude-run.sh
#!/bin/bash
# claude を無人実行するときの共通ラッパー。実コストを記録する。
# 注意: --bare は認証を読まないため "Not logged in" で失敗する。使わない。

_CLAUDE_RUN_LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

claude_run() {
  local label="$1"; shift
  claude --output-format json \
         --fallback-model claude-sonnet-5 \
         --max-budget-usd 3 \
         "$@" 2>/dev/null \
    | python3 "$_CLAUDE_RUN_LIB/record-cost.py" "$label"
}

呼び出し側はこうです(auto-lead-collect.sh より)。

auto-lead-collect.sh
source "$(dirname "$0")/lib/claude-run.sh"
claude_run lead-collect --effort low --allowedTools "WebSearch,WebFetch,Write" -p "
  (リード収集の指示)
"

ポイントは3つだけです。

  1. --allowedTools で 使えるツールを列挙(許可リスト方式)
  2. --max-budget-usd で 1回あたりの予算上限
  3. --output-format json で結果とコストを 機械可読で受け取り記録

この3点をそのまま SDK に移植します。

Step 1: プロジェクト作成

mkdir lead-agent && cd lead-agent
npm init -y
npm i @anthropic-ai/claude-agent-sdk zod
npm i -D tsx typescript @types/node

package.json に "type": "module" を追加しておきます。

Step 2: 自前のMCPツールを1つ定義する

業務エージェントで一番効くのは「社内データへの安全な窓口」をMCPツールとして渡すことです。ここではリード候補をTSVに追記するだけのツールを作ります。Write ツールを丸ごと渡すより、書ける場所と形式をツール側で固定できる のが利点です。

src/tools.ts
import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
import { appendFile } from "node:fs/promises";
import { z } from "zod";

const LEADS_FILE = "./leads.tsv";

export const crmServer = createSdkMcpServer({
  name: "crm",
  version: "1.0.0",
  tools: [
    tool(
      "add_lead",
      "リード候補を1件追記する。重複チェックは呼び出し側で行うこと",
      {
        company: z.string().min(1),
        url: z.string().url(),
        reason: z.string().max(200),
      },
      async ({ company, url, reason }) => {
        const line = [new Date().toISOString(), company, url, reason].join("\t");
        await appendFile(LEADS_FILE, line + "\n");
        return { content: [{ type: "text", text: `added: ${company}` }] };
      },
    ),
  ],
});

zod スキーマがそのままツールの入力定義になるので、モデルが URL でない値を渡すとツール実行前に弾かれます。

Step 3: エージェント本体

src/agent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";
import { crmServer } from "./tools.js";

const prompt = `
中小企業向けAI業務自動化に関心がありそうな企業を3社探し、
crm の add_lead ツールで1社ずつ登録してください。
`;

for await (const message of query({
  prompt,
  options: {
    mcpServers: { crm: crmServer },
    // シェル版の --allowedTools に相当。MCPツールは mcp__<server>__<tool> 形式
    allowedTools: ["WebSearch", "WebFetch", "mcp__crm__add_lead"],
    maxTurns: 20,
    maxBudgetUsd: 3, // シェル版の --max-budget-usd 3
    fallbackModel: "claude-sonnet-5",
  },
})) {
  if (message.type !== "result") continue;

  // シェル版の record-cost.py に相当
  console.log(JSON.stringify({
    label: "lead-collect",
    subtype: message.subtype,
    cost_usd: message.total_cost_usd,
    turns: message.num_turns,
  }));
  if (message.subtype !== "success") process.exit(1);
}

実行します。

npx tsx src/agent.ts
cat leads.tsv

leads.tsv に3行入り、最後に {"label":"lead-collect","subtype":"success","cost_usd":...} が出れば完成です。

Step 4: 外部のMCPサーバーをつなぐ

自前ツールと同じ mcpServers に、stdio の外部サーバーも並べられます。当社は note の操作を MCP サーバー経由で行っているので、例えば次のようになります。

mcpServers: {
  crm: crmServer,
  "note-api": {
    command: "node",
    args: ["/path/to/note-mcp-server/build/index.js"],
  },
},
allowedTools: ["mcp__crm__add_lead", "mcp__note-api__search-notes"],

読み取り系のツールだけを列挙する のがコツです。サーバーが投稿系ツールを持っていても、allowedTools に書かなければ使われません。

落とし穴(全部踏みました)

1. 「確認なし送信」は必ず事故る

以前、AIが生成した営業メールを確認なしで自動送信し、取引先に敬語のおかしいメールが届いたことがあります。それ以来、対外送信するツールはエージェントに渡さず、ドラフトをファイルに書かせて承認キューに積む構成にしています。上の add_lead が「追記だけ」なのはこのためです。

2. 危険なコマンドは許可リストで閉じる

AIが git push --force を実行してブランチが消えたこともありました。Bash を渡すなら、allowedTools: ["Bash(git status)"] のようにコマンド単位で絞ってください。業務エージェントなら、そもそも Bash を渡さずMCPツールに寄せるほうが安全です。

3. コストは「呼び出し回数」でしか削れない

実測すると、「2+2」を聞くだけの1回でも $0.27〜0.47 かかっていました。内訳の大半はキャッシュ作成の約41,000トークンで、自社の CLAUDE.md・エージェント・スキルは5,800トークンしかありません。プロンプトを削るより、1回の実行でまとめて処理させるほうが効きます。maxBudgetUsd は暴走時の保険として必ず付けましょう。

4. cron は macOS で黙って死ぬ

フルディスクアクセスの問題で cron ジョブが全滅したことがあり、launchd に全面移行しました。macOS で定期実行するなら最初から launchd を使ってください。

~/Library/LaunchAgents/jp.example.lead-agent.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>jp.example.lead-agent</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/zsh</string><string>-lc</string>
    <string>cd ~/lead-agent && npx tsx src/agent.ts >> agent.log 2>&1</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict><key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer></dict>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/jp.example.lead-agent.plist
launchctl start jp.example.lead-agent   # 即時に1回実行して動作確認
tail -f ~/lead-agent/agent.log

検証チェックリスト

  • allowedTools に無いツールを指示に書いても使われない(例: 「Bashでlsして」と頼んで拒否されるか)
  • maxBudgetUsd: 0.01 にして、subtype が成功以外で終了し exit code 1 になる
  • add_lead に不正なURLを渡させると、ツールが実行されずエラーが返る
  • launchd 経由の実行でもログに結果JSONが出る(ログイン環境の差で認証が切れていないか)

シェル版とSDK版、どちらを使うか

当社の実感では、1プロンプト完結のジョブはシェル版で十分 です。SDK版に移す価値があるのは、

  • 社内データへの窓口をMCPツールとして型付きで固定したいとき
  • 結果メッセージを見て、次の処理を分岐させたいとき

の2つです。いきなり全部をSDKにせず、事故りやすい「書き込み」を持つジョブから移すのがおすすめです。

おわりに

業務エージェントの本質は、AIの能力を制限することではなく「安心して委任するための仕組み」を作ることだと考えています。許可リスト・予算上限・コスト記録の3点さえ押さえれば、最小構成でも十分に本番で回ります。

MCPサーバーを自作して業務エージェントに組み込む手順は、書籍『Claude Code × MCP サーバー開発入門』で、stdio/HTTP サーバーの実装から権限設計まで体系的にまとめています。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?