rightcard-mcp 0.1.2 → 0.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RightCard
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/data.js CHANGED
@@ -25,7 +25,7 @@ async function getAll(path, fetchImpl) {
25
25
  }
26
26
  export async function loadCatalog(fetchImpl = fetch) {
27
27
  const [cardRows, overrideRows, merchantRows] = await Promise.all([
28
- getAll("cards?select=id,name,issuer,reward_currency,base_rate,standard_multipliers,annual_fee,verify_status&country=eq.US&order=id", fetchImpl),
28
+ getAll("cards?select=id,name,issuer,reward_currency,base_rate,standard_multipliers,annual_fee,verify_status,network&country=eq.US&order=id", fetchImpl),
29
29
  getAll("active_overrides?select=*&order=id", fetchImpl),
30
30
  getAll("merchants?select=id,display_name,category,sub_key,merchant_type,is_online,aliases&country=eq.US&order=id", fetchImpl),
31
31
  ]);
package/dist/engine.d.ts CHANGED
@@ -33,6 +33,8 @@ export interface RecommendInput {
33
33
  cardConfigs?: Record<string, Set<SpendCategory>>;
34
34
  now: Date;
35
35
  strictMerchantMatch?: boolean;
36
+ /** Register network lock (Costco = visa-only). null/undefined = no restriction. */
37
+ acceptedNetworks?: Set<string> | null;
36
38
  }
37
39
  export declare function formatPercent(v: number): string;
38
40
  export declare function formatMultiplier(v: number): string;
package/dist/engine.js CHANGED
@@ -48,19 +48,19 @@ function tieNote(winner, runnerUp, value, v) {
48
48
  if (wRank > rRank) {
49
49
  const winProgram = effectiveProgram(v, winner);
50
50
  if (v.mode === "cash" && winProgram !== "cash" && cashOutCPP(winProgram) >= 1.0) {
51
- return `${win} ties ${other} at ${rate} — picked ${win}: its points cash out the same, and can be worth more if you ever redeem for travel.`;
51
+ return `${other} also earns ${rate} here. We picked ${win} because its points cash out the same and can be worth more for travel.`;
52
52
  }
53
53
  const smart = { mode: "smart", pooledPrograms: v.pooledPrograms };
54
54
  if (v.mode === "cash" && centsPerPoint(smart, runnerUp) > 1.0) {
55
- return `${win} and ${other} tie at ${rate} for cash. ${other}'s points can be worth more if you redeem for travel — switch to Points & miles to compare.`;
55
+ return `${other} also earns ${rate} here. Its points can be worth more for travel, so try Points & miles.`;
56
56
  }
57
57
  const why = v.mode === "smart" ? "transfer flexibility" : "guaranteed cash back";
58
- return `${win} ties ${other} at ${rate} — picked ${win} for ${why}.`;
58
+ return `${other} also earns ${rate} here. We picked ${win} for ${why}.`;
59
59
  }
60
60
  if (premiumRank(winner) > premiumRank(runnerUp)) {
61
- return `${win} ties ${other} at ${rate} — picked ${win}: the premium card's purchase protections come free at the same rate.`;
61
+ return `${other} also earns ${rate} here. We picked ${win} for its purchase protection.`;
62
62
  }
63
- return `${win} ties ${other} at ${rate} — either works.`;
63
+ return `${other} also earns ${rate} here. Either one works.`;
64
64
  }
65
65
  const PORTAL_BONUSES = [
66
66
  { needles: ["sapphire reserve"], rate: 10, portal: "Chase Travel", premium: true },
@@ -202,7 +202,14 @@ function rateContext(card, earn, valueCents, override, configured, v, category,
202
202
  }
203
203
  else {
204
204
  reason = `${card.displayName} earns the most for ${cat} — ${valueStr} per dollar.`;
205
- lines.push(`Base ${cat} earn: ${valueStr}`);
205
+ // Mirrors the Swift engine (Sep 2026): a points/miles card in cash mode
206
+ // still EARNS points — never blur the earn (4x points) with its value (4%).
207
+ if (card.rewardCurrency !== null && card.rewardCurrency !== "cashback") {
208
+ lines.push(`Base ${cat} earn: ${formatMultiplier(earn)} ${earnUnitLabel(v, card)} → ${valueStr}`);
209
+ }
210
+ else {
211
+ lines.push(`Base ${cat} earn: ${valueStr}`);
212
+ }
206
213
  }
207
214
  let caption = configured ? "your bonus category · value per $1" : override ? "rotating bonus · value per $1" : "value per $1";
208
215
  let valueMath = null;
@@ -241,9 +248,24 @@ function subcategoryBreakdown(cards, category, overrides, cardConfigs, v, now) {
241
248
  }
242
249
  /** MerchantYieldEngine.recommend — base-rate mode (offers: []) */
243
250
  export function recommend(input) {
244
- const { cards, category, overrides, now } = input;
251
+ const { category, overrides, now } = input;
252
+ let cards = input.cards;
245
253
  if (cards.length === 0)
246
254
  return null;
255
+ // Network-acceptance gate (Costco's Visa-only registers). Mirrors the Swift
256
+ // engine: a KNOWN-incompatible card can't win when a compatible (or
257
+ // unknown-network) card exists; if every card is known-incompatible, the
258
+ // full wallet answers (works on the merchant's site) and the warehouse
259
+ // caveat carries the register story. Unknown (null) network never gates.
260
+ const accepted = input.acceptedNetworks;
261
+ if (accepted && accepted.size > 0) {
262
+ const eligible = cards.filter((c) => {
263
+ const net = c.network?.toLowerCase();
264
+ return net == null || accepted.has(net);
265
+ });
266
+ if (eligible.length > 0)
267
+ cards = eligible;
268
+ }
247
269
  const v = withPooling(input.valuation, cards);
248
270
  const cardConfigs = input.cardConfigs ?? {};
249
271
  const strict = input.strictMerchantMatch ?? false;
package/dist/model.d.ts CHANGED
@@ -15,6 +15,8 @@ export interface CreditCard {
15
15
  multipliers: Record<string, MultiplierField>;
16
16
  annualFee?: number | null;
17
17
  verifyStatus?: string | null;
18
+ /** Payment network (migration 0017): visa|mastercard|amex|discover; null = unknown, never gates. */
19
+ network?: string | null;
18
20
  }
19
21
  /** Supabase `cards` row → CreditCard (SupabaseCard.toCreditCard). */
20
22
  export declare function cardFromRow(row: any): CreditCard;
@@ -34,7 +36,13 @@ export interface Override {
34
36
  merchant: string | null;
35
37
  }
36
38
  export declare function overrideFromRow(r: any): Override;
37
- /** UTC-day canon (SupabaseDateFormat.utcCalendar): compare yyyy-MM-dd strings as UTC days. */
39
+ /** Compare yyyy-MM-dd strings as UTC days.
40
+ *
41
+ * The iOS app judges windows in the USER'S calendar date (Sep 13 2026: an offer
42
+ * or quarter marked 9/30 is live through the end of 9/30 where the user is).
43
+ * This server has no user timezone, so it keeps UTC; the two can differ only
44
+ * inside the few hours around midnight on a window's first or last day. The
45
+ * golden fixture is exported at noon UTC so parity is unaffected. */
38
46
  export declare function utcDay(now: Date): string;
39
47
  export declare function overrideIsActive(o: Override, now: Date): boolean;
40
48
  /** SupabaseOverride.isPermanentMerchantBenefit: merchant-scoped, no activation, ends > 2 years out. */
@@ -51,5 +59,11 @@ export interface Merchant {
51
59
  export declare function merchantFromRow(r: any): Merchant;
52
60
  /** Merchant.exclusionCaveat */
53
61
  export declare function exclusionCaveat(m: Merchant): string | null;
62
+ /** Merchant.acceptedNetworksAtRegister — Costco's Visa-only register lock.
63
+ * EVERY physical Costco row (checkout, pharmacy, gas, tire) is Visa-only
64
+ * for credit; null for everything else (no restriction). Costco.com /
65
+ * online rows are never gated (they take other networks). Mirrors the
66
+ * Swift twin exactly. */
67
+ export declare function acceptedNetworksAtRegister(m: Merchant): Set<string> | null;
54
68
  /** Merchant.isCuratedBrand */
55
69
  export declare function isCuratedBrand(m: Merchant): boolean;
package/dist/model.js CHANGED
@@ -27,6 +27,7 @@ export function cardFromRow(row) {
27
27
  multipliers,
28
28
  annualFee: row.annual_fee ?? null,
29
29
  verifyStatus: row.verify_status ?? null,
30
+ network: row.network ?? null,
30
31
  };
31
32
  }
32
33
  function fieldFromJSON(v) {
@@ -85,7 +86,13 @@ export function overrideFromRow(r) {
85
86
  label: r.label ?? null, merchant: r.merchant ?? null,
86
87
  };
87
88
  }
88
- /** UTC-day canon (SupabaseDateFormat.utcCalendar): compare yyyy-MM-dd strings as UTC days. */
89
+ /** Compare yyyy-MM-dd strings as UTC days.
90
+ *
91
+ * The iOS app judges windows in the USER'S calendar date (Sep 13 2026: an offer
92
+ * or quarter marked 9/30 is live through the end of 9/30 where the user is).
93
+ * This server has no user timezone, so it keeps UTC; the two can differ only
94
+ * inside the few hours around midnight on a window's first or last day. The
95
+ * golden fixture is exported at noon UTC so parity is unaffected. */
89
96
  export function utcDay(now) {
90
97
  return now.toISOString().slice(0, 10);
91
98
  }
@@ -117,12 +124,42 @@ export function exclusionCaveat(m) {
117
124
  switch (m.merchantType) {
118
125
  case "superstore":
119
126
  return `${m.displayName} usually codes as a superstore (not a supermarket), so grocery bonuses often don't post — this is your best guaranteed card.`;
120
- case "warehouse_club":
121
- return `${m.displayName} codes as a warehouse club, so category bonuses usually don't post — this is your best guaranteed card.`;
127
+ case "warehouse_club": {
128
+ const base = `${m.displayName} codes as a warehouse club, so category bonuses usually don't post — this is your best guaranteed card.`;
129
+ // Costco alone is also NETWORK-locked: warehouse registers take Visa
130
+ // only (Costco.com takes other networks). Mirrors the Swift engine —
131
+ // keep both sides identical or the parity gate fails.
132
+ if (m.displayName.toLowerCase().includes("costco")) {
133
+ return base + " Note: Costco warehouses take Visa only at the register; other networks work on Costco.com.";
134
+ }
135
+ return base;
136
+ }
122
137
  default:
138
+ // Physical Costco DEPARTMENTS (pharmacy / gas / tire / …) share the
139
+ // warehouse's Visa-only registers; their own MCC stays honest (no
140
+ // "bonuses don't post" clause). Mirrors the Swift engine (Sep 2026).
141
+ if (acceptedNetworksAtRegister(m) !== null) {
142
+ return "Costco takes Visa only at the register — this is your best card that works in-store. Other networks work on Costco.com.";
143
+ }
123
144
  return null;
124
145
  }
125
146
  }
147
+ /** Merchant.acceptedNetworksAtRegister — Costco's Visa-only register lock.
148
+ * EVERY physical Costco row (checkout, pharmacy, gas, tire) is Visa-only
149
+ * for credit; null for everything else (no restriction). Costco.com /
150
+ * online rows are never gated (they take other networks). Mirrors the
151
+ * Swift twin exactly. */
152
+ export function acceptedNetworksAtRegister(m) {
153
+ const name = m.displayName.toLowerCase();
154
+ if (!name.includes("costco"))
155
+ return null;
156
+ // NAME is the online signal, deliberately not isOnline — the LLM-minted
157
+ // flag is noise on this family (costco_pharmacy sat is_online=true).
158
+ // Mirrors the Swift twin exactly.
159
+ if (name.includes(".com") || name.includes("online"))
160
+ return null;
161
+ return new Set(["visa"]);
162
+ }
126
163
  /** Merchant.isCuratedBrand */
127
164
  export function isCuratedBrand(m) {
128
165
  if (m.aliases.length > 0)
package/dist/search.js CHANGED
@@ -96,7 +96,11 @@ export function matchScore(q, qc, rawQuery, candidates) {
96
96
  const words = n.split(" ");
97
97
  if (words.slice(1).some((w) => w.startsWith(q)))
98
98
  best = Math.max(best, 70);
99
- if (n.includes(q))
99
+ // INTERIOR substring, only for a query long enough to mean something. A 2–3
100
+ // character query matching mid-word is noise: "LG" returned Algenist,
101
+ // Bulgari, Trafalgar and the glued alias "naturalgrocers.com" and no LG
102
+ // (user report, Sep 17 2026). Mirrors Merchant.matchScore in the app.
103
+ if ([...q].length >= 4 && n.includes(q))
100
104
  best = Math.max(best, 55);
101
105
  if (best < 45 && merchantMatches(c.raw, rawQuery))
102
106
  best = Math.max(best, 45);
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // and `caveats`; verified rows only in rankings; quarantined cards are NAMED, never ranked.
3
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
4
  import { z } from "zod";
5
- import { ALL_CATEGORIES, exclusionCaveat, hasAnyReward, multiplier, overrideIsActive, isPermanentMerchantBenefit } from "./model.js";
5
+ import { ALL_CATEGORIES, acceptedNetworksAtRegister, exclusionCaveat, hasAnyReward, multiplier, overrideIsActive, isPermanentMerchantBenefit } from "./model.js";
6
6
  import { recommend, formatPercent, formatMultiplier } from "./engine.js";
7
7
  import { rankedBrandMatches, searchNormalized } from "./search.js";
8
8
  import { makeValuation, centsPerPoint, withPooling, effectiveProgram, PROGRAM_DISPLAY, TRAVEL_CPP } from "./valuation.js";
@@ -13,6 +13,10 @@ function cardSummary(c) {
13
13
  return {
14
14
  id: c.id, name: c.displayName, issuer: c.issuer, reward_currency: c.rewardCurrency,
15
15
  verify_status: c.verifyStatus ?? "unknown", annual_fee: c.annualFee ?? null,
16
+ // Payment network (visa|mastercard|amex|discover) or null = unknown.
17
+ // Stated so the model never guesses it (Costco's registers are Visa-only;
18
+ // a guessed network in an answer is exactly the hallucination this bans).
19
+ network: c.network ?? null,
16
20
  };
17
21
  }
18
22
  function resolveWallet(catalog, ids) {
@@ -92,7 +96,8 @@ export function buildServer(getCatalog) {
92
96
  configs[k] = new Set(v);
93
97
  const now = on ? new Date(on + "T00:00:00Z") : new Date();
94
98
  const r = recommend({ cards, category: cat, subKey, merchantName: merchant ?? null, overrides: catalog.overrides,
95
- valuation: makeValuation(mode), cardConfigs: configs, now });
99
+ valuation: makeValuation(mode), cardConfigs: configs, now,
100
+ acceptedNetworks: dir ? acceptedNetworksAtRegister(dir) : null });
96
101
  if (!r)
97
102
  return err("No recommendation could be computed.");
98
103
  const answer = {
@@ -1,6 +1,6 @@
1
1
  import type { CreditCard } from "./model.js";
2
2
  export type ValuationMode = "cash" | "smart";
3
- export type RewardProgram = "cash" | "amexMR" | "chaseUR" | "capOneMiles" | "citiTYP" | "wellsFargo" | "bilt" | "tdRewards" | "scenePlus" | "rbcAvion" | "bmoRewards" | "hyatt" | "marriott" | "ihg" | "hilton" | "delta" | "united" | "american" | "southwest" | "jetBlue" | "alaska" | "avios" | "aeroplan" | "fixedValue" | "genericPoints" | "genericMiles";
3
+ export type RewardProgram = "cash" | "amexMR" | "chaseUR" | "capOneMiles" | "citiTYP" | "wellsFargo" | "bilt" | "tdRewards" | "scenePlus" | "rbcAvion" | "bmoRewards" | "hyatt" | "marriott" | "ihg" | "choice" | "hilton" | "delta" | "united" | "american" | "southwest" | "jetBlue" | "alaska" | "avios" | "aeroplan" | "fixedValue" | "genericPoints" | "genericMiles";
4
4
  export declare const PROGRAM_DISPLAY: Record<RewardProgram, string>;
5
5
  export declare const TRAVEL_CPP: Record<RewardProgram, number>;
6
6
  /** RewardProgram.cashOutCPP */
@@ -20,5 +20,12 @@ export declare function centsPerPoint(v: Valuation, card: CreditCard): number;
20
20
  export declare function valuesPointsAboveCash(v: Valuation, card: CreditCard): boolean;
21
21
  export declare function earnUnitLabel(v: Valuation, card: CreditCard): string;
22
22
  export declare function basisLabel(v: Valuation, card: CreditCard): string;
23
- /** Swift String(format: "%.1f") */
23
+ /** Swift String(format: "%.1f") — printf rounds the EXACT binary value, and
24
+ * only an exact tie (1.25 is representable) goes to even ("1.2"). Surfaced
25
+ * Aug 29 2026 when a live card hit a 1.25x earn and 27 golden cases split.
26
+ * Sep 15 2026: the first mirror scaled by 10 first, and 1.5 × 1.1 =
27
+ * 1.6500000000000001 × 10 landed on exactly 16.5 — a FALSE tie, rounded to
28
+ * "1.6" where Swift prints "1.7" (Delta Biz Platinum transit 1.5x @ 1.1¢).
29
+ * toFixed(20) is the exact decimal expansion of the double, so a tie is a
30
+ * tie only when the digits past the tenths are literally 5000…0. */
24
31
  export declare function fmt1(x: number): string;
package/dist/valuation.js CHANGED
@@ -2,17 +2,19 @@ export const PROGRAM_DISPLAY = {
2
2
  cash: "Cash back", amexMR: "Amex Membership Rewards", chaseUR: "Chase Ultimate Rewards",
3
3
  capOneMiles: "Capital One miles", citiTYP: "Citi ThankYou Points", wellsFargo: "Wells Fargo Rewards",
4
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",
5
+ bmoRewards: "BMO Rewards", hyatt: "World of Hyatt", marriott: "Marriott Bonvoy", ihg: "IHG One Rewards", choice: "Choice Privileges",
6
6
  hilton: "Hilton Honors", delta: "Delta SkyMiles", united: "United MileagePlus",
7
7
  american: "American AAdvantage", southwest: "Southwest Rapid Rewards", jetBlue: "JetBlue TrueBlue",
8
8
  alaska: "Alaska Mileage Plan", avios: "British Airways Avios", aeroplan: "Air Canada Aeroplan",
9
9
  fixedValue: "Fixed-value miles", genericPoints: "Points", genericMiles: "Miles",
10
10
  };
11
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,
12
+ // Sep 2026: median of TPG / Frequent Miler / Upgraded Points / OMAAT, rounded
13
+ // down, never above (mirrors Swift RewardProgram.travelCPP).
14
+ cash: 1.0, amexMR: 1.8, chaseUR: 1.8, capOneMiles: 1.7, citiTYP: 1.6, wellsFargo: 1.5, bilt: 1.8,
13
15
  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
+ hyatt: 1.4, marriott: 0.7, ihg: 0.5, choice: 0.6, hilton: 0.4,
17
+ delta: 1.1, united: 1.2, american: 1.4, southwest: 1.2, jetBlue: 1.3, alaska: 1.4, avios: 1.2, aeroplan: 1.3,
16
18
  fixedValue: 1.0, genericPoints: 2.0, genericMiles: 1.5,
17
19
  };
18
20
  /** RewardProgram.cashOutCPP */
@@ -40,6 +42,10 @@ export function inferProgram(issuer, id, currency) {
40
42
  return "hyatt";
41
43
  if (has("ihg"))
42
44
  return "ihg";
45
+ // Choice Privileges (Wells Fargo-issued) must precede the "wells" needle,
46
+ // which valued these hotel points at 1.5¢ (mirrors Swift, Sep 24 2026).
47
+ if (has("choice"))
48
+ return "choice";
43
49
  if (has("delta", "skymiles"))
44
50
  return "delta";
45
51
  if (has("united", "mileageplus"))
@@ -84,6 +90,10 @@ export function inferProgram(issuer, id, currency) {
84
90
  return "fixedValue";
85
91
  if (has("altitude"))
86
92
  return "fixedValue";
93
+ // GM Rewards (Barclays, Sep 2026): points pegged at 1¢, redeemable only toward
94
+ // GM — the generic 2¢ fallback read its flat 3x as "6%" (mirrors Swift).
95
+ if (has("gm_rewards", "gm rewards"))
96
+ return "fixedValue";
87
97
  return currency === "miles" ? "genericMiles" : "genericPoints";
88
98
  }
89
99
  export function makeValuation(mode = "cash") {
@@ -154,7 +164,23 @@ export function basisLabel(v, card) {
154
164
  const pooled = v.pooledPrograms[card.id] !== undefined ? " (pooled with your wallet)" : "";
155
165
  return `${PROGRAM_DISPLAY[effectiveProgram(v, card)]} · ${fmt1(cpp)}¢ travel value${pooled}`;
156
166
  }
157
- /** Swift String(format: "%.1f") */
167
+ /** Swift String(format: "%.1f") — printf rounds the EXACT binary value, and
168
+ * only an exact tie (1.25 is representable) goes to even ("1.2"). Surfaced
169
+ * Aug 29 2026 when a live card hit a 1.25x earn and 27 golden cases split.
170
+ * Sep 15 2026: the first mirror scaled by 10 first, and 1.5 × 1.1 =
171
+ * 1.6500000000000001 × 10 landed on exactly 16.5 — a FALSE tie, rounded to
172
+ * "1.6" where Swift prints "1.7" (Delta Biz Platinum transit 1.5x @ 1.1¢).
173
+ * toFixed(20) is the exact decimal expansion of the double, so a tie is a
174
+ * tie only when the digits past the tenths are literally 5000…0. */
158
175
  export function fmt1(x) {
159
- return (Math.round(x * 10) / 10).toFixed(1);
176
+ const exact = Math.abs(x).toFixed(20);
177
+ const [ip, fp] = exact.split(".");
178
+ if (/^[0-9]50{18}$/.test(fp)) {
179
+ let n = parseInt(ip, 10) * 10 + parseInt(fp[0], 10);
180
+ if (n % 2 !== 0)
181
+ n += 1;
182
+ const s = (n / 10).toFixed(1);
183
+ return x < 0 ? "-" + s : s;
184
+ }
185
+ return x.toFixed(1);
160
186
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "rightcard-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "mcpName": "ai.rightcard/mcp",
5
- "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
+ "description": "RightCard MCP — the card-selection oracle for agents. Which of your credit cards to use at any store, from verified data. No bank login.",
6
6
  "type": "module",
7
7
  "license": "MIT",
8
8
  "bin": {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "repository": {
27
27
  "type": "git",
28
- "url": "https://github.com/parikshitmadahar/rightcard-mcp"
28
+ "url": "git+https://github.com/rightcard/rightcard-mcp.git"
29
29
  },
30
30
  "files": [
31
31
  "dist",