mcpField
E
extension-http-endpoints

extension-http-endpoints

github.com/caffeinelabs開発23.8k 回インストール今日更新
リポジトリ

extension-http-endpoints について

`caffeineai-http-endpoints`を使って、Caffeine AIのMotokoバックエンドにHTTPエンドポイントを追加する開発者向けスキルです。クエリと更新リクエストの分離、ゲートウェイURL、外部呼び出し元の認証上の注意点を説明します。

主な機能

  1. 01MotokoバックエンドのHTTP受信処理
  2. 02クエリと更新パスの分離
  3. 03既成HTTPモジュールとmixin
  4. 04Webhook URLとGETホストの指針
  5. 05署名とシークレットの検証指針

活用シーン

  1. 01canisterで外部Webhookを受信
  2. 02ヘルスチェックなどのGETルートを提供
  3. 03状態を変更するPOSTルートを実装

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/caffeinelabs/skills --skill extension-http-endpoints

インストール後、Claude Code で/extension-http-endpointsを入力して読み込まれたか確認します。

詳細

HTTP エンドポイント(インバウンド)

mo:caffeineai-http-endpoints を介して、Caffeine AI の Motoko バックエンドでインバウンド HTTP を処理します。

用途に応じたスキルの選び方

必要なことスキル
外部クライアント(curl、Webhook、ボット)から HTTPS 経由で Canister にアクセスするこのスキル
Canister から外部の HTTPS API を呼び出すextension-http-outcalls
型付きの OpenAI / Google / X / Stripe クライアント対応するドメインスキル(これらはアウトバウンド呼び出しをラップします)

管理 Canister の http_request / Call.httpRequest アウトコール API を使ってインバウンドハンドラーを実装しないでください。方向が逆です。

インバウンド HTTP の仕組み

  1. クライアントは HTTPS 経由で Motoko のバックエンド Canister にアクセスします(*.caffeine.xyz、カスタムドメイン、フロントエンド/アセット Canister ではありません)。
  2. HTTP Gateway が http_request(クエリ)を呼び出します。
  3. 変更を伴うルートは Http.upgrade() を返し、Gateway は http_request_update(アップデート)として再度リクエストを送信します。

URL

  • Webhook/POST: https://<backend-canister-id>.icp0.io/...(または .ic0.app)
  • 未認証 GET(ヘルスチェック/プレーンテキスト): https://<backend-canister-id>.raw.icp0.io/... — raw ではない Gateway は認証済みクエリ本文を必要とします(対象外)

バックエンド

事前構築済みのモジュールを使用してください(変更しないでください)。

module {
  public type Request = { method : Text; url : Text; headers : [HeaderField]; body : Blob; certificate_version : ?Nat16 };
  public type Response = { status_code : Nat16; headers : [HeaderField]; body : Blob; streaming_strategy : ?StreamingStrategy; upgrade : ?Bool };

  public func path(url : Text) : Text;
  public func header(req : Request, name : Text) : ?Text; // case-insensitive
  public func text(status : Nat16, body : Text) : Response;
  public func upgrade() : Response; // query path asks gateway to call http_request_update
  public func notFound() : Response;
  public func methodNotAllowed() : Response;
  public func bodyText(req : Request) : ?Text; // null if body is not UTF-8; HMAC must use req.body
};
mixin (
  onQuery : Http.Request -> Http.Response,
  onUpdate : Http.Request -> async Http.Response,
) {
  public query func http_request(req : Http.Request) : async Http.Response;
  public func http_request_update(req : Http.Request) : async Http.Response;
};

mops add caffeineai-http-endpoints を追加します。main.mo の既存の actor にハンドラーを接続し、MixinAuthorization はそのまま維持してください。actor を置き換えたり、http_request* を再宣言したりしないでください。

onQuery はクエリセーフである必要があります。await や状態への書き込みは行わないでください。変更を伴う処理は、クエリパスから Http.upgrade() を返した後、onUpdate で行います。

import Http "mo:caffeineai-http-endpoints/Http";
import MixinHttpEndpoints "mo:caffeineai-http-endpoints/MixinHttpEndpoints";
import AccessControl "mo:caffeineai-authorization/access-control";
import MixinAuthorization "mo:caffeineai-authorization/MixinAuthorization";

