@tickerbot/mcp-server 0.1.5 → 0.2.1

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
@@ -1,6 +1,6 @@
1
1
  # @tickerbot/mcp-server
2
2
 
3
- Model Context Protocol server for [Tickerbot](https://tickerbot.io) — the stock market, in SQL. Install in Claude, Cursor, VS Code Copilot, and other MCP-compatible clients to scan ~12,000 US tickers and top 100 cryptos, replay any day, or subscribe a query — across 338+ signal columns.
3
+ Model Context Protocol server for [Tickerbot](https://tickerbot.io) — the stock market, in SQL. Install in Claude, Cursor, VS Code Copilot, and other MCP-compatible clients to scan ~14,500 US-listed equities plus a curated set of rates, FX and crypto series, replay any day, or subscribe a query — across 421+ signal columns.
4
4
 
5
5
  ## Install
6
6
 
package/dist/handler.js CHANGED
@@ -48,11 +48,65 @@ export async function runTool(name, args, apiKey, options = {}) {
48
48
  catch {
49
49
  // non-JSON response — fall through with text body
50
50
  }
51
+ // Surface advisory headers instead of discarding them (TB-188): the agent
52
+ // should learn when a route is deprecated/sunsetting, when it is being
53
+ // throttled, or when the Free monthly quota is nearly gone. Mirrors
54
+ // main_service/mcp/handler.js — keep the two in step.
55
+ const advisories = collectAdvisories(res.headers);
51
56
  if (!res.ok) {
52
- throw new ToolError(`Tickerbot ${tool.endpoint.method} ${tool.endpoint.path} failed: ${res.status} ${res.statusText}`, res.status, parsed);
57
+ const err = new ToolError(`Tickerbot ${tool.endpoint.method} ${tool.endpoint.path} failed: ${res.status} ${res.statusText}`, res.status, parsed);
58
+ if (advisories)
59
+ err.advisories = advisories;
60
+ throw err;
61
+ }
62
+ if (advisories && parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
63
+ ;
64
+ parsed._advisories = advisories;
53
65
  }
54
66
  return parsed;
55
67
  }
68
+ // The advisory subset of the response-header contract. Deliberately
69
+ // selective: always-on X-RateLimit-*/X-Quota-* counters would add noise to
70
+ // every call, so rate/quota numbers ride along only when the server is
71
+ // actually warning (throttled, near-limit, or low quota).
72
+ function collectAdvisories(headers) {
73
+ const out = {};
74
+ const dep = headers.get('deprecation');
75
+ const sunset = headers.get('sunset');
76
+ if (dep || sunset) {
77
+ const link = headers.get('link') ?? '';
78
+ const succ = link.match(/<([^>]+)>\s*;\s*rel="successor-version"/);
79
+ out.deprecated = {
80
+ ...(dep ? { since: dep } : {}),
81
+ ...(sunset ? { sunset_on: sunset, warning: `This route stops serving on ${sunset}.` } : {}),
82
+ ...(succ ? { successor: succ[1] } : {}),
83
+ };
84
+ }
85
+ const retryAfter = headers.get('retry-after');
86
+ const rlWarning = headers.get('x-ratelimit-warning');
87
+ if (retryAfter || rlWarning) {
88
+ const remaining = headers.get('x-ratelimit-remaining');
89
+ out.rate_limit = {
90
+ ...(rlWarning ? { warning: rlWarning } : {}),
91
+ ...(retryAfter ? { retry_after_seconds: Number(retryAfter) || retryAfter } : {}),
92
+ ...(headers.get('x-ratelimit-limit') ? { limit: Number(headers.get('x-ratelimit-limit')) } : {}),
93
+ ...(remaining != null && remaining !== '' ? { remaining: Number(remaining) } : {}),
94
+ ...(headers.get('x-ratelimit-reset') ? { reset_epoch_s: Number(headers.get('x-ratelimit-reset')) } : {}),
95
+ };
96
+ }
97
+ const quotaLimit = Number(headers.get('x-quota-limit'));
98
+ const quotaRemaining = Number(headers.get('x-quota-remaining'));
99
+ if (headers.get('x-quota-limit') && Number.isFinite(quotaLimit) && Number.isFinite(quotaRemaining)
100
+ && quotaLimit > 0 && quotaRemaining <= quotaLimit * 0.1) {
101
+ out.monthly_quota = {
102
+ warning: `Free-plan monthly quota nearly exhausted: ${quotaRemaining} of ${quotaLimit} calls left this month.`,
103
+ limit: quotaLimit,
104
+ remaining: quotaRemaining,
105
+ ...(headers.get('x-quota-reset') ? { reset_epoch_s: Number(headers.get('x-quota-reset')) } : {}),
106
+ };
107
+ }
108
+ return Object.keys(out).length ? out : null;
109
+ }
56
110
  function buildRequest(tool, args, baseUrl) {
57
111
  let path = tool.endpoint.path;
58
112
  const query = new URLSearchParams();
package/dist/tools.d.ts CHANGED
@@ -5,7 +5,7 @@ export interface ToolDef {
5
5
  inputSchema: {
6
6
  type: 'object';
7
7
  properties: Record<string, JsonSchemaProp>;
8
- required?: string[];
8
+ required?: readonly string[];
9
9
  };
10
10
  endpoint: {
11
11
  method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
@@ -23,6 +23,9 @@ interface JsonSchemaProp {
23
23
  items?: {
24
24
  type: string;
25
25
  };
26
+ /** Nested object params (create_webhook.trigger) carry their own shape. */
27
+ properties?: Record<string, JsonSchemaProp>;
28
+ required?: readonly string[];
26
29
  }
27
30
  export declare const tools: readonly ToolDef[];
28
31
  export declare function findTool(name: string): ToolDef | undefined;