@mercuryo-ai/magicpay-sdk 0.1.0-test.5 → 0.1.0-test.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +73 -344
  2. package/package.json +19 -9
  3. package/docs/examples.md +0 -432
package/README.md CHANGED
@@ -1,27 +1,46 @@
1
1
  # @mercuryo-ai/magicpay-sdk
2
2
 
3
- MagicPay SDK is the TypeScript and Node client for MagicPay workflow sessions and
4
- secret request flows. It gives a trusted runtime a consistent way to read
5
- session state, fetch a stored-secret catalog, create a secret request, poll for
6
- approval, and claim a one-time secret payload. The root package is the MagicPay
7
- domain surface. AgentBrowse-specific form matching and protected-fill helpers
8
- live in a separate `@mercuryo-ai/magicpay-sdk/agentbrowse` subpath.
3
+ MagicPay SDK is the TypeScript and Node SDK for requesting, approving, and
4
+ claiming stored-secret values for one protected step in a MagicPay workflow.
9
5
 
10
- ## Install
6
+ Use it when your application, worker, agent runtime, or MCP tool needs to:
11
7
 
12
- Install the MagicPay SDK:
8
+ - load the stored-secret catalog for the current protected step;
9
+ - create one approval request for one protected field group;
10
+ - poll until the request is fulfilled or reaches a terminal state;
11
+ - claim the single-use secret payload;
12
+ - continue the surrounding session flow with typed SDK helpers.
13
13
 
14
- ```bash
15
- npm i @mercuryo-ai/magicpay-sdk
16
- ```
14
+ MagicPay SDK focuses on the MagicPay domain flow itself. It is not a browser
15
+ automation package. Your runtime still owns browser control, approval UX,
16
+ orchestration, and the business action that happens after the claim succeeds.
17
+
18
+ ## When To Use It
19
+
20
+ This package is the right entry point when:
21
+
22
+ - you already know which protected step you are continuing;
23
+ - you run trusted Node or TypeScript code;
24
+ - you want a typed client instead of assembling raw HTTP requests yourself.
25
+
26
+ Typical integrations:
17
27
 
18
- For AgentBrowse-based browser execution, install both packages:
28
+ - backend services and workers;
29
+ - MCP tools and agent backends;
30
+ - browser automation runtimes that already know the protected target;
31
+ - provider-specific API flows that consume a claimed payload outside the
32
+ browser.
33
+
34
+ ## Install
35
+
36
+ Install the root SDK:
19
37
 
20
38
  ```bash
21
- npm i @mercuryo-ai/magicpay-sdk @mercuryo-ai/agentbrowse
39
+ npm i @mercuryo-ai/magicpay-sdk
22
40
  ```
23
41
 
