@agentwonderland/mcp 0.1.50 → 0.1.52
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/dist/core/__tests__/api-client.test.js +6 -2
- package/dist/core/__tests__/payments.test.js +23 -2
- package/dist/core/__tests__/principal.test.js +25 -0
- package/dist/core/api-client.js +4 -2
- package/dist/core/config.d.ts +22 -0
- package/dist/core/config.js +20 -0
- package/dist/core/link-cli.js +97 -25
- package/dist/core/payments.js +20 -6
- package/dist/core/principal.d.ts +1 -0
- package/dist/core/principal.js +49 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/index.js +3 -0
- package/dist/tools/__tests__/rebates.test.d.ts +1 -0
- package/dist/tools/__tests__/rebates.test.js +72 -0
- package/dist/tools/__tests__/wallet.test.js +4 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +1 -0
- package/dist/tools/observability.d.ts +2 -0
- package/dist/tools/observability.js +20 -0
- package/dist/tools/rebates.d.ts +2 -0
- package/dist/tools/rebates.js +51 -0
- package/dist/tools/wallet.js +11 -5
- package/package.json +1 -1
- package/src/core/__tests__/api-client.test.ts +8 -1
- package/src/core/__tests__/payments.test.ts +36 -2
- package/src/core/__tests__/principal.test.ts +31 -0
- package/src/core/api-client.ts +4 -2
- package/src/core/config.ts +46 -0
- package/src/core/link-cli.ts +114 -28
- package/src/core/payments.ts +21 -6
- package/src/core/principal.ts +54 -1
- package/src/core/version.ts +1 -1
- package/src/index.ts +3 -0
- package/src/tools/__tests__/rebates.test.ts +91 -0
- package/src/tools/__tests__/wallet.test.ts +10 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/rebates.ts +102 -0
- package/src/tools/wallet.ts +11 -4
package/src/core/payments.ts
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
getDefaultWallet,
|
|
17
17
|
getCardConfig,
|
|
18
18
|
getLinkConfig,
|
|
19
|
+
getLinkCooldown,
|
|
19
20
|
resolveWalletAndChain,
|
|
20
21
|
getApiUrl,
|
|
21
22
|
type WalletEntry,
|
|
@@ -41,8 +42,6 @@ const REGISTRY_METHOD_MAP: Record<string, string> = {
|
|
|
41
42
|
link: "stripe_card",
|
|
42
43
|
};
|
|
43
44
|
|
|
44
|
-
const DEFAULT_LINK_APPROVAL_LIMIT_CENTS = 2_000;
|
|
45
|
-
|
|
46
45
|
const ACCEPTED_PAYMENT_ALIASES: Record<string, string[]> = {
|
|
47
46
|
tempo: ["tempo_usdc", "tempo"],
|
|
48
47
|
base: ["base_usdc", "base"],
|
|
@@ -200,10 +199,25 @@ function formatMinorCurrencyAmount(currency: string, amount: string): string {
|
|
|
200
199
|
function getLinkApprovalLimitAmount(actualAmount: string): string {
|
|
201
200
|
const actualAmountCents = Number(actualAmount);
|
|
202
201
|
const configuredLimit = Number(process.env.AGENTWONDERLAND_LINK_APPROVAL_LIMIT_CENTS);
|
|
203
|
-
const
|
|
204
|
-
? Math.floor(configuredLimit)
|
|
205
|
-
:
|
|
206
|
-
return String(
|
|
202
|
+
const approvalLimit = Number.isFinite(configuredLimit) && configuredLimit > 0
|
|
203
|
+
? Math.max(actualAmountCents, Math.floor(configuredLimit))
|
|
204
|
+
: actualAmountCents;
|
|
205
|
+
return String(approvalLimit);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function assertLinkNotCoolingDown(): void {
|
|
209
|
+
const cooldown = getLinkCooldown();
|
|
210
|
+
if (!cooldown) return;
|
|
211
|
+
const blockedUntil = Date.parse(cooldown.blockedUntil);
|
|
212
|
+
if (!Number.isFinite(blockedUntil) || blockedUntil <= Date.now()) return;
|
|
213
|
+
throw new Error(
|
|
214
|
+
[
|
|
215
|
+
"Link is temporarily blocked by Stripe's projected-spend cap.",
|
|
216
|
+
"Reauthing Link or switching cards in the same Link account will not fix this.",
|
|
217
|
+
`Try again after ${cooldown.blockedUntil}, use USDC, or ask Stripe to raise/clear the merchant projected-spend cap.`,
|
|
218
|
+
`Last Link error: ${cooldown.reason}`,
|
|
219
|
+
].join("\n"),
|
|
220
|
+
);
|
|
207
221
|
}
|
|
208
222
|
|
|
209
223
|
function buildLinkApprovalContext(params: {
|
|
@@ -239,6 +253,7 @@ async function initLink(): Promise<typeof fetch | null> {
|
|
|
239
253
|
expiresAt: number;
|
|
240
254
|
metadata?: Record<string, string>;
|
|
241
255
|
}) => {
|
|
256
|
+
assertLinkNotCoolingDown();
|
|
242
257
|
const approvalAmount = getLinkApprovalLimitAmount(params.amount);
|
|
243
258
|
console.error(
|
|
244
259
|
`Requesting Link approval up to ${formatMinorCurrencyAmount(params.currency, approvalAmount)} ` +
|
package/src/core/principal.ts
CHANGED
|
@@ -80,6 +80,43 @@ async function createFallbackIdentityWallet(): Promise<WalletEntry> {
|
|
|
80
80
|
return entry;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
async function ensureEvmIdentityWallet(): Promise<WalletEntry> {
|
|
84
|
+
const existing = preferredWallets().find((wallet) => walletSupportsEvm(wallet));
|
|
85
|
+
if (existing) return existing;
|
|
86
|
+
|
|
87
|
+
if (await isOwsAvailable()) {
|
|
88
|
+
const existingOwsWallets = await listOwsWallets();
|
|
89
|
+
const linked = existingOwsWallets[0];
|
|
90
|
+
if (linked) {
|
|
91
|
+
const entry: WalletEntry = {
|
|
92
|
+
id: linked.name,
|
|
93
|
+
keyType: "ows",
|
|
94
|
+
owsWalletId: linked.id,
|
|
95
|
+
chains: ["tempo", "base"],
|
|
96
|
+
defaultChain: "base",
|
|
97
|
+
label: linked.name,
|
|
98
|
+
};
|
|
99
|
+
addWallet(entry);
|
|
100
|
+
return entry;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const walletName = `aw-identity-${Date.now()}`;
|
|
104
|
+
const created = await createOwsWallet(walletName, "evm");
|
|
105
|
+
const entry: WalletEntry = {
|
|
106
|
+
id: walletName,
|
|
107
|
+
keyType: "ows",
|
|
108
|
+
owsWalletId: created.walletId,
|
|
109
|
+
chains: ["tempo", "base"],
|
|
110
|
+
defaultChain: "base",
|
|
111
|
+
label: AUTO_IDENTITY_LABEL,
|
|
112
|
+
};
|
|
113
|
+
addWallet(entry);
|
|
114
|
+
return entry;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return createFallbackIdentityWallet();
|
|
118
|
+
}
|
|
119
|
+
|
|
83
120
|
async function ensureIdentityWallet(): Promise<WalletEntry> {
|
|
84
121
|
const wallets = preferredWallets();
|
|
85
122
|
const existing = wallets.find((wallet) => walletSupportsEvm(wallet) || walletSupportsSolana(wallet));
|
|
@@ -131,9 +168,21 @@ export async function getBaseRebatePrincipal(): Promise<string | null> {
|
|
|
131
168
|
return (await principalForChain("base")) ?? principalForChain("tempo");
|
|
132
169
|
}
|
|
133
170
|
|
|
171
|
+
export async function ensureBaseRebatePrincipal(): Promise<string> {
|
|
172
|
+
const existing = await getBaseRebatePrincipal();
|
|
173
|
+
if (existing) return existing;
|
|
174
|
+
|
|
175
|
+
const wallet = await ensureEvmIdentityWallet();
|
|
176
|
+
const principal = await walletPrincipal(wallet);
|
|
177
|
+
if (!principal || !principal.startsWith(`did:pkh:eip155:${BASE_CHAIN_ID}:`)) {
|
|
178
|
+
throw new Error("Could not derive a Base rebate principal from the configured identity wallet.");
|
|
179
|
+
}
|
|
180
|
+
return principal;
|
|
181
|
+
}
|
|
182
|
+
|
|
134
183
|
export async function getConsumerPrincipalForMethod(method?: string): Promise<string | null> {
|
|
135
184
|
if (!method || method === "card" || method === "link") {
|
|
136
|
-
return
|
|
185
|
+
return getBaseRebatePrincipal();
|
|
137
186
|
}
|
|
138
187
|
|
|
139
188
|
const resolved = resolveWalletAndChain(method);
|
|
@@ -158,6 +207,10 @@ export async function ensureConsumerPrincipalForMethod(method?: string): Promise
|
|
|
158
207
|
const existing = await getConsumerPrincipalForMethod(method);
|
|
159
208
|
if (existing) return existing;
|
|
160
209
|
|
|
210
|
+
if (!method || method === "card" || method === "link") {
|
|
211
|
+
return ensureBaseRebatePrincipal();
|
|
212
|
+
}
|
|
213
|
+
|
|
161
214
|
if (method && method !== "card" && method !== "link") {
|
|
162
215
|
throw new Error(
|
|
163
216
|
`Could not derive a consumer principal for payment method "${method}". ` +
|
package/src/core/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const MCP_PACKAGE_VERSION = "0.1.
|
|
1
|
+
export const MCP_PACKAGE_VERSION = "0.1.51";
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { registerWalletTools } from "./tools/wallet.js";
|
|
|
14
14
|
import { registerFavoriteTools } from "./tools/favorites.js";
|
|
15
15
|
import { registerTipTools } from "./tools/tip.js";
|
|
16
16
|
import { registerPassTools } from "./tools/passes.js";
|
|
17
|
+
import { registerRebateTools } from "./tools/rebates.js";
|
|
17
18
|
import { registerUploadTools } from "./tools/upload.js";
|
|
18
19
|
import { registerProbeTools } from "./tools/probe.js";
|
|
19
20
|
import { registerProviderTools } from "./tools/providers.js";
|
|
@@ -71,6 +72,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
71
72
|
"",
|
|
72
73
|
"WALLET HYGIENE:",
|
|
73
74
|
"- wallet_status shows per-chain USDC balance and the active network (mainnet vs testnet).",
|
|
75
|
+
"- rebate_status shows accrued, pending, paid, and blocked consumer rebates plus recent payout transactions.",
|
|
74
76
|
"- To set up payments: wallet_setup({ action: \"start\" }). Link card/bank is recommended for most users.",
|
|
75
77
|
"- To create or import a crypto wallet directly: wallet_setup({ action: \"create\" }) or { action: \"import\", key }.",
|
|
76
78
|
"- NEVER delete or rotate keys programmatically. Direct users to edit ~/.agentwonderland/config.json or ~/.ows/ manually.",
|
|
@@ -89,6 +91,7 @@ export async function startMcpServer(): Promise<void> {
|
|
|
89
91
|
registerFavoriteTools(server);
|
|
90
92
|
registerTipTools(server);
|
|
91
93
|
registerPassTools(server);
|
|
94
|
+
registerRebateTools(server);
|
|
92
95
|
registerUploadTools(server);
|
|
93
96
|
registerProbeTools(server);
|
|
94
97
|
registerProviderTools(server);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
type ToolResult = {
|
|
5
|
+
content: Array<{ type: "text"; text: string }>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const { mockApiGet } = vi.hoisted(() => ({
|
|
9
|
+
mockApiGet: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock("../../core/api-client.js", () => ({
|
|
13
|
+
apiGet: (...args: unknown[]) => mockApiGet(...args),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
async function getRebateStatusTool(): Promise<(args: Record<string, unknown>) => Promise<ToolResult>> {
|
|
17
|
+
const tools = new Map<string, (args: Record<string, unknown>) => Promise<ToolResult>>();
|
|
18
|
+
const server = {
|
|
19
|
+
tool: (
|
|
20
|
+
name: string,
|
|
21
|
+
_description: string,
|
|
22
|
+
_schema: unknown,
|
|
23
|
+
handler: (args: Record<string, unknown>) => Promise<ToolResult>,
|
|
24
|
+
) => {
|
|
25
|
+
tools.set(name, handler);
|
|
26
|
+
},
|
|
27
|
+
} as unknown as McpServer;
|
|
28
|
+
|
|
29
|
+
const { registerRebateTools } = await import("../rebates.js");
|
|
30
|
+
registerRebateTools(server);
|
|
31
|
+
|
|
32
|
+
const tool = tools.get("rebate_status");
|
|
33
|
+
if (!tool) throw new Error("rebate_status tool was not registered");
|
|
34
|
+
return tool;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function flattenText(result: ToolResult): string {
|
|
38
|
+
return result.content.map((item) => item.text).join("\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe("rebate_status tool", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.resetModules();
|
|
44
|
+
mockApiGet.mockReset();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("formats rebate balances and recent payouts", async () => {
|
|
48
|
+
mockApiGet.mockResolvedValueOnce({
|
|
49
|
+
consumer_principal: "did:pkh:eip155:8453:0xabc",
|
|
50
|
+
principal_type: "evm",
|
|
51
|
+
principal_value: "0xabc",
|
|
52
|
+
payout_address: "0xabc",
|
|
53
|
+
payout_chain: "base",
|
|
54
|
+
auto_payout_threshold_usd: "5.000000",
|
|
55
|
+
totals: {
|
|
56
|
+
lifetime_earned_usd: "2.500000",
|
|
57
|
+
pending_usd: "1.250000",
|
|
58
|
+
paid_usd: "1.000000",
|
|
59
|
+
blocked_usd: "0.250000",
|
|
60
|
+
total_count: 5,
|
|
61
|
+
pending_count: 2,
|
|
62
|
+
paid_count: 2,
|
|
63
|
+
blocked_count: 1,
|
|
64
|
+
},
|
|
65
|
+
by_source: [
|
|
66
|
+
{ source_type: "job_execution", status: "earned", count: 2, rebate_usd: "1.250000" },
|
|
67
|
+
],
|
|
68
|
+
recent_payouts: [
|
|
69
|
+
{
|
|
70
|
+
id: "payout-1",
|
|
71
|
+
status: "completed",
|
|
72
|
+
amount_usd: "1.000000",
|
|
73
|
+
earning_count: 2,
|
|
74
|
+
tx_hash: "0xhash",
|
|
75
|
+
processed_at: "2026-05-09T12:00:00.000Z",
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const tool = await getRebateStatusTool();
|
|
81
|
+
const result = await tool({});
|
|
82
|
+
const text = flattenText(result);
|
|
83
|
+
|
|
84
|
+
expect(mockApiGet).toHaveBeenCalledWith("/rebates/status", { ensureConsumerPrincipal: true });
|
|
85
|
+
expect(text).toContain("Lifetime earned: $2.5000 across 5 rebate(s)");
|
|
86
|
+
expect(text).toContain("Pending: $1.2500 across 2 rebate(s)");
|
|
87
|
+
expect(text).toContain("Auto payout: $3.7500 until the $5.0000 threshold");
|
|
88
|
+
expect(text).toContain("job execution / earned: $1.2500 (2)");
|
|
89
|
+
expect(text).toContain("completed: $1.0000 (2) tx=0xhash");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -29,6 +29,12 @@ type PendingLinkSetup = {
|
|
|
29
29
|
createdAt: string;
|
|
30
30
|
} | null;
|
|
31
31
|
|
|
32
|
+
type LinkCooldown = {
|
|
33
|
+
reason: string;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
blockedUntil: string;
|
|
36
|
+
} | null;
|
|
37
|
+
|
|
32
38
|
type WalletToolResult = {
|
|
33
39
|
content: Array<{ type: "text"; text: string }>;
|
|
34
40
|
};
|
|
@@ -39,6 +45,7 @@ const state = vi.hoisted(() => ({
|
|
|
39
45
|
card: null as CardConfig,
|
|
40
46
|
link: null as LinkConfig,
|
|
41
47
|
pendingLinkSetup: null as PendingLinkSetup,
|
|
48
|
+
linkCooldown: null as LinkCooldown,
|
|
42
49
|
pendingCardSetupToken: null as string | null,
|
|
43
50
|
spendPolicies: {} as Record<string, unknown>,
|
|
44
51
|
defaultPaymentMethod: undefined as string | undefined,
|
|
@@ -93,6 +100,7 @@ vi.mock("../../core/config.js", () => ({
|
|
|
93
100
|
},
|
|
94
101
|
getCardConfig: () => state.card,
|
|
95
102
|
getLinkConfig: () => state.link,
|
|
103
|
+
getLinkCooldown: () => state.linkCooldown,
|
|
96
104
|
getPendingLinkSetup: () => state.pendingLinkSetup,
|
|
97
105
|
getPendingCardSetupToken: () => state.pendingCardSetupToken,
|
|
98
106
|
getSpendPolicy: (walletId: string) => state.spendPolicies[walletId] ?? null,
|
|
@@ -167,6 +175,7 @@ vi.mock("../../core/ows-adapter.js", () => ({
|
|
|
167
175
|
|
|
168
176
|
vi.mock("../../core/principal.js", () => ({
|
|
169
177
|
ensureConsumerPrincipal: async () => state.consumerPrincipal,
|
|
178
|
+
ensureConsumerPrincipalForMethod: async () => state.consumerPrincipal,
|
|
170
179
|
getConsumerPrincipal: async () => state.consumerPrincipal,
|
|
171
180
|
}));
|
|
172
181
|
|
|
@@ -176,6 +185,7 @@ function resetState(): void {
|
|
|
176
185
|
state.card = null;
|
|
177
186
|
state.link = null;
|
|
178
187
|
state.pendingLinkSetup = null;
|
|
188
|
+
state.linkCooldown = null;
|
|
179
189
|
state.pendingCardSetupToken = null;
|
|
180
190
|
state.spendPolicies = {};
|
|
181
191
|
state.defaultPaymentMethod = undefined;
|
package/src/tools/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { registerWalletTools } from "./wallet.js";
|
|
|
8
8
|
export { registerFavoriteTools } from "./favorites.js";
|
|
9
9
|
export { registerTipTools } from "./tip.js";
|
|
10
10
|
export { registerPassTools } from "./passes.js";
|
|
11
|
+
export { registerRebateTools } from "./rebates.js";
|
|
11
12
|
export { registerUploadTools } from "./upload.js";
|
|
12
13
|
export { registerProbeTools } from "./probe.js";
|
|
13
14
|
export { registerProviderTools } from "./providers.js";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { apiGet } from "../core/api-client.js";
|
|
3
|
+
|
|
4
|
+
type RebateStatus = {
|
|
5
|
+
consumer_principal: string;
|
|
6
|
+
principal_type: string;
|
|
7
|
+
principal_value: string;
|
|
8
|
+
payout_address: string | null;
|
|
9
|
+
payout_chain: string;
|
|
10
|
+
auto_payout_threshold_usd: string;
|
|
11
|
+
totals: {
|
|
12
|
+
lifetime_earned_usd: string;
|
|
13
|
+
pending_usd: string;
|
|
14
|
+
paid_usd: string;
|
|
15
|
+
blocked_usd: string;
|
|
16
|
+
total_count: number;
|
|
17
|
+
pending_count: number;
|
|
18
|
+
paid_count: number;
|
|
19
|
+
blocked_count: number;
|
|
20
|
+
};
|
|
21
|
+
by_source: Array<{
|
|
22
|
+
source_type: string;
|
|
23
|
+
status: string;
|
|
24
|
+
count: number;
|
|
25
|
+
rebate_usd: string;
|
|
26
|
+
}>;
|
|
27
|
+
recent_payouts: Array<{
|
|
28
|
+
id: string;
|
|
29
|
+
status: string;
|
|
30
|
+
amount_usd: string;
|
|
31
|
+
earning_count: number;
|
|
32
|
+
tx_hash: string | null;
|
|
33
|
+
processed_at: string | null;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function text(t: string) {
|
|
38
|
+
return { content: [{ type: "text" as const, text: t }] };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function money(value: string | number): string {
|
|
42
|
+
const numeric = Number(value);
|
|
43
|
+
return Number.isFinite(numeric) ? `$${numeric.toFixed(4)}` : `$${value}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sourceLabel(value: string): string {
|
|
47
|
+
return value.replace(/_/g, " ");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function registerRebateTools(server: McpServer): void {
|
|
51
|
+
server.tool(
|
|
52
|
+
"rebate_status",
|
|
53
|
+
"Show your Agent Wonderland consumer rebate balance, payout threshold, blocked rebates, and recent payout transactions.",
|
|
54
|
+
{},
|
|
55
|
+
async () => {
|
|
56
|
+
const status = await apiGet<RebateStatus>("/rebates/status", { ensureConsumerPrincipal: true });
|
|
57
|
+
const threshold = Number(status.auto_payout_threshold_usd);
|
|
58
|
+
const pending = Number(status.totals.pending_usd);
|
|
59
|
+
const remaining = Math.max(0, threshold - pending);
|
|
60
|
+
|
|
61
|
+
const lines = [
|
|
62
|
+
"Rebate status",
|
|
63
|
+
`Consumer principal: ${status.consumer_principal}`,
|
|
64
|
+
`Base payout address: ${status.payout_address ?? "not configured"}`,
|
|
65
|
+
"",
|
|
66
|
+
`Lifetime earned: ${money(status.totals.lifetime_earned_usd)} across ${status.totals.total_count} rebate(s)`,
|
|
67
|
+
`Paid out: ${money(status.totals.paid_usd)} across ${status.totals.paid_count} rebate(s)`,
|
|
68
|
+
`Pending: ${money(status.totals.pending_usd)} across ${status.totals.pending_count} rebate(s)`,
|
|
69
|
+
`Blocked: ${money(status.totals.blocked_usd)} across ${status.totals.blocked_count} rebate(s)`,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
if (Number.isFinite(threshold) && threshold > 0) {
|
|
73
|
+
lines.push(
|
|
74
|
+
pending >= threshold
|
|
75
|
+
? `Auto payout: eligible for the next processor run (threshold ${money(threshold)})`
|
|
76
|
+
: `Auto payout: ${money(remaining)} until the ${money(threshold)} threshold`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (status.by_source.length > 0) {
|
|
81
|
+
lines.push("", "By source:");
|
|
82
|
+
for (const row of status.by_source) {
|
|
83
|
+
lines.push(` ${sourceLabel(row.source_type)} / ${row.status}: ${money(row.rebate_usd)} (${row.count})`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (status.recent_payouts.length > 0) {
|
|
88
|
+
lines.push("", "Recent payouts:");
|
|
89
|
+
for (const payout of status.recent_payouts.slice(0, 5)) {
|
|
90
|
+
const tx = payout.tx_hash ? ` tx=${payout.tx_hash}` : "";
|
|
91
|
+
lines.push(` ${payout.status}: ${money(payout.amount_usd)} (${payout.earning_count})${tx}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!status.payout_address) {
|
|
96
|
+
lines.push("", "No Base payout address is configured, so new rebates may be blocked.");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return text(lines.join("\n"));
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
package/src/tools/wallet.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getWallets,
|
|
5
5
|
getCardConfig,
|
|
6
6
|
getLinkConfig,
|
|
7
|
+
getLinkCooldown,
|
|
7
8
|
getPendingLinkSetup,
|
|
8
9
|
setPendingLinkSetup,
|
|
9
10
|
setLinkConfig,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
installOws,
|
|
39
40
|
platformSupportsOws,
|
|
40
41
|
} from "../core/ows-adapter.js";
|
|
41
|
-
import { ensureConsumerPrincipal, getConsumerPrincipal } from "../core/principal.js";
|
|
42
|
+
import { ensureConsumerPrincipal, ensureConsumerPrincipalForMethod, getConsumerPrincipal } from "../core/principal.js";
|
|
42
43
|
import { MCP_PACKAGE_VERSION } from "../core/version.js";
|
|
43
44
|
|
|
44
45
|
function text(t: string) {
|
|
@@ -157,6 +158,12 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
157
158
|
lines.push(auth.authenticated
|
|
158
159
|
? " Link CLI: authenticated"
|
|
159
160
|
: " Link CLI: not authenticated — run npx @stripe/link-cli auth login");
|
|
161
|
+
const cooldown = getLinkCooldown();
|
|
162
|
+
const blockedUntil = cooldown ? Date.parse(cooldown.blockedUntil) : NaN;
|
|
163
|
+
if (cooldown && Number.isFinite(blockedUntil) && blockedUntil > Date.now()) {
|
|
164
|
+
lines.push(` Link status: temporarily blocked by Stripe projected-spend cap until ${cooldown.blockedUntil}`);
|
|
165
|
+
lines.push(" Link note: reauthing or switching cards in the same Link account will not clear this cap.");
|
|
166
|
+
}
|
|
160
167
|
}
|
|
161
168
|
|
|
162
169
|
if (pendingCardSetupToken) {
|
|
@@ -285,7 +292,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
285
292
|
paymentMethodId: selected.id,
|
|
286
293
|
label: name ?? selected.label,
|
|
287
294
|
});
|
|
288
|
-
const principal = await
|
|
295
|
+
const principal = await ensureConsumerPrincipalForMethod("link");
|
|
289
296
|
return text(
|
|
290
297
|
[
|
|
291
298
|
"Link payment method connected.",
|
|
@@ -323,7 +330,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
323
330
|
paymentMethodId: method.id,
|
|
324
331
|
label: method.label,
|
|
325
332
|
});
|
|
326
|
-
const principal = await
|
|
333
|
+
const principal = await ensureConsumerPrincipalForMethod("link");
|
|
327
334
|
return text(
|
|
328
335
|
[
|
|
329
336
|
"Link payment method connected.",
|
|
@@ -373,7 +380,7 @@ export function registerWalletTools(server: McpServer): void {
|
|
|
373
380
|
const result = await pollCardSetup(pendingToken, 250);
|
|
374
381
|
|
|
375
382
|
if (result) {
|
|
376
|
-
const principal = await
|
|
383
|
+
const principal = await ensureConsumerPrincipalForMethod("card");
|
|
377
384
|
return text(
|
|
378
385
|
`Connected! ${result.brand} ****${result.last4} is ready for payments.\n\n` +
|
|
379
386
|
`Consumer principal: ${principal}`,
|