mcpField
A
Agent Output Verifier by Sarnai

Agent Output Verifier by Sarnai

リモート
github.comAI・エージェント04 日前に更新
リポジトリ公式サイト

Agent Output Verifier by Sarnai について

Agent Output Verifierは、AIエージェントの出力をJSON Schemaと業務ルールに照らして検証するホステッドサービスです。合否、修正のヒント、独立して検証できる署名付きレシートを返します。

主な機能

  1. 01JSON Schemaと業務ルールの検証
  2. 02合否結果と修正ヒント
  3. 03Ed25519署名付き証明とレシート
  4. 04AIエージェントのスコアと評判の照会

活用シーン

  1. 01支払い前の請求書合計の検証
  2. 02利用前のAPIやツール呼び出しJSONの確認
  3. 03受け入れルールに沿った成果物の検査
  4. 04依頼や利用前のエージェント履歴の確認

MCP 対応

MCPエンドポイントは、出力検証用の`verify_schema`とエージェントのスコア記録を調べる`get_verification_record`を提供します。検証と照会にはRESTエンドポイントもあります。

設定

リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。

エンドポイントhttps://fastapi-service-5ag4.onrender.com/mcp
インストールコマンドをコピー
Claude CodeTerminal
Cursor.cursor/mcp.json
VS Code.vscode/mcp.json
Claude Desktopclaude_desktop_config.json
Windsurf~/.codeium/windsurf/mcp_config.json

コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。

ツール

2
パラメーター型必須説明
rulesanyいいえ任意のフィールド間ルール:sum_equals、gte、lte、exists_in、unique(重複検出)。enforce_rules が true でない限り、違反は情報フラグとして報告されます。
boundsanyいいえ任意のフィールドごとの最小値/最大値。ドット区切りのパスをキーとし、配列のワイルドカードには '[]' を使います(例:'items[].price')。値には数値または ISO-8601 日付を指定します。enforce_rules が true でない限り、違反は情報フラグとして報告されます。
detailenumいいえ「full」(デフォルト)はすべてを返し、「compact」は概要、次のアクション、レシート、署名のみを返します。トークンを節約するには、verify-repair ループ内で compact を使用してください。「compact」のフィールド:summary、next_actions、result、レシートフィールド(verification_id、task_id、agent_id、output_hash、schema_hash、rules_hash、verified_at、verifier_version、enforce_rules、strict_content_check)、verify、attestation。errors、errors_detail、flags、hints、scores は空になるのではなく、完全に省略されます。どちらの形式も完全に署名されます。full | compact
task_idstringはいこの検証に使用する任意の識別子です。印字可能文字を1~200文字使用し、制御文字や単独の Unicode サロゲートは使用できません。この範囲外のリクエストは切り詰められず、拒否されます(422)。
agent_idanyいいえsubmitted_output を生成した AI エージェント(例:販売者)を識別する任意のラベルです。/reputation と信頼スコアに使用されます。記録とスコアは、このエンドポイントの呼び出し元ではなく、生成者に紐づきます。認証されないラベルです。'test-' で始まる agent_ids はテスト用に予約されています。検証は通常どおり実行されますが、記録されないため、評判や信頼スコアには影響しません。印字可能文字を1~200文字使用します(GET /score/{agent_id} と同じ規則)。これより長い値や形式が不正な値は、切り詰められず拒否されます(422)。
enforce_rulesbooleanいいえオプトインです。true の場合、設定されたすべての境界条件と整合性ルールを満たす必要があります。満たさない場合、結果は 'fail' となり(strict_content_check と同様)、errors に 'enforce_rules: ...' の項目が追加されます。実行できないルールや境界条件(パスがない、値を比較できない、比較対象がない場合など)も対象となり、暗黙に合格するのではなく失敗します。そのため、ルールが適用されるパス全体または配列要素でフィールドを省略しても、拘束力のあるルールを回避できません。フィールドがない場合、if_present: true のルールや境界条件はスキップされます。false(デフォルト)の場合、動作は変わらず、違反や実行不能なルールは情報提供用のフラグとして残ります。署名付きレスポンスに値が含まれます。
expected_schemaobjectはい必須です。submitted_output が満たすべき JSON Schema(draft-04~2020-12)を JSON オブジェクトとして指定します。要件は任意に定義できます。
submitted_outputanyはい必須です。チェック対象の出力です。AI エージェントが生成した内容そのままの、任意の JSON 値(オブジェクト、配列、文字列、数値、ブール値、null)を指定します。
strict_content_checkbooleanいいえオプトインです。true の場合、submitted_output 内に制御文字(例:ヌルバイト)または埋め込み HTML/script マークアップを含む文字列があると、結果は 'fail' になります。false(デフォルト)の場合、このチェックは一切行われません。

詳細

SarnAI によるエージェント出力の検証

支払いの前に AI エージェントの出力を独立して検証できます。AI エージェントが出力した JSON を、JSON Schema とビジネスルール(合計金額、一意の ID)に照らして確認します。

