@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 +1 -1
- package/dist/handler.js +55 -1
- package/dist/tools.d.ts +4 -1
- package/dist/tools.js +1752 -300
- package/package.json +2 -2
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 ~
|
|
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
|
-
|
|
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;
|