0
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?

MCPサーバーをAPIラップで終わらせないための実装チェックリスト

0
Posted at

055-qiita-mcp-intent-tool-checklist.png

はじめに

Anthropicが2026年4月22日に公開した記事で、本番エージェントとMCPサーバーの設計パターンが整理されていました。

Building agents that reach production systems with MCP

この記事の中で、特に実装者が気をつけるべきだと思ったのがこれです。

MCPサーバーは、APIを1対1でラップするだけでは弱い。ツールはエンドポイントではなくintent単位で設計する。

この記事では、この考え方を「実装前に見るチェックリスト」に落とします。

私は FORMLOVA というフォームサービスを作っています。FORMLOVAでは、フォーム作成だけではなく、公開後の回答確認、営業メール分類、分析、通知、ワークフローまでをMCP経由で扱えるようにしています。

その実装で学んだことをベースに、既存APIをMCP化するときに見落としやすいポイントをまとめます。

チェックリスト全体

先に結論です。

MCPサーバーを作る前に、最低限これを確認するとよいです。

  1. API名ではなく、ユーザーの作業単位でツールを切っているか
  2. モデルに毎回やらせている後処理を、サーバー側に寄せられないか
  3. ドメインルールをプロンプトではなくツール仕様に埋め込めているか
  4. ラベルやステータスを表示用で終わらせず、次の操作に接続しているか
  5. AIの判断を人間が上書きでき、その上書きを守れるか
  6. 返すべきものがテキストだけで十分か、UIや確認ステップが必要か

以下、順番に見ていきます。


1. エンドポイントをそのままツールにしない

既存APIをMCP化するとき、最初にやりがちな設計です。

server.registerTool("list_responses", ...);
server.registerTool("get_response", ...);
server.registerTool("update_response", ...);
server.registerTool("delete_response", ...);
server.registerTool("export_responses", ...);

これは必要な場面もあります。

ただし、この粒度だけだと、エージェントは毎回APIの組み合わせを考える必要があります。

たとえばユーザーがこう言ったとします。

営業メールを除いて今月の問い合わせ数を見たい

APIラップ型だと、エージェントはだいたいこう動きます。

1. list_responses を呼ぶ
2. 日付で絞る
3. spam_label を読む
4. sales を除外する
5. 件数を集計する

デモなら動きます。

でも本番では、ページング、権限、未分類データ、手動修正済みラベル、失敗時処理、監査ログが入ります。

これを毎回モデルに考えさせるのは不安定です。

Good: intentをパラメータとして表現する

FORMLOVAでは、回答取得や分析系のツールに exclude_sales を持たせています。

server.registerTool("get_responses", {
  inputSchema: {
    form_id: z.number().int(),
    limit: z.number().int().min(1).max(100).default(50),
    exclude_sales: z.boolean().default(false),
  },
});

server.registerTool("get_form_analytics", {
  inputSchema: {
    form_id: z.number().int(),
    exclude_sales: z.boolean().default(false),
  },
});

こうすると、ユーザーの発話はそのままツール引数に落ちます。

営業メールを除いて分析して
{
  "exclude_sales": true
}

小さいパラメータですが、MCP設計としては大事です。

「営業を除く」という業務上の意図が、モデルの後処理ではなく、サーバー側の仕様になります。


2. 除外ルールをモデルに任せない

exclude_sales=true の中身は、単純なようで意外と重要です。

やってはいけないのは、モデル側にこう指示することです。

取得した回答から営業メールっぽいものを除外してください。

これだと、毎回判断が揺れます。

サーバー側では、ルールを固定できます。

if (exclude_sales) {
  query = query.or("spam_label.is.null,spam_label.neq.sales");
}

このルールでは、sales だけを除外します。

suspicious は残します。null も残します。

理由は、判別困難な回答や未分類回答を勝手に消すと、本物の問い合わせを見落とす可能性があるからです。