請求書、納品物、その他あらゆる構造化出力について、構造、形式、範囲、フィールド間のルール(「明細の合計は請求書の合計と一致しなければならない」など)を、お客様独自の要件に基づいて独立かつ決定論的にチェックします。要件は標準の JSON Schema と任意のルールで記述します。合否、チェック合格率、修正のヒント、そして誰でも公開鍵で検証できる Ed25519 署名付きの証明と領収書を返します。販売者は、提出前に自分の出力をチェックし、署名付きの証明と領収書を添えて納品できます。登録不要です。x402 v2 を利用し、Base または Solana 上の USDC で呼び出しごとに支払います。 呼び出し元ごとに1日1,000回まで無料でチェックでき、それ以降は1回あたり $0.02 です(x402、Base または Solana 上の USDC)。無料枠は MCP と REST で共有されます。現在の値は /llms.txt に記載されています。AI エージェントスコア:スコアがある場合、検索1回あたり $0.01 です。履歴のない AI エージェントの検索は無料で、署名付きで回答されます。

こんな用途に

  • 請求書を検証し、合計金額を確認する:支払い前に、明細項目と請求書の合計金額、およびお客様のルールを照合します。
  • JSON Schema 検証:ツール呼び出しや API レスポンスの JSON を、使用前に JSON Schema と照合します。
  • 納品物の受け入れチェック:合意したスキーマとルールを満たしていることを確認し、検証後に支払います。
  • AI エージェントの評判:採用したり、その出力を信頼したりする前に、AI エージェントの実績と信頼スコアを確認します。

Agent Output Verifier は SarnAI の一部です。SarnAI は、Agent Discovery Board(https://board.sarnai.dev/guide)や AI Agent Scores とともに、AI エージェント同士が連携するための製品エコシステムです。

SarnAI のホームページ:https://sarnai.dev

このリポジトリには、ホスト型サービスのドキュメントと公開メタデータが含まれています。ここにインストールまたは実行できるソースコードはありません。サービス自体は稼働中の API( https://verify.sarnai.dev)です。以前のアドレス https://fastapi-service-5ag4.onrender.com も同じレスポンスとキーで引き続き利用できます。移行前に発行された証明と領収書には、以前のアドレスが記載されています。

エンドポイント

目的エンドポイント
MCP(Streamable HTTP)https://verify.sarnai.dev/mcp
出力の検証(REST)POST https://verify.sarnai.dev/verify/schema
納品物の検証(REST。別名で提供される同一のチェック)POST https://verify.sarnai.dev/verify/deliverable
AI エージェントの信頼スコアを検索(REST)GET https://score.sarnai.dev/score/{agent_id}
AI エージェントの無料の合否履歴(REST)GET https://verify.sarnai.dev/reputation/{agent_id}
稼働状況チェックGET https://verify.sarnai.dev/health
エージェントカード(ディスカバリー)GET https://verify.sarnai.dev/.well-known/agent-card.json
x402 ディスカバリーマニフェストGET https://verify.sarnai.dev/.well-known/x402
llms.txt(実例付きの llms-full.txt も提供)GET https://verify.sarnai.dev/llms.txt
旧 A2A パスで提供される同じエージェントカードGET https://verify.sarnai.dev/.well-known/agent.json
API カタログ(RFC 9727)GET https://verify.sarnai.dev/.well-known/api-catalog
ブラウザー以外のクライアント向け JSON インデックスGET https://verify.sarnai.dev/ に Accept: application/json を指定
対話型 API ドキュメントGET https://verify.sarnai.dev/docs

同じエンドポイントを通じて、2つの MCP ツールが公開されています。verify_schema(POST /verify/schema)と get_verification_record(GET /score/{agent_id})です。

料金とネットワーク

無料枠を超えた後の各呼び出しは、x402 v2 を使用し、USDC で支払います。AI エージェントスコア:スコアがある場合は検索ごとに $0.01 です。履歴のない AI エージェントの検索には、署名付きの回答を無料で返します。アカウントやサインアップは不要です。

ルート価格ネットワーク
POST /verify/schema$0.02 USDCBase (eip155:8453)、Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)
POST /verify/deliverable$0.02 USDCBase (eip155:8453)、Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)
GET /score/{agent_id}スコアがある場合は $0.01 USDC。履歴のない AI エージェントには無料(署名付き、no_data)Base (eip155:8453)、Solana (solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp)

/.well-known/x402 と、ライブの 402 レスポンスに含まれる Payment-Required ヘッダーには、常にその時点での正確な価格、支払い先アドレス、アセットアドレスが含まれます。これらを信頼できる情報源として扱い、この表はクイックリファレンスとしてご利用ください。

支払い自体を示すヘッダーは、名前のとおり次の3つです。未払いのリクエストには、価格と支払い要件を含む Payment-Required レスポンスヘッダーとともに HTTP 402 が返されます。支払い済みのリクエストでは、署名済みの支払い情報が Payment-Signature リクエストヘッダーに含まれます。決済済みのレスポンスでは、決済レシートが Payment-Response レスポンスヘッダーに含まれます。fail の結果は、質問に対する完全かつ有効な回答であり、pass と同じ料金が発生します。どちらの場合も、すべてのチェックを実行する作業は同じです。

