@sharpe-terminal/mcp-server 1.2.0 → 1.5.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
@@ -1,41 +1,24 @@
1
1
  # @sharpe-terminal/mcp-server
2
2
 
3
- Model Context Protocol (MCP) server for the Sharpe crypto derivatives and market data API. Enables AI agents (Claude, Cursor, Windsurf, etc.) to query real-time crypto market data including funding rates, futures, arbitrage opportunities, narratives, ecosystems, and more.
3
+ MCP server for [Sharpe Terminal](https://www.sharpe.ai): crypto derivatives
4
+ and market data for AI agents. Funding rates across 32 exchanges, futures
5
+ (open interest, liquidations, basis, long/short), arbitrage scanners,
6
+ correlation, listings, narratives, and more, exposed as Model Context
7
+ Protocol tools.
4
8
 
5
- ## Installation
6
-
7
- Requires Node.js 18 or later.
8
-
9
- ### npx (no install)
9
+ ## Quick start
10
10
 
11
11
  ```bash
12
12
  npx @sharpe-terminal/mcp-server
13
13
  ```
14
14
 
15
- ### Global install
15
+ Or add it to Claude:
16
16
 
17
17
  ```bash
18
- npm install -g @sharpe-terminal/mcp-server
19
- sharpe-mcp
18
+ claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
20
19
  ```
21
20
 
22
- ## Configuration
23
-
24
- ### Environment Variables
25
-
26
- | Variable | Required | Default | Description |
27
- |---|---|---|---|
28
- | `SHARPE_API_KEY` | No | — | Your Sharpe API key (`sk_live_...`). When set, uses the authenticated `/api/v1/` endpoints with higher rate limits. |
29
- | `SHARPE_API_URL` | No | `https://www.sharpe.ai` | Base URL for authenticated `/api/v1/*` API requests. |
30
- | `SHARPE_FREE_URL` | No | `https://www.sharpe.ai` | Base URL for free `/api/*` fallback requests. |
31
-
32
- Without an API key, the server falls back to free public endpoints when a matching public route exists. Authenticated-only `/api/v1/*` tools require `SHARPE_API_KEY`.
33
-
34
- Custom base URLs must use `https://`, except `http://localhost`/`127.0.0.1`/`::1` for local development. The server refuses API redirects so credentials are not forwarded to unexpected hosts.
35
-
36
- ### Claude Desktop
37
-
38
- Add to your `claude_desktop_config.json` (usually at `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
21
+ Generic MCP client config:
39
22
 
40
23
  ```json
41
24
  {
@@ -43,151 +26,70 @@ Add to your `claude_desktop_config.json` (usually at `~/Library/Application Supp
43
26
  "sharpe": {
44
27
  "command": "npx",
45
28
  "args": ["-y", "@sharpe-terminal/mcp-server"],
46
- "env": {
47
- "SHARPE_API_KEY": "sk_live_your_key_here"
48
- }
29
+ "env": { "SHARPE_API_KEY": "sk_live_..." }
49
30
  }
50
31
  }
51
32
  }
52
33
  ```
53
34
 
54
- ### Claude Code
55
-
56
- ```bash
57
- claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
58
- ```
35
+ ### Hosted endpoint (no install)
59
36
 
60
- Or with an API key:
37
+ The same server runs at `https://www.sharpe.ai/api/mcp` (Streamable HTTP).
38
+ Clients that take a URL need nothing installed; send a key as a header for
39
+ the authenticated routes:
61
40
 
62
41
  ```bash
63
- SHARPE_API_KEY=sk_live_your_key_here claude mcp add sharpe -- npx -y @sharpe-terminal/mcp-server
42
+ claude mcp add --transport http sharpe-mcp https://www.sharpe.ai/api/mcp \
43
+ --header "Authorization: Bearer sk_live_..."
64
44
  ```
65
45
 
66
- ### Cursor
67
-
68
- Add to your `.cursor/mcp.json`:
69
-
70
- ```json
71
- {
72
- "mcpServers": {
73
- "sharpe": {
74
- "command": "npx",
75
- "args": ["-y", "@sharpe-terminal/mcp-server"],
76
- "env": {
77
- "SHARPE_API_KEY": "sk_live_your_key_here"
78
- }
79
- }
80
- }
81
- }
82
- ```
83
-
84
- ### Local development (from source)
85
-
86
- ```bash
87
- cd packages/mcp-server
88
- npm install
89
- npm run build
90
- node dist/index.js
91
- ```
46
+ ## Configuration
92
47
 
93
- ## Available Tools
94
-
95
- The TypeScript package exposes 41 tools: 38 endpoint tools plus 3 composite workflows (`analyze_coin`, `market_briefing`, `find_opportunities`). The Python `sharpe-terminal-mcp` package mirrors this surface and is the primary distribution for the companion CLI.
96
-
97
- All tools are read-only market-data calls. They advertise MCP annotations for `readOnlyHint`, `destructiveHint=false`, `idempotentHint`, and `openWorldHint`. Endpoint tools return both `structuredContent` (`{ "result": ... }`) and text JSON for older clients. Composite workflow tools return `{ "report": "..." }` in `structuredContent` plus the same markdown report in text content.
98
-
99
- Large list responses are capped at 200 rows before they are returned to the MCP client. Truncated payloads include `truncated`, `total`, and `showing` fields.
100
-
101
- Release checklist and cross-package synchronization notes live in [`../../docs/release-packages.md`](../../docs/release-packages.md).
102
-
103
- ### Composite Workflows
104
-
105
- | Tool | Description |
106
- |---|---|
107
- | `analyze_coin` | Single-coin analysis combining funding, futures OI, and prediction data |
108
- | `market_briefing` | Market overview, narrative leaders, and funding highlights in one briefing |
109
- | `find_opportunities` | Funding-rate and arbitrage opportunity scan across multiple sources |
110
-
111
- ### Derivatives
112
-
113
- | Tool | Description |
114
- |---|---|
115
- | `get_funding_rates` | Perpetual funding rates across exchanges (current, accumulated, or historical) |
116
- | `get_futures_data` | Futures chart data: OI, volume, liquidations, long/short ratios, basis, term structure |
117
- | `get_futures_coins` | List of coins with futures data and exchange capability flags |
118
- | `get_derivatives_overview` | Market-wide derivatives snapshot: total OI, weighted funding, top coins, and exchange counts |
119
-
120
- ### Market Data
121
-
122
- | Tool | Description |
123
- |---|---|
124
- | `get_heatmap` | Market heatmap by coins, narratives, or ecosystems |
125
- | `get_correlation_matrix` | Price correlation matrix for portfolio analysis |
126
- | `get_market_overview` | Total market cap, BTC dominance, top gainers/losers, trending coins |
127
- | `get_price_prediction` | ML-based price predictions with confidence intervals |
128
- | `search_market_cap` | Search coins by name or ticker with market cap data |
129
- | `get_news` | Aggregated crypto news with category and coin filters |
130
- | `get_curated_news` | Curated crypto news highlights |
131
- | `get_gem_finder` | Curated low-cap, high-potential token discovery |
132
- | `get_stablecoins` | Stablecoin overview, detail, peg, chain supply, and yield data |
133
- | `get_mindshare` | Narrative mindshare, token-level attention, and historical snapshots |
134
- | `get_web_traffic` | Attention rankings, search trends, and social/developer snapshots |
135
- | `get_katana` | Katana Network TVL, token, holder, sentiment, pool, and ecosystem analytics |
136
-
137
- ### Screener
138
-
139
- | Tool | Description |
140
- |---|---|
141
- | `get_dexscreener_data` | Live DEX pair screening by category, network, or search phrase |
142
- | `get_dexscreener_security` | Token contract security signals for DEX Screener tokens |
143
- | `get_token_scanner` | Read-only hot token, new runner, alpha drop, AI token, and top-new scans |
144
- | `get_rug_check_security` | Token contract and liquidity risk signals |
145
- | `get_rug_check_trending` | Trending tokens suitable for rug-check review |
146
- | `get_insider_selling` | Insider selling pressure and holder-quality risk signals |
147
- | `get_pump_dump` | Pump-and-dump manipulation risk signals |
148
-
149
- ### Arbitrage
150
-
151
- | Tool | Description |
152
- |---|---|
153
- | `get_arbitrage_spot_perp` | Spot-perpetual funding rate arbitrage opportunities |
154
- | `get_arbitrage_cross_exchange` | Cross-exchange price discrepancy arbitrage |
155
- | `get_arbitrage_dated_futures_basis` | Spot-futures cash-and-carry basis rows |
156
- | `get_arbitrage_futures_calendar_spread` | Near-versus-far dated futures curve rows |
157
- | `get_arbitrage_perp_dated_carry` | Perp funding versus dated futures carry rows |
158
- | `get_arbitrage_cex_spot_transfer` | CEX spot-transfer rows with network, fee, depth, and slippage checks |
159
-
160
- ### Categories
161
-
162
- | Tool | Description |
163
- |---|---|
164
- | `get_narratives` | Narrative-level data: L1, L2, DeFi, AI Agents, DePIN, RWA, etc. |
165
- | `get_ecosystems` | Ecosystem-level data: Ethereum, Solana, Arbitrum, Base, etc. with TVL |
166
- | `get_memecoins` | Memecoin narrative data: Dog, Cat, Frog, AI, Solana, Base, Pump.fun, etc. |
167
- | `get_memecoin_launches` | Recently launched memecoin pairs screened by age, liquidity, volume, and transactions |
168
- | `get_exchange_listings` | New Listings aggregate activity by narrative and venue |
169
- | `get_recent_listings` | Recent token listings feed |
170
- | `get_listing_events` | Canonical listing lifecycle events with source confidence |
171
- | `get_listing_exchanges` | Listing connector coverage and parser status |
172
-
173
- ### System
174
-
175
- | Tool | Description |
176
- |---|---|
177
- | `get_api_coverage` | Available data products, exchanges, timeframes, and chart coverage |
178
-
179
- ## Example Prompts
180
-
181
- Once connected, you can ask your AI agent things like:
182
-
183
- - "What are the current BTC funding rates across exchanges?"
184
- - "Show me the top spot-perp arbitrage opportunities"
185
- - "What's the correlation between BTC, ETH, and SOL over the last 90 days?"
186
- - "Show me the DeFi narrative performance data"
187
- - "Search for Solana in the market cap tracker"
188
- - "What are the latest crypto news about Bitcoin?"
189
- - "Get the futures open interest chart for ETH over the last month"
190
-
191
- ## License
192
-
193
- MIT
48
+ | Env var | Required | Purpose |
49
+ | --- | --- | --- |
50
+ | `SHARPE_API_KEY` | No | v1 API key (higher limits). Without it, tools fall back to the free public endpoints. |
51
+ | `SHARPE_API_URL` | No | Override the authenticated API base (HTTPS required outside localhost). |
52
+ | `SHARPE_FREE_URL` | No | Override the free API base (HTTPS required outside localhost). |
53
+
54
+ Get a key at [sharpe.ai/dashboard/api-keys](https://www.sharpe.ai/dashboard/api-keys). Redirects
55
+ are never followed, and keys are never forwarded across hosts.
56
+
57
+ ## What you get
58
+
59
+ - 37 endpoint tools: funding rates (current / accumulated / per-coin
60
+ history), funding settlement (dollar value actually paid, long/short
61
+ split, per-window totals), futures charts, arbitrage (spot-perp,
62
+ cross-exchange, basis, calendar spread, carry), correlation matrix,
63
+ market overview, listings, narratives, ecosystems, memecoins, gem finder,
64
+ rug check, token scanner, stablecoins, news, and more.
65
+ - 3 composite workflows: `analyze_coin`, `market_briefing`,
66
+ `find_opportunities` fan out across several endpoints and return a
67
+ formatted briefing of the fields the API serves (rankings, floors and
68
+ averages are request parameters, never recomputed client-side), ending
69
+ with the freshness of every answer they read.
70
+ - 4 resources and 2 prompts: chart-id catalogs and guided workflows.
71
+
72
+ Paginated endpoints (`get_funding_rates`, `get_gem_finder`) return a
73
+ `pagination` block with the continuation cursor when more rows exist.
74
+
75
+ Large responses are truncated to protect the model context window; the
76
+ payload marks `truncated: true` with the total count when that happens.
77
+
78
+ ## Data freshness
79
+
80
+ Current funding rates refresh every 5 minutes; accumulated funding keeps a
81
+ 30-minute refresh, and funding history rows land per settlement. Futures
82
+ datasets refresh every 10 minutes to 6 hours depending on the metric;
83
+ narrative and ecosystem data refreshes every 2 hours. Responses carry the
84
+ API's freshness metadata.
85
+
86
+ ## Links
87
+
88
+ - API docs: https://www.sharpe.ai/docs
89
+ - OpenAPI spec: https://www.sharpe.ai/openapi.json
90
+ - MCP guide: https://www.sharpe.ai/docs/mcp-server
91
+ - Python server + CLI source:
92
+ [`packages/mcp-server-python`](../mcp-server-python). Its first PyPI release
93
+ is pending; do not use `pip` or `uvx` until the package exists on PyPI.
94
+
95
+ MIT © Sharpe Terminal
@@ -5,9 +5,49 @@ export interface ApiClientOptions {
5
5
  fallbackBaseUrl: string;
6
6
  userAgent: string;
7
7
  fetchImpl?: typeof fetch;
8
+ /** Appended to a keyless 401: how this caller supplies a key. */
9
+ missingKeyHint?: string;
10
+ /** Test seam for retry backoff. */
11
+ sleep?: (ms: number) => Promise<void>;
8
12
  }
13
+ /** How long to wait before retrying, or null when the failure is final. */
14
+ export declare function retryDelayMs(response: Response | null): number | null;
15
+ /**
16
+ * The error an agent reads: status plus the problem detail, suggested action
17
+ * and request id from the API's problem+json body, rather than the raw JSON.
18
+ */
19
+ export declare function describeApiFailure(response: Response, body: string, hint?: string): string;
9
20
  export declare function normalizeBaseUrl(base: string): string;
10
21
  export declare function redactUrl(input: string): string;
11
22
  export declare function buildUrl(base: string, path: string, params: Record<string, unknown>): string;
23
+ /** `{ cursor, has_more, total }` from header-only pagination, or null when absent. */
24
+ export declare function headerPagination(headers: Headers): {
25
+ cursor: string | null;
26
+ has_more: boolean;
27
+ total: number | null;
28
+ } | null;
29
+ /**
30
+ * The freshness envelope an answer carried (engine L5 ServeMeta, WP-S12: v1 `meta`, a free body's `meta`, or the
31
+ * X-Data-* headers of a bare array), as the tools print it beside the result (`data_freshness`).
32
+ */
33
+ export interface DataFreshness {
34
+ dataset_id?: string;
35
+ as_of: string | null;
36
+ stale_after: string | null;
37
+ freshness_status: "fresh" | "stale" | "unknown";
38
+ runtime_status: "ok" | "degraded";
39
+ served_from?: string;
40
+ warnings: string[];
41
+ truncated?: true;
42
+ }
43
+ /** The envelope from a `meta` object that carries one (it has `freshness_status`), else null. */
44
+ export declare function freshnessFromMeta(meta: unknown): DataFreshness | null;
45
+ /**
46
+ * The envelope from the X-Data-* headers: the only place a bare-array answer carries it. Routes not yet on the
47
+ * envelope send X-Data-As-Of / X-Data-Stale of their own, which say the same two things.
48
+ */
49
+ export declare function freshnessFromHeaders(headers: Headers): DataFreshness | null;
50
+ /** What the API said about the freshness of a value callApi returned, or null when it said nothing. */
51
+ export declare function freshnessOf(value: unknown): DataFreshness | null;
12
52
  export declare function createApiCaller(options: ApiClientOptions): ApiCaller;
13
53
  //# sourceMappingURL=api-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsCrD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAQR;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CAgEpE"}
1
+ {"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,SAAS,GAAG,CACtB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC1B,OAAO,CAAC,OAAO,CAAC,CAAC;AAEtB,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mCAAmC;IACnC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAuBD,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAWrE;AASD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,CA+BR;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAsCrD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAS/C;AAED,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,MAAM,CAQR;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,GAAG,IAAI,CAUP;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;IAChD,cAAc,EAAE,IAAI,GAAG,UAAU,CAAC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAyBD,iGAAiG;AACjG,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAwBrE;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CA6B3E;AAID,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAIhE;AAsBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,SAAS,CA8HpE"}
@@ -1,4 +1,76 @@
1
1
  const LOCAL_HTTP_HOSTS = new Set(["localhost", "127.0.0.1", "::1", "[::1]"]);
2
+ const DEFAULT_MISSING_KEY_HINT = "This tool needs an API key: set SHARPE_API_KEY (the free tier works, https://www.sharpe.ai/docs/authentication).";
3
+ /** One retry: transient failures usually clear within a second. */
4
+ const MAX_ATTEMPTS = 2;
5
+ const RETRYABLE_STATUSES = new Set([502, 503, 504]);
6
+ /**
7
+ * A 429 is retried only when the API says the window reopens this soon. A
8
+ * monthly-quota 429 carries a Retry-After of days and fails straight away.
9
+ */
10
+ const MAX_RATE_LIMIT_WAIT_MS = 10_000;
11
+ function retryAfterMs(response) {
12
+ const value = response.headers.get("retry-after");
13
+ if (!value)
14
+ return null;
15
+ const seconds = Number(value);
16
+ if (Number.isFinite(seconds))
17
+ return Math.max(0, seconds * 1000);
18
+ const date = Date.parse(value);
19
+ return Number.isFinite(date) ? Math.max(0, date - Date.now()) : null;
20
+ }
21
+ /** How long to wait before retrying, or null when the failure is final. */
22
+ export function retryDelayMs(response) {
23
+ // Network failure before any response (reset, DNS): retry shortly.
24
+ if (response === null)
25
+ return 500;
26
+ if (RETRYABLE_STATUSES.has(response.status)) {
27
+ return Math.min(retryAfterMs(response) ?? 500, MAX_RATE_LIMIT_WAIT_MS);
28
+ }
29
+ if (response.status === 429) {
30
+ const wait = retryAfterMs(response);
31
+ return wait != null && wait <= MAX_RATE_LIMIT_WAIT_MS ? wait : null;
32
+ }
33
+ return null;
34
+ }
35
+ function isTimeout(error) {
36
+ return (error instanceof Error &&
37
+ (error.name === "TimeoutError" || error.name === "AbortError"));
38
+ }
39
+ /**
40
+ * The error an agent reads: status plus the problem detail, suggested action
41
+ * and request id from the API's problem+json body, rather than the raw JSON.
42
+ */
43
+ export function describeApiFailure(response, body, hint) {
44
+ let detail = body.slice(0, 500);
45
+ try {
46
+ const problem = JSON.parse(body);
47
+ const text = [problem.detail, problem.error, problem.message].find((value) => typeof value === "string" && value !== "");
48
+ if (text) {
49
+ detail = [
50
+ text,
51
+ typeof problem.suggested_action === "string"
52
+ ? problem.suggested_action
53
+ : null,
54
+ typeof problem.request_id === "string"
55
+ ? `(request_id ${problem.request_id})`
56
+ : null,
57
+ ]
58
+ .filter(Boolean)
59
+ .join(" ");
60
+ }
61
+ }
62
+ catch {
63
+ // Not JSON: keep the raw (clipped) body.
64
+ }
65
+ const retryAfter = response.headers.get("retry-after");
66
+ return [
67
+ `API returned ${response.status} ${response.statusText}: ${detail}`,
68
+ retryAfter ? `Retry after ${retryAfter}s.` : null,
69
+ hint ?? null,
70
+ ]
71
+ .filter(Boolean)
72
+ .join(" ");
73
+ }
2
74
  export function normalizeBaseUrl(base) {
3
75
  const normalized = base.trim().replace(/\/+$/, "");
4
76
  if (!normalized)
@@ -54,11 +126,125 @@ export function buildUrl(base, path, params) {
54
126
  }
55
127
  return url.toString();
56
128
  }
129
+ /** `{ cursor, has_more, total }` from header-only pagination, or null when absent. */
130
+ export function headerPagination(headers) {
131
+ const cursor = headers.get("x-next-cursor");
132
+ const totalHeader = headers.get("x-total-count");
133
+ if (cursor == null && totalHeader == null)
134
+ return null;
135
+ const total = totalHeader == null ? Number.NaN : Number(totalHeader);
136
+ return {
137
+ cursor: cursor || null,
138
+ has_more: Boolean(cursor),
139
+ total: Number.isFinite(total) ? total : null,
140
+ };
141
+ }
142
+ /** The ServeMeta keys a free body's `meta` gains (src/engine/serve/envelope.ts SERVE_META_KEYS). */
143
+ const SERVE_META_KEYS = new Set([
144
+ "dataset_id",
145
+ "as_of",
146
+ "stale_after",
147
+ "freshness_sla_seconds",
148
+ "freshness_status",
149
+ "runtime_status",
150
+ "served_from",
151
+ "generated_at",
152
+ "source",
153
+ "coverage",
154
+ "warnings",
155
+ "truncated",
156
+ ]);
157
+ function isRecord(value) {
158
+ return value !== null && typeof value === "object" && !Array.isArray(value);
159
+ }
160
+ const stringOrNull = (value) => typeof value === "string" && value !== "" ? value : null;
161
+ /** The envelope from a `meta` object that carries one (it has `freshness_status`), else null. */
162
+ export function freshnessFromMeta(meta) {
163
+ if (!isRecord(meta))
164
+ return null;
165
+ const status = meta.freshness_status;
166
+ if (status !== "fresh" && status !== "stale" && status !== "unknown") {
167
+ return null;
168
+ }
169
+ return {
170
+ ...(typeof meta.dataset_id === "string"
171
+ ? { dataset_id: meta.dataset_id }
172
+ : {}),
173
+ as_of: stringOrNull(meta.as_of),
174
+ stale_after: stringOrNull(meta.stale_after),
175
+ freshness_status: status,
176
+ runtime_status: meta.runtime_status === "degraded" ? "degraded" : "ok",
177
+ ...(typeof meta.served_from === "string"
178
+ ? { served_from: meta.served_from }
179
+ : {}),
180
+ warnings: Array.isArray(meta.warnings)
181
+ ? meta.warnings.filter((warning) => typeof warning === "string")
182
+ : [],
183
+ ...(meta.truncated === true ? { truncated: true } : {}),
184
+ };
185
+ }
186
+ /**
187
+ * The envelope from the X-Data-* headers: the only place a bare-array answer carries it. Routes not yet on the
188
+ * envelope send X-Data-As-Of / X-Data-Stale of their own, which say the same two things.
189
+ */
190
+ export function freshnessFromHeaders(headers) {
191
+ const status = headers.get("x-data-status");
192
+ const stale = headers.get("x-data-stale");
193
+ const asOf = stringOrNull(headers.get("x-data-as-of"));
194
+ if (status === null && stale === null && asOf === null)
195
+ return null;
196
+ const freshness = asOf === null
197
+ ? "unknown"
198
+ : stale !== null
199
+ ? stale === "true"
200
+ ? "stale"
201
+ : "fresh"
202
+ : status === "fresh"
203
+ ? "fresh"
204
+ : status === "stale"
205
+ ? "stale"
206
+ : "unknown";
207
+ const degraded = status === "degraded" ||
208
+ status === "fallback" ||
209
+ headers.get("x-data-fallback") !== null ||
210
+ headers.get("x-data-partial") !== null;
211
+ return {
212
+ as_of: asOf,
213
+ stale_after: stringOrNull(headers.get("x-data-stale-after")),
214
+ freshness_status: freshness,
215
+ runtime_status: degraded ? "degraded" : "ok",
216
+ warnings: [],
217
+ };
218
+ }
219
+ const FRESHNESS = new WeakMap();
220
+ /** What the API said about the freshness of a value callApi returned, or null when it said nothing. */
221
+ export function freshnessOf(value) {
222
+ return value !== null && typeof value === "object"
223
+ ? (FRESHNESS.get(value) ?? null)
224
+ : null;
225
+ }
226
+ function withFreshnessOf(value, freshness) {
227
+ if (freshness !== null && value !== null && typeof value === "object") {
228
+ FRESHNESS.set(value, freshness);
229
+ }
230
+ return value;
231
+ }
232
+ /** A free body without the envelope keys its `meta` gained (they travel as `data_freshness`); an emptied meta goes. */
233
+ function withoutEnvelopeMeta(body) {
234
+ if (!isRecord(body.meta))
235
+ return body;
236
+ const own = Object.fromEntries(Object.entries(body.meta).filter(([key]) => !SERVE_META_KEYS.has(key)));
237
+ const { meta: _meta, ...rest } = body;
238
+ void _meta;
239
+ return Object.keys(own).length > 0 ? { ...rest, meta: own } : rest;
240
+ }
57
241
  export function createApiCaller(options) {
58
242
  const apiKey = (options.apiKey ?? "").trim();
59
243
  const apiBaseUrl = normalizeBaseUrl(options.apiBaseUrl);
60
244
  const fallbackBaseUrl = normalizeBaseUrl(options.fallbackBaseUrl);
61
245
  const fetchImpl = options.fetchImpl ?? fetch;
246
+ const sleep = options.sleep ??
247
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
62
248
  return async function callApi(apiPath, fallbackPath, args) {
63
249
  const useAuthenticatedApi = apiKey.length > 0;
64
250
  const base = useAuthenticatedApi ? apiBaseUrl : fallbackBaseUrl;
@@ -71,19 +257,48 @@ export function createApiCaller(options) {
71
257
  if (useAuthenticatedApi) {
72
258
  headers.Authorization = `Bearer ${apiKey}`;
73
259
  }
74
- const response = await fetchImpl(url, {
75
- method: "GET",
76
- headers,
77
- redirect: "manual",
78
- signal: AbortSignal.timeout(30_000),
79
- });
80
- if (response.status >= 300 && response.status < 400) {
260
+ let response;
261
+ for (let attempt = 1;; attempt += 1) {
262
+ let attemptResponse = null;
263
+ try {
264
+ attemptResponse = await fetchImpl(url, {
265
+ method: "GET",
266
+ headers,
267
+ redirect: "manual",
268
+ signal: AbortSignal.timeout(30_000),
269
+ });
270
+ }
271
+ catch (error) {
272
+ // A timeout already cost the caller 30s; only fast failures retry.
273
+ if (attempt >= MAX_ATTEMPTS || isTimeout(error))
274
+ throw error;
275
+ }
276
+ const delay = attempt < MAX_ATTEMPTS ? retryDelayMs(attemptResponse) : null;
277
+ if (attemptResponse && delay === null) {
278
+ response = attemptResponse;
279
+ break;
280
+ }
281
+ // Release the discarded attempt's connection before retrying.
282
+ await attemptResponse?.body?.cancel().catch(() => undefined);
283
+ await sleep(delay ?? 0);
284
+ }
285
+ // 304 Not Modified is excluded: it is a cache-validation response, not a
286
+ // redirect, carries no Location, and never forwards credentials anywhere.
287
+ // This client sends no If-None-Match today, so a 304 is unexpected here;
288
+ // let it fall through to the generic !ok handling below instead of
289
+ // misreporting it as a credential-protecting redirect refusal.
290
+ if (response.status >= 300 &&
291
+ response.status < 400 &&
292
+ response.status !== 304) {
81
293
  const location = response.headers.get("location") ?? "unknown";
82
294
  throw new Error(`Sharpe API redirected ${redactUrl(url)} to ${location}. Refusing to follow redirects to protect credentials; set SHARPE_API_URL/SHARPE_FREE_URL to the canonical HTTPS host.`);
83
295
  }
84
296
  if (!response.ok) {
85
297
  const body = await response.text().catch(() => "");
86
- throw new Error(`API returned ${response.status} ${response.statusText}: ${body.slice(0, 500)}`);
298
+ const hint = response.status === 401 && !useAuthenticatedApi
299
+ ? (options.missingKeyHint ?? DEFAULT_MISSING_KEY_HINT)
300
+ : undefined;
301
+ throw new Error(describeApiFailure(response, body, hint));
87
302
  }
88
303
  let data;
89
304
  try {
@@ -92,12 +307,32 @@ export function createApiCaller(options) {
92
307
  catch {
93
308
  throw new Error(`API returned invalid JSON for ${apiPath}`);
94
309
  }
310
+ // The free funding book keeps its body a bare array and carries the page
311
+ // cursor only in X-Next-Cursor / X-Total-Count headers. Wrap it in the v1
312
+ // pagination shape so the runtime reattaches the cursor instead of
313
+ // silently dropping it (2026-09-24 audit).
314
+ if (Array.isArray(data)) {
315
+ const pagination = headerPagination(response.headers);
316
+ return withFreshnessOf(pagination ? { data, pagination } : data, freshnessFromHeaders(response.headers));
317
+ }
95
318
  if (data &&
96
319
  typeof data === "object" &&
97
320
  !Array.isArray(data) &&
98
321
  "data" in data &&
99
322
  "meta" in data) {
100
- return data.data;
323
+ const envelope = data;
324
+ // Sibling fields riding alongside `data`/`meta` -- a pagination cursor,
325
+ // or an aggregate block like funding/settlement's `totals` -- carry
326
+ // state the caller needs. Unwrapping to `.data` alone silently
327
+ // discarded them: paginated results could never be continued
328
+ // (2026-07-27 audit), and an aggregate would have to be recomputed
329
+ // client-side from a truncated page instead of read once.
330
+ const { data: inner, meta, ...siblings } = envelope;
331
+ return withFreshnessOf(Object.keys(siblings).length > 0 ? { data: inner, ...siblings } : inner, freshnessFromMeta(meta) ?? freshnessFromHeaders(response.headers));
332
+ }
333
+ if (isRecord(data)) {
334
+ // A free object body carries the envelope in its own `meta` (merged into any meta it had).
335
+ return withFreshnessOf(withoutEnvelopeMeta(data), freshnessFromMeta(data.meta) ?? freshnessFromHeaders(response.headers));
101
336
  }
102
337
  return data;
103
338
  };
@@ -1 +1 @@
1
- {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}