rightcard-mcp 0.1.0
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/README.md +54 -0
- package/dist/configurable.d.ts +16 -0
- package/dist/configurable.js +47 -0
- package/dist/data.d.ts +15 -0
- package/dist/data.js +54 -0
- package/dist/engine.d.ts +40 -0
- package/dist/engine.js +264 -0
- package/dist/matching.d.ts +7 -0
- package/dist/matching.js +93 -0
- package/dist/model.d.ts +55 -0
- package/dist/model.js +133 -0
- package/dist/search.d.ts +27 -0
- package/dist/search.js +209 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +8 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +205 -0
- package/dist/valuation.d.ts +24 -0
- package/dist/valuation.js +160 -0
- package/dist/worker.d.ts +4 -0
- package/dist/worker.js +38 -0
- package/package.json +33 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const PROGRAM_DISPLAY = {
|
|
2
|
+
cash: "Cash back", amexMR: "Amex Membership Rewards", chaseUR: "Chase Ultimate Rewards",
|
|
3
|
+
capOneMiles: "Capital One miles", citiTYP: "Citi ThankYou Points", wellsFargo: "Wells Fargo Rewards",
|
|
4
|
+
bilt: "Bilt Rewards", tdRewards: "TD Rewards", scenePlus: "Scene+", rbcAvion: "RBC Avion points",
|
|
5
|
+
bmoRewards: "BMO Rewards", hyatt: "World of Hyatt", marriott: "Marriott Bonvoy", ihg: "IHG One Rewards",
|
|
6
|
+
hilton: "Hilton Honors", delta: "Delta SkyMiles", united: "United MileagePlus",
|
|
7
|
+
american: "American AAdvantage", southwest: "Southwest Rapid Rewards", jetBlue: "JetBlue TrueBlue",
|
|
8
|
+
alaska: "Alaska Mileage Plan", avios: "British Airways Avios", aeroplan: "Air Canada Aeroplan",
|
|
9
|
+
fixedValue: "Fixed-value miles", genericPoints: "Points", genericMiles: "Miles",
|
|
10
|
+
};
|
|
11
|
+
export const TRAVEL_CPP = {
|
|
12
|
+
cash: 1.0, amexMR: 2.0, chaseUR: 2.0, capOneMiles: 1.7, citiTYP: 1.7, wellsFargo: 1.5, bilt: 2.0,
|
|
13
|
+
tdRewards: 0.5, scenePlus: 1.0, rbcAvion: 1.5, bmoRewards: 0.7,
|
|
14
|
+
hyatt: 1.7, marriott: 0.7, ihg: 0.6, hilton: 0.5,
|
|
15
|
+
delta: 1.1, united: 1.3, american: 1.4, southwest: 1.3, jetBlue: 1.3, alaska: 1.4, avios: 1.3, aeroplan: 1.3,
|
|
16
|
+
fixedValue: 1.0, genericPoints: 2.0, genericMiles: 1.5,
|
|
17
|
+
};
|
|
18
|
+
/** RewardProgram.cashOutCPP */
|
|
19
|
+
export function cashOutCPP(p) {
|
|
20
|
+
switch (p) {
|
|
21
|
+
case "cash":
|
|
22
|
+
case "chaseUR":
|
|
23
|
+
case "citiTYP":
|
|
24
|
+
case "scenePlus": return 1.0;
|
|
25
|
+
case "amexMR": return 0.6;
|
|
26
|
+
default: return 0.5;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** RewardProgram.infer(issuer:id:currency:) — order matters, mirrors the Swift needle list exactly. */
|
|
30
|
+
export function inferProgram(issuer, id, currency) {
|
|
31
|
+
if (!currency || currency === "cashback")
|
|
32
|
+
return "cash";
|
|
33
|
+
const s = (id + " " + (issuer ?? "")).toLowerCase();
|
|
34
|
+
const has = (...n) => n.some((x) => s.includes(x));
|
|
35
|
+
if (has("hilton"))
|
|
36
|
+
return "hilton";
|
|
37
|
+
if (has("marriott", "bonvoy"))
|
|
38
|
+
return "marriott";
|
|
39
|
+
if (has("hyatt"))
|
|
40
|
+
return "hyatt";
|
|
41
|
+
if (has("ihg"))
|
|
42
|
+
return "ihg";
|
|
43
|
+
if (has("delta", "skymiles"))
|
|
44
|
+
return "delta";
|
|
45
|
+
if (has("united", "mileageplus"))
|
|
46
|
+
return "united";
|
|
47
|
+
if (has("aadvantage", "american airlines"))
|
|
48
|
+
return "american";
|
|
49
|
+
if (has("southwest", "rapid rewards"))
|
|
50
|
+
return "southwest";
|
|
51
|
+
if (has("jetblue", "trueblue"))
|
|
52
|
+
return "jetBlue";
|
|
53
|
+
if (has("alaska"))
|
|
54
|
+
return "alaska";
|
|
55
|
+
if (has("aeroplan", "air canada"))
|
|
56
|
+
return "aeroplan";
|
|
57
|
+
if (has("british airways", "avios"))
|
|
58
|
+
return "avios";
|
|
59
|
+
if (has("bilt"))
|
|
60
|
+
return "bilt";
|
|
61
|
+
if (has("scotia"))
|
|
62
|
+
return "scenePlus";
|
|
63
|
+
if (s.startsWith("td_") || s.endsWith(" td"))
|
|
64
|
+
return "tdRewards";
|
|
65
|
+
if (has("rbc"))
|
|
66
|
+
return "rbcAvion";
|
|
67
|
+
if (has("bmo"))
|
|
68
|
+
return "bmoRewards";
|
|
69
|
+
if (has("amex", "american express", "membership"))
|
|
70
|
+
return "amexMR";
|
|
71
|
+
if (has("chase", "sapphire", "freedom", "ink"))
|
|
72
|
+
return "chaseUR";
|
|
73
|
+
if (has("capital one", "capital_one", "venture"))
|
|
74
|
+
return "capOneMiles";
|
|
75
|
+
if (has("citi", "thankyou", "premier", "strata"))
|
|
76
|
+
return "citiTYP";
|
|
77
|
+
if (has("wells"))
|
|
78
|
+
return "wellsFargo";
|
|
79
|
+
if (has("arrival"))
|
|
80
|
+
return "fixedValue";
|
|
81
|
+
if (has("travel rewards", "travel_rewards"))
|
|
82
|
+
return "fixedValue";
|
|
83
|
+
if (has("discover"))
|
|
84
|
+
return "fixedValue";
|
|
85
|
+
if (has("altitude"))
|
|
86
|
+
return "fixedValue";
|
|
87
|
+
return currency === "miles" ? "genericMiles" : "genericPoints";
|
|
88
|
+
}
|
|
89
|
+
export function makeValuation(mode = "cash") {
|
|
90
|
+
return { mode, pooledPrograms: {} };
|
|
91
|
+
}
|
|
92
|
+
const hay = (c) => (c.id + " " + c.displayName).toLowerCase();
|
|
93
|
+
/** RewardValuation.pooledPrograms(wallet:) */
|
|
94
|
+
export function pooledPrograms(wallet) {
|
|
95
|
+
const map = {};
|
|
96
|
+
const urUnlocked = wallet.some((c) => {
|
|
97
|
+
const s = hay(c);
|
|
98
|
+
return s.includes("sapphire") || (s.includes("ink") && s.includes("preferred"));
|
|
99
|
+
});
|
|
100
|
+
if (urUnlocked) {
|
|
101
|
+
for (const c of wallet) {
|
|
102
|
+
if (c.rewardCurrency !== "cashback")
|
|
103
|
+
continue;
|
|
104
|
+
const s = hay(c);
|
|
105
|
+
if (s.includes("freedom") || (s.includes("ink") && (s.includes("cash") || s.includes("unlimited"))))
|
|
106
|
+
map[c.id] = "chaseUR";
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const isTYPUnlock = (c) => {
|
|
110
|
+
const s = hay(c);
|
|
111
|
+
const citi = s.includes("citi") || s.includes("thankyou") || s.includes("strata");
|
|
112
|
+
return citi && (s.includes("premier") || s.includes("prestige"));
|
|
113
|
+
};
|
|
114
|
+
if (wallet.some(isTYPUnlock)) {
|
|
115
|
+
for (const c of wallet) {
|
|
116
|
+
if (c.rewardCurrency !== "cashback")
|
|
117
|
+
continue;
|
|
118
|
+
const s = hay(c);
|
|
119
|
+
if (s.includes("custom_cash") || s.includes("custom cash") || s.includes("double_cash") || s.includes("double cash"))
|
|
120
|
+
map[c.id] = "citiTYP";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
for (const c of wallet) {
|
|
125
|
+
if (!c.rewardCurrency || c.rewardCurrency === "cashback")
|
|
126
|
+
continue;
|
|
127
|
+
if (!isTYPUnlock(c) && inferProgram(c.issuer, c.id, c.rewardCurrency) === "citiTYP")
|
|
128
|
+
map[c.id] = "cash";
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return map;
|
|
132
|
+
}
|
|
133
|
+
export function withPooling(v, wallet) {
|
|
134
|
+
return { mode: v.mode, pooledPrograms: pooledPrograms(wallet) };
|
|
135
|
+
}
|
|
136
|
+
export function effectiveProgram(v, card) {
|
|
137
|
+
return v.pooledPrograms[card.id] ?? inferProgram(card.issuer, card.id, card.rewardCurrency);
|
|
138
|
+
}
|
|
139
|
+
export function centsPerPoint(v, card) {
|
|
140
|
+
return v.mode === "cash" ? 1.0 : TRAVEL_CPP[effectiveProgram(v, card)];
|
|
141
|
+
}
|
|
142
|
+
export function valuesPointsAboveCash(v, card) {
|
|
143
|
+
return centsPerPoint(v, card) !== 1.0;
|
|
144
|
+
}
|
|
145
|
+
export function earnUnitLabel(v, card) {
|
|
146
|
+
if (v.pooledPrograms[card.id] !== undefined)
|
|
147
|
+
return "points";
|
|
148
|
+
return card.rewardCurrency ? { points: "points", miles: "miles", cashback: "cash back" }[card.rewardCurrency] : "points";
|
|
149
|
+
}
|
|
150
|
+
export function basisLabel(v, card) {
|
|
151
|
+
const cpp = centsPerPoint(v, card);
|
|
152
|
+
if (cpp === 1.0)
|
|
153
|
+
return "Cash value";
|
|
154
|
+
const pooled = v.pooledPrograms[card.id] !== undefined ? " (pooled with your wallet)" : "";
|
|
155
|
+
return `${PROGRAM_DISPLAY[effectiveProgram(v, card)]} · ${fmt1(cpp)}¢ travel value${pooled}`;
|
|
156
|
+
}
|
|
157
|
+
/** Swift String(format: "%.1f") */
|
|
158
|
+
export function fmt1(x) {
|
|
159
|
+
return (Math.round(x * 10) / 10).toFixed(1);
|
|
160
|
+
}
|
package/dist/worker.d.ts
ADDED
package/dist/worker.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Cloudflare Worker: stateless Streamable HTTP MCP at POST /mcp (+ GET / for humans).
|
|
2
|
+
// No sessions, no storage, no identifiers. Catalog cached in the isolate for an hour.
|
|
3
|
+
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
4
|
+
import { buildServer } from "./tools.js";
|
|
5
|
+
import { catalogCache } from "./data.js";
|
|
6
|
+
const getCatalog = catalogCache();
|
|
7
|
+
const LANDING = `RightCard MCP — the card-selection oracle for agents.
|
|
8
|
+
Endpoint: POST https://mcp.rightcard.ai/mcp (MCP Streamable HTTP, stateless)
|
|
9
|
+
Tools: best_card · lookup_merchant · search_cards · card · rotating_calendar
|
|
10
|
+
No accounts. No bank login. Nothing stored. Docs: https://rightcard.ai/mcp
|
|
11
|
+
`;
|
|
12
|
+
export default {
|
|
13
|
+
async fetch(request) {
|
|
14
|
+
const url = new URL(request.url);
|
|
15
|
+
if (url.pathname === "/" || url.pathname === "/health") {
|
|
16
|
+
return new Response(LANDING, { headers: { "content-type": "text/plain; charset=utf-8" } });
|
|
17
|
+
}
|
|
18
|
+
if (url.pathname !== "/mcp")
|
|
19
|
+
return new Response("not found", { status: 404 });
|
|
20
|
+
if (request.method === "OPTIONS")
|
|
21
|
+
return new Response(null, { status: 204, headers: cors() });
|
|
22
|
+
const server = buildServer(getCatalog);
|
|
23
|
+
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined, enableJsonResponse: true });
|
|
24
|
+
await server.connect(transport);
|
|
25
|
+
const res = await transport.handleRequest(request);
|
|
26
|
+
const h = new Headers(res.headers);
|
|
27
|
+
for (const [k, v] of Object.entries(cors()))
|
|
28
|
+
h.set(k, v);
|
|
29
|
+
return new Response(res.body, { status: res.status, headers: h });
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
function cors() {
|
|
33
|
+
return {
|
|
34
|
+
"access-control-allow-origin": "*",
|
|
35
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
36
|
+
"access-control-allow-headers": "content-type, mcp-session-id, mcp-protocol-version, accept",
|
|
37
|
+
};
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rightcard-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "RightCard MCP \u2014 the card-selection oracle for agents. Which of your credit cards to use at any store, from verified data. No bank login.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rightcard-mcp": "dist/stdio.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"export-fixtures": "echo 'run: xcodebuild test -only-testing:RightCardTests/GoldenExportTests in the iOS repo'"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.0.0",
|
|
17
|
+
"typescript": "^5.6.0",
|
|
18
|
+
"vitest": "^2.1.0",
|
|
19
|
+
"wrangler": "^4.125.0"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
23
|
+
"zod": "^4.4.3"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/parikshitmadahar/rightcard-mcp"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"README.md"
|
|
32
|
+
]
|
|
33
|
+
}
|