troxy-cli 1.4.15 → 1.4.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "troxy-cli",
3
- "version": "1.4.15",
3
+ "version": "1.4.17",
4
4
  "description": "AI payment control — protect your agent's payments with policies",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -55,6 +55,7 @@ export const api = {
55
55
  // Evaluate + confirm (agent API key)
56
56
  evaluate: (body, apiKey) => request('POST', '/evaluate', { apiKey, body }),
57
57
  confirmPayment: (auditId, body, apiKey) => request('POST', `/payments/${auditId}/confirm`, { apiKey, body }),
58
+ waitApprovalStatus: (token) => request('GET', `/approvals/${encodeURIComponent(token)}/wait`),
58
59
 
59
60
  // MCP heartbeat (agent API key)
60
61
  mcpHeartbeat: (apiKey, agentName) => request('POST', '/mcp/heartbeat', { apiKey, body: agentName ? { agent_name: agentName } : undefined }),
package/src/mcp-server.js CHANGED
@@ -35,7 +35,7 @@ export async function runMcp() {
35
35
  'You MUST call the evaluate_payment tool before completing any purchase, payment, or financial transaction. ' +
36
36
  'Never submit a payment form, confirm a checkout, or transfer funds without first receiving an ALLOW decision from evaluate_payment. ' +
37
37
  'If the decision is BLOCK, abort the transaction and inform the user. ' +
38
- 'If the decision is ESCALATE, the response includes an approval_token. Wait for the user to approve, then call evaluate_payment again with the same payment details PLUS the approval_token field. The second call will return ALLOW without re-escalating. ' +
38
+ 'If the decision is ESCALATE, the response includes an approval_token. Immediately call wait_for_payment_approval with that token — it polls automatically until the payment is approved or declined (up to 10 minutes). Once it returns approved, call evaluate_payment again with the same payment details PLUS the approval_token field. The second call will return ALLOW without re-escalating. ' +
39
39
  'If the decision is NOTIFY, proceed but the user will be notified. ' +
40
40
  'When calling evaluate_payment, use the merchant\'s domain name as merchant_name when available (e.g. "amazon.com", "indigobloom.co.il") — this ensures consistent matching against policies. ' +
41
41
  'After every ALLOW or NOTIFY decision, attempt the payment, then call confirm_payment with the audit_id and the outcome (success/failed/cancelled). Always include a reason when the charge fails.',
@@ -80,6 +80,23 @@ export async function runMcp() {
80
80
  },
81
81
  },
82
82
  },
83
+ {
84
+ name: 'wait_for_payment_approval',
85
+ description:
86
+ 'Poll Troxy every 10 seconds until a pending payment approval is resolved. ' +
87
+ 'Call this immediately after receiving an ESCALATE decision. ' +
88
+ 'It will block until the account owner approves or declines (up to 10 minutes), then return the result automatically.',
89
+ inputSchema: {
90
+ type: 'object',
91
+ required: ['approval_token'],
92
+ properties: {
93
+ approval_token: {
94
+ type: 'string',
95
+ description: 'The approval_token from the ESCALATE response',
96
+ },
97
+ },
98
+ },
99
+ },
83
100
  {
84
101
  name: 'evaluate_payment',
85
102
  description:
@@ -123,6 +140,51 @@ export async function runMcp() {
123
140
  const toolName = request.params.name;
124
141
  const args = request.params.arguments ?? {};
125
142
 
143
+ if (toolName === 'wait_for_payment_approval') {
144
+ const { approval_token } = args;
145
+ const MAX_MS = 60 * 60_000; // overall cap: 1 hour
146
+ const deadline = Date.now() + MAX_MS;
147
+
148
+ // Long-poll: each request blocks on the server for ~20s, returning the
149
+ // moment the approval is resolved. No sleep needed — instant detection.
150
+ while (Date.now() < deadline) {
151
+ let status;
152
+ try {
153
+ status = await api.waitApprovalStatus(approval_token);
154
+ } catch (err) {
155
+ return {
156
+ content: [{ type: 'text', text: `Troxy error waiting for approval: ${err.message}` }],
157
+ isError: true,
158
+ };
159
+ }
160
+
161
+ if (status.status === 'APPROVED') {
162
+ return {
163
+ content: [{ type: 'text', text: `✅ Payment approved! Call evaluate_payment again with the same payment details and approval_token: "${approval_token}" to proceed.` }],
164
+ isError: false,
165
+ };
166
+ }
167
+ if (status.status === 'DECLINED') {
168
+ return {
169
+ content: [{ type: 'text', text: `❌ Payment declined by the account owner. Do not proceed with this payment.` }],
170
+ isError: true,
171
+ };
172
+ }
173
+ if (status.status === 'EXPIRED') {
174
+ return {
175
+ content: [{ type: 'text', text: `⏱ Approval request has expired. The payment cannot be approved.` }],
176
+ isError: true,
177
+ };
178
+ }
179
+ // status === 'pending': server timed out its 20s window, loop immediately
180
+ }
181
+
182
+ return {
183
+ content: [{ type: 'text', text: `⏳ Still waiting for approval after 1 hour. The request has likely expired.` }],
184
+ isError: true,
185
+ };
186
+ }
187
+
126
188
  if (toolName === 'confirm_payment') {
127
189
  const { audit_id, status, provider, provider_transaction_id, reason } = args;
128
190
  try {
@@ -167,7 +229,7 @@ export async function runMcp() {
167
229
  text = `✗ Payment blocked by policy "${policy}". Do not proceed with this payment. (audit: ${audit_id})`;
168
230
  break;
169
231
  case 'ESCALATE':
170
- text = `⏳ Payment requires human approval — a request has been sent to the account owner. Do not proceed until approved.\n\nApproval token: ${approval_token}\n\nWait for the owner to approve in the Troxy dashboard, then tell me "I approved it" or "continue" so I can proceed. Once you do, I will call evaluate_payment again with the same payment details and approval_token: "${approval_token}". (audit: ${audit_id})`;
232
+ text = `⏳ Payment requires human approval — a request has been sent to the account owner.\n\nApproval token: ${approval_token}\n\nNow call wait_for_payment_approval(approval_token="${approval_token}") to automatically detect approval. Do not proceed until it returns approved. (audit: ${audit_id})`;
171
233
  break;
172
234
  case 'NOTIFY':
173
235
  text = `✓ Payment approved with notification. Policy matched: "${policy}". (audit: ${audit_id})\n\nAfter the charge attempt completes, call confirm_payment with audit_id "${audit_id}" and status "success", "failed", or "cancelled".`;