propline-mcp 0.32.0 → 0.34.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
@@ -53,7 +53,7 @@ The model uses these tools transparently:
53
53
 
54
54
  ## Hosted endpoint (no install)
55
55
 
56
- The same 25 tools are served over **Streamable HTTP** at
56
+ The same 27 tools are served over **Streamable HTTP** at
57
57
 
58
58
  ```
59
59
  https://mcp.prop-line.com/mcp
@@ -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
@@ -116,7 +118,7 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
116
118
  }
117
119
  ```
118
120
 
119
- Restart Claude Desktop. The hammer icon should show 25 PropLine tools. (Or skip the install and add `https://mcp.prop-line.com/mcp` as a custom connector — see the hosted endpoint above.)
121
+ Restart Claude Desktop. The hammer icon should show 27 PropLine tools. (Or skip the install and add `https://mcp.prop-line.com/mcp` as a custom connector — see the hosted endpoint above.)
120
122
 
121
123
  #### Claude Code
122
124
 
package/dist/http.js CHANGED
@@ -280,26 +280,40 @@ var PropLineClient = class {
280
280
  before_id: opts.beforeId
281
281
  });
282
282
  }
283
+ replayWebhookEvents(webhookId, opts = {}) {
284
+ return this.request(`/v1/webhooks/${webhookId}/replay`, {
285
+ since_seq: opts.sinceSeq,
286
+ limit: opts.limit
287
+ });
288
+ }
283
289
  };
284
290
 
285
291
  // src/server.ts
286
- var VERSION = "0.32.0";
292
+ var VERSION = "0.34.0";
287
293
  var DEMO_KEY = "be2b8487fcfacb1fbc292a8aa925a84c";
288
294
  var apiKey = process.env.PROPLINE_API_KEY;
289
295
  var baseUrl = process.env.PROPLINE_BASE_URL;
296
+ var usingDemoKey = !apiKey;
290
297
  var requestClient = new AsyncLocalStorage();
291
- function withClient(c, fn) {
292
- return requestClient.run(c, fn);
298
+ function withClient(c, demo, fn) {
299
+ return requestClient.run({ client: c, demo }, fn);
293
300
  }
294
301
  var _client = null;
295
302
  function client() {
296
303
  const scoped = requestClient.getStore();
297
- if (scoped) return scoped;
304
+ if (scoped) return scoped.client;
298
305
  if (!_client) {
299
306
  _client = new PropLineClient({ apiKey: apiKey ?? DEMO_KEY, baseUrl });
300
307
  }
301
308
  return _client;
302
309
  }
310
+ function demoKeyNote() {
311
+ const scoped = requestClient.getStore();
312
+ const demo = scoped ? scoped.demo : usingDemoKey;
313
+ if (!demo) return null;
314
+ const ref = scoped ? "mcp-hosted" : "mcp";
315
+ 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.`;
316
+ }
303
317
  function filterByMinEv(res, minEvPct) {
304
318
  if (typeof minEvPct !== "number" || Number.isNaN(minEvPct)) return res;
305
319
  if (!res || typeof res !== "object") return res;
@@ -1047,8 +1061,42 @@ var tools = [
1047
1061
  limit: args.limit,
1048
1062
  beforeId: args.before_id
1049
1063
  })
1064
+ },
1065
+ {
1066
+ name: "propline_replay_webhook_events",
1067
+ title: "Replay missed webhook events",
1068
+ description: "Re-read a webhook subscription's events in order from a cursor \u2014 answers 'my endpoint was down, what did I miss?'. Every delivery carries an X-PropLine-Sequence header, a counter monotonic WITHIN one subscription; pass the highest one the user processed as since_seq. Do NOT use the delivery id as the cursor: that id is global across all subscriptions, so gaps in it are other customers' traffic and mean nothing. Events come back OLDEST FIRST (the opposite of propline_get_webhook_deliveries, which is a newest-first debugging log). Page by passing next_seq back as since_seq while has_more is true. ALWAYS check `truncated`: true means events after the cursor already aged out of retention (2 days, max 5,000 deliveries per subscription) and are unrecoverable \u2014 tell the user to resync from the REST endpoints rather than reporting them caught up. latest_seq is not subject to retention, so latest_seq - next_seq is an honest 'how far behind' even when the rows are gone. Sequence numbers always increase and never repeat but are NOT guaranteed to be dense \u2014 a skipped number is normal and is not evidence of loss.",
1069
+ inputSchema: {
1070
+ type: "object",
1071
+ properties: {
1072
+ webhook_id: {
1073
+ type: "number",
1074
+ description: "Webhook id (from propline_list_webhooks)."
1075
+ },
1076
+ since_seq: {
1077
+ type: "number",
1078
+ description: "Read events after this sequence. Default 0 = from the oldest retained event (which on an established subscription will correctly report truncated: true)."
1079
+ },
1080
+ limit: {
1081
+ type: "number",
1082
+ description: "Events per page, 1-500. Default 100."
1083
+ }
1084
+ },
1085
+ required: ["webhook_id"],
1086
+ additionalProperties: false
1087
+ },
1088
+ handler: (args) => client().replayWebhookEvents(args.webhook_id, {
1089
+ sinceSeq: args.since_seq,
1090
+ limit: args.limit
1091
+ })
1050
1092
  }
1051
1093
  ];
1094
+ function withDemoNote(text) {
1095
+ const blocks = [{ type: "text", text }];
1096
+ const note = demoKeyNote();
1097
+ if (note) blocks.push({ type: "text", text: note });
1098
+ return blocks;
1099
+ }
1052
1100
  function createServer() {
1053
1101
  const server = new Server(
1054
1102
  { name: "propline-mcp", version: VERSION },
@@ -1077,25 +1125,20 @@ function createServer() {
1077
1125
  if (!tool) {
1078
1126
  return {
1079
1127
  isError: true,
1080
- content: [
1081
- {
1082
- type: "text",
1083
- text: `Unknown tool: ${req.params.name}`
1084
- }
1085
- ]
1128
+ content: withDemoNote(`Unknown tool: ${req.params.name}`)
1086
1129
  };
1087
1130
  }
1088
1131
  try {
1089
1132
  const data = await tool.handler(req.params.arguments ?? {});
1090
1133
  const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
1091
1134
  return {
1092
- content: [{ type: "text", text }]
1135
+ content: withDemoNote(text)
1093
1136
  };
1094
1137
  } catch (err) {
1095
1138
  const msg = err instanceof PropLineHTTPError ? `PropLine API error ${err.statusCode}: ${err.body.slice(0, 500)}` : err instanceof Error ? err.message : String(err);
1096
1139
  return {
1097
1140
  isError: true,
1098
- content: [{ type: "text", text: msg }]
1141
+ content: withDemoNote(msg)
1099
1142
  };
1100
1143
  }
1101
1144
  });
@@ -1144,7 +1187,7 @@ async function handleMcp(req, res) {
1144
1187
  });
1145
1188
  if (demo) res.setHeader("X-PropLine-Demo-Key", "1");
1146
1189
  await server.connect(transport);
1147
- await withClient(client2, () => transport.handleRequest(req, res));
1190
+ await withClient(client2, demo, () => transport.handleRequest(req, res));
1148
1191
  }
1149
1192
  var manifest = () => ({
1150
1193
  name: "propline-mcp",