無料枠

検証には無料枠があります。呼び出し元ごとに1日1,000回まで無料でチェックでき、それを超えると1回あたり $0.02 です(x402、Base または Solana 上の USDC)。リクエストの IP アドレスで識別される各クライアント(サインアップやアカウントは不要)には、1日あたりのチェック回数が割り当てられます。この枠は、MCP ツール verify_schema と REST ルート POST /verify/schema および POST /verify/deliverable で共有されます。無料枠は固定値ではなく設定可能です。現在の数値は /llms.txt の「Free allowance」セクションに記載されています。

有効な未払いの REST リクエストは、無料枠が残っている間は無料で処理され、残りの回数は Free-Allowance-Remaining、Free-Allowance-Limit、Free-Allowance-Resets ヘッダーで通知されます。サイズが大きすぎるリクエスト、支払い情報を含むリクエスト、およびその日の無料枠を使い切った後のすべてのリクエストは、通常どおり支払いに進みます(HTTP 402)。無効なリクエストが支払いに進むことはありません。検証エラーが返されます(HTTP 422。以下の「エラー」を参照)。無料の検証も、有料の場合とまったく同じように AI エージェントのスコアと評判に反映されます。

エージェントスコア: GET /score/{agent_id} と MCP ツール get_verification_record は、スコアがある場合、検索ごとに $0.01 かかり、無料枠はありません。履歴のない AI エージェント(または予約済みの test- ID)の検索には、reason: no_data を含む署名付きの回答を無料で返します。

未払いの REST 呼び出しに対する 402 の本文には、その呼び出し元自身の実際の現在の状態として、available、remaining_calls_today、resets_at(次の UTC 午前0時)が報告されます。クライアントがその日の無料枠を使い切ると、available は false になり、本文には "code": "free_trial_exhausted" が含まれます。

{
  "free_trial": {
    "available": false,
    "via": "mcp+rest",
    "transport": "streamable-http",
    "url": "https://verify.sarnai.dev/mcp",
    "tool": "verify_schema",
    "calls_per_client_per_day": "<N>",
    "remaining_calls_today": 0,
    "resets_at": "<next UTC midnight>",
    "client": "identified by request IP address; the same address's REST and MCP verification calls share one allowance",
    "rest_free": true,
    "message": "This client's <N> free checks for today are used up (REST and MCP share them); they reset at <next UTC midnight>. Payment applies until then.",
    "code": "free_trial_exhausted"
  }
}

クライアントがその日の無料枠を使い切った後にツールを呼び出した場合、同じ free_trial_exhausted コードが MCP ツール独自の支払い要求結果にも(_meta 内に)含まれます。無料枠を超えた後(またはその日の枠を使い切った後)の呼び出しには、MCP リクエストの _meta 内に x402/payment として含める x402 支払い、または REST の標準 x402 支払いヘッダーが必要です。

Python

公式パッケージの sarnai は、Agent Discovery Board とこの検証ツールをラップします。アカウントやシークレットは不要です。

pip install sarnai
from sarnai import SarnAIClient

with SarnAIClient() as sarnai:
    result = sarnai.verify_output({"type": "object", "required": ["total"]}, {"total": 42})
    print(result.passed, result.hints)

検証は上記の1日の無料枠内であれば無料です。枠を超えるとサービスは 402 を返し、クライアントは PaymentRequiredError を発生させます。

エラー

決済エラー以外のすべてのエラーは、REST と MCP ツールの結果で同じ形式です。

{
  "code": "invalid_request",
  "message": "The request is invalid: rules[0].type is not one of the allowed values. Nothing was charged; fix the field(s) and send it again.",
  "status": 422,
  "retryable": false,
  "charged": false,
  "next_actions": [{"action": "fix_request_fields", "fields": ["rules[0].type"]}],
  "fields": [{"field": "rules[0].type", "problem": "is not one of the allowed values", "fix": "Use one of: 'sum_equals', 'gte', 'lte', 'exists_in' or 'unique'."}]
}

code は安定した値で、retryable は同じリクエストを再試行した場合に成功する可能性があるかを示します(再試行可能なエラーには retry_after も含まれます)。エラーに対して課金されることはありません。入力内容がそのまま返されることもありません。不正なリクエスト(不正な JSON、必須フィールドの欠落、不明なルールタイプ、200 文字を超える ID)は、決済前に 422 で拒否されるため、「決済が必要」と伝えられることはありません。本文のない POST は x402 の検出プローブであり、引き続き 402 が返されます。サイズ制限を超える本文には、制限値を示す 413 が返されます。すべてのコードと決済エラーコードの一覧は、/llms.txt の「Errors」および「Payment errors」セクションと、OpenAPI の説明に記載されています。

レシートの検証手順

/verify/schema と /score/{agent_id} のすべてのレスポンスには、署名付きの verify セクションと attestation ブロックが含まれます。例:

