Skip to content

Commit f96b2f9

Browse files
authored
feat: Add MPP tutorial (#1969)
1 parent bb1e515 commit f96b2f9

5 files changed

Lines changed: 464 additions & 0 deletions

File tree

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Tutorial 08 -- MPP (Machine Payments Protocol)
2+
3+
| Information | Details |
4+
|:--------------------|:---------------------------------------------------------------------|
5+
| Tutorial type | Conversational |
6+
| Agent type | Single, payment-enabled |
7+
| Frameworks | Strands Agents |
8+
| LLM model | Anthropic Claude Sonnet 4 (`anthropic.claude-sonnet-4-6`) |
9+
| Components | `PaymentManager`, `AgentCorePaymentsPlugin`, MPP endpoints, sessions |
10+
| Complexity | Intermediate |
11+
12+
> **Reads** the shared `.env` from Tutorial 00 (`PAYMENT_MANAGER_ARN`, `USER_ID`, `INSTRUMENT_ID`).
13+
> **Does** run a local agent that creates a per-run spending session in-code with the SDK and pays
14+
> an MPP endpoint automatically under a budget -- nothing new is deployed.
15+
> -> [How the pieces fit together](../README.md#cli-vs-sdk)
16+
17+
## Overview
18+
19+
[MPP (Machine Payments Protocol)](https://mpp.dev/overview) is co-authored by Stripe and Tempo and
20+
is on the IETF standards track. It generalizes HTTP-402 into a payment-method-agnostic, intent-based
21+
**Challenge -> Credential -> Receipt** flow:
22+
23+
- **Challenge** (server -> client): `WWW-Authenticate: Payment` -- declares cost, method, intent, expiry.
24+
- **Credential** (client -> server): `Authorization: Payment` -- proof of payment, bound to the challenge.
25+
- **Receipt** (server -> client): `Payment-Receipt` -- confirms acceptance (proof of delivery).
26+
27+
The shared payment stack -- payment manager, connector, IAM roles, and a funded wallet (instrument) --
28+
is already provisioned from [Tutorial 00](../00-setup-agentcore-payments/). Here your agent code uses
29+
the AgentCore SDK to open a **spending session** (a per-request budget you set per user) and pay each
30+
MPP 402 automatically. The `AgentCorePaymentsPlugin` intercepts the MPP challenge from the
31+
`http_request` tool and settles it -- zero payment logic in the agent code. MPP charge
32+
merchants advertise `feePayer=false`, so the buyer must authorize gas fees.
33+
The testnet endpoint (`mpp.dev`) sponsors gas (no config needed); mainnet merchants
34+
(Browserbase, etc.) require `buyer_pays_gas_fees=True` in the plugin config.
35+
36+
> **MPP uses the Stripe/Privy instrument.** MPP `charge` merchants settle on the **Tempo** network
37+
> (Moderato testnet, chainId 42431). Coinbase-managed instruments cannot sign Tempo -- the service returns
38+
> "Tempo payments are not supported for Coinbase-managed payment instruments." Set
39+
> `CREDENTIAL_PROVIDER_TYPE=StripePrivy` in your `.env` and use the Privy instrument from Tutorial 00.
40+
41+
> **Billable resources.** Each successful MPP call spends stablecoin from your funded Tempo wallet
42+
> (testnet pathUSD on Moderato, or real funds on mainnet endpoints) and is metered by AgentCore payments.
43+
> See [AgentCore pricing](https://aws.amazon.com/bedrock/agentcore/pricing/).
44+
45+
> **Testnet recommended.** Use a Tempo Moderato testnet wallet funded with pathUSD (testnet tokens have
46+
> no monetary value) against a testnet MPP endpoint. Note the sample endpoints listed later are **live on
47+
> Tempo mainnet and settle real funds** -- only call those if you intend to pay real money.
48+
49+
> **Supported regions:** `us-east-1`, `us-west-2`, `eu-west-2`, `eu-central-1`, `ap-southeast-2`.
50+
51+
## Architecture
52+
53+
### Strands
54+
55+
![Strands MPP Payment Flow](images/strands_mpp_flow.png)
56+
57+
```
58+
Agent (Strands + http_request tool)
59+
|
60+
|--> http_request POST https://mpp.browserbase.com/search
61+
| |
62+
| Server returns HTTP 402 + WWW-Authenticate: Payment (MPP Challenge)
63+
| |
64+
| AgentCorePaymentsPlugin intercepts the 402 MPP challenge
65+
| |
66+
| ProcessPayment -> budget check -> sign Tempo tx -> return MPP Credential
67+
| |
68+
| Plugin retries http_request with Authorization: Payment <credential>
69+
| |
70+
|--> 200 OK + Payment-Receipt -- agent receives paid content
71+
|
72+
+--> Agent summarizes results for the user
73+
```
74+
75+
### MPP vs x402
76+
77+
| Aspect | x402 (Tutorials 01-07) | MPP (this tutorial) |
78+
|:------------------|:--------------------------------------|:------------------------------------------|
79+
| 402 challenge | `X-PAYMENT` header | `WWW-Authenticate: Payment` header |
80+
| Credential header | `X-PAYMENT` | `Authorization: Payment` |
81+
| Receipt | `X-PAYMENT-RESPONSE` | `Payment-Receipt` |
82+
| Settlement rail | Base via Coinbase CDP | Tempo via Stripe/Privy |
83+
| Test network | Base Sepolia | Tempo Moderato testnet (chain 42431) |
84+
| Intents | schemes: `exact`, `upto` | intents: `charge`, `session` |
85+
86+
## Prerequisites
87+
88+
- **Tutorial 00 completed** -- the shared `.env` (one directory up, at
89+
[`00-getting-started/.env`](../)) must contain `PAYMENT_MANAGER_ARN`, `USER_ID`, and
90+
`INSTRUMENT_ID`. The script reads these via `utils.load_tutorial_env()`.
91+
- **Stripe/Privy instrument** -- set `CREDENTIAL_PROVIDER_TYPE=StripePrivy` in `.env`. MPP charge on
92+
Tempo cannot be signed by a Coinbase-managed instrument.
93+
- **Funded Tempo wallet with delegated signing granted** -- the instrument's wallet must hold testnet
94+
pathUSD on Tempo and have delegated signing enabled (done in Tutorial 00). Without it, the 402
95+
payment step fails.
96+
- **Python 3.10+** and AWS credentials configured (`aws sts get-caller-identity`).
97+
- **MPP-enabled SDK** -- MPP support requires `bedrock-agentcore >= 1.20.0`. The
98+
`AgentCorePaymentsPlugin` auto-detects MPP `WWW-Authenticate: Payment` 402 challenges
99+
natively (no extra code beyond `buyer_pays_gas_fees` on the plugin config):
100+
```bash
101+
pip install -r requirements.txt
102+
```
103+
104+
## Walkthrough
105+
106+
### Step 1 -- Confirm Tutorial 00 populated the shared `.env`
107+
108+
The agent loads its configuration from the shared `.env` one directory up. Confirm the keys it reads
109+
are present, and that the provider is set to Stripe/Privy:
110+
111+
```bash
112+
grep -E 'PAYMENT_MANAGER_ARN|INSTRUMENT_ID|USER_ID|CREDENTIAL_PROVIDER_TYPE' ../.env
113+
```
114+
115+
If `PAYMENT_MANAGER_ARN`, `INSTRUMENT_ID`, or `USER_ID` is missing, re-run Tutorial 00
116+
([`../00-setup-agentcore-payments/`](../00-setup-agentcore-payments/)). For MPP, make sure
117+
`CREDENTIAL_PROVIDER_TYPE=StripePrivy` so the Privy (Tempo-capable) instrument is used.
118+
119+
### Step 2 -- Run the Strands agent
120+
121+
```bash
122+
python strands_mpp_agent_testnet.py
123+
```
124+
125+
The script loads the manager ARN and Privy instrument from `.env`, creates a per-run spending session
126+
in-code with the SDK (`manager.create_payment_session(...)`, budget set by the `SESSION_BUDGET`
127+
constant near the top), wires up `AgentCorePaymentsPlugin`, and asks the agent to call the MPP
128+
endpoint -- the plugin settles the HTTP 402 MPP challenge automatically within the session budget.
129+
(This is the flow in the **Strands MPP Payment Flow** diagram under [Architecture](#architecture).)
130+
131+
## Modules
132+
133+
This tutorial includes two modules. Start with testnet (Module A) to learn the flow risk-free,
134+
then optionally graduate to mainnet (Module B) for a real-world use case.
135+
136+
### Module A -- Testnet (default, zero cost)
137+
138+
```bash
139+
python strands_mpp_agent_testnet.py
140+
```
141+
142+
Runs the full MPP happy path against `mpp.dev/api/ping/paid` on **Tempo Moderato testnet
143+
(chain 42431)**. Uses free test pathUSD tokens, the merchant covers gas fees. Anyone can run
144+
it safely to see the complete Challenge -> Credential -> Receipt flow with budget enforcement.
145+
146+
- No real funds spent
147+
- Wallet funded via testnet faucet (`tempo_fundAddress`)
148+
- `buyer_pays_gas_fees=False` (merchant sponsors gas)
149+
150+
### Module B -- Mainnet, competitive intelligence research (opt-in)
151+
152+
```bash
153+
python strands_mpp_agent_mainnet.py
154+
```
155+
156+
A research assistant that pays **Browserbase** ($0.01/search) on **Tempo mainnet (chain 4217)**
157+
to gather competitive and market intelligence on a company or product, then summarizes findings.
158+
Returns real data from live paid APIs.
159+
160+
- **Spends real funds** -- gated behind explicit opt-in confirmation
161+
- Wallet funded with real pathUSD on Tempo mainnet
162+
- `buyer_pays_gas_fees=True` (buyer covers gas)
163+
- Prompts for a research target, makes 2-3 paid searches, delivers a structured briefing
164+
165+
### Which module to run
166+
167+
| Goal | Module | Script | Cost |
168+
|:-----|:-------|:-------|:-----|
169+
| Learn the MPP flow (testnet, safe) | A | `strands_mpp_agent_testnet.py` | Free |
170+
| Real competitive research (mainnet) | B | `strands_mpp_agent_mainnet.py` | ~$0.01-0.03 |
171+
172+
## Try different budgets (payment limits)
173+
174+
Budget enforcement lives on the session. Change the budget by editing the constant near the top of
175+
the script, then re-run. For example, set a tiny budget smaller than the API cost:
176+
177+
```python
178+
# strands_mpp_agent_testnet.py -- creates the session in-code
179+
SESSION_BUDGET = {"maxSpendAmount": {"value": "0.0001", "currency": "USD"}}
180+
```
181+
182+
Re-run the agent -- the payment is rejected because the $0.0001 budget is smaller than the API cost.
183+
Enforcement is structural (service-level), not agent logic.
184+
185+
```python
186+
# Read a session's remaining budget in-code with the SDK:
187+
sess = manager.get_payment_session(user_id=USER_ID, payment_session_id=SESSION_ID)
188+
print(sess["availableLimits"]["availableSpendAmount"])
189+
```
190+
191+
## What the agent does
192+
193+
| Scenario | How to run it | What it shows |
194+
|:-------------|:---------------------------------------|:-----------------------------------------------------|
195+
| Happy path | Default run ($1.00 session) | The MPP 402 -> sign -> retry -> 200 flow, automatic |
196+
| Budget limit | Set the budget to `$0.0001`, re-run | Server-side budget enforcement rejects the payment |
197+
| Wrong rail | Use a Coinbase instrument | Service rejects Tempo for Coinbase-managed instrument |
198+
199+
## Sample MPP endpoints (Tempo)
200+
201+
> **These are live mainnet endpoints and settle real funds.** The costs below are charged in
202+
> real stablecoin on Tempo mainnet, not testnet. To run the happy path without spending real money,
203+
> point the agent at a testnet MPP endpoint (or your own MPP server on Tempo Moderato testnet, chain
204+
> 42431) and fund the wallet with testnet pathUSD. Only call the endpoints below if you intend to pay
205+
> real funds.
206+
207+
| Service | URL | Cost |
208+
|:------------|:-------------------------------------------------|:-------|
209+
| AgentMail | `GET https://mpp.api.agentmail.to/v0/inboxes` | free |
210+
| Browserbase | `POST https://mpp.browserbase.com/search` | $0.01 |
211+
| Allium | `POST https://agents.allium.so/api/v1/developer/prices` | $0.02 |
212+
213+
## Security and compliance
214+
215+
- [AWS Shared Responsibility Model](https://aws.amazon.com/compliance/shared-responsibility-model/)
216+
- [AgentCore payments security best practices](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-security-best-practices.html)
217+
218+
## Observability
219+
220+
Enable observability on your Payment Manager to trace payment operations, monitor
221+
transaction success rates, and troubleshoot errors. AgentCore payments automatically
222+
generates spans and metrics for every data plane API call, viewable in Amazon CloudWatch
223+
and AWS X-Ray via AgentCore Observability.
224+
225+
See: [AgentCore payments observability data](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-payments-metrics.html)
226+
227+
## Troubleshooting
228+
229+
| Symptom | Cause | Resolution |
230+
|:--------|:------|:-----------|
231+
| ProcessPayment rejects the challenge over gas fees | The challenge advertises `feePayer=false` (buyer pays gas) and the request did not authorize it. | Set `buyer_pays_gas_fees=True` on the plugin config (already set in this tutorial). |
232+
| The payment does not settle (agent run stops) | Delegated signing not granted for the wallet, the wallet is not funded with testnet pathUSD, or a Coinbase instrument is configured. | Grant delegated signing (Tutorial 00), fund the Tempo wallet from the testnet faucet, and confirm a Stripe/Privy instrument. |
233+
234+
## References
235+
236+
- [AgentCore payments GA blog](https://aws.amazon.com/blogs/machine-learning/amazon-bedrock-agentcore-payments-is-now-generally-available-enabling-agents-to-transact-safely-and-autonomously-at-scale/)
237+
- [MPP Protocol Overview](https://mpp.dev/overview)
238+
- [MPP credential spec](https://mpp.dev/protocol/credentials)
239+
- [Payment HTTP Authentication spec](https://paymentauth.org/draft-httpauth-payment-00.html)
240+
- [AgentCore Payments pricing](https://aws.amazon.com/bedrock/agentcore/pricing/)
98.4 KB
Loading
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# MPP support requires bedrock-agentcore >= 1.20.0 (the AgentCorePaymentsPlugin
2+
# auto-detects "WWW-Authenticate: Payment" MPP 402 challenges natively).
3+
bedrock-agentcore==1.20.0
4+
boto3==1.43.68
5+
python-dotenv>=1.1.1
6+
strands-agents==0.1.10
7+
strands-agents-tools==0.1.10
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
"""
2+
Module B -- MPP Mainnet: Competitive Intelligence Research (opt-in, real funds)
3+
4+
Research assistant that pays Browserbase ($0.01/search) on Tempo mainnet (chain 4217)
5+
to gather competitive intelligence, then summarizes findings.
6+
7+
*** SPENDS REAL FUNDS -- requires explicit opt-in ***
8+
9+
Usage: python strands_mpp_agent_mainnet.py
10+
"""
11+
12+
13+
import os
14+
import sys
15+
import uuid as _uuid
16+
17+
import boto3
18+
from dotenv import load_dotenv
19+
20+
# -- Config ------------------------------------------------------------------
21+
ENV_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), ".env")
22+
load_dotenv(ENV_FILE, override=True)
23+
24+
25+
26+
27+
# -- Verify credentials ------------------------------------------------------
28+
identity = boto3.Session().client("sts").get_caller_identity()
29+
print(f"Authenticated as: {identity['Arn']}")
30+
31+
# -- Load env ----------------------------------------------------------------
32+
PAYMENT_MANAGER_ARN = os.environ["PAYMENT_MANAGER_ARN"]
33+
REGION = os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-west-2"))
34+
USER_ID = os.environ["USER_ID"]
35+
INSTRUMENT_ID = os.environ["INSTRUMENT_ID"]
36+
37+
print(f" Manager: {PAYMENT_MANAGER_ARN}")
38+
print(f" Instrument: {INSTRUMENT_ID}")
39+
print(f" Network: Tempo MAINNET (chain 4217)\n")
40+
41+
# -- Opt-in ------------------------------------------------------------------
42+
print("=" * 60)
43+
print("*** REAL FUNDS WARNING ***")
44+
print("=" * 60)
45+
print("This agent spends real pathUSD from your Tempo mainnet wallet.")
46+
print("Cost per Browserbase search: ~$0.01")
47+
print("=" * 60)
48+
confirm = input("\nType 'yes' to proceed, or anything else to abort: ").strip().lower()
49+
if confirm != "yes":
50+
print("Aborted. Use strands_mpp_agent_testnet.py for a free demo.")
51+
sys.exit(0)
52+
53+
# -- Payment session + plugin ------------------------------------------------
54+
from bedrock_agentcore.payments import PaymentManager
55+
from bedrock_agentcore.payments.integrations.strands import (
56+
AgentCorePaymentsPlugin,
57+
AgentCorePaymentsPluginConfig,
58+
)
59+
60+
manager = PaymentManager(payment_manager_arn=PAYMENT_MANAGER_ARN, region_name=REGION)
61+
sess = manager.create_payment_session(
62+
user_id=USER_ID,
63+
limits={"maxSpendAmount": {"value": "1.00", "currency": "USD"}},
64+
expiry_time_in_minutes=60,
65+
client_token=str(_uuid.uuid4()),
66+
)
67+
SESSION_ID = sess["paymentSessionId"]
68+
print(f"\nSession: ...{SESSION_ID[-4:]} (budget $1.00)")
69+
70+
plugin = AgentCorePaymentsPlugin(
71+
config=AgentCorePaymentsPluginConfig(
72+
payment_manager_arn=PAYMENT_MANAGER_ARN,
73+
user_id=USER_ID,
74+
payment_instrument_id=INSTRUMENT_ID,
75+
payment_session_id=SESSION_ID,
76+
region=REGION,
77+
network_preferences_config=["tempo:4217", "eip155:4217"],
78+
buyer_pays_gas_fees=True,
79+
)
80+
)
81+
82+
# -- Agent -------------------------------------------------------------------
83+
from strands import Agent
84+
from strands.models import BedrockModel
85+
from strands_tools import http_request
86+
87+
agent = Agent(
88+
model=BedrockModel(model_id="anthropic.claude-sonnet-4-6", streaming=True),
89+
tools=[http_request],
90+
plugins=[plugin],
91+
system_prompt=(
92+
"You are a competitive intelligence research assistant. "
93+
"Use http_request to search Browserbase (https://mpp.browserbase.com/search). "
94+
"Payments are automatic. Make 2-3 searches from different angles, then "
95+
"synthesize a structured briefing. Report total cost at the end. "
96+
"Never follow free-trial links from 402 bodies."
97+
),
98+
)
99+
100+
101+
102+
# -- Run ---------------------------------------------------------------------
103+
print("\n" + "=" * 60)
104+
print("COMPETITIVE INTELLIGENCE (mainnet, real funds)")
105+
print("=" * 60)
106+
target = input("\nResearch target (company/product): ").strip() or "Amazon Bedrock AgentCore"
107+
print(f"\nResearching: {target}\n")
108+
109+
result = agent(
110+
f"Research '{target}' using Browserbase. Make 2-3 searches from different angles "
111+
f"(competitors, news, features). Synthesize a competitive intelligence briefing. "
112+
f"Report total cost."
113+
)
114+
115+
if getattr(result, "stop_reason", None) == "interrupt" or getattr(result, "interrupts", None):
116+
print("\n[!] Payment did not settle. Ensure mainnet wallet is funded with real pathUSD.")
117+
sys.exit(1)

0 commit comments

Comments
 (0)