agenticbtc-mcp 1.0.9 → 1.0.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/package.json +1 -1
- package/src/server.js +101 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenticbtc-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "Privacy-intelligent payments for AI agents — your privacy, your choice. Universal payment router with Lightning, Strike, Coinbase, PayPal, Venmo support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
package/src/server.js
CHANGED
|
@@ -771,6 +771,107 @@ server.tool(
|
|
|
771
771
|
}
|
|
772
772
|
);
|
|
773
773
|
|
|
774
|
+
// Tool: Universal send_payment — auto-detects recipient type
|
|
775
|
+
server.tool(
|
|
776
|
+
"send_payment",
|
|
777
|
+
"Send a payment to any recipient. Automatically detects recipient type: BOLT11 Lightning invoice (lnbc.../lntb...), Lightning address (user@domain.com), or Strike handle ($username). Routes through the optimal payment rail.",
|
|
778
|
+
{
|
|
779
|
+
recipient: z.string().describe("Where to send: Lightning invoice (lnbc.../lntb...), Lightning address (user@domain.com), or Strike handle ($username)"),
|
|
780
|
+
amount_sats: z.number().optional().describe("Amount in satoshis (required for Lightning address and Strike handle; optional for BOLT11 invoices which encode the amount)"),
|
|
781
|
+
amount_usd: z.number().optional().describe("Amount in USD (for Strike handle payments; alternative to amount_sats)"),
|
|
782
|
+
memo: z.string().optional().default("").describe("Optional payment memo or comment"),
|
|
783
|
+
agent: z.string().optional().describe("Agent wallet name or ID for spending policy enforcement"),
|
|
784
|
+
},
|
|
785
|
+
async ({ recipient, amount_sats, amount_usd, memo, agent }) => {
|
|
786
|
+
// Detect recipient type
|
|
787
|
+
const isBolt11 = /^lnbc|^lntb|^lnbcrt/i.test(recipient);
|
|
788
|
+
const isStrikeHandle = recipient.startsWith("$");
|
|
789
|
+
const isLightningAddress = !isBolt11 && !isStrikeHandle && recipient.includes("@");
|
|
790
|
+
|
|
791
|
+
// Resolve agent for spending policy
|
|
792
|
+
let resolvedAgent = null;
|
|
793
|
+
if (agent) {
|
|
794
|
+
resolvedAgent = await resolveAgent(agent);
|
|
795
|
+
if (!resolvedAgent) return { content: [{ type: "text", text: `Error: Agent '${agent}' not found` }] };
|
|
796
|
+
if (!resolvedAgent.enabled) return { content: [{ type: "text", text: `Error: Agent '${resolvedAgent.name}' is disabled` }] };
|
|
797
|
+
}
|
|
798
|
+
if (!resolvedAgent) {
|
|
799
|
+
const auth = await getAuthLevel();
|
|
800
|
+
if (auth.agent_id) resolvedAgent = { id: auth.agent_id, name: auth.agent_name || auth.agent_id };
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (isBolt11) {
|
|
804
|
+
// Delegate to router via pay_lightning_invoice logic
|
|
805
|
+
if (resolvedAgent) {
|
|
806
|
+
const policy = await checkSpendingPolicy(resolvedAgent.id, amount_sats || 0);
|
|
807
|
+
if (!policy.allowed) return { content: [{ type: "text", text: `Payment blocked: ${policy.reason}` }] };
|
|
808
|
+
}
|
|
809
|
+
const { status, data } = await apiCall("/api/v1/payments", {
|
|
810
|
+
method: "POST",
|
|
811
|
+
body: JSON.stringify({ invoice: recipient, fee_limit_sats: 100 }),
|
|
812
|
+
});
|
|
813
|
+
if (status === 200 && data.success) {
|
|
814
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, type: "lightning_invoice", amount_sats: data.amount_sats, fee_sats: data.fee_sats || 0, rail: data.rail, payment_hash: data.payment_hash }) }] };
|
|
815
|
+
}
|
|
816
|
+
return { content: [{ type: "text", text: `Payment failed: ${data?.detail || data?.error || JSON.stringify(data)}` }] };
|
|
817
|
+
|
|
818
|
+
} else if (isLightningAddress) {
|
|
819
|
+
if (!amount_sats) return { content: [{ type: "text", text: "Error: amount_sats required for Lightning address payments" }] };
|
|
820
|
+
if (resolvedAgent) {
|
|
821
|
+
const policy = await checkSpendingPolicy(resolvedAgent.id, amount_sats);
|
|
822
|
+
if (!policy.allowed) return { content: [{ type: "text", text: `Payment blocked: ${policy.reason}` }] };
|
|
823
|
+
}
|
|
824
|
+
// LNURL resolve → pay via router
|
|
825
|
+
const [user, domain] = recipient.split("@");
|
|
826
|
+
const lnurlRes = await fetch(`https://${domain}/.well-known/lnurlp/${user}`);
|
|
827
|
+
if (!lnurlRes.ok) return { content: [{ type: "text", text: `Error: Could not resolve Lightning address ${recipient}` }] };
|
|
828
|
+
const lnurlData = await lnurlRes.json();
|
|
829
|
+
const minSats = Math.ceil((lnurlData.minSendable || 1000) / 1000);
|
|
830
|
+
const maxSats = Math.floor((lnurlData.maxSendable || 100000000000) / 1000);
|
|
831
|
+
if (amount_sats < minSats || amount_sats > maxSats) {
|
|
832
|
+
return { content: [{ type: "text", text: `Error: Amount must be between ${minSats} and ${maxSats} sats for this address` }] };
|
|
833
|
+
}
|
|
834
|
+
let callbackUrl = `${lnurlData.callback}${lnurlData.callback.includes("?") ? "&" : "?"}amount=${amount_sats * 1000}`;
|
|
835
|
+
if (memo && lnurlData.commentAllowed) callbackUrl += `&comment=${encodeURIComponent(memo)}`;
|
|
836
|
+
const invoiceRes = await fetch(callbackUrl);
|
|
837
|
+
if (!invoiceRes.ok) return { content: [{ type: "text", text: `Error: Failed to get invoice from ${domain}` }] };
|
|
838
|
+
const invoiceData = await invoiceRes.json();
|
|
839
|
+
if (!invoiceData.pr) return { content: [{ type: "text", text: `Error: No invoice returned from ${domain}` }] };
|
|
840
|
+
const { status, data } = await apiCall("/api/v1/payments", {
|
|
841
|
+
method: "POST",
|
|
842
|
+
body: JSON.stringify({ invoice: invoiceData.pr, fee_limit_sats: Math.max(100, Math.floor(amount_sats * 0.01)) }),
|
|
843
|
+
});
|
|
844
|
+
if (status === 200 && data.success) {
|
|
845
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, type: "lightning_address", recipient, amount_sats, fee_sats: data.fee_sats || 0, payment_hash: data.payment_hash, message: `Sent ${amount_sats} sats to ${recipient} ⚡` }) }] };
|
|
846
|
+
}
|
|
847
|
+
return { content: [{ type: "text", text: `Payment failed: ${data?.detail || data?.error || JSON.stringify(data)}` }] };
|
|
848
|
+
|
|
849
|
+
} else if (isStrikeHandle) {
|
|
850
|
+
const handle = recipient.slice(1); // strip "$"
|
|
851
|
+
if (!amount_usd && !amount_sats) return { content: [{ type: "text", text: "Error: amount_usd or amount_sats required for Strike payments" }] };
|
|
852
|
+
if (resolvedAgent && amount_sats) {
|
|
853
|
+
const policy = await checkSpendingPolicy(resolvedAgent.id, amount_sats);
|
|
854
|
+
if (!policy.allowed) return { content: [{ type: "text", text: `Payment blocked: ${policy.reason}` }] };
|
|
855
|
+
}
|
|
856
|
+
const body = { handle, description: memo || `Payment to $${handle}` };
|
|
857
|
+
if (amount_usd) body.amount_usd = amount_usd;
|
|
858
|
+
if (amount_sats) body.amount_sats = amount_sats;
|
|
859
|
+
if (resolvedAgent) body.agent_id = resolvedAgent.id;
|
|
860
|
+
const { status, data } = await apiCall("/api/v1/strike/pay", {
|
|
861
|
+
method: "POST",
|
|
862
|
+
body: JSON.stringify(body),
|
|
863
|
+
});
|
|
864
|
+
if (status === 200 && data.success) {
|
|
865
|
+
return { content: [{ type: "text", text: JSON.stringify({ success: true, type: "strike_handle", recipient, amount_usd: data.amount_usd, fee_usd: data.fee_usd || 0, payment_id: data.payment_id, message: `Sent to ${recipient} via Strike 💸` }) }] };
|
|
866
|
+
}
|
|
867
|
+
return { content: [{ type: "text", text: `Payment failed: ${data?.detail || data?.error || JSON.stringify(data)}` }] };
|
|
868
|
+
|
|
869
|
+
} else {
|
|
870
|
+
return { content: [{ type: "text", text: `Error: Unrecognized recipient format. Use a BOLT11 invoice (lnbc.../lntb...), Lightning address (user@domain.com), or Strike handle ($username).` }] };
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
);
|
|
874
|
+
|
|
774
875
|
// Tool: Decode Lightning invoice
|
|
775
876
|
server.tool(
|
|
776
877
|
"decode_invoice",
|