"verify": {
  "public_key_url": "https://verify.sarnai.dev/.well-known/agent-card.json",
  "key_version": "v1-2026-09c",
  "canonicalization": "RFC 8785 (JCS)",
  "hash_algorithm": "SHA-256",
  "service": "Agent Output Verifier",
  "mcp": "https://verify.sarnai.dev/mcp"
},
"attestation": {
  "algorithm": "Ed25519",
  "key_version": "v1-2026-09c",
  "timestamp": "2026-10-01T01:01:09.606993+00:00",
  "signature": "8UexIObSAwQxjj8/rbEpLRgbYY6XO+wg5+b0D8I3BRfbOxl+ouwcKNPE/hp+9p37Tm2wwdmCqPjBgOMLGQtGAQ=="
}

必要なのはレスポンス自体と verify.public_key_url だけです。

  1. 公開鍵を取得します。 verify.public_key_url にあるエージェントカードに GET リクエストを送信します。その capabilities.extensions 配列には、uri が urn:json-schema-verifier:extension:attestation: で始まるエントリがあります。その params.publicKeys には、サービスがこれまでに署名で使用したすべての鍵が {keyVersion, algorithm, publicKey} の形式で列挙されています(Ed25519 の生の 32 バイト公開鍵を Base64 でエンコードしたもの)。keyVersion が response.attestation.key_version と一致するエントリを選びます。常に最新のエージェントカードが正確な情報源です。これは現在の鍵の便宜的なコピーなので、まず最新のエージェントカードと照合することをおすすめします。

    {
      "keyVersion": "v1-2026-09c",
      "algorithm": "Ed25519",
      "publicKey": "d2IHH0/Xduci/5J8IewYmauUvgqf+gFIeCEXDwjHUhw=",
      "publicKeyEncoding": "base64 of the raw 32-byte Ed25519 public key"
    }
    
  2. 署名対象のドキュメントを再構築します。 次の 5 つのキーだけを持つ JSON オブジェクトです。

    {
      "context": "json-schema-verifier/verify-schema-attestation/v1",
      "algorithm": "Ed25519",
      "key_version": "<attestation.key_version>",
      "attested_at": "<attestation.timestamp>",
      "response": { "...": "the full response, with the attestation field removed" }
    }
    

    /verify/schema のレスポンスと /verify/deliverable のレスポンスでは、context は json-schema-verifier/verify-schema-attestation/v1 です(同じ検証を別の名前で提供しており、deliverable 専用の context はありません)。/score/{agent_id} のレスポンスでは、json-schema-verifier/trust-score-attestation/v1 です。

  3. RFC 8785(JCS)で正規化します。 上記の JSON ドキュメントを UTF-8 バイト列にし、オブジェクトのキーを再帰的にソートし、意味のない空白を除き、ECMAScript による表記で数値を出力します(1.0 は 1 になります)。

  4. Ed25519 署名を検証します。 response.attestation.signature を Base64 デコードし、手順 1 の公開鍵を使って、正規化したバイト列に対する署名を検証します。

  5. 4 つのレシートハッシュを確認します。 各ハッシュは、指定された値の RFC 8785(JCS)正規化 JSON に対する SHA-256 です。対象は output_hash(submitted_output のハッシュ)、schema_hash(expected_schema のハッシュ)、rules_hash({"bounds": ..., "rules": ...} のハッシュ)、request_hash(解析後のリクエスト全体のハッシュ。詳細モードのみ)です。自分で再計算して比較してください。これにより、レシートが検証対象の正確なデータに結び付けられます。request_hash の計算では、ハッシュ化の前に、各ルールおよび境界のフィールドにそれぞれの既定値が補われます。省略された tolerance は 1e-9、if_present は false、equals_field/equals/other_field/value/in_field/values は null としてハッシュ化されます。そのため、任意フィールドを明示したかどうかだけが異なる 2 つのリクエストは、ここでは同じハッシュになります。一方、rules_hash はその逆で、既定値を補わず、指定したフィールドだけをそのままハッシュ化します。

標準ライブラリと cryptography だけを使った、最小限の Python 検証コードです。

import base64, json
from decimal import Decimal
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

def es_number(x: float) -> str:
    """A float exactly as ECMAScript / RFC 8785 (JCS) print it (1.0 -> "1", 1e-7 -> "1e-7")."""
    if x == 0:
        return "0"
    sign = "-" if x < 0 else ""
    digits_tuple = Decimal(repr(abs(x))).as_tuple()
    digits = "".join(map(str, digits_tuple.digits)).rstrip("0") or "0"
    n = len(digits_tuple.digits) + digits_tuple.exponent  # position of the decimal point
    k = len(digits)
    if k <= n <= 21:
        body = digits + "0" * (n - k)
    elif 0 < n <= 21:
        body = digits[:n] + "." + digits[n:]
    elif -6 < n <= 0:
        body = "0." + "0" * (-n) + digits
    else:
        e = n - 1
        body = digits[0] + ("." + digits[1:] if k > 1 else "") + f"e{'+' if e >= 0 else '-'}{abs(e)}"
    return sign + body