こういう判断は、MCPツールの説明文やプロンプトにだけ書くより、サーバー側のクエリとして固定したほうが安定します。

チェックポイント

MCPツールの中に、次のような暗黙ルールがないか確認します。

  • どのステータスを含めるか
  • どのラベルを除外するか
  • 未分類をどう扱うか
  • 手動修正を優先するか
  • エラー時に空配列を返すのか、明示的に失敗を返すのか

これらをモデルの推論に任せるほど、本番ではブレます。


3. ラベルは表示用ではなく、操作条件にする

LLM分類を入れると、ついラベル表示で満足しがちです。

type SpamLabel = "legitimate" | "sales" | "suspicious";

ダッシュボードで sales バッジを出す。

これだけだと、分類は見た目の情報で終わります。

MCPサーバーで扱うなら、ラベルを次の操作に接続します。

legitimate  -> 分析に使う / 通知する
sales       -> 分析から除外する / 営業除外リストへ送る
suspicious  -> 人間の確認キューに回す

FORMLOVAでは、分類結果がワークフローのトリガーにもなります。

await executeWorkflows(formId, "response.classified", {
  form_id: formId,
  response_id: responseId,
  spam_score: spamResult.score,
  spam_label: spamResult.label,
});

これで、分類後に条件分岐できます。

when response.classified
if spam_label == "legitimate"
then Slackへ通知

when response.classified
if spam_label == "suspicious"
then 担当者へ確認依頼

when response.classified
if spam_label == "sales"
then 通知しない

ここまで来ると、MCPサーバーが返しているのは「回答データ」ではありません。

回答を次の業務に流すための状態です。

--

4. AIの判断は、人間が上書きできるようにする

LLM分類を本番で使うときに、避けたい失敗があります。

ユーザーが誤判定を直したのに、後から自動分類が再実行されて元に戻ることです。

これは一度起きると信頼を失います。

FORMLOVAでは、ラベルの出所を持たせています。

type LabelSource = "auto" | "manual";

自動分類のUPDATEでは、手動修正済みの行を対象外にします。

await db
  .from("responses")
  .update({
    spam_label: spamResult.label,
    spam_score: spamResult.score,
    spam_label_source: "auto",
    spam_classified_at: new Date().toISOString(),
  })
  .eq("id", responseId)
  .or("spam_label_source.is.null,spam_label_source.eq.auto");

手動修正時は manual にします。

await db
  .from("responses")
  .update({
    spam_label: newLabel,
    spam_label_source: "manual",
    spam_classified_at: new Date().toISOString(),
  })
  .eq("id", responseId);

MCPツール側では、update_responsespam_label を持たせます。

server.registerTool("update_response", {
  inputSchema: {
    response_id: z.number().int(),
    status: z.enum(["new", "in_progress", "resolved", "spam"]).optional(),
    notes: z.string().optional(),
    tags: z.array(z.string().max(50)).max(20).optional(),
    spam_label: z.enum(["legitimate", "sales", "suspicious"]).optional(),
  },
});

ユーザーの発話はこうです。

この回答は営業ではなく正当な問い合わせなので、分類を直して

ここで spam_label_source = "manual" に切り替わるので、以後の自動処理から守れます。

チェックポイント

分類や推論をMCPに載せるなら、必ず考えてください。

  • 人間は結果を直せるか
  • 直した結果は自動処理から守られるか
  • 直した理由をメモできるか
  • 後続の分析やワークフローは、修正済みラベルを見ているか

「AIが当てる」より、「外れても運用が壊れない」のほうが重要です。


5. 破壊的操作だけでなく、自動実行も確認対象にする

MCPサーバーでは、削除やメール送信のような破壊的操作に確認を入れるのは自然です。

ただ、業務系のMCPでは「保存した後に自動で動く設定」も注意が必要です。

たとえばワークフローです。

