コンテンツにスキップ
Subako Docs
日本語
Esc
↑↓移動↵開く⌘Jプレビュー
このページの内容

その他の言語 (ABI)

任意の言語で Package を書くための ABI。

SDK は手間を省くためのもので、必須ではありません。WASI preview 1 向けの WebAssembly Module をビルドできる言語なら、ABI 1 を直接実装して Function package を書けます。wasi-sdk を使った C や C++、Zig、AssemblyScript、TinyGo、Swift、自作の言語でもかまいません。

ABI 1 の仕様は小さなものです。Module は MCP の stdio transport で通信する MCP サーバーで、ステートレスなリビジョン 2026-07-28 を使います。HTTP 用には host module を 1 つ import できます。

Module

要件
種類 WebAssembly の core module (component ではありません)
ターゲット WASI preview 1。wasm32-wasip1、古いツールチェーンでは wasm32-wasi
Export 引数も戻り値もない _start (WASI command) と、memory という名前の linear memory
Import wasi_snapshot_preview1 と subako_http からのみ
機能 GC なしの WebAssembly 2.0: bulk memory、reference types、multi-value、sign extension、non-trapping float-to-int、128-bit SIMD。threads、shared memory、exceptions、tail calls、memory64、multi-memory、relaxed SIMD は使えません。

アップロード時にはこれらがすべてチェックされ、拒否された場合はその理由が示されます。たとえば、Subako が提供しない import がある、export が足りない、Module ではなく component である、などです。

呼び出しの流れ

Subako が Module を起動する

Subako は Module をインスタンス化し、要素が 1 つの argv を渡して、環境変数なしで _start を呼び出します。この時点で、最初のリクエストはすでに stdin で待っています。

Module が各リクエストに応答する

リクエストは stdin に、1 行に 1 つの JSON-RPC 2.0 メッセージとして届きます。それぞれに対し、stdout へ \n で終わるコンパクトな JSON を 1 行書いて応答し、flush してください。Subako はリクエストを 1 つずつ送ります。stdin は最後のリクエストに応答するまで開いたままなので、入力の終わりを待たず、リクエストが届くたびに応答してください。

Subako が stdin を閉じる

最後のリクエストに応答すると、stdin が end of file に達します。Module はその後 1 秒以内に、終了コード 0 で終了しなければなりません (_start から return するか、proc_exit(0) を呼びます)。

stderr はログに自由に使えます。stdout はプロトコル専用で、JSON-RPC メッセージでない行が 1 行でもあると呼び出しは失敗します。

プロトコル

どのリクエストにも、_meta にプロトコルのリビジョンとクライアントの capabilities が含まれています。initialize によるハンドシェイクはありません。

{"jsonrpc":"2.0","id":"call-0192f5c4","method":"tools/call","params":{"name":"greet","arguments":{"name":"Ada"},"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"subako","version":"0.2.3"},"io.modelcontextprotocol/clientCapabilities":{}}}}

レスポンスには、リクエストの id をそのまま入れて返してください。id は文字列か数値で、中身に意味はなく、毎回異なります。

Package をアップロードしたとき

Subako は Tool の一覧を得るために Module を一度実行します。このときは 10 秒以内に応答する必要があり、Volume もネットワークも使えず、マウントされるのは /package だけです。

  1. server/discover: 対応しているリビジョンと、Tool を提供することを返します。

    {"jsonrpc":"2.0","id":"discover-1","result":{"supportedVersions":["2026-07-28"],"capabilities":{"tools":{}}}}
  2. tools/list: すべての Tool を返します。nextCursor を返すと、次のページを要求されます (最大 16 ページ)。

    {"jsonrpc":"2.0","id":"tools-list-1","result":{"tools":[{"name":"greet","description":"Greet someone by name.","inputSchema":{"type":"object","properties":{"name":{"type":"string","description":"Who to greet."}},"required":["name"]}}]}}
  3. stdin が閉じたら、終了コード 0 で終了します。

tools/list で返した内容が、次のバージョンまでその Package の提供するものになります。決まりは次のとおりです。

決まり
Tool 1 から 64 個。名前は重複不可
name 1 から 64 文字: a–z、0–9、_、-
description 最大 1,024 文字。モデルが読みます。
inputSchema 最上位が "type": "object" の JSON Schema。最大 64 KiB
outputSchema 任意。最大 64 KiB。モデルではなく人に表示されます。
すべての Tool の合計 最大 256 KiB

Agent が Tool を呼び出したとき

Tool の name と、オブジェクト形式の arguments を持つ tools/call が 1 つ届きます。MCP の CallToolResult で応答してください。

{"jsonrpc":"2.0","id":"call-0192f5c4","result":{"content":[{"type":"text","text":"{\"message\":\"Hello, Ada!\"}"}],"structuredContent":{"message":"Hello, Ada!"}}}

Subako は arguments を Schema に照らしてチェックしないので、Module の側で検証してください。モデルに読ませたい失敗は、JSON-RPC のエラーにせず、isError を付けた結果として返します。

{"jsonrpc":"2.0","id":"call-0192f5c4","result":{"content":[{"type":"text","text":"name must not be empty"}],"isError":true}}

Subako が読む content block は次のとおりです。

Block モデルへの見せ方
{"type": "text", "text": ...} そのテキスト
{"type": "image", "mimeType": "image/png", "data": "<base64>"} その画像。stdout の 1 MiB に含めて数えます
{"type": "resource_link", "uri": "volume:///tmp/chart.png", "name": "chart.png"} /volume/tmp/chart.png の画像ファイル。Module の終了後に読み取ります

volume:/// の URI は /volume より下の Path を表し、各セグメントはパーセントエンコードします。画像は PNG、JPEG、GIF、WebP のいずれかです。これ以外の resource_link はリンク先を示す 1 行として、表にないそのほかの block は省略したことを伝える注記として表示されます。

呼び出しが失敗する場合

Module が次のいずれかに当てはまると、モデルには Function がクラッシュしたことが、stderr の最後の 4 KiB とともに伝えられます。

  • JSON-RPC の error で応答した、または resultType が complete 以外だった
  • stdout に JSON-RPC メッセージでない行を出力した、または要求されていない id に応答した
  • 自分からリクエストを送った (ステートレスなサーバーはリクエストを送りません)
  • 応答する前に終了した、0 以外の終了コードで終了した、または stdin が閉じてから 1 秒以内に終了しなかった
  • trap した、またはメモリや実行時間を使い切った

通知 (id のないメッセージ) は無視されます。

ファイル

ファイルシステムは WASI の preopen として、/volume/tmp、/volume/data (Agent が有効にしている場合)、/package (Package に Asset がある場合)、/skills と各 /skills/<name> の順に渡されます。ファイルは絶対パスで開いてください。

fd_allocate は ENOTSUP を返します。ファイルへの書き込みが 1 回の呼び出しの上限 1 GiB を超えると、EDQUOT が返ります。sock_* 関数はリンクされていますが、ソケットが渡されることはありません。HTTP は subako_http を通して送ります。

HTTP: subako_http

subako_http Module から import する関数は次の 2 つで、どちらも i32 の値を扱います。

Import 動作
request(ptr, len) -> len ptr にある len バイトの JSON のリクエストを読み取って送信し、返ってきた JSON の応答を保持して、その長さを返します。応答が届くまでブロックします。
read_response(ptr, len) -> 0 保持している応答を ptr にコピーします。len には request が返した値を渡します。0 を返し、保持していた応答を破棄します。

負の戻り値は ABI の使い方の誤りを表します。-1 は Module のメモリ外の範囲を指定した、-2 は保持している応答がない、-3 は長さが一致しない、という意味です。

__attribute__((import_module("subako_http"), import_name("request")))
int32_t subako_http_request(const uint8_t *ptr, int32_t len);

__attribute__((import_module("subako_http"), import_name("read_response")))
int32_t subako_http_read_response(uint8_t *ptr, int32_t len);

リクエストは最大 8 MiB で、次のフィールドだけを受け付けます。

{
  "method": "POST",
  "url": "https://api.example.com/orders",
  "headers": { "accept": "application/json" },
  "body": { "type": "json", "json": { "order": 1042 } },
  "output": { "type": "inline" },
  "follow_redirects": true,
  "timeout_ms": 5000
}
フィールド
method GET (既定)、HEAD、POST、PUT、PATCH、DELETE、OPTIONS のいずれか
url 必須。https のみ。
headers 名前は小文字。host や content-length などの接続に関する Header は取り除かれます。
body {"type": "text", "text": ...}、{"type": "json", "json": ...}、{"type": "file", "path": "/volume/tmp/in.bin"} のいずれか
output {"type": "inline"} (既定)、または本文を Volume に保存する {"type": "file", "path": "/volume/tmp/out.bin"}
follow_redirects 既定は true。最大 10 ホップ
timeout_ms リクエスト全体にかかる制限時間。指定の有無にかかわらず、呼び出し自体の Timeout も適用されます。

応答は {"ok": ...} か {"error": ...} です。

{"ok":{"status":200,"headers":{"content-type":"application/json"},"body":{"type":"inline","text":"{\"status\":\"shipped\"}"}}}
{"ok":{"status":200,"headers":{},"body":{"type":"file","path":"/volume/tmp/out.bin","bytes":48211}}}
{"error":{"kind":"host_not_allowed","message":"api.example.com is not in this function's network allowlist"}}

インラインの本文は最大 256 KiB で、UTF-8 のテキストでなければなりません。そうでない場合、リクエストは too_large か not_text で失敗するので、本文はファイルに保存するようにしてください。エラーの種類は invalid_request、host_not_allowed、forbidden_destination、credential_header、credential_unavailable、too_many_redirects、timeout、too_large、not_text、write_budget、bad_path、connection、abi です。

ローカルでのテスト

WASI preview 1 の command を実行できるランタイムなら、どれでも手元のマシンで Module を試せます。stdin にリクエストを 1 つ渡し、stdout から応答を読みます。

printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}},"name":"greet","arguments":{"name":"Ada"}}}'

Module を読み込むには、ランタイムが subako_http を提供する必要があります。Rust と Go のテンプレートには、Module を別の Module にリンクできるランタイム向けに、すべてのリクエストを失敗させるスタブ tests/subako_http_stub.wat が含まれています。

(module
  (func (export "request") (param i32 i32) (result i32) i32.const -1)
  (func (export "read_response") (param i32 i32) (result i32) i32.const -2))

チェックリスト

  1. _start と memory を export する WASI preview 1 の command module を、threads と exceptions なしでビルドします。
  2. stdin を 1 行ずつ読み、end of file に達したら終了コード 0 で終了します。
  3. id のないメッセージは無視します。
  4. server/discover には "supportedVersions": ["2026-07-28"] と tools capability で応答します。
  5. tools/list には、Tool の一覧と、それぞれのオブジェクト型の入力 Schema で応答します。
  6. tools/call には CallToolResult で応答し、失敗には isError を使います。
  7. 応答ごとにコンパクトな JSON を 1 行 stdout に書き、flush します。ログは stderr に書きます。
  8. ファイルは /volume/tmp、/volume/data、/package、/skills の下で読み書きします。
  9. HTTP は subako_http を通して送ります。

あとは、abi を 1 にした subako-package.json と module.wasm をまとめれば完成です。

このページは役に立ちましたか?