feat(cli): add mppx validate command for end-to-end server validation#595
Conversation
Validates MPP server implementations by testing the full protocol flow:
discovery, challenge structure, error handling, and payment round-trip.
Structure:
src/cli/validate/
index.ts - command definition and orchestration
challenge.ts - challenge parsing and error handling checks
payment.ts - payment flow, wallet provisioning, receipt validation
discovery.ts - OpenAPI fetching, endpoint extraction, body generation
helpers.ts - shared types, utilities, output formatting
Features:
- Zero-setup testnet validation (ephemeral wallet + faucet)
- Pre-flight balance check on mainnet with correct wallet resolution
- --yes/-y for non-interactive payment approval
- --endpoint for manual single-endpoint testing (skips discovery)
- --body with per-path mapping support in discovery mode
- Bare-request-first strategy (retry with body on 400)
- Recursive request body generation from OpenAPI schemas
- Correctly identifies x402 endpoints as non-MPP
- Tempo explorer links for on-chain proof
- 25 end-to-end integration tests
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GIT_VALID_PII_OVERRIDE
brendanjryan
left a comment
There was a problem hiding this comment.
I like this! Some notes on implementation complexity / structure
|
|
||
| export const HTTP_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']) | ||
|
|
||
| export const MAINNET_CHAIN_ID = 4217 |
There was a problem hiding this comment.
assuming this will only work for tempo? I thin we can pull this and some of other others from shared constants
| const validate = Cli.create('validate', { | ||
| description: 'Validate an MPP server implementation end-to-end', | ||
| args: z.object({ | ||
| url: z.string().describe('Base URL of the MPP server to validate'), |
There was a problem hiding this comment.
can this work on local services too?
There was a problem hiding this comment.
yep this will work on local services!
| .default(0) | ||
| .meta({ count: true }) | ||
| .describe('Verbosity level'), | ||
| yes: z.boolean().default(false).describe('Auto-approve mainnet payments'), |
There was a problem hiding this comment.
testnet as well? or are testnet payments automatically approved always?
There was a problem hiding this comment.
they're automatically approved (does that sound reasonable to you?)
There was a problem hiding this comment.
yeah I think that's fine on testnet
| process.exit(1) | ||
| } | ||
|
|
||
| // Summary |
There was a problem hiding this comment.
this method is a little gnarly / long .. can we separate each stage into its own functional check?
There was a problem hiding this comment.
agreed, let me take a stab at that!
| const account = privateKeyToAccount(key) | ||
|
|
||
| const { createClient, http } = await import('viem') | ||
| const { tempoModerato } = await import('viem/tempo/chains') |
There was a problem hiding this comment.
why do we need so many async imports here?
There was a problem hiding this comment.
ah, it's so make the CLI a bit faster (saves about ~250ms upfront). this is probably a premature optimization, I'll undo this.
| @@ -0,0 +1,391 @@ | |||
| import * as Challenge from '../../Challenge.js' | |||
There was a problem hiding this comment.
from a skim this file seems correct but also duplicates a ton of functinoality and relies on a lot of manual parsing -- is this strictly necessary? can we pull from more things in mppx
There was a problem hiding this comment.
I was able to re-use a bit more, but the core reason why this duplicates so much is that mppx.fetch is a monolith that does all of the following at once:
- Makes initial request
- Checks if 402
- Selects challenge + method
- Creates credential
- Retries with credential
- Emits events
We want to run each piece at a time here to validate. We could consider decomposing that monolith fetch into the parts we need here?
There was a problem hiding this comment.
ah I see -- I think this is fine to have a little verbose for now. we can always collapse later
6678f63 to
45aa1d3
Compare
45aa1d3 to
370b133
Compare
commit: |
- Use shared tempoChainIds from internal/defaults instead of hardcoded constant - Extract printResults/Counts to deduplicate result counting - Switch to static imports in payment.ts (measured ~230ms, negligible) - Auto-skip mainnet prompt in non-interactive mode (no TTY) - Extract resolveWalletAddress to shared account.ts helper - Break run() into discoverEndpoints/validateEndpoints/printSummary phases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Committed-By-Agent: claude
370b133 to
585ddcc
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> GIT_VALID_PII_OVERRIDE
Overview
Validates MPP server implementations by testing discovery, challenges, error handling, and end-to-end payment flows.
Right now, this only does end-to-end livenet/testnet payments with tempo, but I plan to add support for stripe payments in a follow-up.
When a server defines a
openapi.json, the validate command automatically discovers and hits machine-payable endpoints, validating their behavior and error handling. The tool constructs request bodies for these endpoints in the following priority order (1) one provided explicitly with--body, (2) an example body documented in theopenapi.json, (3) an auto-generated body using the API schema.CLI subcommand structure:
Demonstration
livenet server with
openapi.json:Without an
openapi.json, the CLI printsThe user can then pass a
--endpointoverride to testIf a server supporting the testnet is provided, the CLI will automatically provision and fund a testnet wallet for testing.