Skip to content

Authorize Link cards at checkout and steer the Link Pay Token flow - #251

Merged
rgarcia merged 3 commits into
mainfrom
hypeship/link-pay-token-vault-tools-v2
Oct 14, 2026
Merged

rgarcia merged 3 commits into
mainfrom
hypeship/link-pay-token-vault-tools-v2

Conversation

@rgarcia

@rgarcia rgarcia commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Brings the vault tools in line with the final Link card API: Link cards are authorized at the final checkout page with browser_id and page_url. Kernel inspects the checkout and delivers either a Link Pay Token or a virtual card. Card updates still work until a card is authorized at a checkout.

Builds on #247, which is already on main: API errors still pass through unchanged. The only addition is a recovery step for the seven Link checkout-inspection error codes.

Behavior

  • manage_vault_cards authorize accepts Link cards. It takes optional top-level browser_id and page_url, which must be passed together or not at all. They are forwarded as {"type":"authorize","browser_id":…,"page_url":…}. Re-checking a pending card omits them. A KERNEL card that gets browser_id/page_url is rejected after the item read, and an AgentCard authorize is rejected before any request.
  • manage_vault_items invoke authorize validates inputs against a strict schema: {} or { browser_id, page_url }, with an https page_url. Unknown keys such as merchant_account_id are named and rejected before any request.
  • fill can omit fields. The schema now matches FillVaultItemOperationRequest, so a Link Pay Token fill sends {type, browser_id, page_url}.
  • Link authorize errors. For browser_not_found, browser_unavailable, destination_denied, page_not_found, ambiguous_page, timeout, and browser_error, the API's status, message, and code pass through, followed by: "no payment request was made. make sure the checkout is open in exactly one tab at page_url, then authorize again. if checkout inspection keeps failing, authorize without browser_id and page_url to issue a virtual card for the merchant_url origin." Other errors, including a 409 from updating an authorized card and the virtual-card amount limit, pass through unchanged as in Validate vault fill inputs and pass API errors through #247. Link authorize no longer goes through the KERNEL-card error path, which added KERNEL-specific advice and removed URLs from messages.
  • Wallet description. It is now included in item projections, so agents see the API's Link wallet guidance.
  • Invocation hints. A requested Link card's authorize hint carries required_inputs: ["browser_id", "page_url"]. Without it, an agent replaying the hint unchanged would take the fallback virtual-card path by accident.

Agent-facing text

manage_vault_cards, manage_vault_items, manage_vault_wallets, manage_vaults, the Link card item guidance, and the amount and inputs parameter descriptions now describe one order. Reach checkout, create the card, authorize with browser_id and page_url, share the approval URL, wait for fill, fill, then submit yourself. The text points agents at the item's available_operations descriptions and the wallet description and doesn't restate them. It says "link pay token" and "virtual card" in the repo's lowercase brand casing. It never asks for or shows a merchant account and never names how the credential is delivered. Every failure has a recovery step. The generic "do not retry … payments" guidance line is no longer added to Link card items. "Do not inspect or invoke the checkout's WebMCP tools yourself" is scoped to authorize and Link Pay Token fills. When the API advertises webmcp_invoke on a Link virtual card, the item keeps the generic webmcp_invoke guidance, and the webmcp tool description is unchanged from main.

docs/vault-payments.md: the Link flow is rewritten in the same order, with authorize and fill examples. The intro, the error bullet, and the update bullet are updated. The fill examples now use the real inputs parameter instead of a nonexistent nested fill key. The README line for manage_vault_cards is updated.

SDK

Updates @onkernel/sdk to 0.126.0, which adds browser_id/page_url to AuthorizeVaultItemOperationRequest and makes fields optional on fill. The authorize request in manage_vault_cards is now typed directly with no cast.

The bump also picks up the 0.124.0 change that types browserPools.acquire as BrowserPoolAcquireResponse | null (204 on long-poll timeout). manage_browser_pools already returns an error for a null acquire; only its response type alias changes, to NonNullable<…>.

Testing

  • bun test: 1064 pass, 0 fail (1013 on main), against @onkernel/sdk 0.126.0.
  • bunx tsc --noEmit --incremental false: clean.
  • bun run check:managed-auth-app: clean.
  • bunx prettier --check "src/**/*.ts" docs README.md: clean. bun run format:check flags only AGENTS.md, which is pre-existing on main. bun run lint is broken on main (next lint was removed in Next 16).

New tests:

  • vault-items.test.ts: authorize forwards checkout context, {}, or no inputs. One-of, http, and unknown-key inputs are rejected before any request. Each checkout error code passes through with its recovery step. Other errors pass through unchanged. A Link Pay Token fill without fields is forwarded, and its result guidance doesn't claim fields were written. A lost response after the authorize POST reports "after the operation was submitted"; a lost response before it doesn't. page_url values that the API's pattern rejects (uppercase scheme, userinfo, spaces, *) are rejected locally.
  • vaults.test.ts: tool descriptions name the flow in order and contain no internal terms. manage_vault_cards authorizes with checkout context and re-checks without it. The rejection cases, the KERNEL-card guard, error pass-through, update 409 pass-through, the wallet description projection, and lost responses before and after the authorize POST are covered.
  • vault-hints.test.ts: hints for Link authorize through both tools.
  • vault-operation-errors.test.ts: throwLinkAuthorizeError for each code, unchanged pass-through for other codes, and transport loss.
  • vault-payment-guidance.test.ts: Link guidance order, recovery steps (including status_reason on decline), WebMCP steering scoped to authorize and Pay Token fills, webmcp_invoke guidance kept for virtual cards, and required_inputs only on requested cards.

