mppx 0.7.0 → 0.8.0
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.
- package/CHANGELOG.md +33 -0
- package/README.md +20 -11
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +18 -6
- package/dist/Challenge.js.map +1 -1
- package/dist/Mcp.d.ts +3 -0
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +2 -0
- package/dist/Mcp.js.map +1 -1
- package/dist/PaymentRequest.d.ts +10 -10
- package/dist/PaymentRequest.js +8 -8
- package/dist/client/Mppx.js +2 -2
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/Transport.d.ts +11 -16
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +55 -75
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +46 -7
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/protocols/Mcp.d.ts +7 -0
- package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
- package/dist/client/internal/protocols/Mcp.js +159 -0
- package/dist/client/internal/protocols/Mcp.js.map +1 -0
- package/dist/client/internal/protocols/Mpp.d.ts +4 -0
- package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
- package/dist/client/internal/protocols/Mpp.js +18 -0
- package/dist/client/internal/protocols/Mpp.js.map +1 -0
- package/dist/client/internal/protocols/Protocol.d.ts +10 -0
- package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
- package/dist/client/internal/protocols/Protocol.js +2 -0
- package/dist/client/internal/protocols/Protocol.js.map +1 -0
- package/dist/client/internal/protocols/Shared.d.ts +5 -0
- package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
- package/dist/client/internal/protocols/Shared.js +20 -0
- package/dist/client/internal/protocols/Shared.js.map +1 -0
- package/dist/client/internal/protocols/X402.d.ts +8 -0
- package/dist/client/internal/protocols/X402.d.ts.map +1 -0
- package/dist/client/internal/protocols/X402.js +39 -0
- package/dist/client/internal/protocols/X402.js.map +1 -0
- package/dist/evm/client/index.d.ts +1 -0
- package/dist/evm/client/index.d.ts.map +1 -1
- package/dist/evm/client/index.js +1 -0
- package/dist/evm/client/index.js.map +1 -1
- package/dist/evm/index.d.ts +2 -0
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +2 -0
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/server/index.d.ts +1 -0
- package/dist/evm/server/index.d.ts.map +1 -1
- package/dist/evm/server/index.js +1 -0
- package/dist/evm/server/index.js.map +1 -1
- package/dist/mcp/client/McpClient.d.ts +101 -0
- package/dist/mcp/client/McpClient.d.ts.map +1 -0
- package/dist/mcp/client/McpClient.js +162 -0
- package/dist/mcp/client/McpClient.js.map +1 -0
- package/dist/mcp/client/index.d.ts.map +1 -0
- package/dist/mcp/client/index.js.map +1 -0
- package/dist/mcp/server/Transport.d.ts.map +1 -0
- package/dist/mcp/server/Transport.js.map +1 -0
- package/dist/mcp/server/index.d.ts.map +1 -0
- package/dist/mcp/server/index.js.map +1 -0
- package/dist/server/Mppx.d.ts +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +9 -0
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Transport.d.ts +1 -1
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +1 -1
- package/dist/server/Transport.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/Proof.d.ts +85 -1
- package/dist/tempo/Proof.d.ts.map +1 -1
- package/dist/tempo/Proof.js +35 -0
- package/dist/tempo/Proof.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts +13 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +38 -25
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +5 -3
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/client/Methods.js +4 -2
- package/dist/tempo/client/Methods.js.map +1 -1
- package/dist/tempo/client/ResolveAccount.d.ts +40 -0
- package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
- package/dist/tempo/client/ResolveAccount.js +2 -0
- package/dist/tempo/client/ResolveAccount.js.map +1 -0
- package/dist/tempo/internal/fee-payer.d.ts +9 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +35 -6
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/internal/proof.d.ts +71 -5
- package/dist/tempo/internal/proof.d.ts.map +1 -1
- package/dist/tempo/internal/proof.js +42 -6
- package/dist/tempo/internal/proof.js.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.js +10 -3
- package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +42 -18
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +4 -2
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +4 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +10 -0
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +135 -23
- package/dist/tempo/server/Subscription.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -3
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +7 -10
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
- package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/client/ChannelStore.js +63 -0
- package/dist/tempo/session/client/ChannelStore.js.map +1 -0
- package/dist/tempo/session/client/CredentialState.d.ts +7 -24
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +51 -49
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +8 -2
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +22 -8
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +4 -40
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +124 -174
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/index.d.ts +3 -4
- package/dist/tempo/session/client/index.d.ts.map +1 -1
- package/dist/tempo/session/client/index.js +1 -0
- package/dist/tempo/session/client/index.js.map +1 -1
- package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
- package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Voucher.js +24 -25
- package/dist/tempo/session/precompile/Voucher.js.map +1 -1
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
- package/dist/tempo/session/server/Settlement.js +4 -2
- package/dist/tempo/session/server/Settlement.js.map +1 -1
- package/dist/tempo/session/server/Sse.d.ts.map +1 -1
- package/dist/tempo/session/server/Sse.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.d.ts.map +1 -1
- package/dist/tempo/subscription/Store.js +16 -1
- package/dist/tempo/subscription/Store.js.map +1 -1
- package/dist/x402/index.d.ts +1 -0
- package/dist/x402/index.d.ts.map +1 -1
- package/dist/x402/index.js +1 -0
- package/dist/x402/index.js.map +1 -1
- package/package.json +21 -10
- package/src/Challenge.test.ts +40 -0
- package/src/Challenge.ts +19 -6
- package/src/Mcp.ts +4 -0
- package/src/PaymentRequest.ts +10 -10
- package/src/cli/cli.test.ts +15 -15
- package/src/client/Mppx.test-d.ts +21 -1
- package/src/client/Mppx.test.ts +1 -1
- package/src/client/Mppx.ts +2 -2
- package/src/client/Transport.test.ts +225 -178
- package/src/client/Transport.ts +77 -83
- package/src/client/index.ts +14 -0
- package/src/client/internal/Fetch.test.ts +207 -2
- package/src/client/internal/Fetch.ts +52 -6
- package/src/client/internal/protocols/Mcp.test.ts +220 -0
- package/src/client/internal/protocols/Mcp.ts +162 -0
- package/src/client/internal/protocols/Mpp.ts +21 -0
- package/src/client/internal/protocols/Protocol.ts +10 -0
- package/src/client/internal/protocols/Shared.ts +25 -0
- package/src/client/internal/protocols/X402.ts +42 -0
- package/src/discovery/OpenApi.test.ts +1 -1
- package/src/evm/PublicInterface.test-d.ts +1 -1
- package/src/evm/client/index.ts +1 -0
- package/src/evm/index.ts +2 -0
- package/src/evm/server/Charge.test.ts +1 -1
- package/src/evm/server/index.ts +1 -0
- package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
- package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
- package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
- package/src/mcp/client/McpClient.ts +307 -0
- package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
- package/src/middlewares/elysia.test.ts +1 -1
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/internal/mppx.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/proxy/Proxy.test.ts +1 -1
- package/src/proxy/services/anthropic.test.ts +1 -1
- package/src/proxy/services/openai.test.ts +1 -1
- package/src/proxy/services/stripe.test.ts +1 -1
- package/src/server/Mppx.authorize.test.ts +1 -1
- package/src/server/Mppx.test-d.ts +1 -1
- package/src/server/Mppx.test.ts +20 -2
- package/src/server/Mppx.ts +14 -1
- package/src/server/Transport.test.ts +6 -6
- package/src/server/Transport.ts +1 -1
- package/src/stripe/Charge.integration.test.ts +1 -1
- package/src/stripe/client/Charge.test.ts +1 -1
- package/src/stripe/server/Charge.test.ts +1 -1
- package/src/stripe/server/internal/html/package.json +1 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/Proof.conformance.test.ts +146 -0
- package/src/tempo/Proof.test-d.ts +15 -0
- package/src/tempo/Proof.ts +52 -1
- package/src/tempo/Subscription.integration.test.ts +1 -1
- package/src/tempo/client/Charge.test.ts +173 -0
- package/src/tempo/client/Charge.ts +65 -36
- package/src/tempo/client/Methods.ts +4 -2
- package/src/tempo/client/ResolveAccount.ts +46 -0
- package/src/tempo/internal/fee-payer.test.ts +65 -10
- package/src/tempo/internal/fee-payer.ts +42 -6
- package/src/tempo/internal/proof.test.ts +12 -4
- package/src/tempo/internal/proof.ts +55 -6
- package/src/tempo/legacy/client/SessionManager.ts +11 -3
- package/src/tempo/legacy/server/Session.test.ts +91 -26
- package/src/tempo/server/Charge.test.ts +388 -17
- package/src/tempo/server/Charge.ts +46 -24
- package/src/tempo/server/Methods.ts +4 -2
- package/src/tempo/server/Subscription.test.ts +465 -3
- package/src/tempo/server/Subscription.ts +174 -19
- package/src/tempo/server/internal/html/package.json +2 -2
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/client/ChannelOps.ts +5 -19
- package/src/tempo/session/client/ChannelStore.ts +111 -0
- package/src/tempo/session/client/CredentialState.test.ts +206 -62
- package/src/tempo/session/client/CredentialState.ts +58 -73
- package/src/tempo/session/client/Session.test.ts +41 -1
- package/src/tempo/session/client/Session.ts +36 -10
- package/src/tempo/session/client/SessionManager.test.ts +154 -65
- package/src/tempo/session/client/SessionManager.ts +141 -235
- package/src/tempo/session/client/index.ts +8 -5
- package/src/tempo/session/precompile/Voucher.test.ts +45 -7
- package/src/tempo/session/precompile/Voucher.ts +27 -25
- package/src/tempo/session/server/Session.test.ts +4 -4
- package/src/tempo/session/server/Settlement.test.ts +88 -1
- package/src/tempo/session/server/Settlement.ts +2 -1
- package/src/tempo/session/server/Sse.ts +0 -2
- package/src/tempo/session/server/Ws.ts +0 -4
- package/src/tempo/subscription/Store.ts +27 -9
- package/src/x402/Exact.e2e.test.ts +1 -1
- package/src/x402/PublicInterface.test-d.ts +1 -1
- package/src/x402/index.ts +1 -0
- package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
- package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
- package/dist/mcp-sdk/client/McpClient.js +0 -118
- package/dist/mcp-sdk/client/McpClient.js.map +0 -1
- package/dist/mcp-sdk/client/index.d.ts.map +0 -1
- package/dist/mcp-sdk/client/index.js.map +0 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
- package/dist/mcp-sdk/server/Transport.js.map +0 -1
- package/dist/mcp-sdk/server/index.d.ts.map +0 -1
- package/dist/mcp-sdk/server/index.js.map +0 -1
- package/src/mcp-sdk/client/McpClient.ts +0 -228
- /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
- /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
- /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
- /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
package/src/client/Transport.ts
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
import * as Challenge from '../Challenge.js'
|
|
2
|
-
import * as Constants from '../Constants.js'
|
|
3
2
|
import * as Credential from '../Credential.js'
|
|
4
3
|
import * as Mcp from '../Mcp.js'
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const credentialHeaders = [
|
|
11
|
-
Constants.Headers.authorization,
|
|
12
|
-
x402_Types.paymentRequiredHeader,
|
|
13
|
-
x402_Types.paymentResponseHeader,
|
|
14
|
-
x402_Types.paymentSignatureHeader,
|
|
15
|
-
]
|
|
4
|
+
import { mcp as mcpProtocol } from './internal/protocols/Mcp.js'
|
|
5
|
+
import { mpp as mppProtocol } from './internal/protocols/Mpp.js'
|
|
6
|
+
import type { Protocol } from './internal/protocols/Protocol.js'
|
|
7
|
+
import { paymentRequiredStatus } from './internal/protocols/Shared.js'
|
|
8
|
+
import { x402 as x402Protocol } from './internal/protocols/X402.js'
|
|
16
9
|
|
|
17
10
|
/**
|
|
18
11
|
* Client-side transport adapter.
|
|
@@ -23,12 +16,21 @@ const credentialHeaders = [
|
|
|
23
16
|
export type Transport<in out request = unknown, in out response = unknown> = {
|
|
24
17
|
/** Transport name for identification. */
|
|
25
18
|
name: string
|
|
26
|
-
/**
|
|
27
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Checks if a response indicates payment is required. May inspect the request (to gate
|
|
21
|
+
* body reads) and be async (to read a response body).
|
|
22
|
+
*/
|
|
23
|
+
isPaymentRequired: (response: response, request?: request) => boolean | Promise<boolean>
|
|
28
24
|
/** Extracts all challenges from a payment-required response, when the transport supports multiple offers. */
|
|
29
|
-
getChallenges?: (
|
|
25
|
+
getChallenges?: (
|
|
26
|
+
response: response,
|
|
27
|
+
request?: request,
|
|
28
|
+
) => Challenge.Challenge[] | Promise<Challenge.Challenge[]>
|
|
30
29
|
/** Extracts the challenge from a payment-required response. */
|
|
31
|
-
getChallenge: (
|
|
30
|
+
getChallenge: (
|
|
31
|
+
response: response,
|
|
32
|
+
request?: request,
|
|
33
|
+
) => Challenge.Challenge | Promise<Challenge.Challenge>
|
|
32
34
|
/** Attaches a credential to a request. */
|
|
33
35
|
setCredential: (
|
|
34
36
|
request: request,
|
|
@@ -74,87 +76,79 @@ export function from<request, response>(
|
|
|
74
76
|
return transport
|
|
75
77
|
}
|
|
76
78
|
|
|
77
|
-
/**
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
79
|
+
/** HTTP transport that composes payment protocols while keeping `fetch` as the single boundary. */
|
|
80
|
+
export function http(): Transport<RequestInit, Response> {
|
|
81
|
+
const protocols: readonly Protocol[] = [mppProtocol(), x402Protocol(), mcpProtocol()]
|
|
82
|
+
const protocolForChallenge = new WeakMap<Challenge.Challenge, Protocol>()
|
|
83
|
+
|
|
84
|
+
const remember = (protocol: Protocol, challenges: Challenge.Challenge[]) => {
|
|
85
|
+
for (const challenge of challenges) protocolForChallenge.set(challenge, protocol)
|
|
86
|
+
return challenges
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Collect every protocol offer. Header-only 402 paths stay synchronous; MCP returns a promise
|
|
90
|
+
// only when it has to inspect a JSON-RPC/SSE body.
|
|
91
|
+
const collect = (
|
|
92
|
+
response: Response,
|
|
93
|
+
request?: RequestInit,
|
|
94
|
+
): Challenge.Challenge[] | Promise<Challenge.Challenge[]> => {
|
|
95
|
+
const collectFrom = (
|
|
96
|
+
index: number,
|
|
97
|
+
collected: Challenge.Challenge[],
|
|
98
|
+
): Challenge.Challenge[] | Promise<Challenge.Challenge[]> => {
|
|
99
|
+
for (let i = index; i < protocols.length; i++) {
|
|
100
|
+
const protocol = protocols[i]!
|
|
101
|
+
const challenges = protocol.getChallenges(response, request)
|
|
102
|
+
if (challenges instanceof Promise)
|
|
103
|
+
return challenges.then((list) =>
|
|
104
|
+
collectFrom(i + 1, [...collected, ...remember(protocol, list)]),
|
|
105
|
+
)
|
|
106
|
+
collected.push(...remember(protocol, challenges))
|
|
107
|
+
}
|
|
108
|
+
return collected
|
|
109
|
+
}
|
|
110
|
+
return collectFrom(0, [])
|
|
111
|
+
}
|
|
112
|
+
|
|
86
113
|
return from<RequestInit, Response>({
|
|
87
114
|
name: 'http',
|
|
88
115
|
|
|
89
|
-
isPaymentRequired(response) {
|
|
90
|
-
|
|
116
|
+
isPaymentRequired(response, request) {
|
|
117
|
+
if (response.status === paymentRequiredStatus) return true // HTTP 402 — sync fast path
|
|
118
|
+
const challenges = collect(response, request)
|
|
119
|
+
return challenges instanceof Promise
|
|
120
|
+
? challenges.then((list) => list.length > 0)
|
|
121
|
+
: challenges.length > 0
|
|
91
122
|
},
|
|
92
123
|
|
|
93
|
-
getChallenges(response) {
|
|
94
|
-
return
|
|
124
|
+
getChallenges(response, request) {
|
|
125
|
+
return collect(response, request)
|
|
95
126
|
},
|
|
96
127
|
|
|
97
|
-
getChallenge(response) {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
128
|
+
getChallenge(response, request) {
|
|
129
|
+
const pick = (challenges: Challenge.Challenge[]): Challenge.Challenge => {
|
|
130
|
+
const challenge = challenges[0]
|
|
131
|
+
if (!challenge) throw new Error('No challenge in response.')
|
|
132
|
+
return challenge
|
|
133
|
+
}
|
|
134
|
+
const challenges = collect(response, request)
|
|
135
|
+
return challenges instanceof Promise ? challenges.then(pick) : pick(challenges)
|
|
101
136
|
},
|
|
102
137
|
|
|
103
138
|
setCredential(request, credential, options) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
} else {
|
|
109
|
-
headers.set(Constants.Headers.authorization, credential)
|
|
110
|
-
}
|
|
111
|
-
return { ...request, headers }
|
|
139
|
+
const protocol = options?.challenge ? protocolForChallenge.get(options.challenge) : undefined
|
|
140
|
+
const fallback = protocols[0]
|
|
141
|
+
if (!protocol && !fallback) throw new Error('No protocol to attach the credential.')
|
|
142
|
+
return (protocol ?? fallback)!.setCredential(request, credential)
|
|
112
143
|
},
|
|
113
144
|
})
|
|
114
145
|
}
|
|
115
146
|
|
|
116
|
-
function paymentRequiredChallenges(response: Response): Challenge.Challenge[] {
|
|
117
|
-
return [
|
|
118
|
-
...(response.headers.has(Constants.Headers.wwwAuthenticate)
|
|
119
|
-
? Challenge.fromResponseList(response)
|
|
120
|
-
: []),
|
|
121
|
-
...x402Challenges(response),
|
|
122
|
-
]
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function x402Challenges(response: Response): Challenge.Challenge[] {
|
|
126
|
-
const header = response.headers.get(x402_Types.paymentRequiredHeader)
|
|
127
|
-
if (!header) return []
|
|
128
|
-
const paymentRequired = x402_Header.decodePaymentRequired(header)
|
|
129
|
-
if (response.url && paymentRequired.resource.url !== response.url)
|
|
130
|
-
throw new Error('x402 payment-required resource does not match response URL.')
|
|
131
|
-
return paymentRequired.accepts.map((accepted, index) =>
|
|
132
|
-
x402_ChallengeBrand.mark(
|
|
133
|
-
Challenge.from({
|
|
134
|
-
id: `${x402_Types.syntheticChallengeIdPrefix}${index}`,
|
|
135
|
-
intent: x402_Types.exactIntent,
|
|
136
|
-
method: x402_Types.paymentMethod,
|
|
137
|
-
realm: new URL(paymentRequired.resource.url).host,
|
|
138
|
-
request: {
|
|
139
|
-
...accepted,
|
|
140
|
-
...(paymentRequired.extensions ? { extensions: paymentRequired.extensions } : {}),
|
|
141
|
-
resource: paymentRequired.resource,
|
|
142
|
-
},
|
|
143
|
-
}),
|
|
144
|
-
),
|
|
145
|
-
)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function isX402Challenge(challenge: Challenge.Challenge | undefined): boolean {
|
|
149
|
-
return x402_ChallengeBrand.is(challenge)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
147
|
/**
|
|
153
|
-
* MCP transport for
|
|
148
|
+
* MCP protocol transport for direct JSON-RPC objects.
|
|
154
149
|
*
|
|
155
|
-
* -
|
|
156
|
-
*
|
|
157
|
-
* - Sends credentials via `_meta["org.paymentauth/credential"]`
|
|
150
|
+
* Prefer {@link http} for MCP-over-HTTP fetches; this remains for callers that already operate on
|
|
151
|
+
* parsed MCP request/response objects.
|
|
158
152
|
*/
|
|
159
153
|
export function mcp() {
|
|
160
154
|
return from<Mcp.Request, Mcp.Response>({
|
|
@@ -184,8 +178,8 @@ export function mcp() {
|
|
|
184
178
|
...request,
|
|
185
179
|
params: {
|
|
186
180
|
...request.params,
|
|
187
|
-
_meta: {
|
|
188
|
-
...request.params?._meta,
|
|
181
|
+
['_meta']: {
|
|
182
|
+
...request.params?.['_meta'],
|
|
189
183
|
[Mcp.credentialMetaKey]: parsed,
|
|
190
184
|
},
|
|
191
185
|
},
|
package/src/client/index.ts
CHANGED
|
@@ -11,5 +11,19 @@ export {
|
|
|
11
11
|
stripe,
|
|
12
12
|
tempo,
|
|
13
13
|
} from './Methods.js'
|
|
14
|
+
export {
|
|
15
|
+
createChannelStore,
|
|
16
|
+
createJsonChannelStore,
|
|
17
|
+
entryKey,
|
|
18
|
+
type ChannelStore,
|
|
19
|
+
type JsonChannelKv,
|
|
20
|
+
} from '../tempo/session/client/ChannelStore.js'
|
|
21
|
+
export type { ChargeContext } from '../tempo/client/Charge.js'
|
|
22
|
+
export type {
|
|
23
|
+
ResolveAccount,
|
|
24
|
+
ResolveAccountCall,
|
|
25
|
+
ResolveAccountInfo,
|
|
26
|
+
ResolveAccountOperation,
|
|
27
|
+
} from '../tempo/client/ResolveAccount.js'
|
|
14
28
|
export * as Mppx from './Mppx.js'
|
|
15
29
|
export * as Transport from './Transport.js'
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Challenge, Errors, Receipt } from 'mppx'
|
|
1
|
+
import { Challenge, Credential, Errors, Mcp, Receipt } from 'mppx'
|
|
2
2
|
import { tempo } from 'mppx/client'
|
|
3
3
|
import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
|
|
4
|
+
import { Header as x402_Header, Types as x402_Types, type PaymentRequired } from 'mppx/x402'
|
|
4
5
|
import { createClient, defineChain } from 'viem'
|
|
5
6
|
import { describe, expect, test, vi } from 'vp/test'
|
|
6
7
|
import * as Http from '~test/Http.js'
|
|
@@ -10,7 +11,7 @@ import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
|
|
|
10
11
|
import * as Fetch from './Fetch.js'
|
|
11
12
|
|
|
12
13
|
const realm = 'api.example.com'
|
|
13
|
-
const secretKey = 'test-secret-key'
|
|
14
|
+
const secretKey = 'test-secret-key-test-secret-key-32'
|
|
14
15
|
|
|
15
16
|
const server = Mppx_server.create({
|
|
16
17
|
methods: [
|
|
@@ -334,6 +335,21 @@ const noopMethod = {
|
|
|
334
335
|
createCredential: async () => 'credential',
|
|
335
336
|
} as any
|
|
336
337
|
|
|
338
|
+
const x402PaymentRequired = {
|
|
339
|
+
accepts: [
|
|
340
|
+
{
|
|
341
|
+
amount: '10000',
|
|
342
|
+
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
343
|
+
maxTimeoutSeconds: 60,
|
|
344
|
+
network: 'eip155:84532',
|
|
345
|
+
payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
346
|
+
scheme: x402_Types.schemes[0],
|
|
347
|
+
},
|
|
348
|
+
],
|
|
349
|
+
resource: { url: 'https://example.com/api' },
|
|
350
|
+
x402Version: 2,
|
|
351
|
+
} satisfies PaymentRequired
|
|
352
|
+
|
|
337
353
|
/** Builds a valid 402 response with a WWW-Authenticate header. */
|
|
338
354
|
function make402(overrides?: { expires?: string; intent?: string; method?: string }) {
|
|
339
355
|
const method = overrides?.method ?? 'test'
|
|
@@ -350,6 +366,19 @@ function make402(overrides?: { expires?: string; intent?: string; method?: strin
|
|
|
350
366
|
})
|
|
351
367
|
}
|
|
352
368
|
|
|
369
|
+
function makeCombined402() {
|
|
370
|
+
const response = make402()
|
|
371
|
+
const headers = new Headers(response.headers)
|
|
372
|
+
headers.set(
|
|
373
|
+
x402_Types.paymentRequiredHeader,
|
|
374
|
+
x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
375
|
+
)
|
|
376
|
+
return new Response(null, {
|
|
377
|
+
status: 402,
|
|
378
|
+
headers,
|
|
379
|
+
})
|
|
380
|
+
}
|
|
381
|
+
|
|
353
382
|
describe('Fetch.from: init passthrough (non-402)', () => {
|
|
354
383
|
test('preserves init object identity while adding Accept-Payment', async () => {
|
|
355
384
|
const receivedInits: (RequestInit | undefined)[] = []
|
|
@@ -669,6 +698,182 @@ describe('Fetch.from: 402 retry path', () => {
|
|
|
669
698
|
expect(headers.get('Authorization')).toBe('credential')
|
|
670
699
|
})
|
|
671
700
|
|
|
701
|
+
test('chooses native Payment-auth from combined MPP and x402 HTTP 402 offers', async () => {
|
|
702
|
+
let callCount = 0
|
|
703
|
+
const calls: { init: RequestInit | undefined }[] = []
|
|
704
|
+
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
705
|
+
calls.push({ init })
|
|
706
|
+
callCount++
|
|
707
|
+
if (callCount === 1) return makeCombined402()
|
|
708
|
+
return new Response('OK', { status: 200 })
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const fetch = Fetch.from({
|
|
712
|
+
fetch: mockFetch,
|
|
713
|
+
methods: [noopMethod],
|
|
714
|
+
})
|
|
715
|
+
|
|
716
|
+
const response = await fetch('https://example.com/api')
|
|
717
|
+
|
|
718
|
+
expect(response.status).toBe(200)
|
|
719
|
+
expect(calls).toHaveLength(2)
|
|
720
|
+
const retryHeaders = new Headers(calls[1]!.init?.headers)
|
|
721
|
+
expect(retryHeaders.get('Authorization')).toBe('credential')
|
|
722
|
+
expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBeNull()
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
test('settles MCP-over-HTTP JSON-RPC payment challenges at the fetch boundary', async () => {
|
|
726
|
+
const mcpChallenge = Challenge.from({
|
|
727
|
+
id: 'mcp-challenge',
|
|
728
|
+
intent: 'test',
|
|
729
|
+
method: 'test',
|
|
730
|
+
realm: 'test',
|
|
731
|
+
request: { amount: '1' },
|
|
732
|
+
})
|
|
733
|
+
const method = {
|
|
734
|
+
...noopMethod,
|
|
735
|
+
createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
|
|
736
|
+
Credential.serialize({ challenge, payload: { source: 'mcp' } }),
|
|
737
|
+
}
|
|
738
|
+
const initialBody = JSON.stringify({
|
|
739
|
+
jsonrpc: '2.0',
|
|
740
|
+
id: 1,
|
|
741
|
+
method: 'tools/call',
|
|
742
|
+
params: { name: 'paid-tool' },
|
|
743
|
+
})
|
|
744
|
+
let callCount = 0
|
|
745
|
+
const calls: { init: RequestInit | undefined }[] = []
|
|
746
|
+
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
747
|
+
calls.push({ init })
|
|
748
|
+
callCount++
|
|
749
|
+
if (callCount === 1)
|
|
750
|
+
return Response.json({
|
|
751
|
+
jsonrpc: '2.0',
|
|
752
|
+
id: 1,
|
|
753
|
+
error: {
|
|
754
|
+
code: Mcp.paymentRequiredCode,
|
|
755
|
+
message: 'Payment Required',
|
|
756
|
+
data: { challenges: [mcpChallenge] },
|
|
757
|
+
},
|
|
758
|
+
})
|
|
759
|
+
|
|
760
|
+
const body = JSON.parse(init?.body as string)
|
|
761
|
+
expect(new Headers(init?.headers).get('Authorization')).toBeNull()
|
|
762
|
+
expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
|
|
763
|
+
payload: { source: 'mcp' },
|
|
764
|
+
})
|
|
765
|
+
return Response.json({ ok: true })
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const fetch = Fetch.from({
|
|
769
|
+
fetch: mockFetch,
|
|
770
|
+
methods: [method],
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
const response = await fetch('https://example.com/mcp', {
|
|
774
|
+
method: 'POST',
|
|
775
|
+
headers: { accept: 'application/json, text/event-stream' },
|
|
776
|
+
body: initialBody,
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
expect(response.status).toBe(200)
|
|
780
|
+
expect(calls).toHaveLength(2)
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
test('settles MCP-over-HTTP when the JSON-RPC request body is carried by Request input', async () => {
|
|
784
|
+
const mcpChallenge = Challenge.from({
|
|
785
|
+
id: 'mcp-request-input-challenge',
|
|
786
|
+
intent: 'test',
|
|
787
|
+
method: 'test',
|
|
788
|
+
realm: 'test',
|
|
789
|
+
request: { amount: '1' },
|
|
790
|
+
})
|
|
791
|
+
const method = {
|
|
792
|
+
...noopMethod,
|
|
793
|
+
createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
|
|
794
|
+
Credential.serialize({ challenge, payload: { source: 'request-input' } }),
|
|
795
|
+
}
|
|
796
|
+
const initialBody = JSON.stringify({
|
|
797
|
+
jsonrpc: '2.0',
|
|
798
|
+
id: 1,
|
|
799
|
+
method: 'tools/call',
|
|
800
|
+
params: { name: 'paid-tool' },
|
|
801
|
+
})
|
|
802
|
+
let callCount = 0
|
|
803
|
+
const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
|
|
804
|
+
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
805
|
+
calls.push({ input, init })
|
|
806
|
+
callCount++
|
|
807
|
+
if (callCount === 1) {
|
|
808
|
+
expect(input).toBeInstanceOf(Request)
|
|
809
|
+
expect(await (input as Request).text()).toBe(initialBody)
|
|
810
|
+
return Response.json({
|
|
811
|
+
jsonrpc: '2.0',
|
|
812
|
+
id: 1,
|
|
813
|
+
error: {
|
|
814
|
+
code: Mcp.paymentRequiredCode,
|
|
815
|
+
message: 'Payment Required',
|
|
816
|
+
data: { challenges: [mcpChallenge] },
|
|
817
|
+
},
|
|
818
|
+
})
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const body = JSON.parse(init?.body as string)
|
|
822
|
+
expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
|
|
823
|
+
payload: { source: 'request-input' },
|
|
824
|
+
})
|
|
825
|
+
return Response.json({ ok: true })
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const fetch = Fetch.from({
|
|
829
|
+
fetch: mockFetch,
|
|
830
|
+
methods: [method],
|
|
831
|
+
})
|
|
832
|
+
const request = new Request('https://example.com/mcp', {
|
|
833
|
+
method: 'POST',
|
|
834
|
+
headers: { accept: 'application/json, text/event-stream' },
|
|
835
|
+
body: initialBody,
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
const response = await fetch(request)
|
|
839
|
+
|
|
840
|
+
expect(response.status).toBe(200)
|
|
841
|
+
expect(calls).toHaveLength(2)
|
|
842
|
+
expect(calls[1]?.input).toBe(request)
|
|
843
|
+
})
|
|
844
|
+
|
|
845
|
+
test('settles native HTTP 402 when a POST body is carried by Request input', async () => {
|
|
846
|
+
const initialBody = JSON.stringify({ ok: true })
|
|
847
|
+
let callCount = 0
|
|
848
|
+
const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
|
|
849
|
+
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
850
|
+
calls.push({ input, init })
|
|
851
|
+
callCount++
|
|
852
|
+
const request = input instanceof Request && !init ? input : new Request(input, init)
|
|
853
|
+
expect(await request.text()).toBe(initialBody)
|
|
854
|
+
if (callCount === 1) return make402()
|
|
855
|
+
|
|
856
|
+
expect(request.headers.get('Authorization')).toBe('credential')
|
|
857
|
+
return new Response('OK', { status: 200 })
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const fetch = Fetch.from({
|
|
861
|
+
fetch: mockFetch,
|
|
862
|
+
methods: [noopMethod],
|
|
863
|
+
})
|
|
864
|
+
const request = new Request('https://example.com/api', {
|
|
865
|
+
method: 'POST',
|
|
866
|
+
headers: { 'Content-Type': 'application/json' },
|
|
867
|
+
body: initialBody,
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
const response = await fetch(request)
|
|
871
|
+
|
|
872
|
+
expect(response.status).toBe(200)
|
|
873
|
+
expect(calls).toHaveLength(2)
|
|
874
|
+
expect(calls[1]?.input).toBe(request)
|
|
875
|
+
})
|
|
876
|
+
|
|
672
877
|
test('sends credential retry to the final same-origin 402 response URL', async () => {
|
|
673
878
|
let callCount = 0
|
|
674
879
|
const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
|
|
@@ -176,6 +176,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
176
176
|
const callerHeaders = getCallerHeaders(input, init?.headers)
|
|
177
177
|
const hasExplicitAcceptPayment = callerHeaders.has(Constants.Headers.acceptPayment)
|
|
178
178
|
const paymentPreferences = resolvePaymentPreferences(callerHeaders, resolvedAcceptPayment)
|
|
179
|
+
const capturedBody = captureRequestBody(input, init, callerHeaders)
|
|
179
180
|
const initialRequest = prepareInitialRequest(
|
|
180
181
|
input,
|
|
181
182
|
init,
|
|
@@ -184,9 +185,10 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
184
185
|
hasExplicitAcceptPayment,
|
|
185
186
|
acceptPaymentPolicy,
|
|
186
187
|
)
|
|
187
|
-
const response = await baseFetch(initialRequest.input, initialRequest.init)
|
|
188
|
+
const response = await baseFetch(cloneRequestInput(initialRequest.input), initialRequest.init)
|
|
189
|
+
const transportRequest = withCapturedBody(initialRequest.init, await capturedBody)
|
|
188
190
|
|
|
189
|
-
if (!transport.isPaymentRequired(response)) return response
|
|
191
|
+
if (!(await transport.isPaymentRequired(response, transportRequest as never))) return response
|
|
190
192
|
|
|
191
193
|
// Only extract context for payment handling after confirming 402.
|
|
192
194
|
const context = (init as Record<string, unknown> | undefined)?.context
|
|
@@ -194,7 +196,7 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
194
196
|
context: _,
|
|
195
197
|
orderChallenges: requestOrderChallenges,
|
|
196
198
|
...fetchInit
|
|
197
|
-
} = (
|
|
199
|
+
} = (transportRequest ?? {}) as Record<string, unknown>
|
|
198
200
|
|
|
199
201
|
let challenge: Challenge.Challenge | undefined
|
|
200
202
|
let challenges: readonly Challenge.Challenge[] | undefined
|
|
@@ -202,8 +204,8 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
202
204
|
|
|
203
205
|
try {
|
|
204
206
|
challenges = transport.getChallenges
|
|
205
|
-
? transport.getChallenges(response)
|
|
206
|
-
: [transport.getChallenge(response)]
|
|
207
|
+
? await transport.getChallenges(response, transportRequest as never)
|
|
208
|
+
: [await transport.getChallenge(response, transportRequest as never)]
|
|
207
209
|
|
|
208
210
|
const candidates = AcceptPayment.selectChallengeCandidates(
|
|
209
211
|
challenges,
|
|
@@ -666,7 +668,13 @@ function snapshotInit<methods extends readonly Method.AnyClient[]>(
|
|
|
666
668
|
}
|
|
667
669
|
|
|
668
670
|
function snapshotInput(input: RequestInfo | URL | undefined): RequestInfo | URL | undefined {
|
|
669
|
-
if (input instanceof Request)
|
|
671
|
+
if (input instanceof Request) {
|
|
672
|
+
try {
|
|
673
|
+
return input.clone()
|
|
674
|
+
} catch {
|
|
675
|
+
return input
|
|
676
|
+
}
|
|
677
|
+
}
|
|
670
678
|
if (input instanceof URL) return new URL(input)
|
|
671
679
|
return input
|
|
672
680
|
}
|
|
@@ -738,6 +746,35 @@ function prepareInitialRequest<methods extends readonly Method.AnyClient[]>(
|
|
|
738
746
|
}
|
|
739
747
|
}
|
|
740
748
|
|
|
749
|
+
function captureRequestBody<methods extends readonly Method.AnyClient[]>(
|
|
750
|
+
input: RequestInfo | URL,
|
|
751
|
+
init: from.RequestInit<methods> | undefined,
|
|
752
|
+
headers: Headers,
|
|
753
|
+
): Promise<string | undefined> | undefined {
|
|
754
|
+
if (!(input instanceof Request) || init?.body !== undefined || !input.body || input.bodyUsed)
|
|
755
|
+
return undefined
|
|
756
|
+
if (!headers.has('mcp-method')) {
|
|
757
|
+
const accept = headers.get('accept')?.toLowerCase() ?? ''
|
|
758
|
+
if (!accept.includes('application/json') || !accept.includes('text/event-stream'))
|
|
759
|
+
return undefined
|
|
760
|
+
}
|
|
761
|
+
try {
|
|
762
|
+
return input
|
|
763
|
+
.clone()
|
|
764
|
+
.text()
|
|
765
|
+
.catch(() => undefined)
|
|
766
|
+
} catch {
|
|
767
|
+
return undefined
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function withCapturedBody<methods extends readonly Method.AnyClient[]>(
|
|
772
|
+
init: from.RequestInit<methods> | undefined,
|
|
773
|
+
body: string | undefined,
|
|
774
|
+
): from.RequestInit<methods> | undefined {
|
|
775
|
+
return body === undefined ? init : ({ ...init, body } as from.RequestInit<methods>)
|
|
776
|
+
}
|
|
777
|
+
|
|
741
778
|
/** @internal */
|
|
742
779
|
function getCallerHeaders(input: RequestInfo | URL, headers: HeadersInit | undefined): Headers {
|
|
743
780
|
if (headers) return new Headers(headers)
|
|
@@ -753,6 +790,15 @@ function unwrapFetch(fetch: typeof globalThis.fetch): typeof globalThis.fetch {
|
|
|
753
790
|
return current as typeof globalThis.fetch
|
|
754
791
|
}
|
|
755
792
|
|
|
793
|
+
function cloneRequestInput(input: RequestInfo | URL): RequestInfo | URL {
|
|
794
|
+
if (!(input instanceof Request) || !input.body || input.bodyUsed) return input
|
|
795
|
+
try {
|
|
796
|
+
return input.clone()
|
|
797
|
+
} catch {
|
|
798
|
+
return input
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
756
802
|
/** @internal */
|
|
757
803
|
function isWrappedFetch(fetch: typeof globalThis.fetch): fetch is WrappedFetch {
|
|
758
804
|
return Boolean((fetch as WrappedFetch)[MPPX_FETCH_WRAPPER])
|