tradeblocks-mcp 3.0.2 → 3.0.3

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 (214) hide show
  1. package/README.md +82 -68
  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-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
  7. package/dist/chunk-6S37CXUA.js.map +1 -0
  8. package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
  9. package/dist/chunk-AP7IHUUR.js.map +1 -0
  10. package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
  11. package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
  12. package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
  13. package/dist/chunk-SEUZYQGQ.js.map +1 -0
  14. package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
  15. package/dist/chunk-UBUC5A66.js.map +1 -0
  16. package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
  17. package/dist/chunk-VDU25Z6X.js.map +1 -0
  18. package/dist/daily-log-processor-BY3ISY6K.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-H3ARIVZ4.js +16 -0
  22. package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
  23. package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
  24. package/dist/test-exports.js +358 -254
  25. package/dist/test-exports.js.map +1 -1
  26. package/dist/trade-processor-EYA3I3XB.js +9 -0
  27. package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
  28. package/manifest.json +3 -11
  29. package/package.json +1 -1
  30. package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
  31. package/server/chunk-4P7D7YZP.js.map +1 -0
  32. package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
  33. package/server/chunk-BOPHW5M6.js.map +1 -0
  34. package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
  35. package/server/chunk-GH2552SE.js.map +1 -0
  36. package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
  37. package/server/chunk-OBYKFW2B.js.map +1 -0
  38. package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
  39. package/server/chunk-T66KH2XH.js.map +1 -0
  40. package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
  41. package/server/chunk-W5E7FHC4.js.map +1 -0
  42. package/server/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
  43. package/server/chunk-YUCOAJ4Z.js.map +1 -0
  44. package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
  45. package/server/chunk-ZBJCF4ZG.js.map +1 -0
  46. package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
  47. package/server/config-DK7KOMNL.js.map +1 -0
  48. package/server/daily-log-processor-ENEUT22A.js +10 -0
  49. package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
  50. package/server/http-server.js +2 -5
  51. package/server/index.js +1771 -1538
  52. package/server/index.js.map +1 -1
  53. package/server/iv-solver-worker.js +9 -1
  54. package/server/market-provider-B437HKLW.js +17 -0
  55. package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
  56. package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
  57. package/server/trade-processor-L3PIQ5TG.js +10 -0
  58. package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
  59. package/src/auth/clients-store.ts +4 -4
  60. package/src/auth/code-store.ts +1 -1
  61. package/src/auth/config.ts +11 -12
  62. package/src/auth/login-page.ts +8 -10
  63. package/src/auth/provider.ts +35 -30
  64. package/src/auth/token.ts +17 -15
  65. package/src/db/backtest-schemas.ts +12 -4
  66. package/src/db/connection.ts +107 -40
  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 +127 -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/similarity.ts +316 -283
  114. package/src/tools/edge-decay.ts +124 -197
  115. package/src/tools/exit-analysis.ts +130 -77
  116. package/src/tools/greeks-attribution.ts +84 -35
  117. package/src/tools/guides.ts +4 -6
  118. package/src/tools/imports.ts +9 -12
  119. package/src/tools/market-data.ts +1067 -757
  120. package/src/tools/market-enrichment.ts +3 -3
  121. package/src/tools/market-fetch.ts +148 -67
  122. package/src/tools/market-imports.ts +12 -12
  123. package/src/tools/middleware/sync-middleware.ts +5 -6
  124. package/src/tools/performance.ts +185 -302
  125. package/src/tools/profile-analysis.ts +52 -66
  126. package/src/tools/profiles.ts +106 -69
  127. package/src/tools/regime-advisor.ts +20 -45
  128. package/src/tools/replay.ts +81 -77
  129. package/src/tools/reports/discrepancies.ts +298 -328
  130. package/src/tools/reports/fields.ts +7 -25
  131. package/src/tools/reports/helpers.ts +18 -49
  132. package/src/tools/reports/predictive.ts +27 -70
  133. package/src/tools/reports/slippage-trends.ts +315 -345
  134. package/src/tools/reports/slippage.ts +1 -4
  135. package/src/tools/reports/strategy-matches.ts +399 -441
  136. package/src/tools/schema.ts +43 -40
  137. package/src/tools/shared/filters.ts +3 -9
  138. package/src/tools/snapshot.ts +9 -30
  139. package/src/tools/sql.ts +15 -14
  140. package/src/tools/tickers.ts +1 -4
  141. package/src/utils/batch-exit-analysis.ts +31 -42
  142. package/src/utils/black-scholes.ts +39 -29
  143. package/src/utils/block-loader.ts +68 -82
  144. package/src/utils/calibration-probe.ts +1 -2
  145. package/src/utils/chain-loader.ts +3 -3
  146. package/src/utils/csv-discovery.ts +16 -22
  147. package/src/utils/data-quality.ts +24 -36
  148. package/src/utils/exit-triggers.ts +91 -96
  149. package/src/utils/field-timing.ts +94 -79
  150. package/src/utils/filter-predicates.ts +13 -9
  151. package/src/utils/flatfile-importer.ts +94 -64
  152. package/src/utils/greeks-decomposition.ts +152 -100
  153. package/src/utils/iv-solver-pool.ts +55 -25
  154. package/src/utils/iv-solver-worker.ts +5 -5
  155. package/src/utils/market-enricher.ts +528 -497
  156. package/src/utils/market-importer.ts +31 -12
  157. package/src/utils/market-provider.ts +21 -23
  158. package/src/utils/massive-tier.ts +5 -7
  159. package/src/utils/migrate-option-data-helpers.ts +2 -8
  160. package/src/utils/option-quote-greeks.ts +25 -31
  161. package/src/utils/option-time.ts +4 -8
  162. package/src/utils/output-formatter.ts +1 -4
  163. package/src/utils/provider-capabilities.ts +1 -4
  164. package/src/utils/providers/massive.ts +59 -93
  165. package/src/utils/providers/thetadata/backfill.ts +14 -23
  166. package/src/utils/providers/thetadata/client.ts +12 -8
  167. package/src/utils/providers/thetadata/decode.ts +2 -20
  168. package/src/utils/providers/thetadata/endpoints.ts +32 -40
  169. package/src/utils/providers/thetadata/join.ts +11 -10
  170. package/src/utils/providers/thetadata/proto.ts +12 -10
  171. package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
  172. package/src/utils/providers/thetadata.ts +11 -10
  173. package/src/utils/quote-enricher.ts +4 -4
  174. package/src/utils/quote-parquet-projection.ts +3 -11
  175. package/src/utils/sample-date-selector.ts +3 -5
  176. package/src/utils/schema-metadata.ts +102 -70
  177. package/src/utils/ticker.ts +5 -9
  178. package/src/utils/trade-replay.ts +77 -68
  179. package/dist/chunk-4BLCXNQ6.js.map +0 -1
  180. package/dist/chunk-BKQ4PM4Y.js.map +0 -1
  181. package/dist/chunk-FGZH632F.js.map +0 -1
  182. package/dist/chunk-LDKTV7GW.js.map +0 -1
  183. package/dist/chunk-PRAYH3RT.js.map +0 -1
  184. package/dist/chunk-W2PP3LEH.js.map +0 -1
  185. package/dist/chunk-XXYOUIZY.js.map +0 -1
  186. package/dist/daily-log-processor-6MWJ23JK.js +0 -9
  187. package/dist/market-provider-VDRJUEF2.js +0 -16
  188. package/dist/trade-processor-NHU4VWRX.js +0 -9
  189. package/server/chunk-5EBXHT6C.js.map +0 -1
  190. package/server/chunk-72GKJE2U.js.map +0 -1
  191. package/server/chunk-CSDVJPBB.js.map +0 -1
  192. package/server/chunk-FBNDMCT5.js.map +0 -1
  193. package/server/chunk-NRFXAJF7.js.map +0 -1
  194. package/server/chunk-NZO6PT64.js.map +0 -1
  195. package/server/chunk-PNKG7RY7.js.map +0 -1
  196. package/server/chunk-WA5AAPCH.js.map +0 -1
  197. package/server/config-6IZXEFEX.js.map +0 -1
  198. package/server/daily-log-processor-Y3PVSVBM.js +0 -10
  199. package/server/market-provider-VOYYVYWT.js +0 -17
  200. package/server/trade-processor-JWVS37KM.js +0 -10
  201. /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-BY3ISY6K.js.map} +0 -0
  202. /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
  203. /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-H3ARIVZ4.js.map} +0 -0
  204. /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
  205. /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
  206. /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.js.map} +0 -0
  207. /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
  208. /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-ENEUT22A.js.map} +0 -0
  209. /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
  210. /package/server/{market-provider-VOYYVYWT.js.map → market-provider-B437HKLW.js.map} +0 -0
  211. /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
  212. /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
  213. /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.js.map} +0 -0
  214. /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-ZBJCF4ZG.js";
