tradeblocks-mcp 3.0.2 → 3.3.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.
Files changed (218) hide show
  1. package/README.md +85 -80
  2. package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
  3. package/dist/chunk-27S67XW3.js.map +1 -0
  4. package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
  5. package/dist/chunk-2E63THNI.js.map +1 -0
  6. package/dist/{chunk-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
  7. package/dist/chunk-2I3S2ZLT.js.map +1 -0
  8. package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
  9. package/dist/chunk-4HZLVUOZ.js.map +1 -0
  10. package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
  11. package/dist/chunk-NWNSKALN.js.map +1 -0
  12. package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
  13. package/dist/chunk-WNTYA7ER.js.map +1 -0
  14. package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
  15. package/dist/chunk-YZA4RS76.js.map +1 -0
  16. package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
  17. package/dist/chunk-ZTJXC3LS.js.map +1 -0
  18. package/dist/daily-log-processor-MZW2XBZY.js +9 -0
  19. package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
  20. package/dist/iv-solver-worker.js +9 -1
  21. package/dist/market-provider-6U33TQUT.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
  24. package/dist/test-exports.js +876 -483
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-DKDCWGKD.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/package.json +2 -6
  29. package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
  30. package/server/chunk-536NSFED.js.map +1 -0
  31. package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
  32. package/server/chunk-A3UKD64A.js.map +1 -0
  33. package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
  34. package/server/chunk-OMUDNJBJ.js.map +1 -0
  35. package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
  36. package/server/chunk-RJDJHZ5Y.js.map +1 -0
  37. package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
  38. package/server/chunk-RM4B2VKS.js.map +1 -0
  39. package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
  40. package/server/chunk-SY6GDNVI.js.map +1 -0
  41. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  42. package/server/chunk-T66KH2XH.js.map +1 -0
  43. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  44. package/server/chunk-W5E7FHC4.js.map +1 -0
  45. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  46. package/server/config-DK7KOMNL.js.map +1 -0
  47. package/server/daily-log-processor-B2VPU2FQ.js +10 -0
  48. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  49. package/server/http-server.js +2 -5
  50. package/server/index.js +2497 -2029
  51. package/server/index.js.map +1 -1
  52. package/server/iv-solver-worker.js +9 -1
  53. package/server/market-provider-L56RFHAC.js +17 -0
  54. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  55. package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
  56. package/server/trade-processor-3PI4LHZI.js +10 -0
  57. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  58. package/src/auth/clients-store.ts +4 -4
  59. package/src/auth/code-store.ts +1 -1
  60. package/src/auth/config.ts +11 -12
  61. package/src/auth/login-page.ts +8 -10
  62. package/src/auth/provider.ts +35 -30
  63. package/src/auth/token.ts +17 -15
  64. package/src/db/backtest-schemas.ts +38 -8
  65. package/src/db/connection.ts +107 -40
  66. package/src/db/data-root.ts +22 -0
  67. package/src/db/index.ts +37 -5
  68. package/src/db/json-adapters.ts +22 -31
  69. package/src/db/json-migration.ts +11 -15
  70. package/src/db/json-store.ts +5 -3
  71. package/src/db/market-datasets.ts +48 -15
  72. package/src/db/market-schemas.ts +11 -5
  73. package/src/db/market-views.ts +119 -32
  74. package/src/db/parquet-writer.ts +7 -7
  75. package/src/db/profile-schemas.ts +39 -22
  76. package/src/db/schemas.ts +2 -2
  77. package/src/http-server.ts +7 -12
  78. package/src/index.ts +8 -9
  79. package/src/market/ingestor/index.ts +1 -4
  80. package/src/market/ingestor/market-ingestor.ts +140 -67
  81. package/src/market/ingestor/types.ts +1 -3
  82. package/src/market/stores/chain-sql.ts +2 -8
  83. package/src/market/stores/chain-store.ts +1 -4
  84. package/src/market/stores/duckdb-chain-store.ts +3 -14
  85. package/src/market/stores/duckdb-enriched-store.ts +3 -13
  86. package/src/market/stores/duckdb-quote-store.ts +10 -27
  87. package/src/market/stores/duckdb-spot-store.ts +4 -20
  88. package/src/market/stores/enriched-sql.ts +3 -9
  89. package/src/market/stores/enriched-store.ts +2 -2
  90. package/src/market/stores/parquet-chain-store.ts +5 -22
  91. package/src/market/stores/parquet-enriched-store.ts +4 -16
  92. package/src/market/stores/parquet-oi-daily-store.ts +3 -14
  93. package/src/market/stores/parquet-quote-store.ts +9 -29
  94. package/src/market/stores/parquet-spot-store.ts +14 -33
  95. package/src/market/stores/quote-store.ts +2 -10
  96. package/src/market/stores/spot-sql.ts +3 -15
  97. package/src/market/stores/spot-store.ts +1 -1
  98. package/src/market/tickers/defaults.json +6 -6
  99. package/src/market/tickers/loader.ts +2 -7
  100. package/src/market/tickers/registry.ts +5 -15
  101. package/src/market/tickers/schemas.ts +2 -6
  102. package/src/models/strategy-profile.ts +39 -39
  103. package/src/sync/block-sync.ts +44 -65
  104. package/src/sync/index.ts +1 -4
  105. package/src/sync/metadata.ts +14 -20
  106. package/src/test-exports.ts +139 -123
  107. package/src/tools/analysis.ts +180 -159
  108. package/src/tools/batch-exit-analysis.ts +100 -59
  109. package/src/tools/blocks/analysis.ts +116 -101
  110. package/src/tools/blocks/comparison.ts +281 -364
  111. package/src/tools/blocks/core.ts +208 -253
  112. package/src/tools/blocks/health.ts +571 -563
  113. package/src/tools/blocks/index.ts +2 -0
  114. package/src/tools/blocks/paired-comparison.ts +425 -0
  115. package/src/tools/blocks/similarity.ts +316 -283
  116. package/src/tools/edge-decay.ts +124 -197
  117. package/src/tools/exit-analysis.ts +130 -77
  118. package/src/tools/greeks-attribution.ts +84 -35
  119. package/src/tools/guides.ts +4 -6
  120. package/src/tools/imports.ts +11 -14
  121. package/src/tools/market-data.ts +1067 -757
  122. package/src/tools/market-enrichment.ts +3 -3
  123. package/src/tools/market-fetch.ts +148 -67
  124. package/src/tools/market-imports.ts +12 -12
  125. package/src/tools/middleware/sync-middleware.ts +5 -6
  126. package/src/tools/performance.ts +185 -302
  127. package/src/tools/profile-analysis.ts +52 -66
  128. package/src/tools/profiles.ts +106 -69
  129. package/src/tools/regime-advisor.ts +20 -45
  130. package/src/tools/replay.ts +81 -77
  131. package/src/tools/reports/discrepancies.ts +298 -328
  132. package/src/tools/reports/fields.ts +7 -25
  133. package/src/tools/reports/helpers.ts +18 -49
  134. package/src/tools/reports/predictive.ts +27 -70
  135. package/src/tools/reports/slippage-trends.ts +315 -345
  136. package/src/tools/reports/slippage.ts +1 -4
  137. package/src/tools/reports/strategy-matches.ts +399 -441
  138. package/src/tools/schema.ts +43 -40
  139. package/src/tools/shared/filters.ts +3 -9
  140. package/src/tools/snapshot.ts +9 -30
  141. package/src/tools/sql.ts +15 -14
  142. package/src/tools/tickers.ts +1 -4
  143. package/src/utils/batch-exit-analysis.ts +31 -42
  144. package/src/utils/black-scholes.ts +39 -29
  145. package/src/utils/block-loader.ts +69 -83
  146. package/src/utils/calibration-probe.ts +3 -4
  147. package/src/utils/chain-loader.ts +3 -3
  148. package/src/utils/csv-discovery.ts +16 -22
  149. package/src/utils/data-quality.ts +24 -36
  150. package/src/utils/exit-triggers.ts +91 -96
  151. package/src/utils/field-timing.ts +94 -79
  152. package/src/utils/filter-predicates.ts +13 -9
  153. package/src/utils/flatfile-importer.ts +94 -64
  154. package/src/utils/greeks-decomposition.ts +152 -100
  155. package/src/utils/iv-solver-pool.ts +55 -25
  156. package/src/utils/iv-solver-worker.ts +5 -5
  157. package/src/utils/market-enricher.ts +528 -497
  158. package/src/utils/market-importer.ts +31 -12
  159. package/src/utils/market-provider.ts +21 -23
  160. package/src/utils/massive-tier.ts +5 -7
  161. package/src/utils/migrate-option-data-helpers.ts +2 -8
  162. package/src/utils/option-quote-greeks.ts +25 -31
  163. package/src/utils/option-time.ts +4 -8
  164. package/src/utils/output-formatter.ts +1 -4
  165. package/src/utils/provider-capabilities.ts +1 -4
  166. package/src/utils/providers/massive.ts +59 -93
  167. package/src/utils/providers/thetadata/backfill.ts +14 -23
  168. package/src/utils/providers/thetadata/client.ts +12 -8
  169. package/src/utils/providers/thetadata/decode.ts +2 -20
  170. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  171. package/src/utils/providers/thetadata/join.ts +11 -10
  172. package/src/utils/providers/thetadata/proto.ts +12 -10
  173. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  174. package/src/utils/providers/thetadata.ts +11 -10
  175. package/src/utils/quote-enricher.ts +4 -4
  176. package/src/utils/quote-parquet-projection.ts +3 -11
  177. package/src/utils/sample-date-selector.ts +3 -5
  178. package/src/utils/schema-metadata.ts +102 -70
  179. package/src/utils/ticker.ts +5 -9
  180. package/src/utils/trade-replay.ts +77 -68
  181. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  182. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  183. package/dist/chunk-FGZH632F.js.map +0 -1
  184. package/dist/chunk-LDKTV7GW.js.map +0 -1
  185. package/dist/chunk-PRAYH3RT.js.map +0 -1
  186. package/dist/chunk-QTTR7AAW.js.map +0 -1
  187. package/dist/chunk-W2PP3LEH.js.map +0 -1
  188. package/dist/chunk-XXYOUIZY.js.map +0 -1
  189. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  190. package/dist/market-provider-VDRJUEF2.js +0 -16
  191. package/dist/trade-processor-NHU4VWRX.js +0 -9
  192. package/manifest.json +0 -141
  193. package/server/chunk-5EBXHT6C.js.map +0 -1
  194. package/server/chunk-72GKJE2U.js.map +0 -1
  195. package/server/chunk-CSDVJPBB.js.map +0 -1
  196. package/server/chunk-FBNDMCT5.js.map +0 -1
  197. package/server/chunk-NRFXAJF7.js.map +0 -1
  198. package/server/chunk-NZO6PT64.js.map +0 -1
  199. package/server/chunk-PNKG7RY7.js.map +0 -1
  200. package/server/chunk-WA5AAPCH.js.map +0 -1
  201. package/server/config-6IZXEFEX.js.map +0 -1
  202. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  203. package/server/market-provider-VOYYVYWT.js +0 -17
  204. package/server/trade-processor-JWVS37KM.js +0 -10
  205. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
  206. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  207. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
  208. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  209. /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
  210. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
  211. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  212. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
  213. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  214. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
  215. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  216. /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
  217. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
  218. /package/server/{trades-store-VY26MWER.js.map → trades-store-RTTEYMXH.js.map} +0 -0
@@ -218,7 +218,15 @@ function computeLegGreeks(optionPrice, underlyingPrice, strike, dte, type, riskF
218
218
  const bsType = type === "C" ? "call" : "put";
219
219
  const nullResult = { delta: null, gamma: null, theta: null, vega: null, iv: null };
220
220
  if (dte < BACHELIER_DTE_THRESHOLD) {
221
- const iv2 = solveNormalIV(bsType, optionPrice, underlyingPrice, strike, T, riskFreeRate, dividendYield);
221
+ const iv2 = solveNormalIV(
222
+ bsType,
223
+ optionPrice,
224
+ underlyingPrice,
225
+ strike,
226
+ T,
227
+ riskFreeRate,
228
+ dividendYield
229
+ );
222
230
  if (iv2 === null) return nullResult;
223
231
  return {
224
232
  delta: bachelierDelta(bsType, underlyingPrice, strike, T, riskFreeRate, dividendYield, iv2),
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ _resetProvider,
4
+ getProvider
5
+ } from "./chunk-OMUDNJBJ.js";
6
+ import "./chunk-RJDJHZ5Y.js";
7
+ import "./chunk-536NSFED.js";
8
+ import "./chunk-A3UKD64A.js";
9
+ import "./chunk-SY6GDNVI.js";
10
+ import "./chunk-W5E7FHC4.js";
11
+ import "./chunk-T66KH2XH.js";
12
+ import "./chunk-UT3JLF3M.js";
13
+ export {
14
+ _resetProvider,
15
+ getProvider
16
+ };
17
+ //# sourceMappingURL=market-provider-L56RFHAC.js.map
@@ -3,11 +3,11 @@ import {
3
3
  loadMddsGrpcPackage,
4
4
  loadMddsProtoRoot,
5
5
  resolveMddsProtoPath
6
- } from "./chunk-FBNDMCT5.js";
6
+ } from "./chunk-T66KH2XH.js";
7
7
  import "./chunk-UT3JLF3M.js";
8
8
  export {
9
9
  loadMddsGrpcPackage,
10
10
  loadMddsProtoRoot,
11
11
  resolveMddsProtoPath
12
12
  };
13
- //# sourceMappingURL=proto-DWRZJO4E.js.map
13
+ //# sourceMappingURL=proto-YZ4TLX2N.js.map
@@ -9,12 +9,12 @@ import {
9
9
  syncAllBlocks,
10
10
  syncBlock,
11
11
  upsertSyncMetadata
12
- } from "./chunk-NZO6PT64.js";
13
- import "./chunk-72GKJE2U.js";
14
- import "./chunk-CSDVJPBB.js";
15
- import "./chunk-WA5AAPCH.js";
16
- import "./chunk-5EBXHT6C.js";
17
- import "./chunk-NRFXAJF7.js";
12
+ } from "./chunk-RM4B2VKS.js";
13
+ import "./chunk-RJDJHZ5Y.js";
14
+ import "./chunk-536NSFED.js";
15
+ import "./chunk-A3UKD64A.js";
16
+ import "./chunk-SY6GDNVI.js";
17
+ import "./chunk-W5E7FHC4.js";
18
18
  import "./chunk-UT3JLF3M.js";
19
19
  export {
20
20
  deleteSyncMetadata,
@@ -27,4 +27,4 @@ export {
27
27
  syncBlock,
28
28
  upsertSyncMetadata
29
29
  };
30
- //# sourceMappingURL=sync-QFI5L7S7.js.map
30
+ //# sourceMappingURL=sync-CENKDN53.js.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ TradeProcessor
4
+ } from "./chunk-536NSFED.js";
5
+ import "./chunk-SY6GDNVI.js";
6
+ import "./chunk-UT3JLF3M.js";
7
+ export {
8
+ TradeProcessor
9
+ };
10
+ //# sourceMappingURL=trade-processor-3PI4LHZI.js.map
@@ -13,7 +13,7 @@ import {
13
13
  getTradesPage,
14
14
  searchTrades,
15
15
  updateTradesForBlock
16
- } from "./chunk-NRFXAJF7.js";
16
+ } from "./chunk-W5E7FHC4.js";
17
17
  import "./chunk-UT3JLF3M.js";
