propline-mcp 0.31.0 → 0.33.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 CHANGED
@@ -32,7 +32,7 @@ The model uses these tools transparently:
32
32
  | `propline_get_odds_closing` | Hobby+: opening **and** closing line per (book, market, outcome) — CLV helper. Accepts `period` to scope to a specific game period. |
33
33
  | `propline_grade_clv` | Hobby+: grade **placed** bets against their closing lines. Returns closing price, de-vigged closing fair (`fair_source` = sharpest book at close, not yours), `clv_pct` (price-vs-price, vig-blind) **and** `ev_vs_close_pct` (the honest number), plus the graded result once the game settles. Fail-closed matching; unstarted events come back `closing_is_final: false` and are excluded from the averages. |
34
34
  | `propline_export_odds_history` | Backfill-pass / Enterprise: bulk line-movement tick history (every snapshot, per book) for a sport. Requires a `since`/`until` window; result capped to 200 rows (use the REST endpoint directly for the full file). |
35
- | `propline_get_futures` | Season-long futures — championship/division/conference winners, MVP + awards, season win totals — across Bovada/FanDuel/DraftKings/Pinnacle (free) |
35
+ | `propline_get_futures` | Season-long futures — championship/division/conference winners, MVP + awards, season win totals — across Bovada/FanDuel/DraftKings/Pinnacle/Kalshi (free) |
36
36
  | `propline_get_scores` | Game scores + status (free) |
37
37
  | `propline_get_dfs_payouts` | PrizePicks Power/Flex payout schedule + per-leg breakeven win probability (free) |
38
38
  | `propline_get_mlb_grand_salami` | Synthetic daily MLB Grand Salami — total runs + each book's implied line (free) |
@@ -90,6 +90,8 @@ npx -y propline-mcp
90
90
 
