troxy-cli 1.4.14 → 1.4.16
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/bin/troxy.js +4 -2
- package/package.json +1 -1
- package/src/api.js +1 -0
- package/src/mcp-server.js +66 -2
package/bin/troxy.js
CHANGED
|
@@ -355,8 +355,10 @@ switch (command) {
|
|
|
355
355
|
} catch (err) {
|
|
356
356
|
const stderr = err.stderr?.toString() || '';
|
|
357
357
|
if (stderr.includes('ENOTEMPTY')) {
|
|
358
|
-
// npm bug on macOS:
|
|
359
|
-
|
|
358
|
+
// npm bug on macOS/Homebrew: rename fails because dir has contents.
|
|
359
|
+
// rm -rf the stale dir directly, then reinstall cleanly.
|
|
360
|
+
const globalRoot = execSync('npm root -g', { stdio: 'pipe' }).toString().trim();
|
|
361
|
+
execSync(`rm -rf "${globalRoot}/troxy-cli" && npm install -g troxy-cli@latest`, { stdio: 'pipe', shell: true });
|
|
360
362
|
} else {
|
|
361
363
|
throw err;
|
|
362
364
|
}
|
package/package.json
CHANGED
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
|
+
checkApprovalStatus: (token) => request('GET', `/approvals/${encodeURIComponent(token)}`),
|
|
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.
|
|
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,53 @@ 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 POLL_MS = 10_000; // check every 10 seconds
|
|
146
|
+
const MAX_MS = 10 * 60_000; // give up after 10 minutes
|
|
147
|
+
const deadline = Date.now() + MAX_MS;
|
|
148
|
+
|
|
149
|
+
while (Date.now() < deadline) {
|
|
150
|
+
let status;
|
|
151
|
+
try {
|
|
152
|
+
status = await api.checkApprovalStatus(approval_token);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
return {
|
|
155
|
+
content: [{ type: 'text', text: `Troxy error checking approval: ${err.message}` }],
|
|
156
|
+
isError: true,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (status.resolution === 'APPROVED') {
|
|
161
|
+
return {
|
|
162
|
+
content: [{ type: 'text', text: `✅ Payment approved! Call evaluate_payment again with the same payment details and approval_token: "${approval_token}" to proceed.` }],
|
|
163
|
+
isError: false,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (status.resolution === 'DECLINED') {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: 'text', text: `❌ Payment declined by the account owner. Do not proceed with this payment.` }],
|
|
169
|
+
isError: true,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check for expiry
|
|
174
|
+
if (status.expires_at && new Date(status.expires_at) < new Date()) {
|
|
175
|
+
return {
|
|
176
|
+
content: [{ type: 'text', text: `⏱ Approval request has expired. The payment cannot be approved.` }],
|
|
177
|
+
isError: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await new Promise(resolve => setTimeout(resolve, POLL_MS));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: 'text', text: `⏳ Still waiting for approval after 10 minutes. Call wait_for_payment_approval again to keep checking, or let the user know it is still pending.` }],
|
|
186
|
+
isError: false,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
126
190
|
if (toolName === 'confirm_payment') {
|
|
127
191
|
const { audit_id, status, provider, provider_transaction_id, reason } = args;
|
|
128
192
|
try {
|
|
@@ -167,7 +231,7 @@ export async function runMcp() {
|
|
|
167
231
|
text = `✗ Payment blocked by policy "${policy}". Do not proceed with this payment. (audit: ${audit_id})`;
|
|
168
232
|
break;
|
|
169
233
|
case 'ESCALATE':
|
|
170
|
-
text = `⏳ Payment requires human approval — a request has been sent to the account owner
|
|
234
|
+
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
235
|
break;
|
|
172
236
|
case 'NOTIFY':
|
|
173
237
|
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".`;
|