def canonical(v) -> str:
    if isinstance(v, bool):
        return "true" if v else "false"
    if isinstance(v, float):
        return es_number(v)
    if isinstance(v, dict):
        return "{" + ",".join(f"{canonical(k)}:{canonical(v[k])}" for k in sorted(v)) + "}"
    if isinstance(v, list):
        return "[" + ",".join(canonical(x) for x in v) + "]"
    return json.dumps(v, ensure_ascii=False)  # covers None, int, str

def verify(response: dict, public_key_b64: str, context: str) -> bool:
    att = response["attestation"]
    body = {k: v for k, v in response.items() if k != "attestation"}
    message = canonical({
        "context": context,
        "algorithm": att["algorithm"],
        "key_version": att["key_version"],
        "attested_at": att["timestamp"],
        "response": body,
    }).encode("utf-8")
    key = Ed25519PublicKey.from_public_bytes(base64.b64decode(public_key_b64))
    try:
        key.verify(base64.b64decode(att["signature"]), message)
        return True
    except Exception:
        return False

以下の動作例にある 2 つのレシートでテスト済みです。どちらも検証結果は True です。

request_hash のテストベクトル

request_hash は、解析後のリクエストについて、RFC 8785 に準拠して正規化した JSON の UTF-8 バイト列を SHA-256 でハッシュした値(小文字の 16 進数)です。対象となるのは、task_id、agent_id、expected_schema、submitted_output、strict_content_check、bounds、rules、enforce_rules の 8 つのフィールドです。省略したフィールドにはすべてデフォルト値が設定されます。省略したルールおよび境界の各フィールドもすべて含まれます。省略したルールフィールドは、equals_field/equals/other_field/value/in_field/values が null、tolerance が 1e-9、if_present が false としてハッシュされます。省略した境界フィールドは、if_present が false としてハッシュされます。detail は対象外です(レスポンス形式を選択するだけで、コンパクトなレスポンスには request_hash が含まれないため、確認するにはデフォルトの "detail": "full" を指定してください)。ここが、指定したフィールドだけをハッシュする rules_hash との意図的な違いです。

以下の各ベクトルには、POST するリクエスト本文、ハッシュ対象となる正確な正規形、生成されるハッシュが記載されています。いずれも実際に動作します。リクエストを /verify/schema(または /verify/deliverable)に POST すると、レスポンスの request_hash に以下の値が返ります。同じベクトルは、エージェントカード(/.well-known/agent-card.json、attestation 拡張の requestHashTestVectors)にもあります。

ベクトル 1:最小構成。 ルールも境界もなく、すべての省略可能なフィールドにデフォルト値が設定されます。

リクエスト:

{
  "task_id": "tv-1",
  "expected_schema": {
    "type": "object",
    "required": [
      "ok"
    ],
    "properties": {
      "ok": {
        "type": "boolean"
      }
    }
  },
  "submitted_output": {
    "ok": true
  }
}

正規形(1 行、空白なし):

{"agent_id":null,"bounds":null,"enforce_rules":false,"expected_schema":{"properties":{"ok":{"type":"boolean"}},"required":["ok"],"type":"object"},"rules":null,"strict_content_check":false,"submitted_output":{"ok":true},"task_id":"tv-1"}

request_hash:

5366542000b4226c5efb411c15ec8f0aac3c63844343997aa1bcc3c585086f97

ベクトル 2:rules-and-bounds。 1 つの境界と 2 つのルールがあり、enforce_rules はオンです。省略したルールおよび境界の各フィールドにはデフォルト値が設定されます(tolerance は 1e-9、if_present は false、使用されない参照先は null)。また、JCS では 10.0 は 10 として出力されます。

リクエスト:

{
  "task_id": "tv-2",
  "agent_id": "seller-7",
  "expected_schema": {
    "type": "object",
    "required": [
      "items",
      "total"
    ],
    "properties": {
      "items": {
        "type": "array",
        "items": {
          "type": "object",
          "required": [
            "id",
            "price"
          ],
          "properties": {
            "id": {
              "type": "string"
            },
            "price": {
              "type": "number"
            }
          }
        }
      },
      "total": {
        "type": "number"
      }
    }
  },
  "submitted_output": {
    "total": 30.5,
    "items": [
      {
        "id": "a",
        "price": 10.0
      },
      {
        "id": "b",
        "price": 20.5
      }
    ]
  },
  "bounds": {
    "items[].price": {
      "min": 0,
      "max": 100
    }
  },
  "rules": [
    {
      "type": "sum_equals",
      "field": "items[].price",
      "equals_field": "total",
      "tolerance": 0.01
    },
    {
      "type": "unique",
      "field": "items[].id"
    }
  ],
  "enforce_rules": true
}

正規形(1 行、空白なし):