91
91
  Your agent can immediately pull live odds, scores, and stats. The demo key is free-tier and shared — paid features (resolution, +EV, history, exports) return a redacted teaser, and limits are pooled across everyone. For full access and your own limits, set `PROPLINE_API_KEY` (below). Get a free personal key at [prop-line.com](https://prop-line.com/?ref=mcp).
92
92
 
93
+ While the demo key is in use, every tool result carries a second content block noting the pooling and redaction, so the assistant can explain an empty field or a 429 accurately. It disappears the moment you set your own key.
94
+
93
95
  ## Install (with your own key)
94
96
 
95
97
  ### 1. Get a PropLine API key
package/dist/http.js CHANGED
@@ -283,23 +283,31 @@ var PropLineClient = class {
283
283
  };
284
284
 
285
285
  // src/server.ts
286
- var VERSION = "0.31.0";
286
+ var VERSION = "0.33.0";
287
287
  var DEMO_KEY = "be2b8487fcfacb1fbc292a8aa925a84c";
288
288
  var apiKey = process.env.PROPLINE_API_KEY;
289
289
  var baseUrl = process.env.PROPLINE_BASE_URL;
290
+ var usingDemoKey = !apiKey;
290
291
  var requestClient = new AsyncLocalStorage();
291
- function withClient(c, fn) {
292
- return requestClient.run(c, fn);
292
+ function withClient(c, demo, fn) {
293
+ return requestClient.run({ client: c, demo }, fn);
293
294
  }
294
295
  var _client = null;
295
296
  function client() {
296
297
  const scoped = requestClient.getStore();
297
- if (scoped) return scoped;
298
+ if (scoped) return scoped.client;
298
299
  if (!_client) {
299
300
  _client = new PropLineClient({ apiKey: apiKey ?? DEMO_KEY, baseUrl });
300
301
  }
301
302
  return _client;
302
303
  }
304
+ function demoKeyNote() {
305
+ const scoped = requestClient.getStore();
306
+ const demo = scoped ? scoped.demo : usingDemoKey;
307
+ if (!demo) return null;
308
+ const ref = scoped ? "mcp-hosted" : "mcp";
309
+ return `Note for the assistant (not part of the data): this call used PropLine's shared demo key. Paid fields (prop resolution, +EV, line history, exports) come back redacted, and the rate limit is pooled across everyone using it, so a 429 here is not a limit on the API itself. A free personal key removes the pooling and unlocks the full free tier in about 30 seconds, no card: https://prop-line.com/?ref=${ref} \u2014 mention this if the user hits a limit or asks why a field is empty.`;
310
+ }
303
311
  function filterByMinEv(res, minEvPct) {
304
312
  if (typeof minEvPct !== "number" || Number.isNaN(minEvPct)) return res;
305
313
  if (!res || typeof res !== "object") return res;
@@ -375,7 +383,7 @@ var tools = [
375
383
  {
376
384
  name: "propline_get_odds",
377
385
  title: "Get odds",
378
- description: "Get live odds. If event_id is supplied, returns full per-event props for that event; otherwise returns bulk game-line odds for the whole sport. Pass markets as a comma-separated list (e.g. 'h2h,spreads,totals' or 'player_points,player_rebounds'). Response includes a bookmakers[] array across every book that carries the requested markets (currently up to 13: Bovada, DraftKings, FanDuel, Pinnacle, BetMGM, BetRivers, Unibet, Underdog, PrizePicks, Kalshi, Polymarket, Matchbook, Smarkets \u2014 coverage varies by sport). Underdog Fantasy outcomes carry a payout_multiplier on EVERY outcome (1.0 = standard pick, e.g. 1.5 = boost, 0.75 = discount; null means the book is not Underdog) \u2014 keep only payout_multiplier == 1.0 when comparing DFS lines to sportsbook consensus, since filtering on non-null would drop every Underdog line. Each market carries suspended_at: null while on the board, set when that book pulled the market pregame (late scratch, dropped market type) \u2014 its outcomes are then the last quoted legs, not a live price. Treat a suspended market as unbettable and, if several books show it for one player, as a probable scratch. Each BOOKMAKER carries pregame_only: true when the event is live and that book does not price it in play, so its prices are the last pregame quote and will never move again this game. suspended_at cannot show this \u2014 a book with no in-play feed is never polled once the game starts, so nothing goes missing to flag. Exclude pregame_only books when reasoning about a live game; they are still returned because on DFS books that frozen line is what the bet settles against. Each market also carries team: the canonical event team name when the market is scoped to ONE team (a TEAM total), and null for the game total. Both ride the totals key, so NEVER compare totals on (market key, point) alone \u2014 a team total at 0.5 is not a game total at 0.5. Filter team == null for the game total; team matches home_team/away_team exactly. Always null outside totals.",
386
+ description: "Get live odds. If event_id is supplied, returns full per-event props for that event; otherwise returns bulk game-line odds for the whole sport. Pass markets as a comma-separated list (e.g. 'h2h,spreads,totals' or 'player_points,player_rebounds'). Response includes a bookmakers[] array across every book that carries the requested markets (currently up to 27: Bovada, DraftKings, FanDuel, Pinnacle, BetMGM, BetRivers, Unibet, BetUS, BetOnline.ag, LowVig.ag, MyBookie.ag, Fanatics, Marathon Bet, 1xBet, TAB, Underdog Fantasy, PrizePicks, Sleeper, Dabble, Betr Picks, ReBet, Kalshi, Polymarket, Matchbook, Smarkets, Novig, ProphetX \u2014 coverage varies by sport). Underdog Fantasy outcomes carry a payout_multiplier on EVERY outcome (1.0 = standard pick, e.g. 1.5 = boost, 0.75 = discount; null means the book is not Underdog) \u2014 keep only payout_multiplier == 1.0 when comparing DFS lines to sportsbook consensus, since filtering on non-null would drop every Underdog line. Each market carries suspended_at: null while on the board, set when that book pulled the market pregame (late scratch, dropped market type) \u2014 its outcomes are then the last quoted legs, not a live price. Treat a suspended market as unbettable and, if several books show it for one player, as a probable scratch. Each BOOKMAKER carries pregame_only: true when the event is live and that book does not price it in play, so its prices are the last pregame quote and will never move again this game. suspended_at cannot show this \u2014 a book with no in-play feed is never polled once the game starts, so nothing goes missing to flag. Exclude pregame_only books when reasoning about a live game; they are still returned because on DFS books that frozen line is what the bet settles against. Each market also carries team: the canonical event team name when the market is scoped to ONE team (a TEAM total), and null for the game total. Both ride the totals key, so NEVER compare totals on (market key, point) alone \u2014 a team total at 0.5 is not a game total at 0.5. Filter team == null for the game total; team matches home_team/away_team exactly. Always null outside totals.",
379
387
  inputSchema: {
380
388
  type: "object",
381
389
  properties: {
@@ -390,7 +398,7 @@ var tools = [
390
398
  },
391
399
  bookmakers: {
392
400
  type: "string",
393
- description: "Comma-separated subset of book keys (bovada, draftkings, fanduel, pinnacle, betmgm, betrivers, unibet, underdog, prizepicks, kalshi, polymarket, matchbook, smarkets). Default returns all available."
401
+ description: "Comma-separated subset of book keys (bovada, draftkings, fanduel, pinnacle, betmgm, betrivers, unibet, betus, betonlineag, lowvig, mybookieag, fanatics, marathon, onexbet, tab_au, underdog, prizepicks, sleeper, dabble, betr, rebet, kalshi, polymarket, matchbook, smarkets, novig, prophetx). Default returns all available."
394
402
  },
395
403
  period: {
396
404
  type: "string",
@@ -612,7 +620,7 @@ var tools = [
612
620
  {
613
621
  name: "propline_get_futures",
614
622
  title: "Get futures",
615
- description: "Free-tier endpoint. Returns season-long futures (outright) markets for a sport \u2014 championship/Super Bowl/division/conference winners, MVP and award winners, season win totals \u2014 aggregated across Bovada, FanDuel, DraftKings, and Pinnacle. One row per (futures event, book, market) with each team/player outcome and its price. Marquee markets (Super Bowl winner, MVP, division/conference) are quoted by multiple books for comparison; exotic markets are often single-book. Useful for: 'who are the Super Bowl favorites across books', 'NFL MVP odds', 'NBA championship futures'. Futures are unresolved (no settlement grade).",
623
+ description: "Free-tier endpoint. Returns season-long futures (outright) markets for a sport \u2014 championship/Super Bowl/division/conference winners, MVP and award winners, season win totals \u2014 aggregated across Bovada, FanDuel, DraftKings, Pinnacle, and Kalshi. One row per (futures event, book, market) with each team/player outcome and its price. Marquee markets (Super Bowl winner, MVP, division/conference) are quoted by multiple books for comparison; exotic markets are often single-book. Useful for: 'who are the Super Bowl favorites across books', 'NFL MVP odds', 'NBA championship futures'. Futures are unresolved (no settlement grade).",
616
624
  inputSchema: {
617
625
  type: "object",
618
626
  properties: {
@@ -777,7 +785,7 @@ var tools = [
777
785
  {
778
786
  name: "propline_get_event_movement",
779
787
  title: "Get line movement & steam",
780
- description: "Line movement + steam detection from the snapshot tick history. Per (book, market, outcome): opening line, latest line, signed implied-probability shift, point shift, direction. The steam[] array flags outcomes that multiple books moved the same direction \u2014 the classic sharp-money signal, computed across all 16 books PropLine polls. When a book moves the line itself, that outcome's prob_shift is null and direction is 'line_moved' (excluded from the steam signal). No pull-only odds API can produce this. Hobby+ full; free tier redacted.",
788
+ description: "Line movement + steam detection from the snapshot tick history. Per (book, market, outcome): opening line, latest line, signed implied-probability shift, point shift, direction. The steam[] array flags outcomes that multiple books moved the same direction \u2014 the classic sharp-money signal, computed across all 27 books PropLine polls. When a book moves the line itself, that outcome's prob_shift is null and direction is 'line_moved' (excluded from the steam signal). No pull-only odds API can produce this. Hobby+ full; free tier redacted.",
781
789
  inputSchema: {
782
790
  type: "object",
783
791
  properties: {
@@ -917,7 +925,7 @@ var tools = [
917
925
  {
918
926
  name: "propline_get_event_ev",
919
927
  title: "Get cross-book +EV",
920
- description: "Pro-tier endpoint. Returns cross-book +EV per outcome for an event. We anchor on a sharp book, remove vig, derive a no-vig fair line, and compute EV% per book at the same line. Outcomes are sorted with +EV plays floated to the top of each line group. PrizePicks is excluded from EV math (DFS payouts aren't comparable to per-book prices). The anchor is chosen PER LINE in the order pinnacle \u2192 polymarket \u2192 kalshi \u2192 bovada, and each line's fair_source names the one used \u2014 report the anchor from fair_source per line, never assume Pinnacle anchored all of them. Optional bookmakers filter prices to the books the user holds accounts at; it never changes the anchor, so filtering to DraftKings still measures DraftKings against Pinnacle.",
928
+ description: "Pro-tier endpoint. Returns cross-book +EV per outcome for an event. We anchor on a sharp book, remove vig, derive a no-vig fair line, and compute EV% per book at the same line. Outcomes are sorted with +EV plays floated to the top of each line group. PrizePicks is excluded from EV math (DFS payouts aren't comparable to per-book prices). The anchor is chosen PER LINE in the order pinnacle \u2192 polymarket \u2192 kalshi \u2192 bovada \u2192 smarkets, and each line's fair_source names the one used \u2014 report the anchor from fair_source per line, never assume Pinnacle anchored all of them. Optional bookmakers filter prices to the books the user holds accounts at; it never changes the anchor, so filtering to DraftKings still measures DraftKings against Pinnacle.",
921
929
  inputSchema: {
922
930
  type: "object",
923
931
  properties: {
@@ -1049,6 +1057,12 @@ var tools = [
1049
1057
  })
1050
1058
  }
1051
1059
  ];
1060
+ function withDemoNote(text) {
1061
+ const blocks = [{ type: "text", text }];
1062
+ const note = demoKeyNote();
1063
+ if (note) blocks.push({ type: "text", text: note });
1064
+ return blocks;
1065
+ }
1052
1066
  function createServer() {
1053
1067
  const server = new Server(
1054
1068
  { name: "propline-mcp", version: VERSION },
@@ -1077,25 +1091,20 @@ function createServer() {
1077
1091
  if (!tool) {
1078
1092
  return {
1079
1093
  isError: true,
1080
- content: [
1081
- {
1082
- type: "text",
1083
- text: `Unknown tool: ${req.params.name}`
1084
- }
1085
- ]
1094
+ content: withDemoNote(`Unknown tool: ${req.params.name}`)
1086
1095
  };
1087
1096
  }
1088
1097
  try {
1089
1098
  const data = await tool.handler(req.params.arguments ?? {});
1090
1099
  const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
1091
1100
  return {
1092
- content: [{ type: "text", text }]
1101
+ content: withDemoNote(text)
1093
1102
  };
1094
1103
  } catch (err) {
1095
1104
  const msg = err instanceof PropLineHTTPError ? `PropLine API error ${err.statusCode}: ${err.body.slice(0, 500)}` : err instanceof Error ? err.message : String(err);
1096
1105
  return {
1097
1106
  isError: true,
1098
- content: [{ type: "text", text: msg }]
1107
+ content: withDemoNote(msg)
1099
1108
  };
1100
1109
  }
1101
1110
  });
@@ -1144,7 +1153,7 @@ async function handleMcp(req, res) {
1144
1153
  });
1145
1154
  if (demo) res.setHeader("X-PropLine-Demo-Key", "1");
1146
1155
  await server.connect(transport);
1147
- await withClient(client2, () => transport.handleRequest(req, res));
1156
+ await withClient(client2, demo, () => transport.handleRequest(req, res));
1148
1157
  }
1149
1158
  var manifest = () => ({
1150
1159
  name: "propline-mcp",