server.registerTool("set_workflow", {
  inputSchema: {
    form_id: z.number().int(),
    name: z.string().min(1),
    trigger_type: z.enum([
      "response.created",
      "response.updated",
      "capacity.reached",
      "deadline.approaching",
      "response.classified",
    ]),
    conditions: z.array(conditionSchema).optional(),
    actions: z.array(actionSchema),
  },
});

ワークフローは、作成した瞬間には何も起きないかもしれません。

でも次の回答が来たときに、自動でメール送信やWebhookが走ります。

つまり、設定保存そのものは静かでも、後で外部副作用が出ます。

このタイプのツールは、単なる create ではなく「将来の自動実行を作る操作」として扱う必要があります。

実装上のルール

ツール説明に、次のような制約を入れておきます。

Workflows with send_email or webhook actions execute automatically on trigger.
Always summarize trigger, conditions, and actions, then confirm before saving.

加えて、サーバー側でも確認トークンや状態機械で止めるのが理想です。

プロンプトに「確認して」と書くだけだと、モデルが省略する可能性があります。


6. テキスト返却だけで足りるかを確認する

Anthropicの記事では、MCP AppsやElicitationのようなrich semanticsにも触れられています。

実装者目線では、これは「何でもチャットに返せばよいわけではない」と読めます。

たとえば次のものは、テキストよりUIのほうが向いています。

  • 回答一覧
  • 分類比率
  • 公開前チェックリスト
  • 誤判定の修正画面
  • 分析グラフ

一方で、次のものはチャットが向いています。

  • 「営業を除いて分析して」
  • 「要確認だけ見せて」
  • 「この分類を直して」
  • 「本物の問い合わせだけ通知して」

MCPサーバー設計では、この分担を先に考えておくとよいです。

Chat: intent を受け取る
MCP: 意味・制約・権限・実行を担当する
UI: 一覧、比較、確認、修正を担当する

テキストだけで返すと、確認しづらいものがあります。

逆に、UIだけでやると、意図を渡すのが重くなります。

MCPは、その間に置く意味レイヤーとして設計すると使いやすくなります。


7. Skills / Workflow と分けて考える

最後に、MCPだけで全部を背負わないことも大事です。

Anthropicの記事では、MCPとSkillsは補完関係だと説明されています。

MCPは、ツールとデータへのアクセス。

Skillsは、そのツールで仕事を進める手続き知識。

フォーム運用でも同じです。

MCPツールで get_responsesset_workflow を提供できます。でも、セミナー運用でどの順番に自動返信、リマインド、アンケートを組むべきかは、API仕様ではありません。運用の型です。

この部分は、WorkflowやSkillとして別に扱うほうが自然です。

MCPサーバーに全部の知識を詰め込むのではなく、能力と手順を分けます。

MCP      = 何ができるか
Workflow = どう進めるか
Skill    = どう判断するか

この分け方をしておくと、ツール仕様が肥大化しにくくなります。


まとめ

MCPサーバーは、既存APIをそのまま公開するだけでも作れます。

でも、本番エージェントに使わせるなら、APIラップだけでは足りません。

実装前に、次を確認するとよいです。

  • エンドポイントではなく、ユーザーの作業単位でツールを切る
  • モデルが毎回やっている後処理を、サーバー側のルールに寄せる
  • ラベルやステータスを、次の操作条件にする
  • AIの判断を人間が上書きできるようにする
  • 手動上書きは自動処理から守る
  • 将来の自動実行を作る操作には確認を入れる
  • チャットとUIの分担を設計する
  • MCPとWorkflow / Skillを分けて考える

MCPサーバーを作るときに考えるべきなのは、「どのAPIを公開するか」だけではありません。

ユーザーの仕事のどの単位を、エージェントに安全に渡せる形にするかです。

同じテーマを、別の角度でも書いています。

FORMLOVAは無料で始められます。MCP経由で、フォーム作成だけでなく公開後の運用まで試せます。

0
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
0
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?