18
18
  export {
19
19
  addTrades,
@@ -30,4 +30,4 @@ export {
30
30
  searchTrades,
31
31
  updateTradesForBlock
32
32
  };
33
- //# sourceMappingURL=trades-store-VY26MWER.js.map
33
+ //# sourceMappingURL=trades-store-RTTEYMXH.js.map
@@ -1,6 +1,6 @@
1
- import { randomUUID } from 'node:crypto';
2
- import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
3
- import type { OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
1
+ import { randomUUID } from "node:crypto";
2
+ import type { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
3
+ import type { OAuthClientInformationFull } from "@modelcontextprotocol/sdk/shared/auth.js";
4
4
 
5
5
  export class InMemoryClientsStore implements OAuthRegisteredClientsStore {
6
6
  private clients = new Map<string, OAuthClientInformationFull>();
@@ -10,7 +10,7 @@ export class InMemoryClientsStore implements OAuthRegisteredClientsStore {
10
10
  }
11
11
 
12
12
  registerClient(
13
- clientMetadata: Omit<OAuthClientInformationFull, 'client_id' | 'client_id_issued_at'>
13
+ clientMetadata: Omit<OAuthClientInformationFull, "client_id" | "client_id_issued_at">,
14
14
  ): OAuthClientInformationFull {
15
15
  const clientId = randomUUID();
16
16
  const client: OAuthClientInformationFull = {
@@ -7,7 +7,7 @@ export interface CodeEntry {
7
7
  expiresAt: number;
8
8
  }
9
9
 
10
- type StoreInput = Omit<CodeEntry, 'expiresAt'>;
10
+ type StoreInput = Omit<CodeEntry, "expiresAt">;
11
11
 
12
12
  export class AuthCodeStore {
13
13
  private codes = new Map<string, CodeEntry>();
@@ -8,14 +8,14 @@ export interface AuthConfig {
8
8
  }
9
9
 
10
10
  export function loadAuthConfig(options: { noAuth?: boolean } = {}): AuthConfig {
11
- const noAuth = options.noAuth || process.env.TRADEBLOCKS_NO_AUTH === 'true';
11
+ const noAuth = options.noAuth || process.env.TRADEBLOCKS_NO_AUTH === "true";
12
12
 
13
13
  if (noAuth) {
14
14
  return {
15
- username: '',
16
- password: '',
17
- jwtSecret: '',
18
- jwtExpiry: '24h',
15
+ username: "",
16
+ password: "",
17
+ jwtSecret: "",
18
+ jwtExpiry: "24h",
19
19
  noAuth: true,
20
20
  };
21
21
  }
@@ -23,25 +23,24 @@ export function loadAuthConfig(options: { noAuth?: boolean } = {}): AuthConfig {
23
23
  const username = process.env.TRADEBLOCKS_USERNAME;
24
24
  const password = process.env.TRADEBLOCKS_PASSWORD;
25
25
  const jwtSecret = process.env.TRADEBLOCKS_JWT_SECRET;
26
- const jwtExpiry = process.env.TRADEBLOCKS_JWT_EXPIRY || '24h';
26
+ const jwtExpiry = process.env.TRADEBLOCKS_JWT_EXPIRY || "24h";
27
27
  const issuerUrl = process.env.TRADEBLOCKS_ISSUER_URL;
28
28
 
29
29
  if (!username) {
30
30
  throw new Error(
31
- 'TRADEBLOCKS_USERNAME is required for HTTP mode.\n' +
32
- 'Set it in your .env file or pass --no-auth to disable authentication.'
31
+ "TRADEBLOCKS_USERNAME is required for HTTP mode.\n" +
32
+ "Set it in your .env file or pass --no-auth to disable authentication.",
33
33
  );
34
34
  }
35
35
  if (!password) {
36
36
  throw new Error(
37
- 'TRADEBLOCKS_PASSWORD is required for HTTP mode.\n' +
38
- 'Set it in your .env file.'
37
+ "TRADEBLOCKS_PASSWORD is required for HTTP mode.\n" + "Set it in your .env file.",
39
38
  );
40
39
  }
41
40
  if (!jwtSecret) {
42
41
  throw new Error(
43
- 'TRADEBLOCKS_JWT_SECRET is required for HTTP mode.\n' +
44
- 'Generate one with: openssl rand -hex 32'
42
+ "TRADEBLOCKS_JWT_SECRET is required for HTTP mode.\n" +
43
+ "Generate one with: openssl rand -hex 32",
45
44
  );
46
45
  }
47
46
 
@@ -11,9 +11,7 @@ export interface LoginPageParams {
11
11
  }
12
12
 
13
13
  export function renderLoginPage(params: LoginPageParams): string {
14
- const error = params.error
15
- ? `<div class="error">${escapeHtml(params.error)}</div>`
16
- : '';
14
+ const error = params.error ? `<div class="error">${escapeHtml(params.error)}</div>` : "";
17
15
 
18
16
  return `<!DOCTYPE html>
19
17
  <html lang="en">
@@ -65,11 +63,11 @@ export function renderLoginPage(params: LoginPageParams): string {
65
63
  ${error}
66
64
  <form method="POST" action="/login">
67
65
  <input type="hidden" name="redirect_uri" value="${escapeHtml(params.redirectUri)}">
68
- <input type="hidden" name="state" value="${escapeHtml(params.state || '')}">
66
+ <input type="hidden" name="state" value="${escapeHtml(params.state || "")}">
69
67
  <input type="hidden" name="code_challenge" value="${escapeHtml(params.codeChallenge)}">
70
68
  <input type="hidden" name="client_id" value="${escapeHtml(params.clientId)}">
71
- <input type="hidden" name="scopes" value="${escapeHtml(params.scopes.join(' '))}">
72
- ${params.resource ? `<input type="hidden" name="resource" value="${escapeHtml(params.resource)}">` : ''}
69
+ <input type="hidden" name="scopes" value="${escapeHtml(params.scopes.join(" "))}">
70
+ ${params.resource ? `<input type="hidden" name="resource" value="${escapeHtml(params.resource)}">` : ""}
73
71
  <label for="username">Username</label>
74
72
  <input type="text" id="username" name="username" required autocomplete="username">
75
73
  <label for="password">Password</label>
@@ -83,8 +81,8 @@ export function renderLoginPage(params: LoginPageParams): string {
83
81
 
84
82
  function escapeHtml(str: string): string {
85
83
  return str
86
- .replace(/&/g, '&amp;')
87
- .replace(/"/g, '&quot;')
88
- .replace(/</g, '&lt;')
89
- .replace(/>/g, '&gt;');
84
+ .replace(/&/g, "&amp;")
85
+ .replace(/"/g, "&quot;")
86
+ .replace(/</g, "&lt;")
87
+ .replace(/>/g, "&gt;");
90
88
  }
@@ -1,15 +1,18 @@
1
- import { randomUUID, scryptSync, timingSafeEqual } from 'node:crypto';
2
- import type { Response } from 'express';
3
- import type { OAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/provider.js';
4
- import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
5
- import type { AuthInfo } from '@modelcontextprotocol/sdk/server/auth/types.js';
6
- import type { OAuthClientInformationFull, OAuthTokens } from '@modelcontextprotocol/sdk/shared/auth.js';
7
- import { InvalidTokenError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
8
- import { InMemoryClientsStore } from './clients-store.ts';
9
- import { AuthCodeStore } from './code-store.ts';
10
- import { issueAccessToken, verifyAccessToken as verifyJwt } from './token.ts';
11
- import { renderLoginPage } from './login-page.ts';
12
- import type { AuthConfig } from './config.ts';
1
+ import { randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
2
+ import type { Response } from "express";
3
+ import type { OAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/provider.js";
4
+ import type { OAuthRegisteredClientsStore } from "@modelcontextprotocol/sdk/server/auth/clients.js";
5
+ import type { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
6
+ import type {
7
+ OAuthClientInformationFull,
8
+ OAuthTokens,
9
+ } from "@modelcontextprotocol/sdk/shared/auth.js";
10
+ import { InvalidTokenError } from "@modelcontextprotocol/sdk/server/auth/errors.js";
11
+ import { InMemoryClientsStore } from "./clients-store.ts";
12
+ import { AuthCodeStore } from "./code-store.ts";
13
+ import { issueAccessToken, verifyAccessToken as verifyJwt } from "./token.ts";
14
+ import { renderLoginPage } from "./login-page.ts";
15
+ import type { AuthConfig } from "./config.ts";
13
16
 
14
17
  interface AuthorizationParams {
15
18
  state?: string;
@@ -50,7 +53,7 @@ export class TradeBlocksAuthProvider implements OAuthServerProvider {
50
53
  async authorize(
51
54
  client: OAuthClientInformationFull,
52
55
  params: AuthorizationParams,
53
- res: Response
56
+ res: Response,
54
57
  ): Promise<void> {
55
58
  const html = renderLoginPage({
56
59
  redirectUri: params.redirectUri,
@@ -60,13 +63,13 @@ export class TradeBlocksAuthProvider implements OAuthServerProvider {
60
63
  scopes: params.scopes || [],
61
64
  resource: params.resource?.toString(),
62
65
  });
63
- res.setHeader('Content-Type', 'text/html');
66
+ res.setHeader("Content-Type", "text/html");
64
67
  res.send(html);
65
68
  }
66
69
 
67
70
  handleLogin(body: LoginBody): LoginResult {
68
71
  if (!this.validateCredentials(body.username, body.password)) {
69
- return { error: 'Invalid username or password' };
72
+ return { error: "Invalid username or password" };
70
73
  }
71
74
 
72
75
  const code = randomUUID();
@@ -74,14 +77,14 @@ export class TradeBlocksAuthProvider implements OAuthServerProvider {
74
77
  codeChallenge: body.code_challenge,
75
78
  clientId: body.client_id,
76
79
  redirectUri: body.redirect_uri,
77
- scopes: (body.scopes || '').split(' ').filter(Boolean),
80
+ scopes: (body.scopes || "").split(" ").filter(Boolean),
78
81
  resource: body.resource ? new URL(body.resource) : undefined,
79
82
  });
80
83
 
81
84
  const targetUrl = new URL(body.redirect_uri);
82
- targetUrl.searchParams.set('code', code);
85
+ targetUrl.searchParams.set("code", code);
83
86
  if (body.state) {
84
- targetUrl.searchParams.set('state', body.state);
87
+ targetUrl.searchParams.set("state", body.state);
85
88
  }
86
89
 
87
90
  return { redirectUrl: targetUrl.toString() };
@@ -89,22 +92,22 @@ export class TradeBlocksAuthProvider implements OAuthServerProvider {
89
92
 
90
93
  async challengeForAuthorizationCode(
91
94
  _client: OAuthClientInformationFull,
92
- authorizationCode: string
95
+ authorizationCode: string,
93
96
  ): Promise<string> {
94
97
  const entry = this.codeStore.peek(authorizationCode);
95
- if (!entry) throw new Error('Invalid authorization code');
98
+ if (!entry) throw new Error("Invalid authorization code");
96
99
  return entry.codeChallenge;
97
100
  }
98
101
 
99
102
  async exchangeAuthorizationCode(
100
103
  client: OAuthClientInformationFull,
101
- authorizationCode: string
104
+ authorizationCode: string,
102
105
  ): Promise<OAuthTokens> {
103
106
  const entry = this.codeStore.consume(authorizationCode);
104
- if (!entry) throw new Error('Invalid or expired authorization code');
107
+ if (!entry) throw new Error("Invalid or expired authorization code");
105
108
 
106
109
  if (entry.clientId !== client.client_id) {
107
- throw new Error('Authorization code was not issued to this client');
110
+ throw new Error("Authorization code was not issued to this client");
108
111
  }
109
112
 
110
113
  const { access_token, expires_in } = await issueAccessToken({
@@ -116,15 +119,17 @@ export class TradeBlocksAuthProvider implements OAuthServerProvider {
116
119
 
117
120
  return {
118
121
  access_token,
119
- token_type: 'bearer',
122
+ token_type: "bearer",
120
123
  expires_in,
121
- scope: entry.scopes.join(' '),
124
+ scope: entry.scopes.join(" "),
122
125
  };
123
126
  }
124
127
 
125
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
126
- async exchangeRefreshToken(client: OAuthClientInformationFull, refreshToken: string): Promise<OAuthTokens> {
127
- throw new Error('Refresh tokens are not supported');
128
+ async exchangeRefreshToken(
129
+ _client: OAuthClientInformationFull,
130
+ _refreshToken: string,
131
+ ): Promise<OAuthTokens> {
132
+ throw new Error("Refresh tokens are not supported");
128
133
  }
129
134
 
130
135
  async verifyAccessToken(token: string): Promise<AuthInfo> {
@@ -133,12 +138,12 @@ export class TradeBlocksAuthProvider implements OAuthServerProvider {
133
138
  } catch {
134
139
  // MCP SDK requireBearerAuth checks `error instanceof InvalidTokenError`
135
140
  // to return 401. Generic errors fall through to 500.
136
- throw new InvalidTokenError('Invalid or expired access token');
141
+ throw new InvalidTokenError("Invalid or expired access token");
137
142
  }
138
143
  }
139
144
 
140
145
  private validateCredentials(username: string, password: string): boolean {
141
- const salt = 'tradeblocks-auth';
146
+ const salt = "tradeblocks-auth";
142
147
  const hashA = scryptSync(username, salt, 32);
143
148
  const hashB = scryptSync(this.config.username, salt, 32);
144
149
  const hashC = scryptSync(password, salt, 32);
package/src/auth/token.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { SignJWT, jwtVerify } from 'jose';
1
+ import { SignJWT, jwtVerify } from "jose";
2
2
 
3
3
  // AuthInfo matches the MCP SDK's expected shape
4
4
  export interface AuthInfo {
@@ -19,10 +19,10 @@ export async function issueAccessToken(params: {
19
19
 
20
20
  const jwt = await new SignJWT({
21
21
  client_id: params.clientId,
22
- scope: params.scopes.join(' '),
22
+ scope: params.scopes.join(" "),
23
23
  })
24
- .setProtectedHeader({ alg: 'HS256' })
25
- .setSubject('tradeblocks-user')
24
+ .setProtectedHeader({ alg: "HS256" })
25
+ .setSubject("tradeblocks-user")
26
26
  .setIssuedAt()
27
27
  .setExpirationTime(Math.floor(Date.now() / 1000) + expirySeconds)
28
28
  .sign(secretKey);
@@ -30,17 +30,14 @@ export async function issueAccessToken(params: {
30
30
  return { access_token: jwt, expires_in: expirySeconds };
31
31
  }
32
32
 
33
- export async function verifyAccessToken(
34
- token: string,
35
- secret: string
36
- ): Promise<AuthInfo> {
33
+ export async function verifyAccessToken(token: string, secret: string): Promise<AuthInfo> {
37
34
  const secretKey = new TextEncoder().encode(secret);
38
35
  const { payload } = await jwtVerify(token, secretKey);
39
36
 
40
37
  return {
41
38
  token,
42
- clientId: (payload.client_id as string) || '',
43
- scopes: ((payload.scope as string) || '').split(' ').filter(Boolean),
39
+ clientId: (payload.client_id as string) || "",
40
+ scopes: ((payload.scope as string) || "").split(" ").filter(Boolean),
44
41
  expiresAt: payload.exp,
45
42
  };
46
43
  }
@@ -50,10 +47,15 @@ export function parseExpiry(expiry: string): number {
50
47
  if (!match) return 86400;
51
48
  const n = parseInt(match[1], 10);
52
49
  switch (match[2]) {
53
- case 's': return n;
54
- case 'm': return n * 60;
55
- case 'h': return n * 3600;
56
- case 'd': return n * 86400;
57
- default: return 86400;
50
+ case "s":
51
+ return n;
52
+ case "m":
53
+ return n * 60;
54
+ case "h":
55
+ return n * 3600;
56
+ case "d":
57
+ return n * 86400;
58
+ default:
59
+ return 86400;
58
60
  }
59
61
  }
@@ -37,7 +37,7 @@ export const TEMPLATE_BLOCK_ID = "_template";
37
37
  export async function attachBacktestsDb(
38
38
  conn: DuckDBConnection,
39
39
  backtestsDbPath: string,
40
- mode: "read_write" | "read_only"
40
+ mode: "read_write" | "read_only",
41
41
  ): Promise<void> {
42
42
  await fs.mkdir(path.dirname(backtestsDbPath), { recursive: true });
43
43
  const readOnlyClause = mode === "read_only" ? " (READ_ONLY)" : "";
@@ -47,9 +47,17 @@ export async function attachBacktestsDb(
47
47
  const msg = error instanceof Error ? error.message : String(error);
48
48
  if (msg.includes("corrupt") || msg.includes("Invalid") || msg.includes("cannot open")) {
49
49
  console.error(`backtests.duckdb appears corrupted at ${backtestsDbPath}. Recreating.`);
50
- try { await fs.unlink(backtestsDbPath); } catch { /* file may not exist */ }
50
+ try {
51
+ await fs.unlink(backtestsDbPath);
52
+ } catch {
53
+ /* file may not exist */
54
+ }
51
55
  // Also try removing WAL file
52
- try { await fs.unlink(backtestsDbPath + ".wal"); } catch { /* ignore */ }
56
+ try {
57
+ await fs.unlink(backtestsDbPath + ".wal");
58
+ } catch {
59
+ /* ignore */
60
+ }
53
61
  await conn.run(`ATTACH '${backtestsDbPath}' AS backtests${readOnlyClause}`);
54
62
  } else {
55
63
  throw new Error(`Failed to attach backtests.duckdb at ${backtestsDbPath}: ${msg}`);
@@ -70,14 +78,20 @@ export async function detachBacktestsDb(conn: DuckDBConnection): Promise<void> {
70
78
  }
71
79
 
72
80
  /**
73
- * Ensure backtests tables exist in the attached backtests.duckdb.
81
+ * Ensure the backtests schema (tables + additive migrations) exists in the
82
+ * attached backtests.duckdb. **Pure DDL — requires ONLY the `backtests` catalog
83
+ * attached, never `trades`.** This is the standalone-safe primitive a bare
84
+ * `backtests.duckdb` bootstrap (a `db init` step) calls to mint an empty,
85
+ * correctly-schema'd DB with no other catalog present.
74
86
  *
75
- * Must be called AFTER `ATTACH '...' AS backtests` in openReadWriteConnection.
76
- * Uses CREATE TABLE IF NOT EXISTS for idempotency — safe to call on every RW open.
87
+ * Must be called AFTER `ATTACH '...' AS backtests`. Uses CREATE TABLE IF NOT
88
+ * EXISTS for idempotency — safe to call on every RW open. Does NOT run the
89
+ * Phase-b72 trades purge (that lives in ensureBacktestsTables / the callers that
90
+ * hold the `trades` catalog).
77
91
  *
78
92
  * @param conn - Active DuckDB connection with backtests catalog attached
79
93
  */
80
- export async function ensureBacktestsTables(conn: DuckDBConnection): Promise<void> {
94
+ export async function ensureBacktestsSchema(conn: DuckDBConnection): Promise<void> {
81
95
  // Strategy definitions: one row per (strategy_name, underlying) pair.
82
96
  // definition_json stores the full StrategyDefinition object.
83
97
  await conn.run(`
@@ -118,7 +132,7 @@ export async function ensureBacktestsTables(conn: DuckDBConnection): Promise<voi
118
132
  `);
119
133
 
120
134
  // Migration: add sortino, cagr, total_return columns (Phase 79)
121
- const migrationCols = ['sortino', 'cagr', 'total_return'];
135
+ const migrationCols = ["sortino", "cagr", "total_return"];
122
136
  for (const col of migrationCols) {
123
137
  try {
124
138
  await conn.run(`ALTER TABLE backtests.run_metadata ADD COLUMN ${col} DOUBLE`);
@@ -173,7 +187,23 @@ export async function ensureBacktestsTables(conn: DuckDBConnection): Promise<voi
173
187
  } catch {
174
188
  // Column already exists — idempotent
175
189
  }
190
+ }
176
191
 
192
+ /**
193
+ * Ensure the backtests schema AND run the one-time Phase-b72 purge of stale
194
+ * `source='tradeblocks'` rows from `trades.trade_data`. Behavior is unchanged
195
+ * from before the schema/purge split — every existing caller keeps the same
196
+ * effect. **Requires the `trades` catalog attached RW** (via the purge), as
197
+ * every RW engine open has.
198
+ *
199
+ * New callers that need ONLY the schema (e.g. minting a standalone
200
+ * `backtests.duckdb` with no `trades` catalog attached) must call
201
+ * ensureBacktestsSchema instead — this wrapper would throw on the purge.
202
+ *
203
+ * @param conn - Active DuckDB connection with backtests AND trades catalogs attached (RW)
204
+ */
205
+ export async function ensureBacktestsTables(conn: DuckDBConnection): Promise<void> {
206
+ await ensureBacktestsSchema(conn);
177
207
  // Purge stale backtest rows from trades.trade_data (Phase b72 one-time migration).
178
208
  // Removes all rows written by prior backtest runs (source = 'tradeblocks').
179
209
  // Idempotent — DELETE WHERE returns 0 rows when nothing matches.