{"agent_id":"seller-7","bounds":{"items[].price":{"if_present":false,"max":100,"min":0}},"enforce_rules":true,"expected_schema":{"properties":{"items":{"items":{"properties":{"id":{"type":"string"},"price":{"type":"number"}},"required":["id","price"],"type":"object"},"type":"array"},"total":{"type":"number"}},"required":["items","total"],"type":"object"},"rules":[{"equals":null,"equals_field":"total","field":"items[].price","if_present":false,"in_field":null,"other_field":null,"tolerance":0.01,"type":"sum_equals","value":null,"values":null},{"equals":null,"equals_field":null,"field":"items[].id","if_present":false,"in_field":null,"other_field":null,"tolerance":1e-9,"type":"unique","value":null,"values":null}],"strict_content_check":false,"submitted_output":{"items":[{"id":"a","price":10},{"id":"b","price":20.5}],"total":30.5},"task_id":"tv-2"}

request_hash:

1aa064d21a2c1487d5d319ded6efec48c18dfee45b93f741266b1f491c2e099f

ベクトル 3:strict-and-unicode。 strict_content_check がオンで、if_present を指定したルールがあり、非 ASCII テキストは UTF-8 のリテラルとして保持されます。また、JCS では数値が 1.0 → 1、1e21 → 1e+21、0.000001 → 0.000001 として出力されます。

リクエスト:

{
  "task_id": "tv-3",
  "expected_schema": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "n": {
        "type": "number"
      }
    }
  },
  "submitted_output": {
    "n": 1.0,
    "name": "café ✓",
    "z": [
      1e+21,
      1e-06
    ]
  },
  "strict_content_check": true,
  "rules": [
    {
      "type": "gte",
      "field": "n",
      "value": 0,
      "if_present": true
    }
  ]
}

正規形(1 行、空白なし):

{"agent_id":null,"bounds":null,"enforce_rules":false,"expected_schema":{"properties":{"n":{"type":"number"},"name":{"type":"string"}},"type":"object"},"rules":[{"equals":null,"equals_field":null,"field":"n","if_present":true,"in_field":null,"other_field":null,"tolerance":1e-9,"type":"gte","value":0,"values":null}],"strict_content_check":true,"submitted_output":{"n":1,"name":"café ✓","z":[1e+21,0.000001]},"task_id":"tv-3"}

request_hash:

037abd3fe4643c14e68d9fd57ca93b460c177c5372f058aa2920a2ef120c0432

コードを使わずに再現するには、標準ライブラリのみを使用する、次の自己完結型の Python スニペットを使います。リクエスト本文を受け取り、正規形とハッシュを返します。このベクトルで使われている RFC 8785 の規則(UTF-16 コード単位によるキーのソート、空白なし、文字列の UTF-8 リテラル出力、ECMAScript と同じ数値出力。10.0 は 10、1e21 は 1e+21、1e-9 は 1e-9)を実装しています。任意の JCS ライブラリ(たとえば rfc8785 パッケージ)でも同じ結果が得られます。

import hashlib, json
from decimal import Decimal

RULE = dict(equals_field=None, equals=None, other_field=None, value=None, in_field=None, values=None,
            tolerance=1e-9, if_present=False)
BOUND = dict(min=None, max=None, if_present=False)

def number(x):                                   # ECMAScript Number-to-string, as RFC 8785 requires
    if isinstance(x, int):
        return str(x)
    sign, digits, exp = Decimal(repr(x)).as_tuple()
    raw = "".join(map(str, digits))
    n = len(raw) + exp                           # position of the decimal point
    digits = raw.rstrip("0")
    if not digits:
        return "0"
    k, s = len(digits), "-" if sign else ""
    if k <= n <= 21:  return s + digits + "0" * (n - k)
    if 0 < n <= 21:   return s + digits[:n] + "." + digits[n:]
    if -6 < n <= 0:   return s + "0." + "0" * -n + digits
    e = n - 1
    return s + digits[0] + ("." + digits[1:] if k > 1 else "") + "e" + ("+" if e > 0 else "-") + str(abs(e))

def jcs(v):
    if v is None: return "null"
    if v is True: return "true"
    if v is False: return "false"
    if isinstance(v, str): return json.dumps(v, ensure_ascii=False)
    if isinstance(v, (int, float)): return number(v)
    if isinstance(v, list): return "[" + ",".join(map(jcs, v)) + "]"
    return "{" + ",".join(json.dumps(k, ensure_ascii=False) + ":" + jcs(v[k])
                          for k in sorted(v, key=lambda k: k.encode("utf-16-be"))) + "}"

def request_hash(req):  # req: the request body you POST; returns (canonical form, hash)
    full = {
        "task_id": req["task_id"], "agent_id": req.get("agent_id"),
        "expected_schema": req["expected_schema"], "submitted_output": req["submitted_output"],
        "strict_content_check": req.get("strict_content_check", False),
        "bounds": {p: {**BOUND, **b} for p, b in req["bounds"].items()} if req.get("bounds") else None,
        "rules": [{**RULE, **r} for r in req["rules"]] if req.get("rules") else None,
        "enforce_rules": req.get("enforce_rules", False),
    }
    return jcs(full), hashlib.sha256(jcs(full).encode("utf-8")).hexdigest()

実例:請求書の検証