24
- ## API Access
42
+ Create your API key at
43
+ [`agents.mercuryo.io/signup`](https://agents.mercuryo.io/signup).
25
44
 
26
45
  MagicPay API base URL:
27
46
 
@@ -29,106 +48,31 @@ MagicPay API base URL:
29
48
  https://agents-api.mercuryo.io/functions/v1/api
30
49
  ```
31
50
 
32
- Create an API key in your MagicPay control plane and pass both values into
33
- `createMagicPayClient(...)`.
51
+ ## Choose An Entrypoint
34
52
 
35
- ## Entrypoints
53
+ Most integrations use the root package only.
36
54
 
37
- | Entrypoint | Purpose |
55
+ | Entrypoint | Use it when |
38
56
  | --- | --- |
39
- | `@mercuryo-ai/magicpay-sdk` | Networked MagicPay client and consumer-facing types. |
40
- | `@mercuryo-ai/magicpay-sdk/core` | Pure domain helpers for session state and secret request status interpretation. |
41
- | `@mercuryo-ai/magicpay-sdk/agentbrowse` | Bridge helpers for AgentBrowse observed forms and protected fill execution. |
42
-
43
- ## Runtime Model
44
-
45
- MagicPay SDK is runtime-agnostic. The root package works with any trusted
46
- runtime that can create a MagicPay request input and consume a claimed secret
47
- payload. AgentBrowse is one browser runtime integration path. Claimed secret
48
- values can be consumed by browser automation, API clients, MCP tools, or custom
49
- agent runtimes.
50
-
51
- ## Integration Modes
57
+ | `@mercuryo-ai/magicpay-sdk` | You want the main networked client for catalog, request, poll, claim, and session helpers. |
58
+ | `@mercuryo-ai/magicpay-sdk/core` | You want the pure domain helpers without the networked client wrapper. |
59
+ | `@mercuryo-ai/magicpay-sdk/agentbrowse` | You already use `@mercuryo-ai/agentbrowse` and want the optional bridge from observed forms to MagicPay request and fill input. |
52
60
 
53
- MagicPay SDK supports two explicit integration modes.
61
+ If your runtime already starts from observed protected forms and you want the
62
+ optional browser bridge helpers, add AgentBrowse as well:
54
63
 
55
- ### Mode A. Runtime-Agnostic Secret Flow
56
-
57
- Use only the root `@mercuryo-ai/magicpay-sdk` entrypoint when your runtime already
58
- knows how it wants to consume a claimed secret payload.
59
-
60
- In this mode you:
61
-
62
- - create or read sessions;
63
- - fetch catalogs;
64
- - create secret requests;
65
- - poll until approval or another terminal status;
66
- - claim the one-time payload;
67
- - hand the payload to your own browser automation, API client, MCP tool, or
68
- other runtime.
69
-
70
- This mode does not require AgentBrowse imports.
71
-
72
- ### Mode B. Explicit AgentBrowse Bridge
73
-
74
- Use `@mercuryo-ai/magicpay-sdk/agentbrowse` only when your runtime already uses
75
- `@mercuryo-ai/agentbrowse` and wants a typed bridge from observed forms to MagicPay
76
- request inputs and protected-fill execution.
77
-
78
- In this mode you:
79
-
80
- - observe protected forms with AgentBrowse;
81
- - build MagicPay request input from an observed form;
82
- - enrich observed forms with catalog candidates;
83
- - prepare protected-fill input from a fulfilled MagicPay claim.
84
-
85
- AgentBrowse stays an explicit optional dependency. The root SDK does not
86
- implicitly enable or require it.
87
-
88
- ## Glossary
89
-
90
- | Term | Meaning |
91
- | --- | --- |
92
- | `session` | One MagicPay workflow session. |
93
- | `catalog` | Stored secrets available for a host inside a session. |
94
- | `storedSecretRef` | The saved secret you want MagicPay to provide for a request. |
95
- | `secret request` | A request for a one-time secret payload tied to a workflow step. |
96
- | `claim` | The one-time retrieval of approved secret values. |
97
- | `fillRef` | Stable identifier for the protected fill target tied to a request. |
98
- | `pageRef` | Stable identifier for the observed page that contains the fill target. |
99
- | `scopeRef` | Stable identifier for a protected surface inside the page. |
100
-
101
- ## Main Flow
102
-
103
- ```mermaid
104
- flowchart LR
105
- A[sessionId] --> B[fetchCatalog]
106
- B --> C[pick storedSecretRef]
107
- C --> D{input source}
108
- D -->|manual| E[build request input]
109
- D -->|AgentBrowse| F[buildRequestInputForObservedForm]
110
- E --> G[client.secrets.createRequest]
111
- F --> G
112
- G --> H[pollUntil]
113
- H -->|fulfilled| I[claim]
114
- H -->|denied / expired / failed / canceled| J[branch in your app]
115
- I --> K{payload consumer}
116
- K -->|custom runtime| L[consume secret values]
117
- K -->|AgentBrowse| M[prepareProtectedFillFromClaim]
118
- M --> N[fillProtectedForm]
64
+ ```bash
65
+ npm i @mercuryo-ai/magicpay-sdk @mercuryo-ai/agentbrowse
119
66
  ```
120
67
 
121
68
  ## Quick Start
122
69
 
123
- ### Root Client
124
-
125
- Use the root package when your runtime already knows the request context it
126
- wants to send to MagicPay. This is Mode A: runtime-agnostic secret flow.
70
+ This example shows the primary root-SDK path. It assumes you already have a
71
+ MagicPay workflow session and know which protected step you are continuing.
127
72
 
128
73
  ```ts
129
74
  import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
130
75
 
131
- // Create a networked MagicPay client for your runtime.
132
76
  const client = createMagicPayClient({
133
77
  gateway: {
134
78
  apiKey: process.env.MAGICPAY_API_KEY!,
@@ -139,10 +83,8 @@ const client = createMagicPayClient({
139
83
  const sessionId = 'sess_123';
140
84
  const pageUrl = 'https://checkout.airline.example/payment';
141
85
 
142
- // Load the stored-secret catalog for the current host.
143
86
  const catalog = await client.secrets.fetchCatalog(sessionId, pageUrl);
144
87
 
145
- // Create a request for one stored secret and the exact fields this step needs.
146
88
  const created = await client.secrets.createRequest({
147
89
  sessionId,
148
90
  clientRequestId: 'checkout-card-1',
@@ -157,104 +99,15 @@ const created = await client.secrets.createRequest({
157
99
  storedSecretRef: 'secret_card_primary',
158
100
  kind: 'payment_card',
159
101
  host: catalog.host,
160
- scopeRef: 'payment_surface',
102
+ scopeRef: 'payment_form',
161
103
  fields: ['pan', 'exp_month', 'exp_year', 'cvv'],
162
104
  },
163
105
  });
164
106
 
165
- // Wait for approval or another terminal outcome.
166
- const ready = await client.secrets.pollUntil(sessionId, created.requestId);
167
- if (!ready.success) {
168
- throw new Error(ready.reason);
169
- }
170
- if (ready.result.snapshot.status !== 'fulfilled') {
171
- throw new Error(`Request ended as ${ready.result.snapshot.status}`);
172
- }
173
-
174
- // Claim the one-time secret payload after approval.
175
- const claim = await client.secrets.claim(sessionId, created.requestId);
176
- if (!claim.success) {
177
- throw new Error(claim.reason);
178
- }
179
-
180
- // Use the claimed values in your runtime.
181
- console.log(claim.result.secret.values);
182
- ```
183
-
184
- ### AgentBrowse Bridge
185
-
186
- Use the AgentBrowse subpath when your runtime starts from `observe(...)` and
187
- then wants to request and fill a secret through MagicPay. This is Mode B:
188
- explicit AgentBrowse bridge.
189
-
190
- ```ts
191
- import { act, observe } from '@mercuryo-ai/agentbrowse';
192
- import {
193
- fillProtectedForm,
194
- type ProtectedFillForm,
195
- } from '@mercuryo-ai/agentbrowse/protected-fill';
196
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
197
- import {
198
- buildRequestInputForObservedForm,
199
- enrichObservedFormsForUrl,
200
- prepareProtectedFillFromClaim,
201
- } from '@mercuryo-ai/magicpay-sdk/agentbrowse';
202
-
203
- // Create the MagicPay client that will own the approval flow.
204
- const client = createMagicPayClient({
205
- gateway: {
206
- apiKey: process.env.MAGICPAY_API_KEY!,
207
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
208
- },
107
+ const ready = await client.secrets.pollUntil(sessionId, created.requestId, {
108
+ stopWhen: 'fulfilled',
209
109
  });
210
110
 
211
- const sessionId = 'sess_123';
212
-
213
- // Observe the current page and collect protected fill forms.
214
- const observation = await observe(browserSession, 'Find the login form');
215
- if (!observation.success || !observation.url || !Array.isArray(observation.fillableForms)) {
216
- throw new Error('AgentBrowse did not return observed forms.');
217
- }
218
-
219
- const observedForms = observation.fillableForms as ProtectedFillForm[];
220
-
221
- // Load the stored-secret catalog for the observed host.
222
- const catalog = await client.secrets.fetchCatalog(sessionId, observation.url);
223
-
224
- // Attach catalog candidates to the observed forms.
225
- const enrichedForms = enrichObservedFormsForUrl(
226
- observedForms,
227
- { [catalog.host]: catalog },
228
- observation.url
229
- );
230
- const loginForm =
231
- enrichedForms.find((form) => form.purpose === 'login') ?? enrichedForms[0] ?? null;
232
- if (!loginForm) {
233
- throw new Error('No protected fill form was observed.');
234
- }
235
-
236
- // Convert the observed form into a MagicPay secret request input.
237
- const requestInput = buildRequestInputForObservedForm({
238
- sessionId,
239
- merchantName: 'Airline Example',
240
- storedSecretRef: 'secret_login_primary',
241
- urlOrHost: observation.url,
242
- catalog,
243
- fillableForm: loginForm,
244
- page: {
245
- ref: loginForm.pageRef,
246
- url: observation.url,
247
- ...(observation.title ? { title: observation.title } : {}),
248
- },
249
- });
250
-
251
- if (!requestInput.success) {
252
- throw new Error(requestInput.reason);
253
- }
254
-
255
- // Create the approval request and wait until it is fulfilled.
256
- const created = await client.secrets.createRequest(requestInput.input);
257
- const ready = await client.secrets.pollUntil(sessionId, created.requestId);
258
111
  if (!ready.success) {
259
112
  throw new Error(ready.reason);
260
113
  }
@@ -262,165 +115,41 @@ if (ready.result.snapshot.status !== 'fulfilled') {
262
115
  throw new Error(`Request ended as ${ready.result.snapshot.status}`);
263
116
  }
264
117
 
265
- // Claim the one-time payload once the request is approved.
266
118
  const claim = await client.secrets.claim(sessionId, created.requestId);
267
119
  if (!claim.success) {
268
120
  throw new Error(claim.reason);
269
121
  }
270
122
 
271
- // Convert the claim into AgentBrowse protected-fill input.
272
- const prepared = prepareProtectedFillFromClaim({
273
- fillableForm: loginForm,
274
- catalog,
275
- claim: claim.result,
276
- request: created.snapshot,
277
- });
278
-
279
- // Fill the protected fields in the browser.
280
- const browserFill = await fillProtectedForm({
281
- session: browserSession,
282
- fillableForm: prepared.fillableForm,
283
- protectedValues: prepared.protectedValues,
284
- fieldPolicies: prepared.fieldPolicies,
285
- });
286
-
287
- if (!browserFill.success) {
288
- throw new Error(browserFill.reason);
289
- }
290
-
291
- // Re-observe after the protected fill. The submit button is a fresh action target,
292
- // so reacquire it from the live page instead of guessing from older inventory.
293
- const submitObservation = await observe(
294
- browserSession,
295
- 'Find the single actionable submit target that sends the filled login form.'
296
- );
297
- if (!submitObservation.success || submitObservation.targets.length === 0) {
298
- throw new Error(submitObservation.reason ?? submitObservation.message);
299
- }
300
-
301
- // Goal-based observe narrows the inventory to the submit action we care about.
302
- const submitTarget = submitObservation.targets[0];
303
- if (!submitTarget?.ref || submitTarget.capability !== 'actionable') {
304
- throw new Error('AgentBrowse did not return an actionable submit target.');
305
- }
306
-
307
- const submitAction = await act(browserSession, submitTarget.ref, 'click');
308
- if (!submitAction.success) {
309
- throw new Error(submitAction.reason ?? submitAction.message);
310
- }
311
- ```
312
-
313
- ### Claim And Use Secrets Outside The Browser
314
-
315
- Use the claimed payload directly when your runtime wants to authenticate to an
316
- API, call a provider SDK, or pass a secret into an MCP tool flow.
317
-
318
- ```ts
319
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
320
-
321
- // Create the MagicPay client for the claim step.
322
- const client = createMagicPayClient({
323
- gateway: {
324
- apiKey: process.env.MAGICPAY_API_KEY!,
325
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
326
- },
327
- });
328
-
329
- // Claim the approved secret payload.
330
- const claim = await client.secrets.claim('sess_123', 'req_123');
331
- if (!claim.success) {
332
- throw new Error(claim.reason);
333
- }
334
-
335
- // Forward the claimed values to the API you want to call.
336
- const response = await fetch('https://api.airline.example/login', {
337
- method: 'POST',
338
- headers: { 'content-type': 'application/json' },
339
- body: JSON.stringify({
340
- username: claim.result.secret.values.username,
341
- password: claim.result.secret.values.password,
342
- }),
343
- });
344
-
345
- if (!response.ok) {
346
- throw new Error(`Provider login failed with ${response.status}`);
347
- }
123
+ console.log(claim.result.secret.values);
348
124
  ```
349
125
 
350
- ## Polling Defaults
351
-
352
- `client.secrets.pollUntil(...)` uses a conservative profile by default:
353
-
354
- - timeout: `180_000` ms
355
- - initial interval: `10_000` ms
356
- - max interval: `30_000` ms
357
- - backoff multiplier: `1.2`
358
-
359
- Override these values when your application needs a different approval window.
360
-
361
- ## Error Reference
362
-
363
- ### `buildRequestInputForObservedForm(...)`
364
-
365
- | Kind | When it happens | What to do |
366
- | --- | --- | --- |
367
- | `host_resolution_failed` | No usable host could be resolved from the page URL, host, or catalog. | Pass a valid page URL or a catalog with a host. |
368
- | `stored_secret_not_available` | The chosen `storedSecretRef` is not available for the observed form and host. | Fetch the catalog again or choose a different stored secret. |
369
-
370
- ### `client.secrets.poll(...)` and `client.secrets.pollUntil(...)`
126
+ The normal flow is:
371
127
 
372
- | Kind | When it happens | What to do |
373
- | --- | --- | --- |
374
- | `not_found` | The request ID does not exist in the session. | Recreate the request or refresh local state. |
375
- | `aborted` | The caller aborted the poll. | Decide whether to resume or stop. |
376
- | `other` | MagicPay returned another transport or backend failure. | Inspect `status`, `errorCode`, and logs. |
377
- | `timeout` | `pollUntil(...)` exceeded its timeout. | Continue polling later or surface a timeout to the user. |
128
+ 1. fetch the catalog for the current host
129
+ 2. create one approval request for the exact fields you need
130
+ 3. wait until the request is fulfilled
131
+ 4. claim the one-time payload
132
+ 5. hand the payload to your runtime
378
133
 
379
- ### `client.secrets.claim(...)`
134
+ ## What Happens Next
380
135
 
381
- | Kind | When it happens | What to do |
382
- | --- | --- | --- |
383
- | `not_fulfilled` | The request is not approved yet. | Keep polling or ask the user to approve it. |
384
- | `already_claimed` | The one-time secret payload was already claimed. | Create a new secret request. |
385
- | `aborted` | The caller aborted the claim request. | Retry only if the application still needs the payload. |
386
- | `other` | MagicPay returned another transport or backend failure. | Inspect `status`, `errorCode`, and logs. |
136
+ After the claim succeeds, your runtime decides what to do with the values:
387
137
 
388
- ## `nextAction` Reference
138
+ - fill a protected browser form;
139
+ - authenticate to an external API;
140
+ - hand the payload to another tool;
141
+ - continue a larger workflow session.
389
142
 
390
- `describeSecretRequestStatus(...)` and `pollUntil(...)` return a status contract with
391
- `nextAction` values that are useful for orchestration:
392
-
393
- | `nextAction` | Meaning |
394
- | --- | --- |
395
- | `poll-secret` | Approval is still pending. |
396
- | `fill-secret` | The request is fulfilled and can be claimed. |
397
- | `ask-user` | A human decision or recovery step is needed. |
398
- | `request-secret` | Create a new request instead of continuing the current one. |
399
-
400
- ## Testing
401
-
402
- Pass `fetchImpl` into `createMagicPayClient(...)` to fully control network
403
- responses in tests.
404
-
405
- ```ts
406
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
407
-
408
- const client = createMagicPayClient({
409
- gateway: {
410
- apiKey: 'test_api_key',
411
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
412
- },
413
- fetchImpl: async (url, init = {}) => {
414
- const method = (init.method ?? 'GET').toUpperCase();
415
- if (method === 'GET' && String(url).includes('/secrets/catalog')) {
416
- return new Response(JSON.stringify([]), {
417
- status: 200,
418
- headers: { 'content-type': 'application/json' },
419
- });
420
- }
421
- throw new Error(`Unexpected ${method} ${url}`);
422
- },
423
- });
424
- ```
143
+ ## Continue Reading
425
144
 
426
- See [`docs/examples.md`](./docs/examples.md) for more complete recipes.
145
+ - Start with [Getting Started](./docs/getting-started.md) for the first root
146
+ integration.
147
+ - Read [Integration Modes](./docs/integration-modes.md) if you need help
148
+ choosing between the root SDK, pure helpers, and the optional AgentBrowse
149
+ bridge.
150
+ - Use [API Reference](./docs/api-reference.md) and
151
+ [Error Reference](./docs/error-reference.md) as lookup documents while
152
+ integrating.
153
+ - Use [Examples Index](./docs/examples.md) for runnable snippets.
154
+ - Use [Glossary](./docs/glossary.md) when terms like `storedSecretRef`,
155
+ `fillRef`, or `scopeRef` are still new.
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@mercuryo-ai/magicpay-sdk",
3
- "version": "0.1.0-test.5",
4
- "description": "Official MagicPay SDK for TypeScript and Node session state and secret request flows",
3
+ "version": "0.1.0-test.6",
4
+ "description": "TypeScript and Node SDK for MagicPay stored-secret request, claim, and session flows",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/MercuryoAI/magicpay-sdk.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/MercuryoAI/magicpay-sdk/issues"
13
+ },
14
+ "homepage": "https://github.com/MercuryoAI/magicpay-sdk#readme",
7
15
  "main": "./dist/index.js",
