mppx 0.4.8 → 0.4.10
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 +26 -3
- package/README.md +13 -13
- package/dist/BodyDigest.d.ts.map +1 -1
- package/dist/BodyDigest.js.map +1 -1
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js.map +1 -1
- package/dist/Credential.d.ts.map +1 -1
- package/dist/Credential.js.map +1 -1
- package/dist/Errors.js +64 -67
- package/dist/Errors.js.map +1 -1
- package/dist/PaymentRequest.d.ts.map +1 -1
- package/dist/PaymentRequest.js.map +1 -1
- package/dist/Receipt.d.ts.map +1 -1
- package/dist/Receipt.js.map +1 -1
- package/dist/Store.d.ts +9 -0
- package/dist/Store.d.ts.map +1 -1
- package/dist/Store.js +17 -0
- package/dist/Store.js.map +1 -1
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/account.js +40 -5
- package/dist/cli/account.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +157 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/internal.d.ts.map +1 -1
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/plugins/stripe.d.ts.map +1 -1
- package/dist/cli/plugins/stripe.js.map +1 -1
- package/dist/cli/plugins/tempo.d.ts.map +1 -1
- package/dist/cli/plugins/tempo.js +2 -1
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +1 -1
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/discovery/Discovery.d.ts +146 -0
- package/dist/discovery/Discovery.d.ts.map +1 -0
- package/dist/discovery/Discovery.js +60 -0
- package/dist/discovery/Discovery.js.map +1 -0
- package/dist/discovery/OpenApi.d.ts +61 -0
- package/dist/discovery/OpenApi.d.ts.map +1 -0
- package/dist/discovery/OpenApi.js +139 -0
- package/dist/discovery/OpenApi.js.map +1 -0
- package/dist/discovery/Validate.d.ts +10 -0
- package/dist/discovery/Validate.d.ts.map +1 -0
- package/dist/discovery/Validate.js +63 -0
- package/dist/discovery/Validate.js.map +1 -0
- package/dist/discovery/index.d.ts +4 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +4 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.d.ts.map +1 -1
- package/dist/mcp-sdk/client/McpClient.js +1 -1
- package/dist/mcp-sdk/client/McpClient.js.map +1 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +1 -1
- package/dist/mcp-sdk/server/Transport.js.map +1 -1
- package/dist/middlewares/elysia.d.ts +52 -1
- package/dist/middlewares/elysia.d.ts.map +1 -1
- package/dist/middlewares/elysia.js +17 -0
- package/dist/middlewares/elysia.js.map +1 -1
- package/dist/middlewares/express.d.ts +13 -1
- package/dist/middlewares/express.d.ts.map +1 -1
- package/dist/middlewares/express.js +23 -2
- package/dist/middlewares/express.js.map +1 -1
- package/dist/middlewares/hono.d.ts +19 -1
- package/dist/middlewares/hono.d.ts.map +1 -1
- package/dist/middlewares/hono.js +51 -0
- package/dist/middlewares/hono.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +4 -2
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +10 -3
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/middlewares/nextjs.d.ts +11 -0
- package/dist/middlewares/nextjs.d.ts.map +1 -1
- package/dist/middlewares/nextjs.js +15 -0
- package/dist/middlewares/nextjs.js.map +1 -1
- package/dist/proxy/Proxy.d.ts +6 -0
- package/dist/proxy/Proxy.d.ts.map +1 -1
- package/dist/proxy/Proxy.js +56 -80
- package/dist/proxy/Proxy.js.map +1 -1
- package/dist/proxy/Service.d.ts +16 -23
- package/dist/proxy/Service.d.ts.map +1 -1
- package/dist/proxy/Service.js +20 -84
- package/dist/proxy/Service.js.map +1 -1
- package/dist/proxy/internal/Route.js +1 -1
- package/dist/proxy/internal/Route.js.map +1 -1
- package/dist/proxy/services/anthropic.d.ts.map +1 -1
- package/dist/proxy/services/anthropic.js +5 -0
- package/dist/proxy/services/anthropic.js.map +1 -1
- package/dist/proxy/services/openai.d.ts.map +1 -1
- package/dist/proxy/services/openai.js +6 -3
- package/dist/proxy/services/openai.js.map +1 -1
- package/dist/proxy/services/stripe.d.ts.map +1 -1
- package/dist/proxy/services/stripe.js +6 -3
- package/dist/proxy/services/stripe.js.map +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +35 -17
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Request.d.ts.map +1 -1
- package/dist/server/Request.js.map +1 -1
- package/dist/stripe/Methods.d.ts.map +1 -1
- package/dist/stripe/Methods.js.map +1 -1
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +1 -1
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +1 -1
- package/dist/tempo/internal/auto-swap.js.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +1 -1
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +18 -5
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +8 -0
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -1
- package/dist/tempo/session/Chain.js +1 -1
- package/dist/tempo/session/Chain.js.map +1 -1
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -1
- package/dist/tempo/session/ChannelStore.js.map +1 -1
- package/dist/tempo/session/Receipt.d.ts.map +1 -1
- package/dist/tempo/session/Receipt.js.map +1 -1
- package/dist/tempo/session/Sse.d.ts.map +1 -1
- package/dist/tempo/session/Sse.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/Voucher.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js.map +1 -1
- package/package.json +6 -1
- package/src/BodyDigest.test.ts +1 -1
- package/src/BodyDigest.ts +1 -0
- package/src/Challenge.fuzz.test.ts +121 -0
- package/src/Challenge.test-d.ts +2 -1
- package/src/Challenge.test.ts +1 -1
- package/src/Challenge.ts +1 -0
- package/src/Credential.fuzz.test.ts +62 -0
- package/src/Credential.test.ts +1 -1
- package/src/Credential.ts +1 -0
- package/src/Errors.test.ts +28 -40
- package/src/Expires.test.ts +2 -1
- package/src/Method.test.ts +1 -1
- package/src/PaymentRequest.test.ts +1 -1
- package/src/PaymentRequest.ts +1 -0
- package/src/Receipt.test.ts +1 -1
- package/src/Receipt.ts +1 -0
- package/src/Store.test-d.ts +2 -1
- package/src/Store.test.ts +57 -7
- package/src/Store.ts +25 -0
- package/src/cli/account.ts +65 -30
- package/src/cli/cli.test.ts +215 -2
- package/src/cli/cli.ts +166 -1
- package/src/cli/config.test.ts +1 -0
- package/src/cli/internal.ts +1 -0
- package/src/cli/plugins/stripe.ts +1 -0
- package/src/cli/plugins/tempo.ts +4 -1
- package/src/cli/utils.ts +1 -0
- package/src/client/Mppx.test-d.ts +2 -1
- package/src/client/Mppx.test.ts +1 -1
- package/src/client/Transport.test.ts +1 -1
- package/src/client/internal/Fetch.browser.test.ts +2 -1
- package/src/client/internal/Fetch.test-d.ts +2 -1
- package/src/client/internal/Fetch.test.ts +3 -1
- package/src/client/internal/Fetch.ts +1 -1
- package/src/discovery/Discovery.test.ts +152 -0
- package/src/discovery/Discovery.ts +72 -0
- package/src/discovery/OpenApi.test.ts +425 -0
- package/src/discovery/OpenApi.ts +224 -0
- package/src/discovery/Validate.test.ts +188 -0
- package/src/discovery/Validate.ts +76 -0
- package/src/discovery/index.ts +3 -0
- package/src/internal/constantTimeEqual.test.ts +2 -1
- package/src/internal/types.ts +1 -3
- package/src/mcp-sdk/client/McpClient.test-d.ts +2 -1
- package/src/mcp-sdk/client/McpClient.test.ts +2 -1
- package/src/mcp-sdk/client/McpClient.ts +2 -0
- package/src/mcp-sdk/server/Transport.test.ts +2 -1
- package/src/mcp-sdk/server/Transport.ts +1 -0
- package/src/middlewares/elysia.test.ts +28 -2
- package/src/middlewares/elysia.ts +36 -1
- package/src/middlewares/express.test.ts +95 -7
- package/src/middlewares/express.ts +40 -2
- package/src/middlewares/hono.test.ts +28 -6
- package/src/middlewares/hono.ts +74 -1
- package/src/middlewares/internal/mppx.test.ts +2 -1
- package/src/middlewares/internal/mppx.ts +14 -6
- package/src/middlewares/nextjs.test.ts +32 -6
- package/src/middlewares/nextjs.ts +28 -0
- package/src/proxy/Proxy.test.ts +55 -270
- package/src/proxy/Proxy.ts +73 -93
- package/src/proxy/Service.test.ts +24 -1
- package/src/proxy/Service.ts +48 -88
- package/src/proxy/internal/Headers.test.ts +2 -1
- package/src/proxy/internal/Route.test.ts +9 -1
- package/src/proxy/internal/Route.ts +1 -1
- package/src/proxy/services/anthropic.test.ts +132 -0
- package/src/proxy/services/anthropic.ts +5 -0
- package/src/proxy/services/openai.test.ts +2 -1
- package/src/proxy/services/openai.ts +6 -4
- package/src/proxy/services/stripe.test.ts +132 -0
- package/src/proxy/services/stripe.ts +6 -4
- package/src/server/Mppx.test-d.ts +1 -1
- package/src/server/Mppx.test.ts +194 -1
- package/src/server/Mppx.ts +38 -19
- package/src/server/NodeListener.test.ts +1 -1
- package/src/server/Request.test.ts +2 -1
- package/src/server/Request.ts +1 -0
- package/src/server/Response.test.ts +2 -1
- package/src/server/Transport.test.ts +2 -1
- package/src/stripe/Charge.integration.test.ts +1 -1
- package/src/stripe/Methods.test.ts +1 -1
- package/src/stripe/Methods.ts +1 -0
- package/src/stripe/client/Charge.test.ts +2 -1
- package/src/stripe/server/Charge.test.ts +2 -1
- package/src/tempo/Attribution.test.ts +2 -1
- package/src/tempo/Methods.test.ts +1 -1
- package/src/tempo/Methods.ts +1 -0
- package/src/tempo/client/ChannelOps.test.ts +7 -3
- package/src/tempo/client/ChannelOps.ts +1 -0
- package/src/tempo/client/Charge.ts +1 -0
- package/src/tempo/client/Session.test.ts +6 -2
- package/src/tempo/client/Session.ts +1 -0
- package/src/tempo/client/SessionManager.test.ts +29 -1
- package/src/tempo/client/SessionManager.ts +2 -1
- package/src/tempo/internal/auto-swap.test.ts +2 -1
- package/src/tempo/internal/auto-swap.ts +1 -0
- package/src/tempo/internal/defaults.test.ts +2 -1
- package/src/tempo/internal/fee-payer.test.ts +2 -1
- package/src/tempo/internal/fee-payer.ts +1 -0
- package/src/tempo/server/Charge.test.ts +2 -1
- package/src/tempo/server/Charge.ts +1 -0
- package/src/tempo/server/Session.test.ts +88 -37
- package/src/tempo/server/Session.ts +26 -8
- package/src/tempo/server/Sse.test.ts +2 -1
- package/src/tempo/server/internal/transport.test.ts +25 -1
- package/src/tempo/server/internal/transport.ts +11 -0
- package/src/tempo/session/Chain.test.ts +6 -2
- package/src/tempo/session/Chain.ts +2 -1
- package/src/tempo/session/Channel.test.ts +2 -1
- package/src/tempo/session/ChannelStore.test.ts +2 -1
- package/src/tempo/session/ChannelStore.ts +1 -0
- package/src/tempo/session/Receipt.test.ts +2 -1
- package/src/tempo/session/Receipt.ts +1 -0
- package/src/tempo/session/Sse.fuzz.test.ts +138 -0
- package/src/tempo/session/Sse.test.ts +2 -1
- package/src/tempo/session/Sse.ts +1 -0
- package/src/tempo/session/Voucher.test.ts +2 -1
- package/src/tempo/session/Voucher.ts +1 -0
- package/src/viem/Account.test.ts +2 -1
- package/src/viem/Client.test.ts +2 -1
- package/src/viem/Client.ts +1 -0
- package/src/zod.test.ts +147 -0
package/src/cli/cli.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import * as fs from 'node:fs'
|
|
2
2
|
import { createRequire } from 'node:module'
|
|
3
3
|
import * as path from 'node:path'
|
|
4
|
+
|
|
4
5
|
import { Cli, Errors, z } from 'incur'
|
|
5
6
|
import { Base64 } from 'ox'
|
|
6
7
|
import { type Address, createClient, http } from 'viem'
|
|
7
8
|
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
|
|
8
9
|
import { tempo as tempoMainnet } from 'viem/chains'
|
|
10
|
+
|
|
9
11
|
import * as Challenge from '../Challenge.js'
|
|
12
|
+
import { normalizeHeaders } from '../client/internal/Fetch.js'
|
|
10
13
|
import * as Mppx from '../client/Mppx.js'
|
|
14
|
+
import { validate as validateDiscovery } from '../discovery/Validate.js'
|
|
11
15
|
import { createDefaultStore, createKeychain, resolveAccountName } from './account.js'
|
|
12
16
|
import { loadConfig, resolvePlugin } from './internal.js'
|
|
13
17
|
import type { Plugin } from './plugins/plugin.js'
|
|
@@ -312,7 +316,7 @@ const cli = Cli.create('mppx', {
|
|
|
312
316
|
|
|
313
317
|
// Send credential and get response
|
|
314
318
|
const credentialHeaders = {
|
|
315
|
-
...(init.headers
|
|
319
|
+
...normalizeHeaders(init.headers),
|
|
316
320
|
Authorization: credential,
|
|
317
321
|
}
|
|
318
322
|
plugin?.prepareCredentialRequest?.({ challenge, credential, headers: credentialHeaders })
|
|
@@ -912,7 +916,168 @@ export default defineConfig({
|
|
|
912
916
|
},
|
|
913
917
|
})
|
|
914
918
|
|
|
919
|
+
const discover = Cli.create('discover', {
|
|
920
|
+
description: 'Discovery tooling',
|
|
921
|
+
})
|
|
922
|
+
.command('generate', {
|
|
923
|
+
description: 'Generate a static OpenAPI discovery document from a module',
|
|
924
|
+
args: z.object({
|
|
925
|
+
module: z.string().describe('Path to a module that default-exports a discovery config'),
|
|
926
|
+
}),
|
|
927
|
+
options: z.object({
|
|
928
|
+
output: z.string().optional().describe('Write output to a file instead of stdout'),
|
|
929
|
+
}),
|
|
930
|
+
alias: { output: 'o' },
|
|
931
|
+
async run(c) {
|
|
932
|
+
const modulePath = path.resolve(c.args.module)
|
|
933
|
+
if (!fs.existsSync(modulePath)) {
|
|
934
|
+
return c.error({
|
|
935
|
+
code: 'MODULE_NOT_FOUND',
|
|
936
|
+
message: `Module not found: ${modulePath}`,
|
|
937
|
+
exitCode: 1,
|
|
938
|
+
})
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
let mod: Record<string, unknown>
|
|
942
|
+
try {
|
|
943
|
+
mod = await import(modulePath)
|
|
944
|
+
} catch (error) {
|
|
945
|
+
return c.error({
|
|
946
|
+
code: 'MODULE_IMPORT_FAILED',
|
|
947
|
+
message: `Failed to import module: ${(error as Error).message}`,
|
|
948
|
+
exitCode: 1,
|
|
949
|
+
})
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const exported = (mod.default ?? mod) as Record<string, unknown>
|
|
953
|
+
|
|
954
|
+
// If the export is already a plain OpenAPI doc (has `openapi` key), use it directly.
|
|
955
|
+
// Otherwise, expect { mppx, ...GenerateConfig } and call generate().
|
|
956
|
+
let doc: Record<string, unknown>
|
|
957
|
+
if (typeof exported.openapi === 'string') {
|
|
958
|
+
doc = exported
|
|
959
|
+
} else {
|
|
960
|
+
const { generate } = await import('../discovery/OpenApi.js')
|
|
961
|
+
const mppx = exported.mppx as { methods: readonly any[]; realm: string }
|
|
962
|
+
if (!mppx) {
|
|
963
|
+
return c.error({
|
|
964
|
+
code: 'INVALID_MODULE',
|
|
965
|
+
message:
|
|
966
|
+
'Module must default-export an OpenAPI document (with `openapi` key) or an object with `mppx` (server instance) and `routes`.',
|
|
967
|
+
exitCode: 1,
|
|
968
|
+
})
|
|
969
|
+
}
|
|
970
|
+
doc = generate(mppx, exported as any)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
const json = JSON.stringify(doc, null, 2)
|
|
974
|
+
if (c.options.output) {
|
|
975
|
+
const outPath = path.resolve(c.options.output)
|
|
976
|
+
fs.writeFileSync(outPath, `${json}\n`)
|
|
977
|
+
process.stderr.write(`Wrote ${outPath}\n`)
|
|
978
|
+
} else {
|
|
979
|
+
console.log(json)
|
|
980
|
+
}
|
|
981
|
+
},
|
|
982
|
+
})
|
|
983
|
+
.command('validate', {
|
|
984
|
+
description: 'Validate an OpenAPI discovery document from a file or URL',
|
|
985
|
+
args: z.object({
|
|
986
|
+
input: z.string().describe('Path or URL to a discovery document'),
|
|
987
|
+
}),
|
|
988
|
+
async run(c) {
|
|
989
|
+
const input = c.args.input
|
|
990
|
+
let raw: string
|
|
991
|
+
if (/^https?:\/\//.test(input)) {
|
|
992
|
+
const controller = new AbortController()
|
|
993
|
+
const timeout = setTimeout(() => controller.abort(), 30_000)
|
|
994
|
+
let response: Response
|
|
995
|
+
try {
|
|
996
|
+
response = await globalThis.fetch(input, { signal: controller.signal })
|
|
997
|
+
} catch (error) {
|
|
998
|
+
clearTimeout(timeout)
|
|
999
|
+
const msg =
|
|
1000
|
+
error instanceof DOMException && error.name === 'AbortError'
|
|
1001
|
+
? 'Request timed out after 30s'
|
|
1002
|
+
: (error as Error).message
|
|
1003
|
+
return c.error({
|
|
1004
|
+
code: 'DISCOVERY_FETCH_FAILED',
|
|
1005
|
+
message: `Failed to fetch discovery document: ${msg}`,
|
|
1006
|
+
exitCode: 1,
|
|
1007
|
+
})
|
|
1008
|
+
}
|
|
1009
|
+
clearTimeout(timeout)
|
|
1010
|
+
if (!response.ok) {
|
|
1011
|
+
return c.error({
|
|
1012
|
+
code: 'DISCOVERY_FETCH_FAILED',
|
|
1013
|
+
message: `Failed to fetch discovery document: HTTP ${response.status}`,
|
|
1014
|
+
exitCode: 1,
|
|
1015
|
+
})
|
|
1016
|
+
}
|
|
1017
|
+
const maxSize = 10 * 1024 * 1024 // 10 MB
|
|
1018
|
+
const contentLength = response.headers.get('content-length')
|
|
1019
|
+
if (contentLength && Number(contentLength) > maxSize) {
|
|
1020
|
+
return c.error({
|
|
1021
|
+
code: 'DISCOVERY_TOO_LARGE',
|
|
1022
|
+
message: `Discovery document exceeds 10 MB limit`,
|
|
1023
|
+
exitCode: 1,
|
|
1024
|
+
})
|
|
1025
|
+
}
|
|
1026
|
+
raw = await response.text()
|
|
1027
|
+
if (raw.length > maxSize) {
|
|
1028
|
+
return c.error({
|
|
1029
|
+
code: 'DISCOVERY_TOO_LARGE',
|
|
1030
|
+
message: `Discovery document exceeds 10 MB limit`,
|
|
1031
|
+
exitCode: 1,
|
|
1032
|
+
})
|
|
1033
|
+
}
|
|
1034
|
+
} else {
|
|
1035
|
+
const resolved = path.resolve(input)
|
|
1036
|
+
if (!fs.existsSync(resolved)) {
|
|
1037
|
+
return c.error({
|
|
1038
|
+
code: 'DISCOVERY_NOT_FOUND',
|
|
1039
|
+
message: `Discovery document not found: ${resolved}`,
|
|
1040
|
+
exitCode: 1,
|
|
1041
|
+
})
|
|
1042
|
+
}
|
|
1043
|
+
raw = fs.readFileSync(resolved, 'utf-8')
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
let doc: unknown
|
|
1047
|
+
try {
|
|
1048
|
+
doc = JSON.parse(raw)
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
return c.error({
|
|
1051
|
+
code: 'DISCOVERY_INVALID_JSON',
|
|
1052
|
+
message: `Invalid discovery JSON: ${(error as Error).message}`,
|
|
1053
|
+
exitCode: 1,
|
|
1054
|
+
})
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const issues = validateDiscovery(doc)
|
|
1058
|
+
for (const issue of issues) console.log(`[${issue.severity}] ${issue.path}: ${issue.message}`)
|
|
1059
|
+
|
|
1060
|
+
const errorCount = issues.filter((issue) => issue.severity === 'error').length
|
|
1061
|
+
const warningCount = issues.filter((issue) => issue.severity === 'warning').length
|
|
1062
|
+
|
|
1063
|
+
if (errorCount > 0) {
|
|
1064
|
+
return c.error({
|
|
1065
|
+
code: 'DISCOVERY_INVALID',
|
|
1066
|
+
message: `Discovery document has ${errorCount} error(s) and ${warningCount} warning(s).`,
|
|
1067
|
+
exitCode: 1,
|
|
1068
|
+
})
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
console.log(
|
|
1072
|
+
warningCount > 0
|
|
1073
|
+
? `Discovery document is valid with ${warningCount} warning(s).`
|
|
1074
|
+
: 'Discovery document is valid.',
|
|
1075
|
+
)
|
|
1076
|
+
},
|
|
1077
|
+
})
|
|
1078
|
+
|
|
915
1079
|
cli.command(account)
|
|
1080
|
+
cli.command(discover)
|
|
916
1081
|
cli.command(init)
|
|
917
1082
|
cli.command(sign)
|
|
918
1083
|
|
package/src/cli/config.test.ts
CHANGED
package/src/cli/internal.ts
CHANGED
package/src/cli/plugins/tempo.ts
CHANGED
|
@@ -3,11 +3,14 @@ import * as fs from 'node:fs'
|
|
|
3
3
|
import { createRequire } from 'node:module'
|
|
4
4
|
import * as os from 'node:os'
|
|
5
5
|
import * as path from 'node:path'
|
|
6
|
+
|
|
6
7
|
import { Errors, z } from 'incur'
|
|
7
8
|
import { Base64 } from 'ox'
|
|
8
9
|
import type { Address } from 'viem'
|
|
9
10
|
import { createClient, http } from 'viem'
|
|
10
11
|
import { privateKeyToAccount } from 'viem/accounts'
|
|
12
|
+
|
|
13
|
+
import { normalizeHeaders } from '../../client/internal/Fetch.js'
|
|
11
14
|
import * as Credential from '../../Credential.js'
|
|
12
15
|
import { tempo as tempoMethods } from '../../tempo/client/index.js'
|
|
13
16
|
import type { SessionCredentialPayload } from '../../tempo/session/Types.js'
|
|
@@ -610,7 +613,7 @@ async function closeChannel(opts: {
|
|
|
610
613
|
const closeRes = await globalThis.fetch(opts.fetchUrl, {
|
|
611
614
|
...opts.fetchInit,
|
|
612
615
|
headers: {
|
|
613
|
-
...(opts.fetchInit.headers
|
|
616
|
+
...normalizeHeaders(opts.fetchInit.headers),
|
|
614
617
|
Authorization: closeCred,
|
|
615
618
|
},
|
|
616
619
|
})
|
package/src/cli/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Account } from 'viem'
|
|
2
|
-
import { describe, expectTypeOf, test } from '
|
|
2
|
+
import { describe, expectTypeOf, test } from 'vp/test'
|
|
3
|
+
|
|
3
4
|
import * as Method from '../Method.js'
|
|
4
5
|
import { charge } from '../tempo/client/Charge.js'
|
|
5
6
|
import { tempo } from '../tempo/client/Methods.js'
|
package/src/client/Mppx.test.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Challenge, Credential, Mcp, Method, Receipt } from 'mppx'
|
|
|
2
2
|
import { Mppx, Transport, tempo } from 'mppx/client'
|
|
3
3
|
import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
|
|
4
4
|
import { Methods } from 'mppx/tempo'
|
|
5
|
-
import { afterEach, describe, expect, test } from '
|
|
5
|
+
import { afterEach, describe, expect, test } from 'vp/test'
|
|
6
6
|
import * as Http from '~test/Http.js'
|
|
7
7
|
import { accounts, asset, client } from '~test/tempo/viem.js'
|
|
8
8
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Challenge, Credential, Mcp } from 'mppx'
|
|
2
2
|
import { Transport } from 'mppx/client'
|
|
3
3
|
import { Methods } from 'mppx/tempo'
|
|
4
|
-
import { describe, expect, test } from '
|
|
4
|
+
import { describe, expect, test } from 'vp/test'
|
|
5
5
|
|
|
6
6
|
const realm = 'api.example.com'
|
|
7
7
|
const secretKey = 'test-secret-key'
|
|
@@ -2,10 +2,11 @@ import { 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
4
|
import { createClient, defineChain } from 'viem'
|
|
5
|
-
import { describe, expect, test, vi } from '
|
|
5
|
+
import { describe, expect, test, vi } from 'vp/test'
|
|
6
6
|
import * as Http from '~test/Http.js'
|
|
7
7
|
import { rpcUrl } from '~test/tempo/prool.js'
|
|
8
8
|
import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
|
|
9
|
+
|
|
9
10
|
import * as Fetch from './Fetch.js'
|
|
10
11
|
|
|
11
12
|
const realm = 'api.example.com'
|
|
@@ -15,6 +16,7 @@ const server = Mppx_server.create({
|
|
|
15
16
|
methods: [
|
|
16
17
|
tempo_server({
|
|
17
18
|
getClient: () => client,
|
|
19
|
+
account: accounts[0],
|
|
18
20
|
}),
|
|
19
21
|
],
|
|
20
22
|
realm,
|
|
@@ -193,7 +193,7 @@ export function restore(): void {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/** @internal Normalizes headers to a plain object for spreading. */
|
|
196
|
-
function normalizeHeaders(headers: unknown): Record<string, string> {
|
|
196
|
+
export function normalizeHeaders(headers: unknown): Record<string, string> {
|
|
197
197
|
if (!headers) return {}
|
|
198
198
|
if (headers instanceof Headers) {
|
|
199
199
|
const result: Record<string, string> = {}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { DiscoveryDocument, PaymentInfo, ServiceInfo } from './Discovery.js'
|
|
2
|
+
|
|
3
|
+
describe('PaymentInfo', () => {
|
|
4
|
+
test('parses a valid charge payment info', () => {
|
|
5
|
+
const result = PaymentInfo.safeParse({
|
|
6
|
+
amount: '1000',
|
|
7
|
+
intent: 'charge',
|
|
8
|
+
method: 'tempo',
|
|
9
|
+
})
|
|
10
|
+
expect(result.success).toBe(true)
|
|
11
|
+
expect(result.data).toEqual({ amount: '1000', intent: 'charge', method: 'tempo' })
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('parses a session with null amount', () => {
|
|
15
|
+
const result = PaymentInfo.safeParse({
|
|
16
|
+
amount: null,
|
|
17
|
+
intent: 'session',
|
|
18
|
+
method: 'tempo',
|
|
19
|
+
})
|
|
20
|
+
expect(result.success).toBe(true)
|
|
21
|
+
expect(result.data?.amount).toBeNull()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('accepts custom intents', () => {
|
|
25
|
+
const result = PaymentInfo.safeParse({
|
|
26
|
+
amount: '100',
|
|
27
|
+
intent: 'subscribe',
|
|
28
|
+
method: 'tempo',
|
|
29
|
+
})
|
|
30
|
+
expect(result.success).toBe(true)
|
|
31
|
+
expect(result.data?.intent).toBe('subscribe')
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('rejects invalid amount pattern', () => {
|
|
35
|
+
const result = PaymentInfo.safeParse({
|
|
36
|
+
amount: '01',
|
|
37
|
+
intent: 'charge',
|
|
38
|
+
method: 'tempo',
|
|
39
|
+
})
|
|
40
|
+
expect(result.success).toBe(false)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('accepts x402 format with unknown fields', () => {
|
|
44
|
+
const result = PaymentInfo.safeParse({
|
|
45
|
+
price: '0.54',
|
|
46
|
+
pricingMode: 'fixed',
|
|
47
|
+
protocols: ['x402', 'mpp'],
|
|
48
|
+
})
|
|
49
|
+
expect(result.success).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('ServiceInfo', () => {
|
|
54
|
+
test('parses a full service info', () => {
|
|
55
|
+
const result = ServiceInfo.safeParse({
|
|
56
|
+
categories: ['ai', 'search'],
|
|
57
|
+
docs: {
|
|
58
|
+
apiReference: 'https://example.com/api',
|
|
59
|
+
homepage: 'https://example.com',
|
|
60
|
+
llms: 'https://example.com/llms.txt',
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
expect(result.success).toBe(true)
|
|
64
|
+
expect(result.data?.categories).toEqual(['ai', 'search'])
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('accepts relative paths for doc links', () => {
|
|
68
|
+
const result = ServiceInfo.safeParse({
|
|
69
|
+
docs: {
|
|
70
|
+
llms: '/llms.txt',
|
|
71
|
+
apiReference: '/docs/api',
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
expect(result.success).toBe(true)
|
|
75
|
+
expect(result.data?.docs?.llms).toBe('/llms.txt')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('rejects invalid doc URIs', () => {
|
|
79
|
+
const result = ServiceInfo.safeParse({
|
|
80
|
+
docs: {
|
|
81
|
+
homepage: 'not-a-uri',
|
|
82
|
+
},
|
|
83
|
+
})
|
|
84
|
+
expect(result.success).toBe(false)
|
|
85
|
+
})
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
describe('DiscoveryDocument', () => {
|
|
89
|
+
test('parses a minimal document', () => {
|
|
90
|
+
const result = DiscoveryDocument.safeParse({
|
|
91
|
+
info: { title: 'Test', version: '1.0.0' },
|
|
92
|
+
openapi: '3.1.0',
|
|
93
|
+
})
|
|
94
|
+
expect(result.success).toBe(true)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('parses a document with discovery extensions', () => {
|
|
98
|
+
const result = DiscoveryDocument.safeParse({
|
|
99
|
+
info: { title: 'Test', version: '1.0.0' },
|
|
100
|
+
openapi: '3.1.0',
|
|
101
|
+
paths: {
|
|
102
|
+
'/search': {
|
|
103
|
+
post: {
|
|
104
|
+
'x-payment-info': {
|
|
105
|
+
amount: '100',
|
|
106
|
+
intent: 'charge',
|
|
107
|
+
method: 'tempo',
|
|
108
|
+
},
|
|
109
|
+
responses: {
|
|
110
|
+
'200': { description: 'OK' },
|
|
111
|
+
'402': { description: 'Payment Required' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
'x-service-info': {
|
|
117
|
+
categories: ['search'],
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
expect(result.success).toBe(true)
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('accepts path items with summary, parameters, and extensions', () => {
|
|
124
|
+
const result = DiscoveryDocument.safeParse({
|
|
125
|
+
info: { title: 'Test', version: '1.0.0' },
|
|
126
|
+
openapi: '3.1.0',
|
|
127
|
+
paths: {
|
|
128
|
+
'/search': {
|
|
129
|
+
summary: 'Search endpoints',
|
|
130
|
+
parameters: [{ name: 'q', in: 'query' }],
|
|
131
|
+
'x-custom': 'hello',
|
|
132
|
+
post: {
|
|
133
|
+
'x-payment-info': {
|
|
134
|
+
amount: '100',
|
|
135
|
+
intent: 'charge',
|
|
136
|
+
method: 'tempo',
|
|
137
|
+
},
|
|
138
|
+
responses: { '402': { description: 'Payment Required' } },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
expect(result.success).toBe(true)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test('rejects missing info', () => {
|
|
147
|
+
const result = DiscoveryDocument.safeParse({
|
|
148
|
+
openapi: '3.1.0',
|
|
149
|
+
})
|
|
150
|
+
expect(result.success).toBe(false)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as z from '../zod.js'
|
|
2
|
+
|
|
3
|
+
const uriOrPathPattern = /^([a-zA-Z][a-zA-Z\d+.-]*:\/\/\S+|\/\S*)$/
|
|
4
|
+
|
|
5
|
+
function uriOrPath() {
|
|
6
|
+
return z.string().check(z.regex(uriOrPathPattern, 'Invalid URI or path'))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Schema for the `x-payment-info` OpenAPI extension on an operation.
|
|
11
|
+
*
|
|
12
|
+
* Only validates spec-defined fields when present; unknown fields are ignored.
|
|
13
|
+
* Discovery is advisory only. Runtime 402 challenges remain authoritative.
|
|
14
|
+
*/
|
|
15
|
+
export const PaymentInfo = z.looseObject({
|
|
16
|
+
amount: z.optional(
|
|
17
|
+
z.union([z.null(), z.string().check(z.regex(/^(0|[1-9][0-9]*)$/, 'Invalid amount'))]),
|
|
18
|
+
),
|
|
19
|
+
currency: z.optional(z.string()),
|
|
20
|
+
description: z.optional(z.string()),
|
|
21
|
+
intent: z.optional(z.string()),
|
|
22
|
+
method: z.optional(z.string()),
|
|
23
|
+
})
|
|
24
|
+
export type PaymentInfo = z.infer<typeof PaymentInfo>
|
|
25
|
+
|
|
26
|
+
const ServiceDocs = z.looseObject({
|
|
27
|
+
apiReference: z.optional(uriOrPath()),
|
|
28
|
+
homepage: z.optional(uriOrPath()),
|
|
29
|
+
llms: z.optional(uriOrPath()),
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Schema for the `x-service-info` OpenAPI extension at the document root.
|
|
34
|
+
*/
|
|
35
|
+
export const ServiceInfo = z.looseObject({
|
|
36
|
+
categories: z.optional(z.array(z.string())),
|
|
37
|
+
docs: z.optional(ServiceDocs),
|
|
38
|
+
})
|
|
39
|
+
export type ServiceInfo = z.infer<typeof ServiceInfo>
|
|
40
|
+
|
|
41
|
+
const OperationObject = z.looseObject({
|
|
42
|
+
'x-payment-info': z.optional(PaymentInfo),
|
|
43
|
+
requestBody: z.optional(z.unknown()),
|
|
44
|
+
responses: z.optional(z.record(z.string(), z.unknown())),
|
|
45
|
+
summary: z.optional(z.string()),
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const PathItem = z.looseObject({
|
|
49
|
+
delete: z.optional(OperationObject),
|
|
50
|
+
get: z.optional(OperationObject),
|
|
51
|
+
head: z.optional(OperationObject),
|
|
52
|
+
options: z.optional(OperationObject),
|
|
53
|
+
patch: z.optional(OperationObject),
|
|
54
|
+
post: z.optional(OperationObject),
|
|
55
|
+
put: z.optional(OperationObject),
|
|
56
|
+
trace: z.optional(OperationObject),
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Minimal schema for an OpenAPI discovery document annotated with
|
|
61
|
+
* `x-service-info` and per-operation `x-payment-info`.
|
|
62
|
+
*/
|
|
63
|
+
export const DiscoveryDocument = z.looseObject({
|
|
64
|
+
openapi: z.string(),
|
|
65
|
+
info: z.looseObject({
|
|
66
|
+
title: z.string(),
|
|
67
|
+
version: z.string(),
|
|
68
|
+
}),
|
|
69
|
+
'x-service-info': z.optional(ServiceInfo),
|
|
70
|
+
paths: z.optional(z.record(z.string(), PathItem)),
|
|
71
|
+
})
|
|
72
|
+
export type DiscoveryDocument = z.infer<typeof DiscoveryDocument>
|