import { ZodTypeAny, z } from 'zod'; import { PurchaseCheckResult, PaymentMethodInfo, CustomerBalanceResult, GetUsageResult, LimitResponseWithPlan, LimitAutoRechargeDto, PaywallNextAction, SdkMerchantResponse, SdkProductResponse, components, SolvaPay, PaywallError, PaywallStructuredContent, FreeLimit } from '@solvapay/server'; import { PricingOptionLike } from '@solvapay/core'; export { ProductReadinessInput, ProductReadinessResult, SOLVAPAY_PRODUCT_REF_PLACEHOLDER, assertValidProductRef, creditsToDisplayMinorUnits, evaluateProductReadiness, isZeroDecimalCurrency, minorUnitsPerMajor } from '@solvapay/core'; /** * Canonical MCP tool names for the SolvaPay transport + bootstrap tools. * * Single source of truth for `@solvapay/mcp-core`, `@solvapay/mcp`, * `@solvapay/react/mcp/adapter`, and any third-party adapter (fastmcp, * raw JSON-RPC, etc.). Adding a new tool means editing exactly one file. */ declare const MCP_TOOL_NAMES: { readonly createPayment: "create_payment_intent"; readonly processPayment: "process_payment"; readonly createHostedSession: "create_hosted_session"; readonly setRenewal: "set_renewal"; readonly activatePlan: "activate_plan"; readonly attachBusinessDetails: "attach_business_details"; readonly getHistory: "get_history"; readonly account: "account"; }; type McpToolName = (typeof MCP_TOOL_NAMES)[keyof typeof MCP_TOOL_NAMES]; /** * The single read-only viewer that opens checkout / account / topup. * All three surfaces share one payload; `view` picks the landing screen. */ declare const VIEWER_TOOL_NAME: "account"; /** * LLM-facing intent tools, derived from `MCP_TOOL_NAMES` so a rename * edits exactly one object. Narrators, `TOOL_FOR_VIEW`, and the * scaffolder's `INTENT_TOOLS` arrays must stay aligned with this list. */ declare const INTENT_TOOL_NAMES: readonly ["account", "activate_plan"]; type IntentToolName = (typeof INTENT_TOOL_NAMES)[number]; /** * Slash-command prompt names. Independent of the tool catalogue — * `/upgrade`, `/manage_account`, and `/topup` remap onto the viewer * with the matching `view`. Prompts cost zero tool budget. */ declare const MCP_PROMPT_NAMES: { readonly upgrade: "upgrade"; readonly manageAccount: "manage_account"; readonly topup: "topup"; readonly activatePlan: "activate_plan"; }; type McpPromptName = (typeof MCP_PROMPT_NAMES)[keyof typeof MCP_PROMPT_NAMES]; /** * Framework-neutral MCP contracts shared across every SolvaPay MCP adapter. * * These types are intentionally free of `@modelcontextprotocol/*` imports * so adapters for `fastmcp`, or raw JSON-RPC servers can consume them * without pulling in the official SDK. * * The `ZodTypeAny`-shaped `inputSchema` keeps zod as an optional peer. * Adapters that don't use zod can still pass equivalent shapes. */ /** * Merchant identity surfaced on every `BootstrapPayload`. Structural * alias for the server's `SdkMerchantResponse` so the React shell can * hydrate `` and the rest of the trust-signal surface * without an extra fetch. */ type BootstrapMerchant = SdkMerchantResponse; /** * Product projection surfaced on every `BootstrapPayload`. Structural * alias for the server's `SdkProductResponse`. */ type BootstrapProduct = SdkProductResponse; /** * Plan projection surfaced on every `BootstrapPayload`. Structural * alias for the generated `Plan` schema so the embedded checkout can * mount its plan picker from the snapshot. */ type BootstrapPlan = components['schemas']['Plan']; /** * Per-customer snapshot surfaced on every `BootstrapPayload`. Null when * unauthenticated; individual fields are null when the corresponding * sub-read errored or doesn't apply. */ interface BootstrapCustomer { ref: string; /** * Signed-in identity from the customer record. Projected off the * purchase-check result, which carries them only because that helper * looks the customer up. Null when that sub-read returned none. * The whole `customer` is null for an anonymous caller. */ email: string | null; name: string | null; purchase: PurchaseCheckResult | null; paymentMethod: PaymentMethodInfo | null; balance: CustomerBalanceResult | null; usage: GetUsageResult | null; /** * Runtime allowance from the same `checkLimits` call that feeds * `usage`. Null when the customer is unauthenticated or the limits * sub-read failed. Carries `withinLimits`, `activationRequired`, * `overage`, `needsTopUp` — the flags usage math discards. */ limits: LimitResponseWithPlan | null; /** Whether the latest `checkLimits` allowed the call. */ canCall?: boolean; /** How many calls the current allowance or wallet still covers. */ remainingCalls?: number; /** Credits deducted per call when the response is credit-based. */ creditsPerCall?: number; /** `max(0, creditsPerCall - balance)` when both are known. */ shortfallCredits?: number; /** * True when the limits response carries credit-balance fields. * False means the plan does not spend credits. */ isCreditBased?: boolean; /** Per-provider auto-recharge snapshot from the limits response. */ autoRecharge?: LimitAutoRechargeDto; /** Present only when the latest `checkLimits` blocked the call. */ nextAction?: PaywallNextAction; } /** * MCP tool call result — a structural subset of the official SDK's * `CallToolResult` that every framework produces. Kept local to avoid * coupling to `@modelcontextprotocol/core` type churn. */ /** * Routing hint surfaced on individual content blocks. Mirrors the MCP * `Annotations.audience` field — hosts that honour it (ChatGPT Apps, * Claude Desktop) hide `['assistant']`-tagged blocks from the user pane * while still feeding them to the model. Hosts that don't honour it * (e.g. MCP Inspector) render every block verbatim. */ interface SolvaPayContentAnnotations { audience?: Array<'user' | 'assistant'>; priority?: number; } interface SolvaPayCallToolResult { content: Array<{ type: 'text'; text: string; annotations?: SolvaPayContentAnnotations; } | { type: 'image'; data: string; mimeType: string; annotations?: SolvaPayContentAnnotations; } | { type: 'resource'; resource: Record; annotations?: SolvaPayContentAnnotations; }>; structuredContent?: Record; isError?: boolean; _meta?: Record; } /** * Auth envelope carried on an MCP tool-handler context, as produced by * `buildAuthInfoFromBearer`. */ interface McpAuthInfo { token?: string; clientId?: string; scopes?: string[]; expiresAt?: number; extra?: Record; } /** * Extra context passed into MCP tool handlers. Mirrors the `extra` * parameter shape used by the official SDK's `registerTool` callback. * * `authInfo` moved between SDK majors: v1 exposed it at the top level, * v2 nests it under `http` (`BaseContext.http.authInfo`) because it is * only populated by HTTP transports. Both are declared so this * framework-neutral package keeps working across the official SDK v1/v2 * and third-party adapters (fastmcp, raw JSON-RPC) that still pass the * flat shape. Always read them via `defaultGetCustomerRef` rather than * reaching in directly. */ interface McpToolExtra { /** Official SDK v2 location — populated by HTTP transports only. */ http?: { authInfo?: McpAuthInfo; }; /** Official SDK v1 location; still used by some third-party adapters. */ authInfo?: McpAuthInfo; [key: string]: unknown; } /** * Options for the MCP adapter's `formatResponse` / `getCustomerRef` * hooks. Used by `@solvapay/server`'s `McpAdapter` (backing * `payable().mcp()`) and any descriptor-level customisation. */ interface McpAdapterOptions { getCustomerRef?: (args: any, extra?: McpToolExtra) => string | Promise; transformResponse?: (result: any) => any; } /** * Tool result shape returned by `payable().mcp(...)` when the paywall * fires. Structurally compatible with `SolvaPayCallToolResult` so every * framework adapter can return it directly. */ interface PaywallToolResult { content?: Array<{ type: 'text' | 'image' | 'resource'; text?: string; data?: string; mimeType?: string; }>; isError?: boolean; structuredContent?: Record; _meta?: Record; } /** * Which view a SolvaPay MCP server knows how to bootstrap. * * Advertised landings on the `account` viewer are `checkout`, * `account`, and `topup`. `'auto-recharge'` is a deprecated leftover * stamp: the tool schema does not offer it, but a leftover argument * still opens the account surface (status row + portal link). There is * no `paywall` or `nudge` view — those responses are plain text * narrations per the text-only paywall refactor (merchant paywall / * nudge tool results ship `content[0].text` + `structuredContent = * gate` and never open the iframe). * * The legacy `'about'`, `'activate'`, and `'usage'` surfaces were * dropped earlier — About is served by tool descriptions + docs * resources, Activate merged into checkout's * `PlanActivationDispatcher`, and Usage folds inline into the account * view. */ type SolvaPayMcpAdvertisedViewKind = 'checkout' | 'account' | 'topup'; type SolvaPayMcpViewKind = SolvaPayMcpAdvertisedViewKind | 'auto-recharge'; declare const SOLVAPAY_MCP_ADVERTISED_VIEW_KINDS: readonly ["checkout", "account", "topup"]; declare const SOLVAPAY_MCP_VIEW_KINDS: readonly ["checkout", "account", "topup", "auto-recharge"]; /** * Payload returned by every `open_*` bootstrap tool and consumed by the * React MCP App shell to render the right view. Single source of truth — * the react bootstrap (`@solvapay/react/mcp`) imports this type so field * renames can't silently drift between server and client. * * Product-scoped fields (`merchant`, `product`, `plans`) are always * present so the shell can render without firing follow-up read tools. * `customer` is `null` when the call is unauthenticated; each nested * field is `null` when the corresponding sub-read errored or doesn't * apply (e.g. `paymentMethod: null` when no card is on file). * * The legacy `paywall` / `nudge` / `data` fields were removed as part * of the text-only paywall refactor: merchant paywall / nudge * responses are plain narrations, not widget payloads, so the * bootstrap no longer carries a gate or a nudge spec for the shell to * render. */ interface BootstrapPayload { view: SolvaPayMcpViewKind; productRef: string; stripePublishableKey: string | null; returnUrl: string; merchant: BootstrapMerchant; product: BootstrapProduct; plans: BootstrapPlan[]; customer: BootstrapCustomer | null; /** Hosted checkout URL for the current customer, when one could be minted. */ checkoutUrl?: string | null; /** Customer portal URL for the current customer, when one could be minted. */ portalUrl?: string | null; /** * Hosted portal URL that opens the auto-recharge form (`tab=credits&intent=autorecharge`). * Null when no portal session could be minted. Suffixed in `@solvapay/mcp-core` * from the same `customerUrl` as `portalUrl`. */ autoRechargeUrl?: string | null; } /** * Content Security Policy allow-list inputs merged with the Stripe * baseline by `SOLVAPAY_DEFAULT_CSP` / `mergeCsp`. */ interface SolvaPayMcpCsp { resourceDomains?: string[]; connectDomains?: string[]; frameDomains?: string[]; } /** * MCP tool annotations — hints to hosts about the tool's behaviour. * Structural subset of the official spec's `ToolAnnotations` so hosts * can surface confirmation prompts (destructive actions) and filter * tools appropriately (read-only vs. mutating). * * See https://modelcontextprotocol.io/specification for the full * semantics. Hosts on pre-annotations SDK versions ignore the field * without error — the wire shape is additive. */ interface SolvaPayToolAnnotations { /** Human-readable title override (prefer the descriptor's `title`). */ title?: string; /** Tool only retrieves data — no side effects on SolvaPay state. */ readOnlyHint?: boolean; /** Tool has destructive / non-reversible side effects (charges, cancellations). */ destructiveHint?: boolean; /** Repeated calls with the same args produce the same effect. */ idempotentHint?: boolean; /** Tool interacts with external systems (true for every SolvaPay tool). */ openWorldHint?: boolean; } /** * Brand-icon descriptor — mirrors the MCP spec's per-tool / per-resource * `icons[]` element so hosts can swap the default globe / placeholder * in the chrome strip for the merchant's own mark. * * Shape tracks the zod schema exposed by `@modelcontextprotocol/ext-apps` * (see `app.d.ts` icon definitions). Newer MCP SDKs may surface `icons` * as a top-level Tool field; until then we advertise them via * `_meta.ui.icons` where ext-apps-aware hosts can discover them. */ interface SolvaPayToolIcon { /** Absolute URL or `data:` URI for the icon asset. */ src: string; /** MIME type, e.g. `'image/png'` / `'image/svg+xml'`. */ mimeType?: string; /** Size hints, e.g. `['512x512']`. */ sizes?: string[]; /** Render only under the given host theme (defaults to both). */ theme?: 'light' | 'dark'; } /** * Merchant branding that hosts / iframes can use to replace the * generic SolvaPay identity with the merchant's own mark. Pre-fetched * by the integrator at server startup (e.g. via `getMerchantCore`) so * `createSolvaPayMcpServer` stays synchronous. */ interface SolvaPayMerchantBranding { /** Display name used as the MCP `Implementation.name`. */ brandName?: string; /** * Absolute URL to a square logomark (recommended 512×512 PNG or SVG). * Preferred input for MCP host-chrome icon slots. */ iconUrl?: string; /** * Absolute URL to a landscape logo. Used as a fallback when * `iconUrl` isn't available — hosts that can only render a square * mark may letterbox it. */ logoUrl?: string; } /** * Framework-neutral tool descriptor. Adapters translate this to their * own registration API (`registerAppTool` on the official SDK, * `tool.define` on `fastmcp`, etc.). */ interface SolvaPayToolDescriptor { name: string; title?: string; description: string; /** * Zod raw shape — `{ fieldName: z.string() }`. Kept as `ZodTypeAny` * valued so zod stays an optional peer. Empty object = no args. */ inputSchema: Record; /** * Optional Standard Schema for `structuredContent`. When set, hosts * can hydrate against it and the server MUST conform. Fields we omit * when absent must be optional here — never required-with-a-default. */ outputSchema?: ZodTypeAny; meta?: Record; /** * Portable MCP tool annotations surfaced on `tools/list`. Adapters * that support the upstream `ToolAnnotations` field forward these * verbatim; adapters that don't silently ignore them. */ annotations?: SolvaPayToolAnnotations; /** * Brand icons surfaced on `tools/list`. Adapters include these on * the tool advertisement so hosts can replace the default globe / * placeholder in their chrome strip with the merchant's mark. * Derived from `SolvaPayMerchantBranding` when the descriptor is * built. */ icons?: SolvaPayToolIcon[]; handler: (args: Record, extra?: McpToolExtra) => Promise; } /** * UI resource descriptor for the MCP App HTML bundle served alongside * the tool surface. */ interface SolvaPayResourceDescriptor { uri: string; mimeType: string; csp: Required; readHtml: () => Promise; } /** * Docs-style resource descriptor — a static markdown / text blob the * agent can `resources/read` for narrated context (e.g. the * `docs://solvapay/overview.md` "start here" guide). Kept separate from * `SolvaPayResourceDescriptor` because it has no CSP metadata and its * body is plain text, not the UI shell HTML. */ interface SolvaPayDocsResourceDescriptor { /** Stable URI — typically `docs://solvapay/.md`. */ uri: string; /** Human-readable name surfaced in `resources/list`. */ name: string; /** Optional short title for host UIs that distinguish it from `name`. */ title?: string; /** Short one-liner surfaced in `resources/list` metadata. */ description: string; /** MIME type, typically `text/markdown` or `text/plain`. */ mimeType: string; /** Returns the body — sync or async. */ readBody: () => string | Promise; } /** * Bootstrap snapshot resource — a JSON `BootstrapPayload` the widget can * `resources/read` when the host scrubs `structuredContent` from the * opening tool-result notification. Auth-aware via `readPayload(extra)`. */ interface SolvaPayBootstrapResourceDescriptor { uri: string; name: string; title?: string; description: string; mimeType: string; readPayload: (extra?: McpToolExtra) => Promise; } /** * One MCP prompt — rendered as `/` in hosts with slash-command * support. Kept framework-neutral so every adapter (`@modelcontextprotocol/server`, * `fastmcp`, raw JSON-RPC) can map it to their own `registerPrompt` * shape. * * `argsSchema` matches the zod raw-shape shape the official SDK's * `registerPrompt` accepts — a plain object of `z.*` fields. Adapters * without zod-shape prompts can still call each field's `parse()` * themselves. */ interface SolvaPayPromptDescriptor { name: string; title?: string; description: string; argsSchema?: Record; handler: (args: Record) => SolvaPayPromptResult | Promise; } /** * Minimal `GetPromptResult` shape — structural subset of the official * SDK's type so adapters can forward it without importing * `@modelcontextprotocol/core`. */ interface SolvaPayPromptResult { messages: Array<{ role: 'user' | 'assistant'; content: { type: 'text'; text: string; }; }>; } /** * View → intent-tool map. All three surfaces collapse onto the single * viewer tool; `view` on the payload (or the optional tool argument) * picks the landing screen. Derived from `VIEWER_TOOL_NAME` so a * rename edits exactly one object. * * `paywall` has no dedicated intent tool — the paywall response carries * the bootstrap payload directly (`paywallToolResult` merges it into * `structuredContent`), so the shell renders the paywall view without * re-invoking a tool. */ declare const TOOL_FOR_VIEW: { readonly checkout: "account"; readonly account: "account"; readonly topup: "account"; readonly 'auto-recharge': "account"; }; /** * Inverse of `TOOL_FOR_VIEW` for host-entry classification. The viewer * is one tool, so this cannot recover the landing view from the name * alone — default to `account` and let `structuredContent.view` win. */ declare const VIEW_FOR_TOOL: Record; /** * @deprecated Use `TOOL_FOR_VIEW`. Kept as an alias so the rename lands * without a simultaneous import update across the ecosystem. */ declare const OPEN_TOOL_FOR_VIEW: { readonly checkout: "account"; readonly account: "account"; readonly topup: "account"; readonly 'auto-recharge': "account"; }; /** * @deprecated Use `VIEW_FOR_TOOL`. */ declare const VIEW_FOR_OPEN_TOOL: Record; /** * Inline upsell strip attached to a successful tool response. Rendered * below the tool result by the host; dismissible, non-blocking. * * V1 ships three default kinds, each with a default CTA that opens the * `account` viewer. `kind: 'custom'` is reserved for V1.1. */ interface NudgeSpec { kind: 'low-balance' | 'cycle-ending' | 'approaching-limit'; message: string; } /** * Intermediate content block emitted via `ctx.emit(block)`. Structural * subset of `SolvaPayCallToolResult.content` entries — text, image, or * resource. */ type ContentBlock = { type: 'text'; text: string; } | { type: 'image'; data: string; mimeType: string; } | { type: 'resource'; resource: Record; }; /** * Options for `ctx.respond(data, options?)`. * * `text` overrides the SDK's default narrator output for this response. * `nudge` attaches an inline upsell strip. * `units` is reserved for V1.1 variable-unit billing; V1 silently * ignores the field (billing stays at one usage unit per tool call; * credit debit = units × plan.creditsPerUnit). */ interface ResponseOptions { /** Override `content[0].text` with merchant-supplied text. */ text?: string; /** Inline upsell strip rendered below the tool result. */ nudge?: NudgeSpec; /** * Append the serialized payload as a trailing text block so the model * receives it on hosts that read `content` and ignore * `structuredContent`. Defaults to `true` — the MCP tools spec's * server-directed SHOULD, and the only way a response is complete on * both host families. Set `false` for payloads large enough that the * duplicate is not worth the tokens. */ dataInText?: boolean; /** * [V1.1] Units to bill for this call. Defaults to 1. Must be >= 0. * * V1 behaviour: field is accepted for forward compatibility but * silently ignored. Billing stays fixed at one usage unit per tool * call; credit debit = units × plan.creditsPerUnit. V1.1 behaviour: * threaded into `trackUsage` and applied by the backend. */ units?: number; } /** * Branded envelope returned by `ctx.respond(data, options?)`. The brand * symbol is module-private — merchants never construct `ResponseResult` * values directly. `buildPayableHandler` unwraps the envelope into a * `SolvaPayCallToolResult` before shipping. */ interface ResponseResult { /** * Opaque brand marker — typed as `true` so the envelope is * structurally distinguishable from raw merchant data even without * the runtime symbol. */ readonly __solvapayResponse: true; readonly data: TData; readonly options?: ResponseOptions; /** * Content blocks queued via `ctx.emit(block)` before the terminal * `respond()` call. V1 flushes these into `content[]` at respond * time; V1.1 emits them over SSE. */ readonly emittedBlocks?: ContentBlock[]; /** * Pre-check limits captured at `ctx.respond()` so unwrap can build * honest nudge copy. Not part of the merchant-facing contract. */ readonly limits?: LimitResponseWithPlan | null; } /** * Cached snapshot of customer state at handler invocation. * * Populated from the existing `LimitResponse` returned by * `payable().mcp()`'s pre-check — zero additional fetch cost. * * IMPORTANT: values here may be up to 10 seconds stale after mutations * (topup, plan change, cancel) within the same process. For fresh * state, call `.fresh()`. */ interface CustomerSnapshot { /** Backend customer ref (`cus_...`). */ readonly ref: string; /** * Credit balance when the plan spends credits. `undefined` when the * backend did not surface a credit system — not `0`. */ readonly balance: number | undefined; /** True when the limits response carries credit-balance fields. */ readonly isCreditBased: boolean; /** Remaining usage units before hitting the limit. `null` when unlimited. */ readonly remaining: number | null; /** Whether the customer is within their usage limits at snapshot time. */ readonly withinLimits: boolean; /** * True when this request is served past the included cap and paid * from prepaid credits (`onExceed: draw_credits`). */ readonly overage: boolean; /** * Active plan on the purchase. `null` when the customer has no * active purchase or is on a free plan. */ readonly plan: BootstrapPlan | null; /** * Force a fresh fetch bypassing the 10s limits cache. * Returns a new snapshot; does NOT mutate the current one. */ fresh(): Promise; } /** * Handler context passed as the second positional argument to merchant * `registerPayable` handlers opting into the V1 `ctx` API. * * V1 surface: `customer`, `product`, `respond`, `gate`. * Reserved (V1.1): `emit`, `progress`, `progressRaw`, `signal` — types * ship in V1 so merchants can write forward-compatible code. */ interface ResponseContext { /** * Customer snapshot at handler invocation. See `CustomerSnapshot` * for staleness semantics. */ customer: CustomerSnapshot; /** Read-only product configuration from the bootstrap payload. */ product: BootstrapProduct; /** Build a response. Two forms, both valid. */ respond(data: TData): ResponseResult; respond(data: TData, options: ResponseOptions): ResponseResult; /** * Explicitly trigger a paywall response. Handler execution stops * and the adapter routes the gate through `formatGate` — the MCP * transport emits a clean narration + `structuredContent` payload * with `isError: false`, same shape as a pre-check gate outcome. * * Implemented as `throw new PaywallError(reason)` for the compat * window; the adapter's catch block at the boundary unwraps it * and delivers the transport response. Merchants who catch the * error themselves can still rely on `instanceof PaywallError`. * * Rare — normally the SDK fires the paywall automatically via * `payable().mcp()` pre-check. */ gate(reason?: string): never; /** * [V1.1] Emit an intermediate content block. * * V1 behaviour: queued and flushed at the terminal `respond()`. No SSE. * V1.1 behaviour: emits immediately over Streamable HTTP + SSE. * * Reserved so merchants can write forward-compatible streaming code * today. */ emit(block: ContentBlock): Promise; /** * [V1.1] Emit a progress notification with a percent. * * V1 behaviour: no-op. * V1.1 behaviour: sends `notifications/progress` if the client * supplied a `progressToken`; no-op otherwise. */ progress(options: { percent: number; message?: string; }): Promise; /** * [V1.1] Emit a progress notification with non-percent units. * * V1 behaviour: no-op. * V1.1 behaviour: sends `notifications/progress` with raw * progress/total. */ progressRaw(options: { progress: number; total?: number; message?: string; }): Promise; /** * [V1.1] AbortSignal that fires when the client cancels the tool call. * * V1 behaviour: always an unaborted signal. * V1.1 behaviour: wired to the underlying transport cancellation. * * Merchants pass this to their upstream fetch/LLM/image-gen calls to * cancel expensive operations when the client goes away. */ signal: AbortSignal; } /** * Merchant handler signature for `registerPayable`. Receives the * parsed input `args` (typed from the tool's `schema` when provided) * and a `ResponseContext` with `ctx.respond(...)`, `ctx.customer`, * `ctx.gate(...)`, and the reserved streaming surface (`ctx.emit`, * `ctx.progress`, `ctx.signal`). * * Must return the branded envelope produced by `ctx.respond(data, options?)`. * Returning a raw value is a type error at compile time and throws a * merchant-actionable error at runtime. */ type PayableHandler, TData = unknown> = (args: TArgs, ctx: ResponseContext) => Promise>; /** * Shared building blocks for `buildSolvaPayDescriptors` and any hand-rolled * SolvaPay MCP server that prefers to register tools directly. * * Lifted from the canonical example at `examples/mcp-checkout-app/src/server.ts` * so every integrator gets the same behavior for price enrichment, synthetic * `Request` construction, and tool-result wrapping. */ /** * Shape of `toolErrorResult`'s `structuredContent`. Union this into any * tool `outputSchema` so a host that validates structured content does * not replace a real diagnostic with a schema complaint. */ declare const ToolErrorEnvelopeSchema: z.ZodObject<{ error: z.ZodString; status: z.ZodNumber; details: z.ZodOptional; }, z.core.$strip>; /** * Augment a purchase with human-readable price strings so callers (LLMs * rendering the JSON directly) don't have to reason about minor units. * * Raw `amount` / `originalAmount` / `currency` fields are preserved for * programmatic consumers (e.g. the React transport). */ declare function enrichPurchase(purchase: Record): Record; declare function defaultGetCustomerRef(extra?: McpToolExtra): string | null; interface BuildSolvaPayRequestOptions { method?: string; query?: Record; body?: unknown; /** * Override the customer ref that is forwarded as the `x-user-id` header. * Defaults to reading `extra.authInfo.extra.customer_ref`. */ getCustomerRef?: (extra?: McpToolExtra) => string | null; /** * Override the synthetic origin used in the request URL. Defaults to * `http://solvapay-mcp-server.local/`. */ origin?: string; } /** * Build a synthetic Web `Request` the core `*Core` helpers can consume. * * The `x-user-id` header is what `getAuthenticatedUserCore` reads as the * authoritative user identity, so forwarding the `customer_ref` from the * MCP OAuth bridge keeps the entire flow headless. */ declare function buildSolvaPayRequest(extra: McpToolExtra | undefined, options?: BuildSolvaPayRequestOptions): Request; /** * Wrap arbitrary data in a `SolvaPayCallToolResult`. Produces a `text` * content block and `structuredContent` so both LLM-facing and tool-call * consumers see a consistent shape. */ declare function toolResult(data: unknown): SolvaPayCallToolResult; /** * Requested rendering mode per-call. Passed through the `mode` input * arg of every intent tool. * * - `'auto'` (default) — emit the narrated markdown as `content[0]` * and keep the UI resource ref on `_meta.ui`. Text-only hosts see * a self-sufficient first text block; UI hosts still open the iframe. * - `'text'` — strip the UI resource ref and emit the narrated * markdown so CLI / text-only hosts get a human summary. * - `'ui'` — emit a one-line placeholder in `content[0]` alongside * the UI resource ref. The placeholder is still self-sufficient * (plan, price, https URL) — never a pointer at "the panel". */ type SolvaPayToolMode = 'ui' | 'text' | 'auto'; declare function parseMode(raw: unknown): SolvaPayToolMode; /** * Build a `SolvaPayCallToolResult` that respects the requested `mode`: * * - `auto` (default) emits the narrated markdown as `content[0]` and * keeps `_meta.ui.*` so UI-rendering hosts still open the iframe. * `structuredContent` still carries the raw bootstrap payload. * - `text` emits the narrated markdown (plus any `resource_link` * blocks) and strips `_meta.ui.*` so UI-capable hosts render * text-only for this call. * - `ui` emits a one-line self-sufficient placeholder in `content[0]`, * the narrated markdown, and the same `resource_link` blocks as * the other modes. Do not annotate the narrated block with * `audience: ['assistant']` — audience-aware hosts hide those * blocks from the user, and hosts that ignore the annotation must * still see a useful `content[0]`. * * The narrator is picked by the `tool` name; unknown tools fall back * to the JSON dump that `toolResult` produces today. * * Meta keys other than `ui` (notably `openai/widgetSessionId`, the * ChatGPT MCP routing-bug workaround stamped by intent tools — see * the descriptors.ts top-of-file comment) are preserved across all * three modes. Don't strip additional `_meta` keys here without * checking `descriptors.ts` first. */ declare function narratedToolResult(view: SolvaPayMcpViewKind | string, data: BootstrapPayload, mode?: SolvaPayToolMode, baseMeta?: Record | undefined): SolvaPayCallToolResult; /** * Wrap an error payload (typically the result of `handleRouteError`) in a * `SolvaPayCallToolResult` with `isError: true`. * * `content[0].text` carries a human-readable message — `details` when * present (typically the full multi-line recovery text from * `handleRouteError`), falling back to the short `error`. MCP clients * that show tool errors verbatim therefore display recovery guidance * instead of a stringified JSON blob. * * `structuredContent` still carries the full `{ error, status, details }` * envelope so programmatic consumers (verify.mjs, test.mjs, the * paywall transport) can branch on `status` without parsing text. */ declare function toolErrorResult(error: { error: string; status: number; details?: string; }): SolvaPayCallToolResult; /** * Truncate a JSON preview to a max length so trace logs stay readable. */ declare function previewJson(value: unknown, max?: number): string; /** * Zod schema for `BootstrapPayload`. Every field the narrator may omit * is optional here — a default would invent data the backend did not * send (the no-fallback rule). */ declare const BootstrapPayloadSchema: z.ZodObject<{ view: z.ZodString; productRef: z.ZodString; stripePublishableKey: z.ZodNullable; returnUrl: z.ZodString; merchant: z.ZodRecord; product: z.ZodRecord; plans: z.ZodArray>; customer: z.ZodNullable>; checkoutUrl: z.ZodOptional>; portalUrl: z.ZodOptional>; autoRechargeUrl: z.ZodOptional>; }, z.core.$loose>; /** * Narrator map — one function per intent tool that renders a sparse, * text-friendly markdown summary of the bootstrap payload. * * Style rules (see the discoverable-UX plan for rationale): * 1. First line is a single `**bold title**` — no headings. * 2. Body uses `Label: value` rows (one per line). Inline `·` * separator for compound values. No bullet lists. * 3. Recovery is a named tool call with arguments, never a slash * command — slash names are host prompt UI a model cannot fire. * 4. External URLs are named markdown links (`[Open checkout](url)`) * plus a matching `resource_link` block. Never dump the raw URL * as the visible label — hosts render the name; the href stays * in the text for the model and for terminals that do not render * markdown. * * Missing fields skip their row entirely — we never emit `Balance: —` * or `Customer: unknown`. A narrator returning just `title + 1 row` * is still well-formed. */ interface NarratorOutput { text: string; links?: Array<{ uri: string; name: string; }>; } /** * Frozen plan captured on a purchase. Deliberately separate from * `PlanShape`: the snapshot carries `isMetered` and no `type` or * `requiresPayment`. It does carry the plan's `options[]` frozen at * purchase time, which is what the customer is actually billed on. */ interface PlanSnapshotShape { name?: string; price?: number; currency?: string; options?: PricingOptionLike[]; isMetered?: boolean; requiresPayment?: boolean; reference?: string; } interface PurchaseShape { status?: string; productRef?: string; planRef?: string; planSnapshot?: PlanSnapshotShape | null; amount?: number; currency?: string; startDate?: string; endDate?: string; cancelledAt?: string | null; isRecurring?: boolean; /** Lives on the purchase, not the snapshot, which freezes only `options`. */ billingCycle?: string | null; metadata?: { purpose?: string; }; } interface UsageShape { used?: number | null; total?: number | null; remaining?: number | null; periodEnd?: string; meterRef?: string | null; } interface LimitsShape { remaining?: number | null; withinLimits?: boolean | null; activationRequired?: boolean | null; overage?: boolean | null; needsTopUp?: boolean | null; planRef?: string | null; planName?: string | null; creditBalance?: number; creditsPerUnit?: number; balance?: { creditBalance?: number; creditsPerUnit?: number; remainingUnits?: number; }; } interface CustomerShape { ref?: string | null; email?: string | null; name?: string | null; balance?: { credits?: number | null; displayCurrency?: string; displayExchangeRate?: number; creditsPerMinorUnit?: number; } | null; usage?: UsageShape | null; limits?: LimitsShape | null; purchase?: { purchases?: PurchaseShape[]; } | null; autoRecharge?: { enabled?: boolean; status?: string; } | null; } /** * Human-readable balance summary used by the `'ui'` mode placeholder. * Returns `null` when no balance is available so the caller can skip * the segment entirely. */ declare function balanceSummary(customer: CustomerShape | null | undefined): string | null; declare function narrateAlreadyActive(result: { creditBalance?: number; creditsPerUnit?: number; }): string; declare function narrateActivatePlan(result: { status: 'activated' | 'already_active' | 'already_purchased' | 'topup_required' | 'payment_required' | 'invalid'; creditBalance?: number; creditsPerUnit?: number; checkoutUrl?: string; message?: string; planName?: string; }): string; declare function narrateManageAccount(data: BootstrapPayload, options?: { now?: Date; }): NarratorOutput; declare function narrateUpgrade(data: BootstrapPayload): NarratorOutput; declare function narrateTopup(data: BootstrapPayload): NarratorOutput; declare function narrateAutoRecharge(data: BootstrapPayload): NarratorOutput; declare const NARRATORS: Record NarratorOutput>; declare function uiPlaceholder(view: SolvaPayMcpViewKind, data: BootstrapPayload): string; /** * Server-side default for the viewer tool's optional `view` argument. * * The three surfaces share one payload; omitting `view` must still land * somewhere useful instead of failing. Rule: * - no active plan → checkout (plans) * - active plan and blocked by a top-up gate → topup * - otherwise → account * * When the preferred view is disabled on this server, fall through the * same priority order to the first enabled view. Omitting `view` is * never a failure if at least one view is enabled. */ /** * Pick the landing view from a bootstrap snapshot. Pure — does not * fetch. Call after `buildBootstrapPayload` (the view argument there * is only an echoed label) and stamp the result onto `payload.view`. */ declare function deriveDefaultView(data: Pick & Pick, 'productRef'>, enabledViews?: ReadonlySet): SolvaPayMcpViewKind; /** * One ordered selector for "which plan purchase is active" so narration, * bootstrap usage, and the default-view heuristic cannot disagree. * * Filter: `status === 'active'`, current `productRef` when given, not a * credit top-up. Newest `startDate` wins; paid-over-free is only a * same-timestamp tiebreak so this matches the console's `createdAt: -1`. */ type ActivePlanPurchaseLike = { status?: string; productRef?: string; planSnapshot?: unknown; amount?: number; startDate?: string; metadata?: { purpose?: string; }; origin?: string; }; declare function isPlanPurchase(purchase: ActivePlanPurchaseLike): boolean; /** * Pick the customer's current plan purchase, or `null` if none match. */ declare function selectActivePlanPurchase(purchases: T[] | undefined, productRef?: string): T | null; /** * Standalone `buildBootstrapPayload` factory — same logic * `buildSolvaPayDescriptors` wires under the intent tools, exposed * separately for adapters that want to produce a fresh intent-tool * bootstrap without going through the full descriptor bundle. * * This used to be invoked from the widget-paywall branch of * `buildPayableHandler` / `paywallToolResult` (to embed the full * `BootstrapPayload` on the gate response), but those branches are * text-only now and no longer need the closure. */ interface CreateBuildBootstrapPayloadOptions { solvaPay: SolvaPay; productRef: string; publicBaseUrl: string; getCustomerRef?: (extra?: McpToolExtra) => string | null; } type BuildBootstrapPayloadFn = (view: SolvaPayMcpViewKind, extra: McpToolExtra | undefined) => Promise; /** * Produce a reusable `buildBootstrapPayload(view, extra)` closure * wired against the same SolvaPay instance + product as a * `buildSolvaPayDescriptors` bundle. * * The returned function runs `getMerchant`, `getProduct`, `listPlans`, * `checkPurchase`, `getPaymentMethod`, `getCustomerBalance`, `checkLimits` * in parallel, failing loudly when merchant or product can't load (the * React shell can't render meaningfully without them) and degrading * gracefully on per-customer sub-reads. Usage is derived from the * purchase + the same limits result so metered plans do not call * `checkLimits` twice. */ declare function createBuildBootstrapPayload(options: CreateBuildBootstrapPayloadOptions): BuildBootstrapPayloadFn; /** * Helper for hand-rolled MCP tool handlers that need to emit a * text-only paywall response without adopting `buildPayableHandler` / * `registerPayableTool` wholesale. * * The widget iframe is no longer opened for merchant payable data * tools (per SEP-1865 + the text-only paywall refactor), so this * helper ships a plain narration + `structuredContent = gate` pair. * The gate's `message` field — built by the * `classifyPaywallState` / `buildGateMessage` engine in * `@solvapay/server` — names the recovery intent tool and inlines * `checkoutUrl` for terminal-first hosts. */ interface PaywallToolResultContext { /** * Builds a full `BootstrapPayload`. Still accepted on the context * for intent-tool reuse, but NOT consumed here — the text-only * paywall ships the gate verbatim. Leaving the field on the type * preserves compatibility for callers that thread it through a * bound helper. * * @deprecated Not called by `paywallToolResult`. Will be removed in * a future major. */ buildBootstrap?: BuildBootstrapPayloadFn; /** Forwarded for logging / telemetry; not consumed by the text path. */ extra?: McpToolExtra; } /** * Convert a paywall gate (either a `PaywallError` or the underlying * `PaywallStructuredContent` returned by `paywall.decide()`) into a * text-only `PaywallToolResult`. * * ```ts * const decision = await solvaPay.paywall.decide(args, { product }) * if (decision.outcome === 'gate') { * return paywallToolResult(decision.gate) * } * ``` * * The legacy `PaywallError`-first form continues to work for custom * adapters that still `try/catch`: * * ```ts * try { * return await solvaPay.payable({ product }).mcp(handler)(args, extra) * } catch (err) { * if (err instanceof PaywallError) return paywallToolResult(err) * throw err * } * ``` */ declare function paywallToolResult(errOrGate: PaywallError | PaywallStructuredContent, _ctx?: PaywallToolResultContext): Promise; /** * Default Content Security Policy allow-list for SolvaPay MCP Apps. * * The Stripe baseline covers `js.stripe.com`, `api.stripe.com`, and the * `hooks.stripe.com` 3DS frame. `assets.claude.ai` is included because * MCP hosts (notably Claude) inject Anthropic Sans `@font-face` CSS via * `hostContext.styles.css.fonts`; under the declared-CSP model the widget * must permit that origin in `resourceDomains` (which hosts map to * `font-src` as well as `script-src` / `style-src`). Adapters merge * integrator-provided overrides on top via `mergeCsp`. */ declare const SOLVAPAY_DEFAULT_CSP: Required; /** * Merge integrator CSP overrides on top of `SOLVAPAY_DEFAULT_CSP` and * (optionally) the configured SolvaPay API origin. Deduplicates per * domain list so repeated entries don't balloon the resulting * `_meta.ui.csp` envelope. * * `apiBaseUrl`, when provided, is appended to `resourceDomains` + * `connectDomains` so the widget iframe can render merchant branding * images (`GET /v1/files/public/...`) and make XHR / fetch calls back * to the SolvaPay API without the integrator hand-extending the CSP. * Skipped silently when the URL doesn't parse (see `parseOrigin`). */ declare function mergeCsp(overrides: SolvaPayMcpCsp | undefined, apiBaseUrl?: string): Required; /** * `applyHideToolsByAudience(server, audiences, options?)` — wraps the * `tools/list` request handler on an `@modelcontextprotocol/server` * `McpServer` so tool descriptors whose `_meta.audience` matches one * of the supplied values are filtered out of the response. * * The tools stay `enabled: true` on the server, so `tools/call` still * reaches their handlers — this helper only affects the `tools/list` * response shape. Use `['ui']` when deploying to a text-host MCP * client (Claude Desktop, MCPJam, ChatGPT connector) that won't * embed the SolvaPay iframe surface, while still allowing the iframe * to invoke the hidden transport tools (`create_payment_intent` etc.) * for server-side work. * * # ChatGPT auto-bypass * * ChatGPT's Custom Connector gateway re-validates iframe-initiated * `tools/call` against the cached `tools/list` catalog. A tool hidden * from `tools/list` becomes uncallable from the embedded iframe and * surfaces in the UI as `MCP error -32000: MCP Resource not found`. * * To keep the cleaner LLM-facing catalog on every other host while * the iframe still works on ChatGPT, the default behaviour * automatically returns the **full** unfiltered catalog when the * incoming `tools/list` request originates from ChatGPT — detected by * matching `requestInfo.headers.get('user-agent')` against * `/openai-mcp/i`. On 2026-era connections there is no `initialize` * handshake, so the User-Agent header is the primary signal; the * post-`initialize` `getClientVersion()` fallback remains for legacy-era * relays that strip the client UA. * * Override the detection by passing `bypassWhen` — useful when a * future iframe-capable host needs the same treatment, or when * ChatGPT-served deployments want the LLM-narrow catalog regardless * (`bypassWhen: () => false`). * * No-op when `audiences` is empty or falsy. * * This helper is exported from `@solvapay/mcp-core` so both * `@solvapay/mcp` (stacked `createSolvaPayMcpServer`) and * `@solvapay/mcp/fetch` (unified `createSolvaPayMcpFetch`) can apply * the same filter without each re-implementing the reach-in. */ /** Headers from a Web `Request` or legacy isomorphic record shape. */ type RequestHeaders = Headers | Record; /** * Subset of the MCP SDK handler context that the bypass predicate inspects. * Typed loosely so we don't couple to the SDK's exact shape. */ interface ApplyHideToolsByAudienceExtra { requestInfo?: Request | { headers?: RequestHeaders; } | undefined; [key: string]: unknown; } interface ApplyHideToolsByAudienceContext { /** The MCP server instance the filter is being applied to. */ server: unknown; /** * Handler context the SDK passed to the wrapped `tools/list` handler. * May be undefined for non-HTTP transports (e.g. stdio). */ extra?: ApplyHideToolsByAudienceExtra; } type HideToolsByAudienceBypass = (ctx: ApplyHideToolsByAudienceContext) => boolean; interface ApplyHideToolsByAudienceOptions { /** * When this predicate returns `true` for an incoming `tools/list` * request, the audience filter is skipped and the full catalog is * returned. Defaults to `defaultIsChatGptRequest` — see the file * header for the rationale. */ bypassWhen?: HideToolsByAudienceBypass; } /** * Default `bypassWhen` — returns true when the incoming request looks * like it's coming from ChatGPT's MCP runtime. */ declare function defaultIsChatGptRequest(ctx: ApplyHideToolsByAudienceContext): boolean; declare function applyHideToolsByAudience(server: unknown, audiences: readonly string[] | undefined, options?: ApplyHideToolsByAudienceOptions): void; /** * `buildSolvaPayDescriptors(options)` — framework-neutral tool surface * builder that every SolvaPay MCP adapter (`@solvapay/mcp`, future * `@solvapay/mcp-fastmcp`, raw JSON-RPC adapters) maps onto its own * registration API. * * Body is lifted from the original * `packages/server/src/mcp/server.ts#createSolvaPayMcpServer`. The only * mechanical difference: instead of calling `registerAppTool(server, ...)`, * we push `{ name, handler, ... }` onto a `tools[]` array the adapter * iterates. * * ---- * * `_meta["openai/widgetSessionId"]` workaround. Every intent-tool * response stamps a freshly-minted UUID on `_meta["openai/widgetSessionId"]`. * This is a low-risk forward-looking workaround for the ChatGPT MCP * connector's stale `link_` routing bug, where the host returns * `-32000 MCP Resource not found` on the second `tools/call` of a * session even though the call never reaches the server. A fresh UUID * per invocation gives the host a routing key that changes every call, * which the OpenAI Apps SDK community thread reports unsticks the * failure mode. * * Sources: * - https://community.openai.com/t/connector-tool-calls-generating-fresh-mcp-session-each-invocation/1364975 * - https://github.com/openai/openai-apps-sdk-examples/issues/165 * - https://developers.openai.com/apps-sdk/reference/ (`_meta` payload) * - openai/openai-apps-sdk-examples shopping_cart_python uses the * same `meta["openai/widgetSessionId"]` shape. * * Removable once the upstream bug ships a fix; safe on any host that * doesn't consume the key. */ /** * Project `SolvaPayMerchantBranding` into an `icons[]` array suitable * for MCP host chrome — either the per-tool `SolvaPayToolDescriptor` * or the server-level `Implementation.icons[]` returned at * `initialize`. Prefers the square `iconUrl` (expected shape for * avatar slots); falls back to the landscape `logoUrl` with a note * that hosts may need to letterbox. Returns `undefined` when neither * asset is set. * * Exported so the MCP adapter (`@solvapay/mcp`) can reuse the same * branding → icon projection when building the server-level * `Implementation` payload, keeping per-tool and server-wide icons in * lock-step. */ declare function deriveIcons(branding: SolvaPayMerchantBranding | undefined): SolvaPayToolIcon[] | undefined; interface BuildSolvaPayDescriptorsOptions { /** Initialised SolvaPay instance. */ solvaPay: SolvaPay; /** Default product ref for this MCP server (used when tool args omit it). */ productRef: string; /** UI resource URI served by this server (e.g. `'ui://my-app/mcp-app.html'`). */ resourceUri: string; /** * Absolute filesystem path to the built HTML bundle referenced by * `resourceUri`. Node-only convenience — dynamic-imports * `node:fs/promises` internally. Provide `readHtml` instead for edge * runtimes. */ htmlPath?: string; /** * Edge-neutral alternative to `htmlPath`. One of `htmlPath` or * `readHtml` must be provided. */ readHtml?: () => Promise; /** * Public `https://` origin used as `return_url` for Stripe confirmations. * Required because MCP hosts set `window.location.origin` to `"null"`, * which Stripe's `confirmPayment` validator rejects. */ publicBaseUrl: string; /** Which viewer surfaces the `account` tool may open. Defaults to every known view. */ views?: SolvaPayMcpViewKind[]; /** Additional CSP allow-lists merged with the Stripe baseline. */ csp?: SolvaPayMcpCsp; /** * Configured SolvaPay API origin (e.g. `'https://api.solvapay.com'` * or `'https://api-dev.solvapay.com'`). When provided, the origin is * auto-appended to `csp.resourceDomains` + `csp.connectDomains` so * the widget iframe can load merchant branding images (served by * `GET /v1/files/public/provider-assets/...`) and make XHR / fetch * calls back to the API without the integrator hand-extending the * CSP. Pass the same value you pass to `createSolvaPay({ apiBaseUrl })`. */ apiBaseUrl?: string; /** * Override customer-ref extraction. Defaults to reading * `extra.http.authInfo.extra.customer_ref` (populated by the MCP * OAuth bridge), falling back to the SDK v1 flat `extra.authInfo`. */ getCustomerRef?: (extra?: McpToolExtra) => string | null; /** * Fired for every tool call so integrators can add tracing / logging. * Called before the core helper runs; the result is available on the * `response` callback (`onToolResult`). */ onToolCall?: (name: string, args: unknown, extra?: McpToolExtra) => void; /** Fired after every tool call completes (success or error). */ onToolResult?: (name: string, result: SolvaPayCallToolResult, meta: { durationMs: number; }) => void; /** * Merchant branding used to personalise the MCP host chrome — when * provided, every emitted tool descriptor carries an `icons[]` the * adapter surfaces on `tools/list` so hosts can replace the default * globe / placeholder with the merchant's mark. Prefer fetching the * SDK merchant payload at server startup (`getMerchantCore` exposes * `iconUrl` / `logoUrl` / `displayName`) and passing the result in. */ branding?: SolvaPayMerchantBranding; } interface SolvaPayDescriptorBundle { tools: SolvaPayToolDescriptor[]; resource: SolvaPayResourceDescriptor; /** * Slash-command prompts that hosts with prompt support (Claude * Desktop, Cursor, etc.) surface as `/upgrade`, `/manage_account`, * `/topup`, and `/activate_plan`. Hosts without prompt support * silently ignore the list — registration is purely additive. */ prompts: SolvaPayPromptDescriptor[]; /** * Narrated docs resources — agent-facing "read me first" content * served over `docs://solvapay/*`. Lives alongside the UI resource so * agents can `resources/read` before trying a tool. */ docsResources: SolvaPayDocsResourceDescriptor[]; /** * Idempotent bootstrap snapshot at `solvapay://bootstrap.json` — the * widget reads this when the host scrubs `structuredContent` from the * opening tool-result notification. */ bootstrapResource: SolvaPayBootstrapResourceDescriptor; /** * Parallelised fetch of merchant + product + plans + (optional) * customer snapshot that backs every `open_*` tool. Exposed so the * paywall envelope (`paywallToolResult`, `buildPayableHandler`) can * embed the full payload in its `structuredContent`. */ buildBootstrapPayload: BuildBootstrapPayloadFn; } /** * Build the framework-neutral SolvaPay tool + resource descriptors. The * returned bundle is adapter-shaped — pass it to the registration helper * exported by `@solvapay/mcp` (or any future adapter package). */ declare function buildSolvaPayDescriptors(options: BuildSolvaPayDescriptorsOptions): SolvaPayDescriptorBundle; /** * Build the framework-neutral slash-command prompt descriptors for the * four SolvaPay intent tools. Exposed standalone so adapters that don't * want the full descriptor bundle (or want to register prompts on an * already-built server) can still pick them up. * * Each prompt is intentionally one `user` message that mirrors how a * human would invoke the intent — this makes slash-commands feel like * natural shortcuts, and keeps the prompts compatible with text hosts * that don't expose the MCP UI shell. */ declare function buildSolvaPayPrompts(options?: { enabledViews?: Set; }): SolvaPayPromptDescriptor[]; /** * Idempotent bootstrap snapshot served at `solvapay://bootstrap.json`. * * Widgets read this via `app.readServerResource` when the host scrubs * `structuredContent` from the opening `toolresult` notification (e.g. * MCPJam's AI-SDK `toModelOutput` path). Resource reads bypass chat * cards and model-output scrubbing — same auth context as tool calls. */ declare const SOLVAPAY_BOOTSTRAP_URI = "solvapay://bootstrap.json"; declare const SOLVAPAY_BOOTSTRAP_MIME_TYPE = "application/json"; /** * Narrated overview for the SolvaPay MCP server — served verbatim at * `docs://solvapay/overview.md` so agents can `resources/read` before * calling any tool. * * Inlined as a TS constant (not a standalone `.md` file) because the * package bundles via tsup — a markdown asset next to this file would * need copy-on-build and runtime `fs` access, neither of which the * framework-neutral `@solvapay/mcp` package should take on. */ declare const SOLVAPAY_OVERVIEW_URI = "docs://solvapay/overview.md"; declare const SOLVAPAY_OVERVIEW_MIME_TYPE = "text/markdown"; declare const SOLVAPAY_OVERVIEW_MARKDOWN = "# SolvaPay MCP server \u2014 overview\n\nThis MCP server connects a SolvaPay-protected product to your host so users can\nmanage their plan, usage, and billing **without leaving the chat**. It is\ndual-audience: every tool returns a UI bootstrap for hosts that render MCP UI\nresources (`basic-host`, Claude Desktop, ChatGPT, etc.) and a markdown summary\nwith clickable URLs for text-only hosts.\n\n## What the user can do\n\n- **Account** \u2014 one read-only viewer for every billing surface. Pass `view`:\n `checkout` (upgrade / change plan / buy / subscribe), `account` (current\n plan, cancel, billing), or `topup` (add credits). Omit `view` and the\n server picks (no plan \u2192 checkout, out of credits \u2192 topup, else account).\n Slash commands `/upgrade`, `/manage_account`, and `/topup` remap onto\n this tool. `account`.\n- **Activate plan** \u2014 activate a specific plan by `planRef`. Free plans\n activate immediately, usage-based plans activate when balance covers the\n configured usage, paid plans return checkout. Requires `planRef` \u2014 to list\n plans, call `account` with `view: \"checkout\"`. `/activate_plan`.\n\n## How it fits together\n\nThe viewer returns a full `BootstrapPayload` (merchant + product + plans\n+ customer snapshot) so the embedded UI never fires per-view read calls. When\na paywalled data tool hits the usage limit, its response is plain text: the\n`content[0].text` narration names the recovery tool (`account`, or\n`activate_plan` when a specific planRef is known) and inlines\n`gate.checkoutUrl` for terminal-first hosts. The structured gate rides on\n`structuredContent` for programmatic consumers. No widget iframe opens for a\ngate \u2014 the LLM reads the narration and, if the user agrees, calls the named\ntool which mounts the checkout / topup / account surface.\n\nAuth is handled by the SolvaPay OAuth bridge (see `createMcpOAuthBridge`). The\nbridge injects `customer_ref` onto every authenticated request; tools that\nneed an authenticated caller return `Unauthorized` when it is missing.\n\n## Also see\n\n- `docs://solvapay/overview.md` (this resource) \u2014 agent-facing narration.\n- `ui:///mcp-app.html` \u2014 the embedded UI shell.\n- `tools/list` + `prompts/list` \u2014 programmatic discovery of the intent tools\n and their slash-command shortcuts.\n"; /** * Hosted-portal deep links minted from a customer session URL. * * Phase 1 suffixes the already-minted `customerUrl` in the SDK so the * MCP widget can land on the auto-recharge form without a new network * call or a platform deploy. The query params are the same ones the * customer-app manage page already parses (`tab=credits&intent=autorecharge`). * * Keep this constant in lockstep with * `apps/customer-app/src/pages/customer/manage/index.tsx`. A rename * there without a matching change here degrades to the Credits tab * without the form open — not a dead link, but not the intended * destination either. */ declare const PORTAL_AUTO_RECHARGE_QUERY = "tab=credits&intent=autorecharge"; /** * Suffix a minted customer-portal URL so it opens the auto-recharge * form (or the Credits summary when auto-recharge is already on). * * Returns `null` for a missing or non-http URL — the same guard * `narrate.ts` uses. No silent default: a missing portal URL yields * `null` and the widget renders status without an action. */ declare function autoRechargeUrlFrom(customerUrl: string | null | undefined): string | null; /** * `buildPayableHandler(solvaPay, ctx, handler)` — framework-neutral * wrapper that produces an MCP tool handler enforcing the SolvaPay * paywall. * * Merchant handlers receive a `ResponseContext` as their second * argument and return the `ResponseResult` envelope produced by * `ctx.respond(data, options?)`. `buildPayableHandler` unwraps the * envelope and — for text-only paywall / nudge responses — ships * a clean narration + structuredContent pair without routing through * any widget iframe. * * Per SEP-1865 / MCP Apps (2026-01-26), the widget iframe for payable * data tools has been deprecated outright: merchant-registered * paywalled tools no longer advertise `_meta.ui.resourceUri` at the * descriptor level, so hosts never open an uninvited iframe on a * successful tool call. Paywall / nudge / activation responses are * plain text narrations naming the recovery tool (`account` / * `activate_plan`) and inlining `gate.checkoutUrl` for * terminal-only hosts. * * The widget iframe is reserved for the three SolvaPay intent tools * (`account`) where the user deliberately * asked for a checkout UI. * * Every SolvaPay MCP adapter (`@solvapay/mcp`, future `fastmcp` * adapters) wraps this in its framework-specific `registerTool` / * `registerAppTool` call. */ interface BuildPayableHandlerContext { /** SolvaPay product ref the tool is protected against. */ product: string; /** * Builds the full `BootstrapPayload`. Still accepted on the context * for intent-tool reuse, but NO LONGER consumed by the payable * branch: merchant paywall / nudge responses are text-only now. A * future intent-tool helper may re-use this hook; leaving it on the * type preserves compat for direct callers (`registerPayableTool`). * * @deprecated No longer called by `buildPayableHandler`. Kept on the * context for backwards compatibility with callers that pass it * through a bound helper (e.g. `registerPayable`). */ buildBootstrap?: BuildBootstrapPayloadFn; /** * Override customer-ref extraction. Defaults to the MCP adapter's * behavior (reads `extra.http.authInfo.extra.customer_ref`, falling * back to the SDK v1 flat `extra.authInfo`). */ getCustomerRef?: (args: Record, extra?: McpToolExtra) => string | Promise; /** * Registered tool name, forwarded onto `PaywallMetadata` so usage * events are attributable to a tool. */ toolName?: string; /** * SDK-only free-tool allowance. When set, the paywall kernel counts * against this free meter instead of the plan meter. */ freeLimit?: FreeLimit; } /** * Merchant handler contract: `(args, ctx) => ctx.respond(data, options?)`. * `buildPayableHandler` unwraps the returned `ResponseResult` envelope * into the adapter-facing `SolvaPayCallToolResult`. */ type MerchantHandler = (args: TArgs, ctx: ResponseContext) => Promise>; /** * Build a paywall-protected MCP tool handler. Returned function is a * `(args, extra) => Promise` that any MCP * adapter can register directly. * * The handler: * 1. Builds a `ResponseContext` from the pre-check `LimitResponseWithPlan` * and passes it as the second arg to the merchant handler. * 2. Routes the call through `solvaPay.payable({ product }).mcp(wrappedBusinessLogic)`. * 3. Detects paywall results via `isPaywallStructuredContent` and * ships them verbatim — the adapter's `formatGate` already * produced the clean `{ isError: false, content[0].text = * gate.message, structuredContent = gate }` shape. * 4. Unwraps the merchant's `ResponseResult` envelope into the * terminal `SolvaPayCallToolResult`: applies `options.text` / * `options.nudge` (as a text suffix) and flushes `ctx.emit(...)` * blocks into `content[]`. * 5. Silently ignores `options.units` (V1 billing stays at one usage * unit per call; credit debit = units × plan.creditsPerUnit). */ declare function buildPayableHandler, TResult>(solvaPay: SolvaPay, ctx: BuildPayableHandlerContext, handler: MerchantHandler): (args: Record, extra?: McpToolExtra) => Promise; /** * One-line MCP config summary to stderr, once per process. * * Stderr keeps MCP stdio transports' stdout clean. The module-scope guard * matters for fetch/edge handlers that reconstruct the bridge per request * without memoization. */ type McpConfigLogInput = { apiBaseUrl: string; productRef: string; publicBaseUrl: string; }; /** * Emit `[solvapay] mcp config …` once. Safe to call from every construction * path; subsequent calls are no-ops. */ declare function logMcpConfigOnce(config: McpConfigLogInput): void; /** Test-only: reset the once-per-process guard. */ declare function resetMcpConfigLogForTests(): void; /** * Enrich DCR (dynamic client registration) upstream failures with a * diagnostic that names the likely cause. Costs nothing — the request * already failed; we only log. * * Platform 400 bodies that say "Invalid identifier" mean the product_ref * did not resolve to a provider, not that the DCR JSON was malformed. */ type DcrFailureDiagnosticInput = { productRef: string; apiBaseUrl: string; status: number; bodyText?: string; }; declare function logDcrFailureDiagnostic(input: DcrFailureDiagnosticInput): void; /** * Framework-neutral OAuth discovery JSON builders. These are runtime-agnostic * (no Node, no fetch, no Express) — both `@solvapay/mcp/express` and * `@solvapay/mcp/fetch` import them to produce the well-known responses. * * Kept in `@solvapay/mcp-core` so third-party adapter authors (raw JSON-RPC, * `fastmcp`, …) can reuse the exact same shapes with zero transitive deps. */ interface OAuthBridgePaths { register?: string; authorize?: string; token?: string; revoke?: string; } interface OAuthAuthorizationServerOptions { publicBaseUrl: string; paths?: OAuthBridgePaths; } declare const DEFAULT_OAUTH_PATHS: Required; declare function withoutTrailingSlash(value: string): string; declare function resolveOAuthPaths(paths?: OAuthBridgePaths): Required; declare function getOAuthProtectedResourceResponse(publicBaseUrl: string): { resource: string; authorization_servers: string[]; scopes_supported: string[]; }; declare function getOAuthAuthorizationServerResponse({ publicBaseUrl, paths, }: OAuthAuthorizationServerOptions): { issuer: string; authorization_endpoint: string; token_endpoint: string; registration_endpoint: string; revocation_endpoint: string; token_endpoint_auth_methods_supported: string[]; response_types_supported: string[]; grant_types_supported: string[]; scopes_supported: string[]; code_challenge_methods_supported: string[]; }; /** * MCP OAuth bearer-token helper utilities. * * These helpers are intentionally lightweight and do not verify JWT * signatures. Use them after token validation (for example via * `/v1/customer/auth/userinfo`). */ declare class McpBearerAuthError extends Error { constructor(message: string); } type McpBearerCustomerRefOptions = { claimPriority?: string[]; }; declare function extractBearerToken(authorization?: string | null): string | null; declare function decodeJwtPayload(token: string): Record; declare function getCustomerRefFromJwtPayload(payload: Record, options?: McpBearerCustomerRefOptions): string; declare function getCustomerRefFromBearerAuthHeader(authorization?: string | null, options?: McpBearerCustomerRefOptions): string; /** * Build an MCP `authInfo` envelope from an `Authorization: Bearer ` * header. Populates `authInfo.extra.customer_ref` so downstream * `getCustomerRef` extractors (adapter + descriptor handlers) can read * the caller identity without re-parsing the token. * * The transport decides where this envelope lands on the tool-handler * context: the official SDK v2 nests it at `extra.http.authInfo`, v1 * exposed it flat at `extra.authInfo`. `defaultGetCustomerRef` reads * both. */ interface BuildAuthInfoFromBearerOptions extends McpBearerCustomerRefOptions { clientId?: string; defaultScopes?: string[]; includePayload?: boolean; } declare function buildAuthInfoFromBearer(authorization?: string | null, options?: BuildAuthInfoFromBearerOptions): McpToolExtra['authInfo'] | null; export { type ActivePlanPurchaseLike, type ApplyHideToolsByAudienceContext, type ApplyHideToolsByAudienceExtra, type ApplyHideToolsByAudienceOptions, type BootstrapCustomer, type BootstrapMerchant, type BootstrapPayload, BootstrapPayloadSchema, type BootstrapPlan, type BootstrapProduct, type BuildAuthInfoFromBearerOptions, type BuildBootstrapPayloadFn, type BuildPayableHandlerContext, type BuildSolvaPayDescriptorsOptions, type BuildSolvaPayRequestOptions, type ContentBlock, type CreateBuildBootstrapPayloadOptions, type CustomerSnapshot, DEFAULT_OAUTH_PATHS, type DcrFailureDiagnosticInput, type HideToolsByAudienceBypass, INTENT_TOOL_NAMES, type IntentToolName, MCP_PROMPT_NAMES, MCP_TOOL_NAMES, type McpAdapterOptions, McpBearerAuthError, type McpBearerCustomerRefOptions, type McpConfigLogInput, type McpPromptName, type McpToolExtra, type McpToolName, NARRATORS, type NarratorOutput, type NudgeSpec, type OAuthAuthorizationServerOptions, type OAuthBridgePaths, OPEN_TOOL_FOR_VIEW, PORTAL_AUTO_RECHARGE_QUERY, type PayableHandler, type PaywallToolResult, type PaywallToolResultContext, type ResponseContext, type ResponseOptions, type ResponseResult, SOLVAPAY_BOOTSTRAP_MIME_TYPE, SOLVAPAY_BOOTSTRAP_URI, SOLVAPAY_DEFAULT_CSP, SOLVAPAY_MCP_ADVERTISED_VIEW_KINDS, SOLVAPAY_MCP_VIEW_KINDS, SOLVAPAY_OVERVIEW_MARKDOWN, SOLVAPAY_OVERVIEW_MIME_TYPE, SOLVAPAY_OVERVIEW_URI, type SolvaPayBootstrapResourceDescriptor, type SolvaPayCallToolResult, type SolvaPayDescriptorBundle, type SolvaPayDocsResourceDescriptor, type SolvaPayMcpAdvertisedViewKind, type SolvaPayMcpCsp, type SolvaPayMcpViewKind, type SolvaPayMerchantBranding, type SolvaPayPromptDescriptor, type SolvaPayPromptResult, type SolvaPayResourceDescriptor, type SolvaPayToolAnnotations, type SolvaPayToolDescriptor, type SolvaPayToolIcon, type SolvaPayToolMode, TOOL_FOR_VIEW, ToolErrorEnvelopeSchema, VIEWER_TOOL_NAME, VIEW_FOR_OPEN_TOOL, VIEW_FOR_TOOL, applyHideToolsByAudience, autoRechargeUrlFrom, balanceSummary, buildAuthInfoFromBearer, buildPayableHandler, buildSolvaPayDescriptors, buildSolvaPayPrompts, buildSolvaPayRequest, createBuildBootstrapPayload, decodeJwtPayload, defaultGetCustomerRef, defaultIsChatGptRequest, deriveDefaultView, deriveIcons, enrichPurchase, extractBearerToken, getCustomerRefFromBearerAuthHeader, getCustomerRefFromJwtPayload, getOAuthAuthorizationServerResponse, getOAuthProtectedResourceResponse, isPlanPurchase, logDcrFailureDiagnostic, logMcpConfigOnce, mergeCsp, narrateActivatePlan, narrateAlreadyActive, narrateAutoRecharge, narrateManageAccount, narrateTopup, narrateUpgrade, narratedToolResult, parseMode, paywallToolResult, previewJson, resetMcpConfigLogForTests, resolveOAuthPaths, selectActivePlanPurchase, toolErrorResult, toolResult, uiPlaceholder, withoutTrailingSlash };