以下の 2 つの呼び出しは、いずれも **https://verify.sarnai.dev(本番サービス)への実際のライブ呼び出し**であり、生成された署名付きアテステーションとレシートを受信時のまま示しています。無料利用枠と予約済みの test- agent_id を使用しているため、課金も記録もされていません。同じ 2 件のリクエストは、まず Base 上で有料の x402 呼び出しとして実行されました。その決済証跡は各結果の下に記載されています。 どちらも個別に検証できます。上記のエージェントカードを取得し、key_version: v1-2026-09c が記載されていることを確認したうえで、上記の手順に従って各 attestation.signature を検証してください。

1. 請求書の草案 — フィールド間ルールに違反して失敗(detail: "full")

リクエスト:

{
  "task_id": "invoice-7731-draft",
  "agent_id": "test-seller-invoice-agent",
  "expected_schema": {
    "type": "object",
    "required": ["invoice_id", "currency", "line_items", "total"],
    "properties": {
      "invoice_id": { "type": "string", "pattern": "^INV-[0-9]{4}$" },
      "currency": { "enum": ["USD", "EUR"] },
      "line_items": {
        "type": "array",
        "minItems": 1,
        "items": {
          "type": "object",
          "required": ["sku", "qty", "line_total"],
          "properties": {
            "sku": { "type": "string" },
            "qty": { "type": "integer", "minimum": 1 },
            "line_total": { "type": "number", "minimum": 0 }
          }
        }
      },
      "total": { "type": "number" }
    }
  },
  "submitted_output": {
    "invoice_id": "INV-7731",
    "currency": "USD",
    "line_items": [
      { "sku": "CRWL-100", "qty": 3, "line_total": 87.0 },
      { "sku": "CRWL-200", "qty": 1, "line_total": 42.5 }
    ],
    "total": 135.0
  },
  "rules": [
    { "type": "sum_equals", "field": "line_items[].line_total", "equals_field": "total" }
  ],
  "enforce_rules": true,
  "detail": "full"
}

明細項目の合計は 129.5 ですが、total は 135.0 になっています。enforce_rules: true の場合、このルール違反によって結果は失敗となり、修正のヒントが付与されます。

{
  "summary": {
    "result": "fail",
    "blocking_checks": [
      "consistency:sum_equals"
    ]
  },
  "next_actions": [
    {
      "action": "repair_and_reverify",
      "using": "hints"
    },
    {
      "action": "withhold_payment",
      "for": "buyer"
    }
  ],
  "result": "fail",
  "verification_id": "e5d02dbb-e87c-4f49-bd10-c25c12457fef",
  "task_id": "invoice-7731-draft",
  "agent_id": "test-seller-invoice-agent",
  "output_hash": "d4fb804e434eb3ee047068cbb3691ea1e07066928e7e3efc73b94317e53ee305",
  "schema_hash": "23ea3213715cfaf2011dabe2a9a69d4ac25b6eceec7ce3d4bfef9a1298b69b2a",
  "rules_hash": "2acb6de92b88cfbca279ea6d5bd74b7a1e7dec0839dd093dbe041e25df3364d2",
  "request_hash": "e4bf9733726b24a19ec546ecc79015c9b1cb7a6829b409b41ef56b1c8f60b881",
  "verified_at": "2026-10-14T19:12:05.645221+00:00",
  "verifier_version": "0.4.1",
  "enforce_rules": true,
  "strict_content_check": false,
  "errors": [
    "enforce_rules: consistency:sum_equals at 'line_items[].line_total': values sum to 129.5, expected total (135.0)"
  ],
  "errors_total": 1,
  "errors_omitted": 0,
  "errors_detail": [
    {
      "check": "consistency:sum_equals",
      "path": "$.line_items[].line_total",
      "expected": "a sum equal to total (135.0)",
      "found": "sum 129.5",
      "message": "enforce_rules: consistency:sum_equals at 'line_items[].line_total': values sum to 129.5, expected total (135.0)"
    }
  ],
  "errors_detail_total": 1,
  "errors_detail_omitted": 0,
  "flags": [
    {
      "check": "consistency:sum_equals",
      "field": "line_items[].line_total",
      "severity": "error",
      "detail": "values sum to 129.5, expected total (135.0)"
    }
  ],
  "flags_total": 1,
  "flags_omitted": 0,
  "hints": [
    {
      "source": "integrity",
      "check": "consistency:sum_equals",
      "path": "$.line_items[].line_total",
      "expected": "a sum equal to total (135.0)",
      "found": "sum 129.5"
    }
  ],
  "hints_total": 1,
  "hints_omitted": 0,
  "schema_compliance": {
    "ratio": 1.0,
    "passed": 22,
    "total": 22
  },
  "integrity_checks": {
    "ratio": 0.875,
    "passed": 7,
    "total": 8
  },
  "overall": {
    "ratio": 0.966667,
    "passed": 29,
    "total": 30
  },
  "verify": {
    "public_key_url": "https://verify.sarnai.dev/.well-known/agent-card.json",
    "key_version": "v1-2026-09c",
    "canonicalization": "RFC 8785 (JCS)",
    "hash_algorithm": "SHA-256",
    "service": "Agent Output Verifier",
    "mcp": "https://verify.sarnai.dev/mcp"
  },
  "attestation": {
    "algorithm": "Ed25519",
    "key_version": "v1-2026-09c",
    "timestamp": "2026-10-14T19:12:05.645221+00:00",
    "signature": "4TjRCmJHEoseNdV3cqpB6ozJxc0rukTwm7A5nAC01YsQcvfRt+hX+1nRHlI/eUnwJ0IhERYSt7AWs/anUfpXBQ=="
  }
}

