feat: MCP server + Paytaca AI integration (ai configure, provider, API keys) - #7
Conversation
- expose wallet and Paytaca AI tools over MCP (src/mcp/*) - add ai commands for models, plans, credits, purchase, auto-refill, chat - replace packaged agent skills with MCP integration - bump version to 0.7.0
The backend re-quotes price_sats from the live BCH rate, so probe samples never match exactly. Allow up to 5% drift and only fail on unreachability or wild swings.
Chat was a plain completion with no tool-calling, so the model could not request agent tools. Remove it until proper tool passthrough is implemented.
- retire `paytaca mcp config` in favor of `paytaca ai configure <harness>` - provision a wallet-bound API key via BCH OAuth and write the opencode provider (base URL, models, key) plus the MCP server - offer a plan purchase when no credits are active - support read-only wallets: add `paytaca ai api-key create` and `--api-key` handoff - tolerate JSONC harness configs (comments, trailing commas)
Code Review: PR #7 — MCP server + Paytaca AI integrationBuild & Tests
Security Findings1. Pre-existing bug:
|
| Area | Verdict |
|---|---|
| OAuth / API key provisioning | ✅ Correct argument order, isolated derivation path |
| MCP server / tool schemas | ✅ Proper destructive annotations, address validation |
| x402 / purchase flow | ✅ Plan probe, 402 parsing, payment+retry are sound |
| Read-only wallet support | ✅ Good split; minor gap in MCP get_credits |
| Config merging (JSONC) | ✅ Deep merge preserves siblings, strips comments safely |
| Tests | ✅ 172/172 passing |
| Build | ✅ Clean |
Recommendation: Approve after addressing (or ticket-ing) the two findings above. The pre-existing signMessageBCH bug is out of scope for this PR but should be fixed in a follow-up.
- swap signMessageHashDER args in src/utils/x402.ts (privateKey, messageHash) and add a regression test verifying the DER signature - resolve wallet via loadWalletRef() in MCP get_credits so read-only wallets can report credits
Code Review: PR #7 — MCP server + Paytaca AI integrationOverviewThis is a substantial, well-architected PR. The OAuth flow, MCP tool design, and plan-stability probe are sound. Tests cover the new surface well. I found 4 actionable bugs in the CLI layer and need to correct 2 false positives from the prior automated review. Bugs & Issues1.
|
| Command | Uses opts.backendUrl? |
Broken? |
|---|---|---|
ai models |
yes | ✅ broken |
ai plans |
yes | ✅ broken |
ai credits |
yes | ✅ broken |
ai usage |
yes | ✅ broken |
ai purchase |
yes | ✅ broken |
ai configure |
opts.backend |
❌ works |
Fix: Rename the property accesses to opts.backend, or rename the option declaration to --backend-url.
2. ai auto-refill --enable accepts non-numeric values without validation
Severity: Medium — stores invalid state
src/commands/ai.ts:672-679
minutes: opts.minutes !== undefined ? Number(opts.minutes) : undefined,
maxMinutes: opts.maxMinutes !== undefined ? Number(opts.maxMinutes) : undefined,Number("abc") yields NaN. Because NaN !== undefined is true, NaN is passed to armAutoRefill and persisted to ~/.paytaca/auto-refill.json. remainingBudget() then returns NaN, and autoRefillCanBuy() can return true unexpectedly.
Fix: Validate with Number.isFinite() before calling armAutoRefill, mirroring the validation in ai purchase.
3. CLI ai credits and ai usage do not support read-only wallets
Severity: Low — functionality gap
The PR description states: "Read-only wallets report credits from the shared wallet hash." The MCP get_credits tool correctly uses loadWalletRef(), but the CLI commands use requireWallet():
src/commands/ai.ts:396(ai credits)src/commands/ai.ts:453(ai usage)
A read-only wallet can query credits via MCP but gets WalletNotConfiguredError via CLI.
Fix: Use loadWalletRef() and pass walletHash to getWalletStatus.
4. API key written to harness config without restricted file permissions
Severity: Low — information disclosure on multi-user systems
writeJsonFile() in src/ai/configure.ts:215-218 creates ~/.config/opencode/opencode.json without a mode option. With a typical umask, the file is world-readable (0o644), exposing the Paytaca API key to other users on the same machine.
Fix: Pass mode: 0o600 to writeFileSync (or chmod after write), matching the correct approach used for auto-refill.json.
Corrections to Prior Automated Review
Finding A: signMessageBCH argument order
Verdict: Incorrect — no bug exists.
The automated review claimed src/utils/x402.ts:138 passes arguments as (hash, privateKey). The actual code is:
const signature = secp256k1.signMessageHashDER(privateKey, hash)This is the same correct order used in src/ai/oauth.ts:54 (privateKey, digest). The test at src/utils/x402.test.ts:253-282 verifies the signature with verifySignatureDER and passes.
Finding B: MCP get_credits fails on read-only wallets
Verdict: Incorrect — read-only wallets are already supported.
The automated review claimed the tool calls requireWallet. The actual implementation uses loadWalletRef():
// src/mcp/tools.ts:313-315
const wallet = loadWalletRef()
if (!wallet) throw new WalletNotConfiguredError()
const status = await getWalletStatus(wallet.walletHash, { ... })This correctly returns the hash without requiring a signing mnemonic.
Security Assessment
| Area | Verdict | Notes |
|---|---|---|
| OAuth signing / key derivation | ✅ | Correct signMessageHashDER(privateKey, digest) order; isolated path m/44'/145'/0'/2/0 |
| API key provisioning | ✅ | Challenge-response flow is sound; 409 idempotency handled |
| MCP tool annotations | ✅ | send, buy_plan, auto_refill correctly marked destructiveHint: true |
| x402 purchase flow | ✅ | 3-sample probe with 5% drift tolerance; confirmed: true only for MCP (host approval) |
| Auto-refill file permissions | ✅ | 0o600 for ~/.paytaca/auto-refill.json |
| Config merging / JSONC parser | ✅ | Deep merge preserves siblings; stripJsonComments correctly handles escaped quotes and // inside strings |
| API key in harness config | Documented trade-off; fix file permissions as noted above | |
| Read-only wallet handoff | ✅ | loadWalletRef() cleanly separates canSign; ai api-key create guards correctly |
Tests
- Build:
tscclean (0 errors) - Tests: 172/172 passing across 14 files
- Coverage is solid for OAuth verification, configure merging, JSONC parsing, MCP tool schemas, credits/active-session logic, and auto-refill budget math.
Recommendation
Approve after addressing (or ticket-ing) bugs 1–4 above. Bug 1 is the most user-facing; the --backend override is completely non-functional in the majority of AI commands. Bugs 2 and 4 are security-adjacent and quick fixes. Bug 3 is a CLI/MCP parity gap.
…config perms - read opts.backend (not opts.backendUrl) for ai models/plans/credits/usage/purchase - validate --minutes/--max-minutes in ai auto-refill before persisting state - resolve ai credits/usage via loadWalletRef() so read-only wallets work - write harness config with 0600 file permissions to protect the API key
Code Review: PR #7 — MCP server + Paytaca AI integrationBuild & Tests
Corrections to Prior Automated ReviewThe second automated review flagged 4 actionable bugs that are all false positives against the current branch:
The pre-existing Security Assessment
Suggestions (non-blocking)1. Add BCH address validation in
|
- reject an invalid server BCH payment address before broadcasting in buyPlan - chmod 0600 the appended TOML harness config for consistency - resolve promptSecret on stdin end so closed/piped input cannot hang
Code Review: PR #7 — MCP server + Paytaca AI integrationBuild & Tests
Independent Verification of Prior Automated Review ClaimsI read the current branch directly to verify the disputed findings from earlier automated reviews.
Verification of Review 3 Suggestions (Current Branch Status)The third automated review raised three non-blocking suggestions. All three are already implemented in the code as it stands on this branch:
Security Assessment
Minor Observations (Non-blocking)
SummaryNo security blockers or functional bugs were found in the new code. The prior automated review disputes are resolved: all claimed bugs were false positives, and the three suggestions from the third review are already implemented in the branch. The architecture is sound — OAuth keys are isolated, API keys are scoped and stored with restricted permissions, MCP spending tools are properly annotated, and read-only wallets are supported cleanly. Recommendation: Approve. |




Summary
Adds first-class AI agent integration to the CLI: a stdio MCP server, a full set of Paytaca AI commands (models, plans, credits, usage, balance, plan purchase, auto-refill), and a one-step
paytaca ai configure <harness>that installs the MCP server and the Paytaca AI provider (models + wallet-bound API key) so Paytaca AI models are usable immediately. Also refactors the wallet commands onto shared core helpers and replaces the packaged agent skills with MCP.What's included
MCP server (
src/mcp/)paytaca mcp,--chipnet) exposing wallet and AI tools.get_balance,get_transactions,get_receiving_address,get_tokens,send.get_models,get_plans,get_credits,buy_plan,auto_refill,get_help.zod;@modelcontextprotocol/sdkadded as a dependency.ai chat/ai_chat: chat was a plain completion with no tool-calling, so the model could not request agent tools. Removed until proper tool passthrough lands.Paytaca AI commands (
src/ai/,src/commands/ai.ts)ai models,ai plans,ai credits,ai usage,ai balance.ai purchase— full x402 flow for BCH and LIFT (via Cauldron), with confirmation.ai auto-refill— arm/disarm/status with a budget cap.ai creditsnow reports all active sessions (CLI and MCP).fix: tolerate live price drift in AI plan stability probe— the backend re-quotesprice_satsfrom the live BCH rate, so probe samples never match exactly; allow up to 5% drift and only fail on unreachability or wild swings.One-step harness configuration (
ai configure)paytaca mcp configin favor ofpaytaca ai configure [harness](defaults toopencode; reuses the existing templates forclaude | opencode | cursor | codex | pi | generic).paytaca-aiprovider (base URL, full model catalogue, API key) plus thepaytacaMCP server into~/.config/opencode/opencode.json.src/ai/oauth.ts): signs thebitcoincash-oauth|oauth|<walletHash>|<ts>challenge, registers/tokenizes, then creates the key withPOST /v1/api-keys. The rawsk-pytc-…key is written once and shown only on creation.Read-only wallets
loadWalletRef()(src/wallet/index.ts) exposes{ walletHash, mnemonic?, canSign }without requiring a signing seed.ai configureon a read-only wallet prompts for an API key (masked TTY prompt) or accepts--api-key sk-pytc-….paytaca ai api-key create(--name,--backend,--json) runs from a full wallet and prints the key once for handoff to a read-only machine.CLI refactor
src/core/context.ts(WalletContext,requireWallet,tryWallet),src/core/wallet.ts(balance/history/address helpers),src/utils/format.ts(sats/BCH/USD formatting).address,balance,history,receive,send, andtokencommands refactored onto these helpers, removing duplicated wallet/format logic.Breaking change / packaging
paytaca mcp config— usepaytaca ai configure <harness>.skills/directory and theskillsentry frompackage.json#files; agent integration is now delivered via MCP.0.7.0; added@modelcontextprotocol/sdkandzod.Testing
npm run build(tsc) passes.npm testpasses (172 tests, 14 files), including new tests for AI models/credits/auto-refill, MCP tool schemas, configure merging/JSONC parsing, and OAuth signing.paytaca ai configure,paytaca ai api-key, andpaytaca mcphelp output.Notes
provider['paytaca-ai'].options.apiKey. It is a scoped identity key; usage is still metered against the wallet's AI credits (a direct key does not bypass x402/402 responses).signMessageBCHinsrc/utils/x402.tscallssecp256k1.signMessageHashDER(hash, privateKey)with the arguments swapped relative to libauth's(privateKey, messageHash)signature. The new OAuth code uses the correct order; the x402 helper was left untouched.