8
16
  "types": "./dist/index.d.ts",
9
17
  "exports": {
@@ -50,8 +58,7 @@
50
58
  "files": [
51
59
  "dist",
52
60
  "README.md",
53
- "LICENSE.md",
54
- "docs"
61
+ "LICENSE.md"
55
62
  ],
56
63
  "publishConfig": {
57
64
  "access": "public"
@@ -71,14 +78,17 @@
71
78
  "@types/node": "^22.0.0",
72
79
  "typescript": "5.9.2",
73
80
  "vitest": "^4.0.18",
74
- "@mercuryo-ai/agentbrowse": "0.2.52"
81
+ "@mercuryo-ai/agentbrowse": "0.2.53"
75
82
  },
76
83
  "scripts": {
77
- "build": "node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.build.json",
78
- "check-types": "tsc --noEmit",
79
- "docs:verify": "node scripts/verify-public-docs.mjs",
84
+ "build:deps": "pnpm --dir ../.. --filter @mercuryo-ai/agentbrowse build",
85
+ "build": "npm run build:deps && node -e \"require('node:fs').rmSync('dist',{ recursive: true, force: true })\" && tsc -p tsconfig.build.json",
86
+ "check-types": "npm run build:deps && tsc --noEmit",
87
+ "docs:check-types": "npm run build && tsc -p tsconfig.examples.json --noEmit",
88
+ "docs:export": "node scripts/export-public-repo.mjs",
89
+ "docs:verify": "npm run docs:check-types && node scripts/verify-public-docs.mjs",
80
90
  "pack:verify": "node scripts/verify-pack-artifact.mjs",
81
91
  "smoke:pack-install": "node scripts/verify-pack-install-smoke.mjs",
82
- "test": "vitest run"
92
+ "test": "npm run build:deps && vitest run"
83
93
  }
84
94
  }