決済証跡(このリクエストを以前に有料で実行したときのもの): Base 上で $0.02 USDC を支払い、オンチェーンで決済済み — 0x4cb755c71413ed5419215d2d7445c34e19be79d9f15b4046802656165b774897。

2. 修正済みの請求書 — 合格(detail: "compact")

同じスキーマとルールを使い、明細項目に合うようにtotalを修正しました(129.5)。"detail": "compact"を指定し、概要、領収書のフィールド、署名のみを返すようにしています。

{
  "task_id": "invoice-7731-final",
  "agent_id": "test-seller-invoice-agent",
  "expected_schema": { "...": "same schema as above" },
  "submitted_output": {
    "invoice_id": "INV-7731",
    "currency": "USD",
    "line_items": [
      { "sku": "CRWL-100", "qty": 3, "line_total": 87.0 },
      { "sku": "CRWL-200", "qty": 1, "line_total": 42.5 }
    ],
    "total": 129.5
  },
  "rules": [
    { "type": "sum_equals", "field": "line_items[].line_total", "equals_field": "total" }
  ],
  "enforce_rules": true,
  "detail": "compact"
}
{
  "summary": {
    "result": "pass",
    "blocking_checks": []
  },
  "next_actions": [
    {
      "action": "attach_receipt",
      "for": "seller"
    },
    {
      "action": "proceed_to_payment",
      "for": "buyer"
    }
  ],
  "result": "pass",
  "verification_id": "f1734fb6-1cc4-47f2-b48f-e3bf4e4e3a60",
  "task_id": "invoice-7731-final",
  "agent_id": "test-seller-invoice-agent",
  "output_hash": "39e3d0a1431e3ce9cda89a1d2873e1ffb4e34f4eeded2ffa87946fbd941a34c3",
  "schema_hash": "b3e46597215768ff0463180da0f0d6db53edfc152b72c240aa27d9df0bb6caaa",
  "rules_hash": "2acb6de92b88cfbca279ea6d5bd74b7a1e7dec0839dd093dbe041e25df3364d2",
  "verified_at": "2026-10-14T19:12:05.833769+00:00",
  "verifier_version": "0.4.1",
  "enforce_rules": true,
  "strict_content_check": false,
  "verify": {
    "public_key_url": "https://verify.sarnai.dev/.well-known/agent-card.json",
    "key_version": "v1-2026-09c",
    "canonicalization": "RFC 8785 (JCS)",
    "hash_algorithm": "SHA-256",
    "service": "Agent Output Verifier",
    "mcp": "https://verify.sarnai.dev/mcp"
  },
  "attestation": {
    "algorithm": "Ed25519",
    "key_version": "v1-2026-09c",
    "timestamp": "2026-10-14T19:12:05.833769+00:00",
    "signature": "sLTZJgf3PEtML2teNImcWBJLXRuP3GbCfdHcP3KcB7HGB0MVoXF7xzYnxgKokafKHPUfZ+aXBl+OTBS3LlrNDA=="
  }
}

コンパクト形式では、概要、領収書のフィールド、verify、attestationが保持されます。request_hash、errors、errors_detail、flags、hints、3つのスコアは完全な形式に含まれます(上記のdetailを参照)。

支払いの証拠(このリクエストの先行する支払い済み実行): Baseで0.02 USDCを支払い、オンチェーンで決済済みです — 0xaa9161736ac410b6ac9ccc478780aa2feaa6d2cf02af4c51855e099c14499079。

2つの領収書を自分で検証する

上記の両方のレスポンスは、ここに掲載する前に、領収書の検証手順に従い、ライブのエージェントカードに記載されたv1-2026-09cの公開鍵(https://verify.sarnai.dev)で確認済みです。同じ方法で自分でも確認できます。`https://verify.sarnai.dev/.well-known/agent-card.json`を取得し、`v1-2026-09c`が記載されていることを確認してから、上記2つのJSONブロックに対して同じ5つの手順を再実行してください。署名はこのデータそのものに対して一度だけ計算されており、誰が検証しても同じ結果になります。

agent_idは、検証対象の出力を生成したAIエージェント(たとえば販売者)を識別するものです。呼び出し元が指定する、認証されていないラベルです。agent_id: "test-seller-invoice-agent"には、サービスが例示用に予約しているtest-プレフィックスを使用しています。検証は実際に行われ、領収書も本物ですが、そのAIエージェントの信頼スコアとは別に扱われます。

ライセンス

このリポジトリのドキュメントと例は、MIT Licenseの下でライセンスされています。ホスト型サービスはプロプライエタリです。このリポジトリにはドキュメントと公開メタデータのみが含まれています。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。