actor {
  let accessControlState : AccessControl.AccessControlState;
  include MixinAuthorization(accessControlState, null);

  func onHttpQuery(req : Http.Request) : Http.Response {
    let path = Http.path(req.url);
    if (req.method == "POST" and path == "/webhook") {
      return Http.upgrade();
    };
    if (req.method == "GET" and (path == "/" or path == "/health")) {
      return Http.text(200, "ok");
    };
    Http.notFound();
  };

  func onHttpUpdate(req : Http.Request) : async Http.Response {
    let path = Http.path(req.url);
    if (req.method == "POST" and path == "/webhook") {
      // Signature checks: Http.header(req, "stripe-signature"), HMAC over req.body
      ignore Http.bodyText(req);
      return Http.text(200, "received");
    };
    Http.notFound();
  };

  include MixinHttpEndpoints(onHttpQuery, onHttpUpdate);
};

マイグレーションチェーンの先頭:

import AccessControl "mo:caffeineai-authorization/access-control";

module {
  type NewActor = {
    accessControlState : AccessControl.AccessControlState;
  };

  public func migration(_old : {}) : NewActor {
    { accessControlState = AccessControl.initState() };
  };
};

基本的なルール

  • 変更処理は onUpdate でのみ行います。 書き込みを伴う POST/PUT/PATCH/DELETE ルートでは、クエリが Http.upgrade() を返します。
  • Webhook URL にはバックエンド Canister ID のみを使用してください。プロジェクト/フロントエンドのオリジンから Motoko の http_request に到達することはありません。
  • 未認証 GET → .raw.icp0.io。 レスポンス証明書を独自に作成しないでください。
  • アプリ UI にはCandid + Reactを優先し、インバウンド HTTP は外部 HTTP クライアントにのみ使用してください。
  • 認証: Webhook の送信者から IC 呼び出し元の principal は得られません。署名/シークレットを自分で検証してください(Http.header、生の req.body)。
  • 大きな JSON: ペイロードを小さくスキーマをシンプルにするか、データ本体を保存して別の場所で処理してください。

curl の使い方

raw ではないホストへの未認証 GET は、ハンドラーからの 404 ではなく、Gateway の致命的なエラー(503/backend_response_verification/Certification values not found)になります。Http.upgrade() を返す POST では、このチェックはスキップされます。

# Reads — must be raw
curl https://<backend-canister-id>.raw.icp0.io/health

# Writes — non-raw is fine (update path)
curl -X POST https://<backend-canister-id>.icp0.io/webhook \
  -H 'content-type: application/json' \
  -H 'x-webhook-secret: …' \
  -d '{"event":"…"}'

https://icp0.io/... の ?canisterId=<id> は、ホストを指定する別の形式です。Canister のサブドメインを優先してください。?canisterId= のみを付けた IP/localhost への GET は、レスポンス本文の代わりに空の 204 を返すことがあります。これはハンドラーが空なのではなく、URL の形式が誤っているものとして扱ってください。

関連項目

  • extension-http-outcalls — Canister → インターネット(モジュールヘルパー、インバウンド mixin なし)
  • extension-stripe — アウトコールによるチェックアウト。インバウンドの Stripe Webhook には引き続きこのスキルを使用します
  • icskills#337 — 上流のスキルギャップに関する議論

ローカル PocketIC(制限事項)

PocketIC はアップグレードの一連の流れを検証するのに便利ですが、curl の完全な代替としてそのまま使えるものではありません。

  • pocket-ic バイナリはコントロールサーバーです。クライアント(@dfinity/pic)がインスタンス(NNS サブネットを含む)を作成し、makeLive() を呼び出してポートを返すまでは、curl を受け付けません。
  • curl の URL を表示する mops のワンライナーはありません。ゲートウェイは、その Node/pic プロセスとともに停止します。
  • 稼働を開始した後は、同じルールが適用されます。
    • http://<canister-id>.raw.localhost:<port>/health — 未認証の GET
    • http://127.0.0.1:<port>/notes?canisterId=<id> — POST / アップグレード(稼働中のゲートウェイテストで動作)
    • http://<canister-id>.localhost:<port>/health(.raw なし)— 503 の認証エラー
  • PocketIC を、本番環境の Webhook にアクセスする方法として案内しないでください。本番環境の URL は、上記の *.icp0.io / *.raw.icp0.io 形式です。

コメント