@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
mcp Ready Ready Preview Oct 14, 2026 5:53pm UTC

@socket-security

socket-security Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​onkernel/​sdk@​0.121.0 ⏵ 0.126.076 -5100100 +199 +1100

View full report

@rgarcia
rgarcia requested a review from bmsaadat October 14, 2026 17:17

@bmsaadat bmsaadat left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Request changes for item 1 (small fix). Item 2 is a test ask; the rest is optional.

The authorize body, the both-or-neither rule, the seven checkout error codes, the optional fields on fill and the PATCH 409 wording all match the API on main. bun test 1055 pass, tsc clean, prettier clean, CI green.

  1. Link virtual cards are told not to use webmcp_invoke, but the API still advertises it (vault-responses.ts:511, webmcp.ts:30, linkCardGuidance, the manage_vault_items description). When Kernel issues a virtual card, renderVaultItem advertises webmcp_invoke next to fill (only Pay Token cards drop it). The PR now says "for link cards, use the advertised fill operation instead; KERNEL invokes the checkout's webmcp tools itself". That's only true for a Pay Token: a virtual card means the checkout had no usable Link tools (or authorize ran without checkout context), so Kernel has nothing to invoke. Meanwhile hints.invocation still lists webmcp_invoke for the same item, and webmcpInvokeItemGuidance (may submit, untrusted output, never retry unknown) is no longer attached. Could we scope the "don't invoke WebMCP yourself" text to authorize and Pay Token fills, and keep webmcpInvokeItemGuidance whenever webmcp_invoke is advertised? If the intent is to retire webmcp_invoke for Link, it would need to go from the API first. The "Link cards are not told to drive checkout webmcp tools" test would need updating either way.

  2. No test covers a lost response after a Link authorize was sent. I swapped authorizationSubmitted (vault-cards.ts) and operationSubmitted (vault-items.ts) for false in the Link authorize error path, and the full suite still passed (1055/1055). That flag is what produces "…after the operation was submitted", the hint that a Link spend request may exist. Could we add one connection-error-on-POST test per tool?

Optional

  • vault-items.ts:271: after a Pay Token fill, "completed means the fields were written" isn't accurate; no fields are written (the result has fields: []). The docs already say "the credential was supplied to the page".
  • linkCardGuidance: the API's decline step also says "read the item's status_reason" first.
  • vault-schemas.ts:97: z.url({ protocol: /^https$/ }) accepts HTTPS://…, userinfo, spaces and *, which the API's page_url pattern rejects. The API rejects these with a 400 ("invalid Link card checkout") before any provider call, so this is harmless; using ^https://[^/?#@*\s]+(?:[/?#][^\s]*)?$ would make it a true mirror.
  • The seven codes are typed out twice in code (linkCheckoutErrorCodes and linkCardGuidance), and the "exactly one tab at page_url" recovery step is written out five times across the tools, guidance and docs. Deriving the list from the Set would at least keep the codes in sync.

API side (not this PR): authorizing with a browser that isn't attached to the vault (or is deleted, an unleased pool instance, or in another project) returns 500 provider_error "failed to perform item operation". fillUnavailableError only implements Is(conflict), so linkCardCheckoutError's errors.As misses it. That's probably the most likely agent mistake on this path, and it gets no recovery step from either the API or the MCP. I raised the same thing on cli#298.

@rgarcia

rgarcia commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

Thanks. All addressed in 0ea97c4.

  1. WebMCP steering on Link virtual cards. The "don't inspect or invoke the checkout's WebMCP tools yourself" text now covers only authorize and Link Pay Token fills. The Pay Token fill guidance reads "KERNEL supplies the token through the checkout's webmcp tool, so do not inspect or invoke it yourself", and the virtual-card fill sentence no longer mentions WebMCP. webmcpInvokeItemGuidance is attached again whenever webmcp_invoke is advertised, Link included. The webmcp tool description is reverted to main. The old test is replaced by two: virtual cards keep the webmcp_invoke guidance and hint, and the do-not-invoke text stays out of the virtual-card fill sentence.
  2. Lost response after authorize was sent. Added one test per tool for a connection error on the POST, expecting "…after the operation was submitted.", and one for an error on the GET before it, which must not say that. Swapping either flag for false now fails its tool's test; I checked both.

Optional items:

  • The fill result guidance now says "completed means the credential was supplied to the page (fields were written, or a link pay token was delivered)…", and a test covers it on a Pay Token fill.
  • Decline now says "read the item's status_reason, then delete the card and create a new one", in the item guidance, the manage_vault_cards description, and the docs.
  • page_url now uses the API's pattern ^https://[^/?#@*\s]+(?:[/?#][^\s]*)?$. Tests cover the uppercase-scheme, userinfo, space and * cases.
  • linkCheckoutErrorCodes is now a single list that drives both the error check and the guidance sentence. A shared linkCheckoutRecovery string is used in the error note, the item guidance, and the manage_vault_cards and manage_vault_items descriptions. The docs still state it separately.

API side: agreed. A browser that isn't attached to the vault falls through linkCardCheckoutError as a 500 provider_error with no recovery step. I left the MCP side alone: matching a generic 500 here would put the wrong advice on real provider failures. It should be fixed in the API's error mapping.

bun test 1064 pass, tsc clean, prettier clean.

@rgarcia
rgarcia merged commit 3a54a74 into main Oct 14, 2026
10 checks passed
@rgarcia
rgarcia deleted the hypeship/link-pay-token-vault-tools-v2 branch October 14, 2026 20:17

This branch was successfully deployed

1 active deployment
Preview — 0ea97c4b Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants