その他の言語 (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 だけです。
-
server/discover: 対応しているリビジョンと、Tool を提供することを返します。{"jsonrpc":"2.0","id":"discover-1","result":{"supportedVersions":["2026-07-28"],"capabilities":{"tools":{}}}} -
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"]}}]}} -
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))
チェックリスト
_startとmemoryを export する WASI preview 1 の command module を、threads と exceptions なしでビルドします。- stdin を 1 行ずつ読み、end of file に達したら終了コード 0 で終了します。
idのないメッセージは無視します。server/discoverには"supportedVersions": ["2026-07-28"]とtoolscapability で応答します。tools/listには、Tool の一覧と、それぞれのオブジェクト型の入力 Schema で応答します。tools/callにはCallToolResultで応答し、失敗にはisErrorを使います。- 応答ごとにコンパクトな JSON を 1 行 stdout に書き、flush します。ログは stderr に書きます。
- ファイルは
/volume/tmp、/volume/data、/package、/skillsの下で読み書きします。 - HTTP は
subako_httpを通して送ります。
あとは、abi を 1 にした subako-package.json と module.wasm をまとめれば完成です。