package/docs/examples.md DELETED
@@ -1,432 +0,0 @@
1
- # Examples
2
-
3
- This document collects recipe-style examples for the current public MagicPay SDK
4
- surface.
5
-
6
- ## Choose An Integration Mode
7
-
8
- The public SDK supports two explicit modes:
9
-
10
- - Mode A: use `@mercuryo-ai/magicpay-sdk` when your runtime can create request
11
- input and consume claimed secrets on its own.
12
- - Mode B: use `@mercuryo-ai/magicpay-sdk/agentbrowse` only when your runtime
13
- already depends on `@mercuryo-ai/agentbrowse` and needs the observed-form
14
- bridge.
15
-
16
- The examples below stay grouped the same way: root-package recipes first, then
17
- the explicit AgentBrowse bridge.
18
-
19
- ## Read Session State
20
-
21
- ```ts
22
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
23
-
24
- // Create a networked MagicPay client.
25
- const client = createMagicPayClient({
26
- gateway: {
27
- apiKey: process.env.MAGICPAY_API_KEY!,
28
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
29
- },
30
- });
31
-
32
- // Fetch the latest server-backed session envelope.
33
- const session = await client.sessions.get('sess_123');
34
-
35
- // Project the envelope into a simpler orchestration state.
36
- const state = client.sessions.describe(session);
37
-
38
- // Use the projected state for logs, UI, or control flow.
39
- console.log({
40
- closed: state.closed,
41
- approvalPending: state.approvalPending,
42
- activeRequestDomain: state.activeRequest?.domain ?? null,
43
- });
44
- ```
45
-
46
- ## Request, Poll, And Claim
47
-
48
- ```ts
49
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
50
-
51
- // Create a networked MagicPay client.
52
- const client = createMagicPayClient({
53
- gateway: {
54
- apiKey: process.env.MAGICPAY_API_KEY!,
55
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
56
- },
57
- });
58
-
59
- const sessionId = 'sess_123';
60
- const pageUrl = 'https://checkout.airline.example/payment';
61
-
62
- // Load the stored-secret catalog for the current host.
63
- const catalog = await client.secrets.fetchCatalog(sessionId, pageUrl);
64
-
65
- // Create a request for one stored secret and the exact fields this step needs.
66
- const created = await client.secrets.createRequest({
67
- sessionId,
68
- clientRequestId: 'payment-card-1',
69
- fillRef: 'checkout_card',
70
- purpose: 'payment_card',
71
- merchantName: 'Airline Example',
72
- page: {
73
- url: pageUrl,
74
- title: 'Payment',
75
- },
76
- secretHint: {
77
- storedSecretRef: 'secret_card_primary',
78
- kind: 'payment_card',
79
- host: catalog.host,
80
- scopeRef: 'payment_surface',
81
- fields: ['pan', 'exp_month', 'exp_year', 'cvv'],
82
- },
83
- });
84
-
85
- // Wait until the request is fulfilled or another terminal status appears.
86
- const ready = await client.secrets.pollUntil(sessionId, created.requestId, {
87
- stopWhen: 'fulfilled',
88
- });
89
-
90
- if (!ready.success) {
91
- throw new Error(ready.reason);
92
- }
93
- if (ready.result.snapshot.status !== 'fulfilled') {
94
- throw new Error(`Request ended as ${ready.result.snapshot.status}`);
95
- }
96
-
97
- // Claim the one-time payload after approval.
98
- const claim = await client.secrets.claim(sessionId, created.requestId);
99
- if (!claim.success) {
100
- throw new Error(claim.reason);
101
- }
102
-
103
- // Use the claimed secret values in your runtime.
104
- console.log(claim.result.secret.values);
105
- ```
106
-
107
- ## Poll Until A Terminal Status
108
-
109
- ```ts
110
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
111
-
112
- // Create a networked MagicPay client.
113
- const client = createMagicPayClient({
114
- gateway: {
115
- apiKey: process.env.MAGICPAY_API_KEY!,
116
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
117
- },
118
- });
119
-
120
- // Poll until the request reaches any terminal status.
121
- const result = await client.secrets.pollUntil('sess_123', 'req_123', {
122
- stopWhen: 'terminal',
123
- timeoutMs: 180_000,
124
- intervalMs: 10_000,
125
- maxIntervalMs: 30_000,
126
- backoffMultiplier: 1.2,
127
- });
128
-
129
- if (!result.success) {
130
- // Transport failures and timeouts still need caller-level handling.
131
- console.log(result.kind, result.reason);
132
- } else {
133
- // Branch on the final request status.
134
- switch (result.result.snapshot.status) {
135
- case 'fulfilled':
136
- console.log('Claim and consume the one-time secret payload.');
137
- break;
138
- case 'denied':
139
- console.log('A user denied the request.');
140
- break;
141
- case 'expired':
142
- console.log('Create a new request.');
143
- break;
144
- case 'failed':
145
- case 'canceled':
146
- console.log('Inspect logs or surface recovery to the caller.');
147
- break;
148
- default:
149
- throw new Error(`Unexpected status ${result.result.snapshot.status}`);
150
- }
151
- }
152
- ```
153
-
154
- ## AgentBrowse Observe -> Request -> Fill
155
-
156
- This example is Mode B: explicit AgentBrowse bridge.
157
-
158
- ```ts
159
- import { act, observe } from '@mercuryo-ai/agentbrowse';
160
- import {
161
- fillProtectedForm,
162
- type ProtectedFillForm,
163
- } from '@mercuryo-ai/agentbrowse/protected-fill';
164
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
165
- import {
166
- buildRequestInputForObservedForm,
167
- enrichObservedFormsForUrl,
168
- prepareProtectedFillFromClaim,
169
- } from '@mercuryo-ai/magicpay-sdk/agentbrowse';
170
-
171
- // Create the MagicPay client that owns the approval flow.
172
- const client = createMagicPayClient({
173
- gateway: {
174
- apiKey: process.env.MAGICPAY_API_KEY!,
175
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
176
- },
177
- });
178
-
179
- const sessionId = 'sess_123';
180
-
181
- // Observe the page and collect protected fill forms.
182
- const observation = await observe(browserSession, 'Find the login form');
183
- if (!observation.success || !observation.url || !Array.isArray(observation.fillableForms)) {
184
- throw new Error('AgentBrowse did not return observed forms.');
185
- }
186
-
187
- const forms = observation.fillableForms as ProtectedFillForm[];
188
-
189
- // Load the stored-secret catalog for the observed host.
190
- const catalog = await client.secrets.fetchCatalog(sessionId, observation.url);
191
-
192
- // Attach stored-secret candidates to the observed forms.
193
- const enrichedForms = enrichObservedFormsForUrl(forms, { [catalog.host]: catalog }, observation.url);
194
- const form = enrichedForms.find((candidate) => candidate.purpose === 'login') ?? enrichedForms[0] ?? null;
195
- if (!form) {
196
- throw new Error('No observed form is available.');
197
- }
198
-
199
- // Build a MagicPay request input from the observed login form.
200
- const requestInput = buildRequestInputForObservedForm({
201
- sessionId,
202
- merchantName: 'Airline Example',
203
- storedSecretRef: 'secret_login_primary',
204
- urlOrHost: observation.url,
205
- catalog,
206
- fillableForm: form,
207
- page: {
208
- ref: form.pageRef,
209
- url: observation.url,
210
- ...(observation.title ? { title: observation.title } : {}),
211
- },
212
- });
213
-
214
- if (!requestInput.success) {
215
- throw new Error(requestInput.reason);
216
- }
217
-
218
- // Create the approval request and wait until it is fulfilled.
219
- const created = await client.secrets.createRequest(requestInput.input);
220
- const ready = await client.secrets.pollUntil(sessionId, created.requestId, {
221
- stopWhen: 'fulfilled',
222
- });
223
-
224
- if (!ready.success) {
225
- throw new Error(ready.reason);
226
- }
227
- if (ready.result.snapshot.status !== 'fulfilled') {
228
- throw new Error(`Request ended as ${ready.result.snapshot.status}`);
229
- }
230
-
231
- // Claim the one-time payload after approval.
232
- const claim = await client.secrets.claim(sessionId, created.requestId);
233
- if (!claim.success) {
234
- throw new Error(claim.reason);
235
- }
236
-
237
- // Convert the claim into AgentBrowse protected-fill input.
238
- const prepared = prepareProtectedFillFromClaim({
239
- fillableForm: form,
240
- catalog,
241
- claim: claim.result,
242
- request: created.snapshot,
243
- });
244
-
245
- // Fill the protected fields in the browser.
246
- const browserFill = await fillProtectedForm({
247
- session: browserSession,
248
- fillableForm: prepared.fillableForm,
249
- protectedValues: prepared.protectedValues,
250
- fieldPolicies: prepared.fieldPolicies,
251
- });
252
-
253
- if (!browserFill.success) {
254
- throw new Error(browserFill.reason);
255
- }
256
-
257
- // Re-observe after the protected fill so the submit action comes from the live page.
258
- const submitObservation = await observe(
259
- browserSession,
260
- 'Find the single actionable submit target that sends the filled login form.'
261
- );
262
- if (!submitObservation.success || submitObservation.targets.length === 0) {
263
- throw new Error(submitObservation.reason ?? submitObservation.message);
264
- }
265
-
266
- // Goal-based observe already narrows the inventory to the submit step we need next.
267
- const submitTarget = submitObservation.targets[0];
268
- if (!submitTarget?.ref || submitTarget.capability !== 'actionable') {
269
- throw new Error('AgentBrowse did not return an actionable submit target.');
270
- }
271
-
272
- const submitAction = await act(browserSession, submitTarget.ref, 'click');
273
- if (!submitAction.success) {
274
- throw new Error(submitAction.reason ?? submitAction.message);
275
- }
276
- ```
277
-
278
- ## Claim And Call An External API
279
-
280
- ```ts
281
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
282
-
283
- // Create the MagicPay client for the claim step.
284
- const client = createMagicPayClient({
285
- gateway: {
286
- apiKey: process.env.MAGICPAY_API_KEY!,
287
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
288
- },
289
- });
290
-
291
- // Claim the approved secret payload.
292
- const claim = await client.secrets.claim('sess_123', 'req_123');
293
- if (!claim.success) {
294
- throw new Error(claim.reason);
295
- }
296
-
297
- // Forward the claimed values to the external API call.
298
- const response = await fetch('https://api.airline.example/login', {
299
- method: 'POST',
300
- headers: { 'content-type': 'application/json' },
301
- body: JSON.stringify({
302
- username: claim.result.secret.values.username,
303
- password: claim.result.secret.values.password,
304
- }),
305
- });
306
-
307
- if (!response.ok) {
308
- throw new Error(`Provider login failed with ${response.status}`);
309
- }
310
- ```
311
-
312
- ## Claim And Hand Off To An MCP Tool
313
-
314
- ```ts
315
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
316
-
317
- // Create the MagicPay client for the claim step.
318
- const client = createMagicPayClient({
319
- gateway: {
320
- apiKey: process.env.MAGICPAY_API_KEY!,
321
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
322
- },
323
- });
324
-
325
- // Claim the approved secret payload.
326
- const claim = await client.secrets.claim('sess_123', 'req_123');
327
- if (!claim.success) {
328
- throw new Error(claim.reason);
329
- }
330
-
331
- // Repackage the claimed values for your MCP tool contract.
332
- const mcpInput = {
333
- type: 'provider_login',
334
- payload: {
335
- username: claim.result.secret.values.username,
336
- password: claim.result.secret.values.password,
337
- },
338
- };
339
-
340
- console.log(mcpInput);
341
- ```
342
-
343
- ## Testing With `fetchImpl`
344
-
345
- ```ts
346
- import { createMagicPayClient } from '@mercuryo-ai/magicpay-sdk';
347
-
348
- // Predefine the fake transport responses your test expects.
349
- const responses = new Map([
350
- [
351
- 'GET https://agents-api.mercuryo.io/functions/v1/api/sessions/sess_123/secrets/catalog?host=checkout.airline.example',
352
- new Response(JSON.stringify([]), {
353
- status: 200,
354
- headers: { 'content-type': 'application/json' },
355
- }),
356
- ],
357
- ]);
358
-
359
- // Inject fetchImpl so tests never hit the real network.
360
- const client = createMagicPayClient({
361
- gateway: {
362
- apiKey: 'test_api_key',
363
- apiUrl: 'https://agents-api.mercuryo.io/functions/v1/api',
364
- },
365
- fetchImpl: async (url, init = {}) => {
366
- const method = (init.method ?? 'GET').toUpperCase();
367
- const key = `${method} ${String(url)}`;
368
- const response = responses.get(key);
369
- if (!response) {
370
- throw new Error(`Unexpected ${key}`);
371
- }
372
- return response;
373
- },
374
- });
375
-
376
- // Run the SDK call against the mocked transport.
377
- await client.secrets.fetchCatalog('sess_123', 'https://checkout.airline.example/login');
378
- ```
379
-
380
- ## Pure Domain Helpers
381
-
382
- `@mercuryo-ai/magicpay-sdk/core` keeps the pure helpers that do not need network
383
- state from the client object.
384
-
385
- ```ts
386
- import {
387
- describeSecretRequestStatus,
388
- evaluateSecretRequestForFill,
389
- resolveSecretCatalogContext,
390
- } from '@mercuryo-ai/magicpay-sdk/core';
391
-
392
- // Interpret what the current request status means for orchestration.
393
- const status = describeSecretRequestStatus('pending', 'secret_read');
394
- console.log(status.nextAction);
395
-
396
- // Check whether the fulfilled request still matches the observed fill context.
397
- const readiness = evaluateSecretRequestForFill(
398
- {
399
- requestId: 'req_123',
400
- fillRef: 'checkout_card',
401
- requestType: 'secret_read',
402
- status: 'fulfilled',
403
- host: 'checkout.airline.example',
404
- scopeRef: 'payment_surface',
405
- createdAt: '2026-03-31T12:00:00.000Z',
406
- updatedAt: '2026-03-31T12:01:00.000Z',
407
- },
408
- {
409
- fillRef: 'checkout_card',
410
- observedHost: 'checkout.airline.example',
411
- observedScopeRef: 'payment_surface',
412
- }
413
- );
414
-
415
- // Consume the readiness result inside your own orchestration layer.
416
- console.log(readiness.kind);
417
-
418
- // Resolve the host-specific catalog entry without creating a client.
419
- const context = resolveSecretCatalogContext(
420
- {
421
- 'checkout.airline.example': {
422
- host: 'checkout.airline.example',
423
- syncedAt: '2026-03-31T12:00:00.000Z',
424
- storedSecrets: [],
425
- },
426
- },
427
- 'https://checkout.airline.example/login'
428
- );
429
-
430
- // Use the resolved host and catalog in pure domain logic.
431
- console.log(context.host, context.catalog);
432
- ```