- Agentic AI Foundation(AAIF) 블로그에 게재된 글을 원저자 중 한 명인 Marco Gonzalez의 허락 하에 번역하였습니다.
- 이 글의 후속편인 x402 + MCP: 예산 확인은 어디에서 해야 할까도 함께 번역하였습니다.
- 원문은 아래 링크에서 확인하실 수 있습니다.
402 Payment Required: 엔터프라이즈 MCP 서버가 비용을 지불하는 에이전트에게 지켜야 할 것
402 Payment Required: What Enterprise MCP Servers Owe the Agents That Pay Them
- 저자: Marco Gonzalez, Prakash Rao Bethapudi
- 게시일: 2026년 9월 22일
7개월 간격으로 나온 두 프로토콜 릴리즈가 서로 관련된 이유로 비슷한 변화를 만들었지만, 두 릴리즈는 대개 서로 무관한 것처럼 논의됩니다.
Two protocol releases seven months apart made a similar move, for related reasons, and the two are usually discussed as if unrelated.
x402 v2는 2025년 12월에 나왔으며, HTTP 바인딩(binding)에서 결제 데이터를 HTTP 헤더에 담습니다. 서버는 402 응답의 PAYMENT-REQUIRED 헤더로 조건을 제시하고, 클라이언트는 서명한 결제 승인(authorization)을 PAYMENT-SIGNATURE 헤더로 돌려보내며, 서버는 정산(settlement) 결과를 PAYMENT-RESPONSE 헤더로 알려줍니다. 세 헤더 모두 base64로 인코딩된 JSON입니다. 핵심 사양(core specification)은 의도적으로 전송 방식과 무관하게(transport agnostic) 설계되었으며, 위의 헤더 이름들은 핵심 사양 옆에 별도로 존재하는 HTTP 바인딩에서 정의합니다 (x402, 2025; x402 Foundation, 2026a).
x402 v2 arrived in December 2025, with its HTTP binding putting payment data in HTTP headers. The server states its terms in
PAYMENT-REQUIREDon a 402 response, the client returns a signed authorization inPAYMENT-SIGNATURE, and the server reports settlement inPAYMENT-RESPONSE, all three base64 encoded JSON. The core specification is deliberately transport agnostic; those header names come from a separate HTTP binding that sits beside it (x402, 2025; x402 Foundation, 2026a).
MCP 2026-07-28은 7월에 나왔으며, 도구 호출(tool call)에서 비슷한 변화를 만들었습니다. 세션이 사라졌고, 그와 함께 Mcp-Session-Id 와 initialize 핸드셰이크도 없어졌습니다. 사용 중인 버전은 기존의 MCP-Protocol-Version 헤더가 식별합니다 (Model Context Protocol, 2025). 7월 릴리즈는 라우팅 메타데이터를 명시적으로 드러내기 위해 Mcp-Method 헤더와, 도구 호출의 경우 Mcp-Name 헤더를 추가했습니다. 릴리즈는 그 목적을 분명하게 밝힙니다. 게이트웨이, 속도 제한기(rate limiter), WAF가 JSON 본문을 파싱하는 대신 이 헤더들을 기준으로 라우팅하고 사용량을 측정(meter)할 수 있다는 것입니다 (Model Context Protocol, 2026).
MCP 2026-07-28 arrived in July and made a similar move for tool calls. Sessions are gone, along with
Mcp-Session-Idand the initialize handshake. The existingMCP-Protocol-Versionheader identifies the version in use (Model Context Protocol, 2025). The July release addsMcp-Methodand, for tool calls,Mcp-Nameto make routing metadata explicit. The release says plainly what this is for: your gateway, rate limiter, or WAF can route and meter on those headers instead of parsing JSON bodies (Model Context Protocol, 2026).
HTTP 결제 바인딩을 사용하면 중간 장비(intermediary)는 JSON-RPC를 파싱하지 않고도 라우팅 메타데이터와 결제 메타데이터를 확인할 수 있습니다. 헤더는 도구를 식별하고, 제시된 조건을 담고, 정산 결과를 보고합니다. 그렇더라도 신뢰할 수 있는 집행 지점(enforcement point)은 라우팅 헤더가 요청 본문과 일치하는지 검증해야 합니다. 헤더는 그 검증을 대체하지도, 결제를 독립적으로 증명하지도 않습니다.
With the HTTP payment binding, intermediaries can inspect routing and payment metadata without parsing JSON-RPC. Headers identify the tool, carry the offered terms, and report the settlement outcome. A trusted enforcement point must still validate that routing headers match the request body; headers do not replace that check or independently prove payment.
엔터프라이즈 규모로 MCP 서버를 운영한다면 이는 유용한 변화이지만, 동시에 하나의 질문이기도 합니다. 인프라가 가격을 볼 수 있게 되었다면, 인프라는 그 가격에 대해 무언가를 결정해야 합니다. 이번 글은 그것이 무엇인지, 그리고 어느 구성 요소가 그 결정을 내려야 하는지에 관한 글입니다.
That is useful if you operate MCP servers at enterprise scale, and it is also a question. Once the price is visible to your infrastructure, your infrastructure has to decide something about it. This article is about what, and about which component should be deciding.
교환 과정을 정확하게 / The exchange, exactly
아래는 실제로 동작하는 서버를 대상으로 추적한 전체 과정입니다. 폭에 맞게 다듬은 실제 출력이며, 이어지는 내용이 설명대로 동작하는지 확인하기 위해 v2 사양에 맞춰 직접 작성한 작은 참조 서버(reference server)에서 얻었습니다. 이 참조 서버는 공개하지 않았습니다. 리소스 서버의 결제 검증 부분을 발췌한 설명용 코드는 이번 글 마지막에 실었으며, 공식 x402 저장소에서 두 바인딩의 실행 가능한 구현을 제공합니다. 이 발췌 코드는 전체 서버나 아래에서 다루는 엔터프라이즈 승인 서비스들을 재현하지 않습니다.
Here is the whole thing, traced against a working server. This is real output, trimmed for width, from a small reference server we wrote against the v2 specifications to check that what follows actually behaves as described. It is not published. An illustrative excerpt of the resource server payment checks appears at the end of this article, and the official x402 repository provides runnable implementations of both bindings. The excerpt does not reproduce the complete server or the enterprise authorization services discussed below.
1차 요청, 결제 정보 없음:
Pass one, no payment attached:
POST /mcp
> MCP-Protocol-Version: 2026-07-28
> Mcp-Method: tools/call
> Mcp-Name: text.wordfreq
< HTTP 402 Payment Required
< PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJwYXltZW50IHJlcXVpcmVkIi...
< content-length: 0
PAYMENT-REQUIRED decoded:
{
"x402Version": 2,
"error": "payment required",
"resource": {
"url": "https://tools.example/mcp#text.wordfreq",
"description": "Word frequency: one call",
"mimeType": "application/json"
},
"accepts": [ {
"scheme": "exact",
"network": "eip155:84532",
"amount": "1100",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x1111111111111111111111111111111111111111",
"maxTimeoutSeconds": 60,
"extra": { "name": "USDC", "version": "2" }
} ]
}
x402에 대한 이해를 v1 예제로 쌓았다면 틀리기 쉬운 세부 사항이 네 가지 있습니다. 이 예제는 본문을 비워 둡니다. HTTP 바인딩은 조건을 헤더에 담고, 응답 본문의 내용은 구현에 맡깁니다. 가격 필드는 v1의 maxAmountRequired 가 아니라 amount 입니다. 그리고 네트워크는 base-sepolia 라는 문자열이 아니라 CAIP-2 식별자인 eip155:84532 입니다. 리소스(resource)는 이제 각 항목 안의 필드가 아니라 그 자체로 독립된 최상위 객체입니다.
Four details there are easy to get wrong if your mental model of x402 came from v1 examples. This example leaves the body empty; the HTTP binding carries the terms in the header and leaves response-body content to the implementation. The price field is
amount, not v1'smaxAmountRequired. And the network is a CAIP-2 identifier,eip155:84532rather than the stringbase-sepolia. The resource is now a top-level object of its own rather than a field inside each entry.
2차 요청, 같은 호출에 서명된 승인을 붙인 경우:
Pass two, the same call with a signed authorization:
POST /mcp
> MCP-Protocol-Version: 2026-07-28
> Mcp-Method: tools/call
> Mcp-Name: text.wordfreq
> PAYMENT-SIGNATURE: eyJ4NDAyVmVyc2lvbiI6MiwicmVzb3VyY2UiOnsidXJsIjoiaHR0cHM6...
PAYMENT-SIGNATURE decoded:
{
"x402Version": 2,
"resource": { "url": "https://tools.example/mcp#text.wordfreq", ... },
"accepted": {
"scheme": "exact", "network": "eip155:84532", "amount": "1100",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x1111111111111111111111111111111111111111",
"maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" }
},
"payload": {
"signature": "0x107de5941f9feaada623a832e3085e420fe86a89...",
"authorization": {
"from": "0xfc9B2F246cFDD54E9853bF315F79BBb0497d4683",
"to": "0x1111111111111111111111111111111111111111",
"value": "1100",
"validAfter": "0",
"validBefore": "1789306605",
"nonce": "0x500c0bb222c04671096fd37564c5daf7be2cd31beb8d12ff..."
}
}
}
< HTTP 200 OK
< PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4U0lNVUxBVEVE...
{ "success": true, "payer": "0xfc9B...", "network": "eip155:84532",
"amount": "1100", "transaction": "0xSIMULATED926e9d3f69e9b83bec8130..." }
accepted 객체는 제시된 조건 중 어떤 것을 선택했는지를 클라이언트가 그대로 되돌려 보내는 부분입니다. v2는 이런 방식으로 선택을 암묵적으로 두지 않고 명시적으로 만듭니다.
The
acceptedobject is the client echoing back which of the offered terms it chose, which is how v2 makes the selection explicit rather than implied.
여기서 실제인 것과 아닌 것. 스키마는 v2 스키마입니다. 서명은 실제 EIP-3009 TransferWithAuthorization 구조체에 대한 실제 EIP-712 서명이며, 서명자를 독립적으로 복원(recover)해 검증했습니다. 정산은 시뮬레이션입니다. 서버는 0xSIMULATED 로 시작하는 임시 트랜잭션 ID를 반환하며, 퍼실리테이터(facilitator)를 호출하지 않습니다. 이 EIP-3009 exact EVM 흐름에서는 지불자(payer)가 승인에 서명하고, 리소스 서버가 이를 검증한 뒤 정산을 시작하며, 퍼실리테이터가 transferWithAuthorization 을 온체인에 브로드캐스트합니다 (x402 Foundation, 2026d). 이번 글의 어떤 내용도 그 마지막 단계에 의존하지 않지만, 어느 부분을 대체(stub)했는지는 알고 계셔야 합니다.
What is real here and what is not. The schemas are the v2 schemas. The signature is a genuine EIP-712 signature over a genuine EIP-3009
TransferWithAuthorizationstruct, verified by recovering the signer independently. Settlement is simulated: the server returns a placeholder transaction id prefixed0xSIMULATEDand never calls a facilitator. In this EIP-3009exactEVM flow the payer signs the authorization and the resource server verifies it and initiates settlement, with a facilitator broadcastingtransferWithAuthorizationon chain (x402 Foundation, 2026d). Nothing in this article depends on that last hop, but you should know which part we stubbed.
MCP 바인딩으로 보낸 같은 호출 / The same call, in the MCP binding
x402 v2는 MCP 전용 바인딩도 정의하며, 이 바인딩은 결제 신호에 HTTP 상태 코드를 사용하지 않습니다 (x402 Foundation, 2026b). 서버는 isError: true 인 일반 도구 결과를 반환하면서 structuredContent 에 PaymentRequired 객체를 담고, 구조화된 콘텐츠를 읽을 수 없는 클라이언트를 위해 같은 객체를 JSON으로 인코딩해 content[0].text 에도 담습니다:
x402 v2 also defines a binding for MCP specifically, and it does not use HTTP status codes for payment signaling (x402 Foundation, 2026b). The server returns an ordinary tool result with
isError: true, carrying thePaymentRequiredobject instructuredContentand the same object JSON encoded incontent[0].textfor clients that cannot read structured content:
tools/call (no payment)
< HTTP 200, JSON-RPC result:
{
"isError": true,
"structuredContent": {
"x402Version": 2,
"error": "payment required",
"resource": { "url": "https://tools.example/mcp#text.wordfreq", ... },
"accepts": [ { "scheme": "exact", "network": "eip155:84532",
"amount": "1100", ... } ]
},
"content": [ { "type": "text", "text": "{\"x402Version\":2,\"error\":..." } ]
}
그러면 클라이언트는 _meta["x402/payment"] 에 승인을 담아 호출을 다시 보내고, 정산 결과는 _meta["x402/payment-response"] 로 돌아옵니다:
The client then resends the call with the authorization in
_meta["x402/payment"], and settlement comes back in_meta["x402/payment-response"]:
tools/call (authorization in _meta["x402/payment"])
< HTTP 200, JSON-RPC result:
{
"isError": false,
"content": [ { "type": "text", "text": "[{\"word\":\"the\",\"count\":3}, ...]" } ],
"_meta": { "x402/payment-response": {
"success": true, "network": "eip155:84532", "amount": "1100",
"payer": "0xfc9B...", "transaction": "0xSIMULATED0ccbf123ec3b32f5..."
} }
}
이것이 무엇이 아닌지에 주목하세요. 이것은 JSON-RPC의 error 멤버가 아닙니다. 호출은 프로토콜 수준에서 성공했고, 도구가 결과 안에서 결제 조건을 보고한 것입니다. 이 구분 덕분에 기존 MCP 클라이언트가 계속 동작할 수 있습니다.
Note what this is not. It is not a JSON-RPC
errormember. The call succeeded at the protocol level and the tool reported a payment condition in its result, which is the distinction that keeps existing MCP clients working.
그렇다면 서버는 어느 바인딩을 사용해야 할까요? 이는 정답을 가리는 문제가 아니라 실제 트레이드오프(trade-off)가 있는 전송 방식의 선택입니다.
So which binding should a server speak? This is a transport choice with real trade-offs rather than a correctness question.
| HTTP 바인딩 | MCP 바인딩 | |
|---|---|---|
| 동작하는 전송 방식 | Streamable HTTP | stdio를 포함한 모든 MCP 전송 방식 |
| 헤더만 읽는 프록시가 볼 수 있는가 | 예 | 아니요, 결과 안에 들어 있음 |
| 2xx가 아닌 응답을 버리는 클라이언트에서도 살아남는가 | 아니요 | 예 |
| 클라이언트가 응답 헤더를 읽어야 하는가 | 예 | 아니요 |
HTTP binding MCP binding Works over Streamable HTTP any MCP transport, stdio included Visible to a proxy that reads only headers yes no, it is inside the result Survives clients that discard non-2xx responses no yes Needs the client to read response headers yes no
이번 글의 나머지 주장은 승인 결정을 어디에 둘 것인가에 관한 것이며, 어느 바인딩이든 성립합니다. 다만 도구에 가격을 매기는 이유가 공유 인프라에서 사용량을 측정하고 정책을 집행하기 위해서라면, 요청 경로에 본문 파서를 두지 않고도 이를 가능하게 하는 것은 HTTP 바인딩입니다. 참조 서버는 같은 가격으로 두 바인딩을 모두 구현했으며, 이것이 둘을 공정하게 비교하는 방법입니다.
The argument in the rest of this article is about where authorization decisions sit, and it holds either way. But if the reason you are pricing tools is so that shared infrastructure can meter and enforce, the HTTP binding is what makes that possible without a body parser in the path. The reference server implements both against the same price, which is the honest way to compare them.
도구에 비용이 들면 실제로 달라지는 것 / What actually changes when a tool costs money
402 이전에는 하나의 구성 요소가 하나의 결정을 내렸습니다. 에이전트가 어떤 도구를 호출할지 골랐습니다. 402 이후에는 네 가지 결정이 존재하며, 각 결정의 주인이 다릅니다.
Before the 402, one component made one decision: the agent chose which tool to call. After it, four decisions exist, and they have different owners.
| 결정 | 누가 내려야 하는가 | 잘못되었을 때의 모습 |
|---|---|---|
| 어떤 작업을 할 것인가 | 에이전트와 그 모델 | 잘못된 계획. 적은 비용으로 폐기할 수 있음 |
| 누가 요청하는가 | ID 공급자(identity provider), 게이트웨이에서 확인 | 인증되지 않았거나 범위를 벗어난 호출자가 유료 도구에 도달함 |
| 비용은 얼마인가 | 서버, 전달받은 인자로부터 | 호출자가 얻은 작업보다 적은 금액을 지불함 |
| 이 지출이 허용되는가 | 에이전트가 영향을 줄 수 없는 예산 권한(budget authority) | 에이전트가 설득당하면 함께 움직여 버리는 지출 한도 |
Decision Who should make it What it looks like when it goes wrong What work to do the agent and its model a bad plan, cheaply discarded Who is asking the identity provider, checked at the gateway an unauthenticated or out of scope caller reaching a priced tool What it costs the server, from the arguments it received a caller that pays less than the work it obtained Whether this spend is permitted a budget authority the agent cannot influence spending limits that move when the agent is talked into moving them
곱씹어 볼 만한 것은 네 번째 행이며, 이번 글의 나머지는 대부분 그에 관한 내용입니다.
The fourth row is the one worth dwelling on, and the rest of this article is mostly about it.
이 주장이 전제하는 위협 모델 / The threat model this argument assumes
보안에 관한 주장은 무엇을 상대로 한 주장인지 밝히지 않으면 아무 의미가 없습니다.
Security claims mean nothing without saying what they are claims against.
적대적이라고 가정하는 것. 에이전트 프로세스와 그에 닿는 모든 것, 즉 계획기(planner), 프롬프트, 도구 출력, 에이전트가 읽는 문서입니다. 에이전트는 문법적으로 가능한 어떤 요청이든 시도하도록 유도될 수 있다고 가정하며, 여기에는 가격을 잘못 기재하거나, 엉뚱한 수취인(payee)을 지정하거나, 이전 승인을 재사용(replay)하는 요청도 포함됩니다.
Assumed hostile. The agent process and everything reaching it: its planner, its prompts, its tool outputs, the documents it reads. Assume it can be induced to attempt any request syntactically available to it, including requests that misstate prices, name the wrong payee, or replay an earlier authorization.
정직하지만 실수할 수 있다고 가정하는 것. 게이트웨이, 서버, 그리고 이들이 집행하는 정책입니다. 이들은 잘못 설정될 수 있습니다. 공격자가 제공한 코드를 실행하고 있다고 가정하지는 않습니다.
Assumed honest but fallible. The gateway, the server, and the policy they enforce. They can be misconfigured. They are not assumed to be running attacker supplied code.
범위 밖. 게이트웨이 바이너리나 호스트의 침해, 배포 권한을 가진 악의적인 운영자, HSM에서의 키 추출, 체인 수준의 공격, 서비스 거부(denial of service) 공격입니다. 이들 각각은 이어지는 내용의 일부를 무력화합니다.
Out of scope. A compromised gateway binary or host, a malicious operator with deploy access, key extraction from an HSM, chain level attacks, and denial of service. Each defeats parts of what follows.
분리가 주는 것을 정확히 말하면. 아래의 모든 주장은 세 가지 조건이 충족될 때 성립합니다. 각 검사가 이전 단계가 넘겨준 값이 아니라 자신의 1차 출처(primary source)에서 입력을 도출할 것, 모든 요청 경로가 우회로 없이 그 검사에 도달할 것, 그리고 어느 검사에서든 실패하면 요청이 중단될 것입니다. 이는 운영자가 지켜야 할 설계 의무이지, 프로토콜이 제공하는 보장이 아닙니다. 이 모델 아래에서 테스트로 어떤 속성을 입증한 경우에는 그 테스트를 밝힙니다. 통과한 테스트는 그 테스트가 실행한 경우들에 대한 동작을 보여줄 뿐, 증명이 아닙니다.
What the separations buy, stated precisely. Every claim below holds when three conditions are met: each check derives its inputs from its own primary source rather than from a value a previous stage passed along, every request path reaches the check with no bypass route, and a failure at any check stops the request. Those are design obligations for the operator, not guarantees the protocols provide. Where a test demonstrates a property under this model we name it. A passing test demonstrates behavior for the cases it exercises; it is not a proof.
누가 요청하는가: 7월 릴리즈가 바꾼 것 / Who is asking: what the July release changed
MCP 2026-07-28은 무료 도구보다 유료 도구에서 더 중요한 방식으로 인가(authorization)를 강화했습니다 (Model Context Protocol, 2026). 인가 서버는 RFC 9207에 따라 iss 파라미터를 반환하는 것이 권장되며(should), 클라이언트는 코드를 교환하기 전에 이를 반드시(must) 검증해야 합니다. 동적 클라이언트 등록(Dynamic Client Registration)은 폐기 예정(deprecated)이 되었고, 클라이언트 ID 메타데이터 문서(Client ID Metadata Documents)가 그 자리를 대신합니다. 클라이언트 자격 증명은 그것을 발급한 발급자(issuer)에 묶이며, 다른 인가 서버에서 재사용할 수 없습니다.
MCP 2026-07-28 hardened authorization in ways that matter more for paid tools than for free ones (Model Context Protocol, 2026). Authorization servers should return the
issparameter per RFC 9207 and clients must validate it before redeeming a code. Dynamic Client Registration is deprecated in favor of Client ID Metadata Documents. Client credentials are bound to the issuer that minted them, with no reuse across authorization servers.
모든 도구 호출에 가격이 붙어 있다고 생각하고 이 목록을 다시 읽어 보세요. 발급자 혼동(issuer confusion)은 더 이상 인가 위험에 그치지 않고, 지출 위험이기도 합니다. 엉뚱한 인가 서버에 대해 통하는 자격 증명은 엉뚱한 예산에 대한 지출을 승인할 수 있는 자격 증명입니다.
Read that list again with a price attached to every tool call. Issuer confusion is no longer only an authorization risk; it is also a spending risk. A credential that works against the wrong authorization server is a credential that can authorize spending against the wrong budget.
이 예시는 agentgateway 1.5 문서를 따라, 단독 실행(standalone) agentgateway의 단순화된 MCP 설정을 사용합니다. 인증(authentication), MCP 인가, 백엔드 자격 증명 전달은 각각 별도의 정책 필드를 가집니다. 예시의 엔드포인트는 실제 배포 환경의 발급자, 공개 리소스 URL, 백엔드로 바꿔야 합니다. 이것은 설정 예시이며, Kubernetes AgentgatewayPolicy 매니페스트가 아닙니다 (agentgateway, n.d.-a, n.d.-b).
This example uses standalone agentgateway’s simplified MCP configuration, following the 1.5 documentation. Authentication, MCP authorization, and backend credential forwarding have distinct policy fields. The illustrative endpoints must be replaced with the deployment’s issuer, public resource URL, and backend. This is a configuration example, not a Kubernetes AgentgatewayPolicy manifest (agentgateway, n.d.-a, n.d.-b).
mcp:
port: 3000
policies:
mcpAuthentication:
mode: strict
issuer: https://id.example/realms/agents
audiences: [https://tools.example/mcp]
jwks:
url: https://id.example/realms/agents/protocol/openid-connect/certs
resourceMetadata:
resource: https://tools.example/mcp
mcpAuthorization:
rules:
- 'has(jwt.dept) && jwt.dept == "eng"'
targets:
- name: paid-tools
mcp:
host: https://resource.internal.example/mcp
policies:
backendAuth:
passthrough: {}
인증 요구 사항을 드러내기 위해 mode: strict 를 명시적으로 설정했습니다. strict는 이 단독 실행 MCP 인증 정책의 문서화된 기본값입니다. optional 모드는 토큰이 없는 요청도 허용하므로, 모든 유료 도구 호출자가 인증되어야 하는 환경에는 적합하지 않습니다 (agentgateway, n.d.-a).
We set mode: strict explicitly to make the authentication requirement visible. Strict is the documented default for this standalone MCP authentication policy. Optional mode permits requests without a token and is unsuitable where every paid-tool caller must be authenticated (agentgateway, n.d.-a).
백엔드는 전달받은 자격 증명을 독립적으로 검증합니다. 대상(target)의 backendAuth.passthrough 정책은 검증된 JWT를 백엔드 요청에 다시 추가합니다. 이 목적이라면 preserveToken: true 는 필요하지 않으며, 이 옵션은 이후의 정책들이 토큰을 더 폭넓게 사용할 수 있도록 남겨 둡니다. 게이트웨이와 백엔드 모두 발급자와 의도된 리소스 대상(audience)을 검증해야 합니다. 토큰을 전달한다고 해서 그 토큰이 다른 리소스에 대해 유효해지지는 않습니다 (agentgateway, n.d.-c).
The backend independently validates the forwarded credential. The target’s
backendAuth.passthroughpolicy re-adds the validated JWT to the backend request. We do not needpreserveToken: truefor this purpose; it leaves the token available to subsequent policies more broadly. Gateway and backend must both validate the issuer and the intended resource audience. Forwarding does not make a token valid for a different resource (agentgateway, n.d.-c).
mcpAuthorization 규칙은 서명된 dept 클레임이 eng 와 같을 것을 요구합니다. has(jwt.dept) 는 클레임이 없는 경우를 명시적으로 처리합니다. 이 규칙은 대상의 도구에 대한 접근을 제한할 뿐, 그 자체로 금전적 예산을 할당하거나 승인하지는 않습니다. 지출 한도는 아래에서 설명하는 별도의 승인 서비스가 책임집니다 (agentgateway, n.d.-b).
The
mcpAuthorizationrule requires a signeddeptclaim equal toeng.has(jwt.dept)makes the missing-claim case explicit. This gates access to the target’s tools; it does not itself allocate or authorize a monetary budget. Spending limits remain the responsibility of the separate authorization service described below (agentgateway, n.d.-b).
설계할 때 감안해야 할 한계가 하나 있습니다. 이 위치의 CEL은 클레임을 비교하고 해시를 계산할 수는 있지만, RFC 8785 JSON 정규화(canonicalization)는 제공하지 않습니다. 설계상 RFC 8785로 정규화한 요청 다이제스트(digest)가 필요하다면, 정규화 단계는 이를 지원하는 구성 요소에서 수행해야 합니다.
One limitation to design around: CEL in this position can compare claims and compute hashes, but it does not provide RFC 8785 JSON canonicalization. If your design needs an RFC 8785-canonicalized request digest, the canonicalization step needs to happen in a component that supports it.
비용은 얼마인가: 호출자가 제시한 숫자는 절대 쓰지 않는다 / What it costs: never a number the caller supplied
서버는 전달받은 인자로 가격을 계산합니다. 당연하게 들리지만, 그 반대가 얼마나 자주 배포되는지 알게 되면 생각이 달라집니다. 대개는 첫 견적을 캐시하는 클라이언트 SDK와, 그 견적을 믿는 실행 경로의 조합으로 나타납니다.
The server computes the price from the arguments it received. That sounds obvious until you notice how often the alternative ships, usually as a client SDK that caches the first quote and an execution path that trusts it.
참조 서버는 기본 1000 단위(base unit)에 킬로바이트당 100 단위를 더해(올림) 견적을 내고, 실행 요청의 인자로 가격을 다시 계산합니다. 5바이트 입력은 1100 단위입니다. 그 승인을 5000 단위가 드는 40,000바이트 입력에 재사용하면 AMOUNT_MISMATCH 로 거부됩니다. 서명된 승인 금액과 선택한 결제 금액 모두 서버가 계산한 가격과 같아야 하며, 두 값이 서로 일치하더라도 가격보다 많이 내는 초과 지불은 거부됩니다 (x402 Foundation, n.d.-b).
The reference server quotes 1000 base units plus 100 per kilobyte, rounded up, and recomputes the price from the execution request’s arguments. A five-byte input costs 1100 units. Reusing its authorization for a 40,000-byte input, which costs 5000 units, is refused with
AMOUNT_MISMATCH. Both the signed authorization value and the selected payment amount must equal the server’s computed price; matching overpayments are refused too (x402 Foundation, n.d.-b).
판매자가 하나라면 이것으로 이야기는 끝입니다. 경쟁하는 여러 판매자 중에서 선택하는 것은 더 어렵습니다. 어느 판매자가 더 저렴했는지를 에이전트가 보고한다면, 에이전트의 보고가 그 보고를 신뢰하는 결정의 입력이 됩니다. 한 가지 대안은 402 응답이 게이트웨이를 거쳐 돌아올 때 게이트웨이가 그 조건을 기록하고, 게이트웨이가 관찰한 것을 기준으로 선택하는 것입니다. 이 방법은 동작하지만, 상태를 가진(stateful) 게이트웨이와 관찰 기간(witnessing window)이라는 비용이 듭니다. 그 대가를 치를 가치가 있는지는 잘못된 선택이 얼마나 큰 비용을 초래하는지에 달려 있습니다.
For a single vendor that is the whole story. Selection across competing vendors is harder: if the agent reports which vendor was cheaper, the agent's report is an input to the decision that trusts it. One alternative is to have the gateway record terms from 402 responses as they pass back through it and select on what the gateway observed. That works, and it costs you a stateful gateway and a witnessing window. Whether the trade is worth it depends on what a wrong selection costs you.
지출이 허용되는가: 같은 답을 두 번 도출하기 / Whether it is permitted: deriving the same answer twice
지출 결정이 중대한 경우, 저희가 경험상 효과를 확인한 패턴은 그 결정을 서로 다른 두 개의 1차 출처에서 두 번 평가하는 것입니다.
Where the spend decision is consequential, the pattern that has held up for us is to evaluate it twice, from two different primary sources.
승인 서비스는 제안된 행동(action)을 RFC 8785에 따라 정규화하고, 해시를 계산하고, 정책을 평가하고, 예산을 예약한 뒤, 수명이 짧은 서명된 영수증(receipt)을 발급합니다. 모든 필드가 서명 안에 포함됩니다:
An authorization service canonicalizes the proposed action per RFC 8785, hashes it, evaluates policy, reserves budget, and issues a short lived signed receipt. Every field is inside the signature:
interface ReceiptPayload {
v: 1;
receiptId: string;
decision: 'permit' | 'deny';
canonicalDigest: string; // 승인된 행동 자체가 아니라, 그 행동의 다이제스트 / of the authorized action, not the action itself
policyVersion: string;
reservationRef: string;
issuer: string;
audience: string;
issuedAt: string;
expiresAt: string;
}
그런 다음 송신(egress) 경로의 집행 지점이 실제로 보내려는 바이트에서 정규화된 행동을 다시 도출하고, 다이제스트를 재계산해 비교합니다. 집행 지점은 호출자로부터 다이제스트도, 결정도, 조건도 가져오지 않습니다.
An enforcement point on the egress path then re-derives the canonical action from the bytes it is actually about to send, recomputes the digest, and compares. It imports no digest, no decision, and no terms from the caller.
위 위협 모델 안에서의 속성은 다음과 같습니다. 승인 이후 요청을 수정할 수는 있지만 집행 지점의 정책 사본에 접근하거나 집행 지점을 우회할 수는 없는 행위자는, 진짜이며 올바르게 서명된 영수증을 갖고 있더라도 수정된 요청을 통과시킬 수 없습니다. 거부는 변조를 변조로 알아봐서가 아니라, 독립적인 두 도출 결과가 서로 달라서 일어납니다.
The property, within the threat model above: an actor who can modify the request after authorization, but who cannot reach the enforcement point's policy copy or bypass it, cannot get the modified request through even holding a genuine and correctly signed receipt. Refusal comes from divergence between two independent derivations, not from recognizing the tampering as such.
이 패턴이 하지 못하는 것도 똑같이 분명히 해야 합니다. 두 곳 모두에서 정책이 틀렸다면 도움이 되지 않습니다. 두 곳 모두 같은 정책을 평가하기 때문입니다. 집행 지점을 우회할 수 있다면 도움이 되지 않습니다. 이는 배포 환경의 속성이며, 실제로 이 패턴이 실패하는 흔한 원인입니다. 그리고 집행 지점 자체가 침해되면 버티지 못하는데, 이는 위에서 범위 밖으로 둔 경우입니다.
What it does not do deserves equal clarity. It does not help if the policy is wrong in both places, since both evaluate the same policy. It does not help if the enforcement point can be bypassed, which is a deployment property and a common way this pattern fails in practice. And it does not survive a compromised enforcement point, which is out of scope above.
영수증이 행동 대신 다이제스트를 담는 데에는 대가가 따릅니다. 집행 지점이 거부할 때 무언가가 달라졌다고는 말할 수 있지만, 어느 필드가 달라졌는지는 말할 수 없습니다. 이는 의도한 트레이드오프입니다. 행동 자체를 담은 영수증은 그것을 가로챈 누구에게나 무엇이 승인되었는지를 드러내며, 송신 경로에 헷갈릴 수 있는 두 번째 진실의 출처를 안겨 줍니다.
The receipt carries a digest rather than the action, which costs something: when enforcement refuses, it can say that something diverged but not which field. That is the intended trade. A receipt carrying the action would disclose what was approved to anyone who intercepted one, and would give the enforcement path a second source of truth to be confused by.
승인과 서명은 서로 다른 구성 요소에 둡니다 / Approval and signing belong in different components
게이트(gate)는 신원 주장(identity assertion)을 결합된 클레임 제약과 대조해 평가하고, 허용으로 평가되면 상한(ceiling)을 담은 승인된 서명 요청을 발급합니다. 키를 보관하는 별도의 서명 서비스는 자체 제약을 평가합니다. 게이트가 500,000의 상한을 승인했고 서명자 자체의 한도가 100,000이라면, 400,000에 대한 서명 요청은 서명자가 거부합니다. 게이트의 상한은 추가적인 상한선으로만 작용하며, 결코 권한을 부여하지 않습니다.
A gate evaluates the identity assertion against a bound claim constraint and, on a permitting evaluation, issues an authorized signing request carrying a ceiling. A separate signing service holds the key and evaluates its own constraints. If the gate approves a ceiling of 500,000 and the signer's own limit is 100,000, a request to sign 400,000 is refused by the signer. The gate's ceiling acts only as an additional upper bound, never as a grant.
저희 구현에서 게이트 모듈에는 서명 키도, 서명 기본 기능(primitive)도, 그것을 만들어낼 수 있는 import도 없으며, 한 테스트가 모듈 자신의 소스를 스캔하고 export를 실행해 이를 확인합니다. 이것이 무엇을 입증하는지는 신중하게 말하고 싶습니다. 이 테스트는 회귀 방지 장치(regression guard)입니다. 누군가 엉뚱한 모듈에 서명 의존성을 추가하는 커밋을 잡아냅니다. 배포된 프로세스가 서명할 수 없다는 것까지 입증하지는 않습니다. 그것은 전체 의존성 트리, 런타임, 호스트에 달려 있으며, 소스 스캔은 그중 어느 것도 보지 못합니다. 불가능성의 증명이 아니라, 범위가 명시된 테스트된 불변 조건(invariant)으로 다루세요.
In our implementation the gate module contains no signing key, no signing primitive, and no import that could produce one, and a test asserts this by scanning the module's own source and exercising its exports. We want to be careful about what that establishes. It is a regression guard: it catches the commit where someone adds a signing dependency to the wrong module. It does not establish that the deployed process is incapable of signing, which depends on the full dependency tree, the runtime, and the host, none of which a source scan sees. Treat it as a tested invariant with a stated scope, not an impossibility proof.
같은 논리가 수취인에도 적용되며, 수취인은 코드 경로를 공유하지 않는 세 곳, 즉 게이트, 서명자, 리소스에서 제약됩니다. 이렇게 반복하는 가치는 설정 하나가 잘못되더라도 결제가 조용히 다른 곳으로 가지 않는다는 데 있습니다. 세 번째가 아래 코드가 보여주는 부분입니다. 다른 to 를 지정한 승인은 클라이언트가 무엇을 전달받았든 상관없이 거부됩니다.
The same reasoning covers the payee, constrained in three places with no shared code path: at the gate, at the signer, and at the resource. The value of the repetition is that one misconfiguration does not silently redirect payment. The third is the one the code below demonstrates: an authorization naming a different
tois refused regardless of what the client was told.
서버가 지켜야 할 것: 다섯 가지 의무 / What the server owes: five obligations
-
요청된 작업에 맞춰 가격을 검증합니다. 이 예시처럼 현재 인자로 가격을 다시 계산하거나, 이전 견적과 그 견적이 리소스, 인자, 조건, 유효 기간에 결합되어 있는지를 검증합니다. 이런 검사 없이 클라이언트가 제시한 가격을 절대 신뢰하지 않습니다.
-
탐색은 무료로 둡니다.
tools/list에는 어떤 비용도 들지 않아야 합니다. 그렇지 않으면 에이전트가 구매하기 전에 비교해 볼 수 없습니다. -
재사용을 명시적으로 거부합니다. 모든 서버 인스턴스에서 승인의 사용 여부를 원자적으로(atomically) 추적하고, 승인 유효 기간 동안 기록을 보존하며, 정산이나 전달이 실패한 뒤 재시도가 어떻게 복구되는지 정의합니다.
-
수취인은 로컬에서 강제합니다. 승인에 무엇이 적혀 있든, 자신의 설정에서 가져온 자신의 주소를 사용합니다.
-
어떤 바인딩을 지원하는지 밝힙니다. HTTP인지 MCP인지, 그리고 실제로 어떤 클라이언트로 테스트했는지 알립니다.
- Validate the price against the work requested. Recompute it from the current arguments, as this example does, or validate an earlier quote and its binding to the resource, arguments, terms, and validity window. Never trust a client-supplied price without those checks.
- Make discovery free.
tools/listshould not cost anything, or agents cannot shop before they buy.- Reject replay explicitly. Track authorization use atomically across every serving instance, retain records for the authorization validity period, and define how retries recover after settlement or delivery failure.
- Enforce the payee locally. Your own address, from your own configuration, regardless of what the authorization names.
- Say which binding you speak. HTTP or MCP, and which clients you have actually tested against.
그리고 게이트웨이 운영자와 서버 운영자가 함께 지는 의무가 하나 있습니다. Mcp-Method 와 Mcp-Name 이 그 뒤에 있는 본문과 일치하는지 확인하는 것입니다. 헤더 기반의 사용량 측정은 본문이 헤더와 모순될 수 없을 때에만 타당합니다. 헤더는 한 도구를 알리는데 본문은 다른 도구를 호출하는 요청을 받아들이는 서버는, 모두의 대시보드를 조용히 무효로 만든 것입니다. 가격 책정이 일어나기 전에 그런 요청을 거부하세요.
And one shared obligation for gateway and server operators: check that
Mcp-MethodandMcp-Nameagree with the body behind them. Metering on headers is only sound if the body cannot contradict them, and a server that accepts a request whose header advertises one tool while the body calls another has quietly invalidated everyone's dashboards. Refuse those before pricing happens.
도달할 수 있다는 것이 권한을 뜻하지는 않습니다 / Reachability is not the same as authority
저희는 예산 권한을 에이전트가 닿을 수 없는 곳에 두어야 한다고 주장해 왔고, 네트워크 격리는 이를 떠올리는 가장 쉬운 방법입니다. 하지만 격리는 여러 통제 수단 중 하나일 뿐, 집행 가능한 한도의 정의가 아닙니다. 에이전트가 닿을 수 있는 서비스라도, 에이전트가 호출할 수 있는 API를 통해 예산을 집행할 수 있습니다. 단, 에이전트의 자격 증명이 지출을 요청할 권한만 주고, 자신이 쓸 수 있는 한도를 바꿀 권한은 주지 않아야 합니다.
We have argued for putting the budget authority somewhere the agent cannot reach, and network isolation is the easiest way to picture that. But isolation is one control, not the definition of an enforceable limit. A service the agent can reach, over an API the agent can call, can still enforce a budget, provided the agent's credential authorizes it to request spending and not to change what it is allowed to spend.
한도가 실제로 통제 수단인지를 결정하는 질문들은 도달 가능성(reachability)보다 범위가 좁습니다:
The questions that actually decide whether a limit is a control are narrower than reachability:
- 에이전트의 자격 증명으로 한도를 수정할 수 있는가, 아니면 한도 안에서 소비만 할 수 있는가?
- 에이전트가 승인을 위조하거나 재사용할 수 있는가, 또는 자신에게 발급되지 않은 승인을 얻을 수 있는가?
- 에이전트가 집행이 실행되지 않는 경로로 유료 리소스에 도달할 수 있는가?
- Can the agent's credential modify the limit, or only consume against it?
- Can the agent forge or replay an approval, or obtain one it was not issued?
- Can the agent reach the priced resource on a path where enforcement does not run?
명시된 위협 모델 안에서, 이 질문들에 대한 답은 에이전트가 그 서비스에 소켓을 열 수 있든 없든 집행 가능한 한도를 확립하는 데 도움이 됩니다. 또한 올바른 정책 평가와 원자적인 예산 회계도 필요합니다. 네트워크 격리는 취약한 경로에 대한 접근을 줄일 수 있지만, 이러한 인가 통제를 대체하지는 못합니다.
Within the stated threat model, those answers help establish an enforceable limit whether or not the agent can open a socket to the service. They also require correct policy evaluation and atomic budget accounting. Network isolation can reduce access to vulnerable paths, but it does not replace those authorization controls.
x402 v2는 가격을 명시하고 승인을 전달하는 방법을 제공합니다. MCP 2026-07-28은 라우팅 메타데이터를 헤더로 노출하고, x402 HTTP 바인딩은 그 옆에 결제 메타데이터를 노출합니다. 그러나 어느 쪽도 누구의 예산을 어떤 한도 아래에서 쓰는지에 대한 결정을 내려주지는 않습니다. 그 결정을 어디에 둘지는 여러분의 몫이며, 두 사양이 다시 바뀐 뒤에도 여전히 중요한 것은 그 결정을 어디에 두었느냐입니다.
x402 v2 gives you a way to state a price and carry an authorization. MCP 2026-07-28 exposes routing metadata in headers; the x402 HTTP binding exposes payment metadata alongside it. Neither gives you a decision about whose budget is being spent and under what limit. That decision is yours to place, and where you place it is the part that will still matter after both specifications have moved on again.
가져다 쓸 만한 부분 / The part worth copying
아래의 리소스 서버 결제 검사는 설계의 한 부분을 보여줍니다. 조직 신원, 예산 예약, 영수증과 행동의 결합, 독립적인 서명자 한도는 구현하지 않습니다. 그러한 통제들은 별도로 존재합니다.
The resource server payment checks below illustrate one part of the design. They do not implement organizational identity, budget reservation, receipt and action binding, or independent signer limits. Those controls remain separate.
이 축약된 JavaScript 클래스 메서드는 ethers의 getAddress, isHexString, verifyTypedData 를 사용합니다. 표준 EIP-3009 TRANSFER_WITH_AUTHORIZATION_TYPES 정의, 체인과 자산과 EIP-712 도메인과 수취인과 maxTimeoutSeconds (여기서는 60)에 대한 검증된 로컬 설정, 그리고 초기화된 this.seenNonces 집합을 가정합니다. 설명을 위한 발췌이며, 독립 실행 서버나 로컬 테스트 스위트가 다루는 구현 원본이 아닙니다.
This abbreviated JavaScript class method uses
getAddress,isHexString, andverifyTypedDatafrom ethers. It assumes the standard EIP-3009TRANSFER_WITH_AUTHORIZATION_TYPESdefinition, validated local configuration for the chain, asset, EIP-712 domain, payee, andmaxTimeoutSeconds(60 here), and an initializedthis.seenNoncesset. It is an explanatory excerpt, not a standalone server or the verbatim implementation covered by the local test suite.
verifyPayment(payment, expectedAmount) {
const refuse = (code) => ({ ok: false, code });
const uint = (value) => {
if (typeof value !== 'string' || !/^[0-9]+$/.test(value)) {
throw new TypeError('Expected an unsigned decimal string');
}
const n = BigInt(value);
if (n >= (1n << 256n)) throw new RangeError('uint256 overflow');
return n;
};
try {
if (payment?.x402Version !== 2) {
return refuse('UNSUPPORTED_VERSION');
}
const accepted = payment.accepted;
const auth = payment.payload?.authorization;
const signature = payment.payload?.signature;
if (!accepted || !auth || !isHexString(signature, 65)) {
return refuse('MALFORMED');
}
if (accepted.scheme !== 'exact') return refuse('UNSUPPORTED_SCHEME');
if (accepted.network !== this.config.network) {
return refuse('WRONG_NETWORK');
}
if (getAddress(accepted.asset) !== getAddress(this.config.asset)) {
return refuse('WRONG_ASSET');
}
const from = getAddress(auth.from);
const payee = getAddress(this.config.payTo);
if (getAddress(auth.to) !== payee ||
getAddress(accepted.payTo) !== payee) {
return refuse('WRONG_PAYEE');
}
const expected = uint(String(expectedAmount));
const authorized = uint(auth.value);
const selected = uint(accepted.amount);
if (authorized !== expected || selected !== expected) {
return refuse('AMOUNT_MISMATCH');
}
if (!isHexString(auth.nonce, 32)) return refuse('MALFORMED');
const nonceKey = [this.config.chainId,
getAddress(this.config.asset), from,
auth.nonce.toLowerCase()].join(':');
if (this.seenNonces.has(nonceKey)) return refuse('REPLAY');
const now = BigInt(Math.floor(Date.now() / 1000));
const validAfter = uint(auth.validAfter);
const validBefore = uint(auth.validBefore);
if (validAfter > now) return refuse('NOT_YET_VALID');
if (validBefore <= now) return refuse('EXPIRED');
if (validBefore < now + 6n) {
return refuse('INSUFFICIENT_SETTLEMENT_TIME');
}
// 시계가 맞춰져 있다고 가정한 추가 로컬 정책. / Additional local policy, assuming aligned clocks.
const timeout = this.config.maxTimeoutSeconds; // 이 예시에서는 60 / 60 in this example
if (accepted.maxTimeoutSeconds !== timeout) {
return refuse('TIMEOUT_MISMATCH');
}
if (validBefore > now + uint(String(timeout))) {
return refuse('TIMEOUT_TOO_LONG');
}
let recovered;
try {
recovered = verifyTypedData(
{ name: this.config.assetName, version: this.config.assetVersion,
chainId: this.config.chainId,
verifyingContract: this.config.asset },
TRANSFER_WITH_AUTHORIZATION_TYPES,
{ from: auth.from, to: auth.to, value: auth.value,
validAfter: auth.validAfter, validBefore: auth.validBefore,
nonce: auth.nonce },
signature,
);
} catch {
return refuse('BAD_SIGNATURE');
}
if (getAddress(recovered) !== from) return refuse('BAD_SIGNATURE');
this.seenNonces.add(nonceKey);
return { ok: true, payer: from, amount: String(auth.value) };
} catch {
return refuse('MALFORMED');
}
}
유효 기간 검증은 두 층으로 이루어집니다. 참조 EIP-3009 클라이언트는 validBefore 를 자신의 현재 시각에 maxTimeoutSeconds 를 더한 값으로 설정하며, validAfter 는 0입니다. 참조 검증기는 미래 시점의 validAfter 를 거부하고, 만료까지 최소 6초가 남아 있을 것을 요구합니다. validBefore 에서 validAfter 를 뺀 간격의 최댓값은 두지 않는데, 시작 타임스탬프가 0인 경우에는 그런 제한이 적절하지 않기 때문입니다 (x402 Foundation, n.d.-a, n.d.-b).
Timeout validation has two layers. The reference EIP-3009 client sets
validBeforeto its current time plusmaxTimeoutSeconds;validAfteris zero. The reference verifier rejects a futurevalidAfterand requires at least six seconds before expiry. It does not impose a maximumvalidBefore-minus-validAfterinterval, which would be inappropriate for a zero start timestamp (x402 Foundation, n.d.-a, n.d.-b).
발췌 코드는 명시적인 로컬 정책을 더합니다. 시계가 맞춰져 있다는 전제에서, 남은 유효 시간은 서버에 설정된 60초를 넘을 수 없으며, 되돌아온 타임아웃 값은 그 설정과 일치해야 합니다. 이 상한은 운영자의 선택이며, x402의 보편적인 요구 사항도, 제안(offer)이 언제 발행되었는지에 대한 증명도 아닙니다. 시계 오차(clock skew)를 허용해야 하거나 제안 시점을 기준으로 만료를 정해야 하는 배포 환경이라면, 그러한 규칙을 정의하고 만료 시한을 신뢰할 수 있는 제안 데이터에 결합해야 합니다.
The excerpt adds an explicit local policy: with aligned clocks, the remaining lifetime may not exceed the server’s configured 60 seconds, and the echoed timeout must match that configuration. This cap is an operator choice, not a universal x402 requirement or proof of when an offer was issued. Deployments needing clock-skew tolerance or offer-anchored expiry must define those rules and bind the deadline to trusted offer data.
이 코드는 유료 호출이 잘못될 수 있는 방식들을 발견 비용이 적은 순서대로 나열한 목록으로 읽으세요. 버전, 그다음 형태, 그다음 스킴과 네트워크, 그다음 자산, 그다음 결제가 아닌 로컬 설정에서 가져온 수취인, 그다음 현재 인자로 다시 계산한 견적과의 금액 비교, 그다음 논스(nonce) 형태, 그다음 재사용, 그다음 유효 기간, 그리고 마지막에야 서명 복원입니다. 비용이 적은 검증을 서명 복원보다 먼저 수행하므로, 형식이 잘못된 결제는 암호 연산 이전에 거부됩니다.
Read it as a list of the ways a paid call can be wrong, in the order that costs least to discover. Version, then shape, then scheme and network, then the asset, then the payee from local configuration rather than from the payment, then the amount against a quote recomputed from the arguments in hand, then nonce shape, then replay, then the validity window, and only then signature recovery. The cheaper validation checks run before signature recovery, so malformed payments are rejected before that cryptographic work.
리소스 서버의 의무를 표현하는 검사는 두 가지입니다. 수취인은 로컬 설정에서 가져와 결제의 두 필드와 모두 비교합니다. 서버는 현재 요청 인자로 expectedAmount 를 계산하므로, 실행이 클라이언트의 이전 견적을 신뢰하는 데 의존하지 않습니다.
Two checks express the resource server obligations. The payee comes from local configuration and is compared with both payment fields. The server computes
expectedAmountfrom the current request arguments, so execution does not depend on trusting the client's earlier quote.
메모리 내(in-memory) 집합은 실행 중인 하나의 프로세스 안에서의 재사용 거부만 보여줍니다. 재시작하면 기록이 사라지고, 인스턴스 사이에서 공유되지 않으며, 만료된 기록을 정리하지도 않습니다. 실제로 배포하는 서비스에는 체인, 자산, 승인자, 논스를 키로 하는 공유된 원자적 기록이 필요하며, 이 기록은 승인 유효 기간 동안 보존되어야 합니다. 또한 대기 중인 정산과 완료된 정산을 추적하고, 전달이 실패하더라도 이중 청구가 일어나거나 이미 결제한 결과를 되찾지 못하는 일이 없도록 멱등적인(idempotent) 재시도 동작을 정의해야 합니다.
The in-memory set only demonstrates replay rejection within one running process. It loses records on restart, is not shared across instances, and has no expiry cleanup. A deployed service needs a shared atomic record keyed by chain, asset, authorizer, and nonce, retained through the authorization validity period. It must track pending and completed settlement and define idempotent retry behavior so a failed delivery does not cause a second charge or prevent recovery of a paid result.
서명 복원은 누가 이 조건에 서명했는지를 확인할 뿐, 자금이 있는지나 그 승인이 이미 온체인에서 사용되지 않았는지는 확인하지 않습니다. 서비스가 결제를 성공으로 처리하기 전에, 검증과 정산 과정에서 관련 체인 상태를 확인해야 합니다. 이 발췌는 표준 EOA 서명에 한정되며, 스마트 컨트랙트 지갑 검증은 다루지 않습니다.
Signature recovery establishes who signed these terms; it does not establish that funds are available or that the authorization has not already been used on chain. Verification and settlement must check the relevant chain state before the service treats the payment as successful. The excerpt is limited to standard EOA signatures and does not cover smart-contract wallet validation.
실행 가능한 코드가 필요하다면, 공식 x402 저장소에 두 바인딩의 참조 구현과 SDK가 있으며, 아래에 링크한 사양들은 처음부터 끝까지 읽을 수 있을 만큼 짧습니다 (x402 Foundation, 2026c). 유료 MCP 서버를 만든다면 HTTP 상태 코드부터 떠올리기 전에 MCP 바인딩을 먼저 읽어 보시고, 프레임워크가 어느 쪽을 더 쉽게 만들어주는지가 아니라 신중하게 따져 둘 중 하나를 선택하세요.
For runnable code, the official x402 repository carries reference implementations and SDKs for both bindings, and the specifications linked below are short enough to read end to end (x402 Foundation, 2026c). If you are building a paid MCP server, read the MCP binding before you reach for HTTP status codes, and decide between them deliberately rather than by which one your framework makes easier.
참고 문헌 / References
agentgateway. (n.d.-a). MCP authentication. MCP authentication – agentgateway | Agent Connectivity Solved
agentgateway. (n.d.-b). MCP authorization. MCP authorization – agentgateway | Agent Connectivity Solved
agentgateway. (n.d.-c). Static keys and passthrough. Static keys and passthrough – agentgateway | Agent Connectivity Solved
Model Context Protocol. (2025). Transports. Transports - Model Context Protocol
Model Context Protocol. (2026, July 28). The 2026-07-28 specification. The 2026-07-28 Specification | Model Context Protocol Blog
x402. (2025, December 11). Introducing x402 v2: Evolving the standard for internet-native payments. Introducing x402 V2: Evolving the Standard for Internet-native Payments – x402
x402 Foundation. (n.d.-a). EIP-3009 client [Computer software]. x402/typescript/packages/mechanisms/evm/src/exact/client/eip3009.ts at main · x402-foundation/x402 · GitHub
x402 Foundation. (n.d.-b). EIP-3009 facilitator [Computer software]. x402/typescript/packages/mechanisms/evm/src/exact/facilitator/eip3009.ts at main · x402-foundation/x402 · GitHub
x402 Foundation. (2026a). HTTP transport binding for x402 v2 [Specification]. x402/specs/transports-v2/http.md at main · x402-foundation/x402 · GitHub
x402 Foundation. (2026b). MCP transport binding for x402 v2 [Specification]. x402/specs/transports-v2/mcp.md at main · x402-foundation/x402 · GitHub
x402 Foundation. (2026c). x402: A payments protocol for the internet [Computer software]. GitHub - x402-foundation/x402: A payments protocol for the internet. Built on HTTP. · GitHub
x402 Foundation. (2026d). x402 specification v2 [Specification]. x402/specs/x402-specification-v2.md at main · x402-foundation/x402 · GitHub
이 글은 GPT 모델로 정리한 초안을 바탕으로 한 것으로, 원문의 내용 또는 의도와 다르게 정리된 내용이 있을 수 있습니다. 관심있는 내용이시라면 원문도 함께 참고해주세요! 읽으시면서 어색하거나 잘못된 내용을 발견하시면 댓글로 알려주시기를 부탁드립니다. ![]()
파이토치 한국 사용자 모임
이 정리한 이 글이 유용하셨나요? 회원으로 가입하시면 주요 글들을 이메일
로 보내드립니다! 텔레그램(Telegram)이나 Slack/Discord/Teams/Dooray/GoogleChat 등으로도 새 글 알림을 받으실 수 있습니다. ![]()
아래
쪽에 좋아요
를 눌러주시면 새로운 소식들을 정리하고 공유하는데 힘이 됩니다~ ![]()