6
+ import "./chunk-BOPHW5M6.js";
7
+ import "./chunk-YUCOAJ4Z.js";
8
+ import "./chunk-GH2552SE.js";
9
+ import "./chunk-OBYKFW2B.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-B437HKLW.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-4P7D7YZP.js";
13
+ import "./chunk-BOPHW5M6.js";
14
+ import "./chunk-YUCOAJ4Z.js";
15
+ import "./chunk-GH2552SE.js";
16
+ import "./chunk-OBYKFW2B.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-V25UQJA3.js.map
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ TradeProcessor
4
+ } from "./chunk-YUCOAJ4Z.js";
5
+ import "./chunk-OBYKFW2B.js";
6
+ import "./chunk-UT3JLF3M.js";
7
+ export {
8
+ TradeProcessor
9
+ };
10
+ //# sourceMappingURL=trade-processor-L3PIQ5TG.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}`);
@@ -118,7 +126,7 @@ export async function ensureBacktestsTables(conn: DuckDBConnection): Promise<voi
118
126
  `);
119
127
 
120
128
  // Migration: add sortino, cagr, total_return columns (Phase 79)
121
- const migrationCols = ['sortino', 'cagr', 'total_return'];
129
+ const migrationCols = ["sortino", "cagr", "total_return"];
122
130
  for (const col of migrationCols) {
123
131
  try {
124
132
  await conn.run(`ALTER TABLE backtests.run_metadata ADD COLUMN ${col} DOUBLE`);