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.
- package/README.md +85 -80
- package/dist/{chunk-LDKTV7GW.js → chunk-27S67XW3.js} +24 -52
- package/dist/chunk-27S67XW3.js.map +1 -0
- package/dist/{chunk-FGZH632F.js → chunk-2E63THNI.js} +13 -11
- package/dist/chunk-2E63THNI.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-2I3S2ZLT.js} +325 -84
- package/dist/chunk-2I3S2ZLT.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-4HZLVUOZ.js} +179 -183
- package/dist/chunk-4HZLVUOZ.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-NWNSKALN.js} +63 -80
- package/dist/chunk-NWNSKALN.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-WNTYA7ER.js} +29 -12
- package/dist/chunk-WNTYA7ER.js.map +1 -0
- package/dist/{chunk-BKQ4PM4Y.js → chunk-YZA4RS76.js} +24 -18
- package/dist/chunk-YZA4RS76.js.map +1 -0
- package/dist/{chunk-W2PP3LEH.js → chunk-ZTJXC3LS.js} +17 -8
- package/dist/chunk-ZTJXC3LS.js.map +1 -0
- package/dist/daily-log-processor-MZW2XBZY.js +9 -0
- package/dist/{daily-logs-store-D3QC2XPV.js → daily-logs-store-3C3OTGPK.js} +2 -2
- package/dist/iv-solver-worker.js +9 -1
- package/dist/market-provider-6U33TQUT.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-3UBC37VW.js} +7 -7
- package/dist/test-exports.js +876 -483
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-DKDCWGKD.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/package.json +2 -6
- package/server/{chunk-CSDVJPBB.js → chunk-536NSFED.js} +29 -12
- package/server/chunk-536NSFED.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-A3UKD64A.js} +17 -8
- package/server/chunk-A3UKD64A.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-OMUDNJBJ.js} +63 -80
- package/server/chunk-OMUDNJBJ.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-RJDJHZ5Y.js} +934 -335
- package/server/chunk-RJDJHZ5Y.js.map +1 -0
- package/server/{chunk-NZO6PT64.js → chunk-RM4B2VKS.js} +167 -177
- package/server/chunk-RM4B2VKS.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-SY6GDNVI.js} +24 -18
- package/server/chunk-SY6GDNVI.js.map +1 -0
- package/server/{chunk-FBNDMCT5.js → chunk-T66KH2XH.js} +13 -11
- package/server/chunk-T66KH2XH.js.map +1 -0
- package/server/{chunk-NRFXAJF7.js → chunk-W5E7FHC4.js} +24 -52
- package/server/chunk-W5E7FHC4.js.map +1 -0
- package/server/{config-6IZXEFEX.js → config-DK7KOMNL.js} +1 -1
- package/server/config-DK7KOMNL.js.map +1 -0
- package/server/daily-log-processor-B2VPU2FQ.js +10 -0
- package/server/{daily-logs-store-NU7M4L5C.js → daily-logs-store-YFNXTB7J.js} +2 -2
- package/server/http-server.js +2 -5
- package/server/index.js +2497 -2029
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-L56RFHAC.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-CENKDN53.js} +7 -7
- package/server/trade-processor-3PI4LHZI.js +10 -0
- package/server/{trades-store-VY26MWER.js → trades-store-RTTEYMXH.js} +2 -2
- package/src/auth/clients-store.ts +4 -4
- package/src/auth/code-store.ts +1 -1
- package/src/auth/config.ts +11 -12
- package/src/auth/login-page.ts +8 -10
- package/src/auth/provider.ts +35 -30
- package/src/auth/token.ts +17 -15
- package/src/db/backtest-schemas.ts +38 -8
- package/src/db/connection.ts +107 -40
- package/src/db/data-root.ts +22 -0
- package/src/db/index.ts +37 -5
- package/src/db/json-adapters.ts +22 -31
- package/src/db/json-migration.ts +11 -15
- package/src/db/json-store.ts +5 -3
- package/src/db/market-datasets.ts +48 -15
- package/src/db/market-schemas.ts +11 -5
- package/src/db/market-views.ts +119 -32
- package/src/db/parquet-writer.ts +7 -7
- package/src/db/profile-schemas.ts +39 -22
- package/src/db/schemas.ts +2 -2
- package/src/http-server.ts +7 -12
- package/src/index.ts +8 -9
- package/src/market/ingestor/index.ts +1 -4
- package/src/market/ingestor/market-ingestor.ts +140 -67
- package/src/market/ingestor/types.ts +1 -3
- package/src/market/stores/chain-sql.ts +2 -8
- package/src/market/stores/chain-store.ts +1 -4
- package/src/market/stores/duckdb-chain-store.ts +3 -14
- package/src/market/stores/duckdb-enriched-store.ts +3 -13
- package/src/market/stores/duckdb-quote-store.ts +10 -27
- package/src/market/stores/duckdb-spot-store.ts +4 -20
- package/src/market/stores/enriched-sql.ts +3 -9
- package/src/market/stores/enriched-store.ts +2 -2
- package/src/market/stores/parquet-chain-store.ts +5 -22
- package/src/market/stores/parquet-enriched-store.ts +4 -16
- package/src/market/stores/parquet-oi-daily-store.ts +3 -14
- package/src/market/stores/parquet-quote-store.ts +9 -29
- package/src/market/stores/parquet-spot-store.ts +14 -33
- package/src/market/stores/quote-store.ts +2 -10
- package/src/market/stores/spot-sql.ts +3 -15
- package/src/market/stores/spot-store.ts +1 -1
- package/src/market/tickers/defaults.json +6 -6
- package/src/market/tickers/loader.ts +2 -7
- package/src/market/tickers/registry.ts +5 -15
- package/src/market/tickers/schemas.ts +2 -6
- package/src/models/strategy-profile.ts +39 -39
- package/src/sync/block-sync.ts +44 -65
- package/src/sync/index.ts +1 -4
- package/src/sync/metadata.ts +14 -20
- package/src/test-exports.ts +139 -123
- package/src/tools/analysis.ts +180 -159
- package/src/tools/batch-exit-analysis.ts +100 -59
- package/src/tools/blocks/analysis.ts +116 -101
- package/src/tools/blocks/comparison.ts +281 -364
- package/src/tools/blocks/core.ts +208 -253
- package/src/tools/blocks/health.ts +571 -563
- package/src/tools/blocks/index.ts +2 -0
- package/src/tools/blocks/paired-comparison.ts +425 -0
- package/src/tools/blocks/similarity.ts +316 -283
- package/src/tools/edge-decay.ts +124 -197
- package/src/tools/exit-analysis.ts +130 -77
- package/src/tools/greeks-attribution.ts +84 -35
- package/src/tools/guides.ts +4 -6
- package/src/tools/imports.ts +11 -14
- package/src/tools/market-data.ts +1067 -757
- package/src/tools/market-enrichment.ts +3 -3
- package/src/tools/market-fetch.ts +148 -67
- package/src/tools/market-imports.ts +12 -12
- package/src/tools/middleware/sync-middleware.ts +5 -6
- package/src/tools/performance.ts +185 -302
- package/src/tools/profile-analysis.ts +52 -66
- package/src/tools/profiles.ts +106 -69
- package/src/tools/regime-advisor.ts +20 -45
- package/src/tools/replay.ts +81 -77
- package/src/tools/reports/discrepancies.ts +298 -328
- package/src/tools/reports/fields.ts +7 -25
- package/src/tools/reports/helpers.ts +18 -49
- package/src/tools/reports/predictive.ts +27 -70
- package/src/tools/reports/slippage-trends.ts +315 -345
- package/src/tools/reports/slippage.ts +1 -4
- package/src/tools/reports/strategy-matches.ts +399 -441
- package/src/tools/schema.ts +43 -40
- package/src/tools/shared/filters.ts +3 -9
- package/src/tools/snapshot.ts +9 -30
- package/src/tools/sql.ts +15 -14
- package/src/tools/tickers.ts +1 -4
- package/src/utils/batch-exit-analysis.ts +31 -42
- package/src/utils/black-scholes.ts +39 -29
- package/src/utils/block-loader.ts +69 -83
- package/src/utils/calibration-probe.ts +3 -4
- package/src/utils/chain-loader.ts +3 -3
- package/src/utils/csv-discovery.ts +16 -22
- package/src/utils/data-quality.ts +24 -36
- package/src/utils/exit-triggers.ts +91 -96
- package/src/utils/field-timing.ts +94 -79
- package/src/utils/filter-predicates.ts +13 -9
- package/src/utils/flatfile-importer.ts +94 -64
- package/src/utils/greeks-decomposition.ts +152 -100
- package/src/utils/iv-solver-pool.ts +55 -25
- package/src/utils/iv-solver-worker.ts +5 -5
- package/src/utils/market-enricher.ts +528 -497
- package/src/utils/market-importer.ts +31 -12
- package/src/utils/market-provider.ts +21 -23
- package/src/utils/massive-tier.ts +5 -7
- package/src/utils/migrate-option-data-helpers.ts +2 -8
- package/src/utils/option-quote-greeks.ts +25 -31
- package/src/utils/option-time.ts +4 -8
- package/src/utils/output-formatter.ts +1 -4
- package/src/utils/provider-capabilities.ts +1 -4
- package/src/utils/providers/massive.ts +59 -93
- package/src/utils/providers/thetadata/backfill.ts +14 -23
- package/src/utils/providers/thetadata/client.ts +12 -8
- package/src/utils/providers/thetadata/decode.ts +2 -20
- package/src/utils/providers/thetadata/endpoints.ts +32 -40
- package/src/utils/providers/thetadata/join.ts +11 -10
- package/src/utils/providers/thetadata/proto.ts +12 -10
- package/src/utils/providers/thetadata/quote-mid-greeks.ts +5 -5
- package/src/utils/providers/thetadata.ts +11 -10
- package/src/utils/quote-enricher.ts +4 -4
- package/src/utils/quote-parquet-projection.ts +3 -11
- package/src/utils/sample-date-selector.ts +3 -5
- package/src/utils/schema-metadata.ts +102 -70
- package/src/utils/ticker.ts +5 -9
- package/src/utils/trade-replay.ts +77 -68
- package/dist/chunk-4BLCXNQ6.js.map +0 -1
- package/dist/chunk-BKQ4PM4Y.js.map +0 -1
- package/dist/chunk-FGZH632F.js.map +0 -1
- package/dist/chunk-LDKTV7GW.js.map +0 -1
- package/dist/chunk-PRAYH3RT.js.map +0 -1
- package/dist/chunk-QTTR7AAW.js.map +0 -1
- package/dist/chunk-W2PP3LEH.js.map +0 -1
- package/dist/chunk-XXYOUIZY.js.map +0 -1
- package/dist/daily-log-processor-6MWJ23JK.js +0 -9
- package/dist/market-provider-VDRJUEF2.js +0 -16
- package/dist/trade-processor-NHU4VWRX.js +0 -9
- package/manifest.json +0 -141
- package/server/chunk-5EBXHT6C.js.map +0 -1
- package/server/chunk-72GKJE2U.js.map +0 -1
- package/server/chunk-CSDVJPBB.js.map +0 -1
- package/server/chunk-FBNDMCT5.js.map +0 -1
- package/server/chunk-NRFXAJF7.js.map +0 -1
- package/server/chunk-NZO6PT64.js.map +0 -1
- package/server/chunk-PNKG7RY7.js.map +0 -1
- package/server/chunk-WA5AAPCH.js.map +0 -1
- package/server/config-6IZXEFEX.js.map +0 -1
- package/server/daily-log-processor-Y3PVSVBM.js +0 -10
- package/server/market-provider-VOYYVYWT.js +0 -17
- package/server/trade-processor-JWVS37KM.js +0 -10
- /package/dist/{daily-log-processor-6MWJ23JK.js.map → daily-log-processor-MZW2XBZY.js.map} +0 -0
- /package/dist/{daily-logs-store-D3QC2XPV.js.map → daily-logs-store-3C3OTGPK.js.map} +0 -0
- /package/dist/{market-provider-VDRJUEF2.js.map → market-provider-6U33TQUT.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-3UBC37VW.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-DKDCWGKD.js.map} +0 -0
- /package/dist/{trades-store-LOL3FQSF.js.map → trades-store-R3NL25T2.js.map} +0 -0
- /package/server/{daily-log-processor-Y3PVSVBM.js.map → daily-log-processor-B2VPU2FQ.js.map} +0 -0
- /package/server/{daily-logs-store-NU7M4L5C.js.map → daily-logs-store-YFNXTB7J.js.map} +0 -0
- /package/server/{market-provider-VOYYVYWT.js.map → market-provider-L56RFHAC.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-CENKDN53.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-3PI4LHZI.js.map} +0 -0
- /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(
|
|
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-
|
|
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-
|
|
13
|
+
//# sourceMappingURL=proto-YZ4TLX2N.js.map
|
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
syncAllBlocks,
|
|
10
10
|
syncBlock,
|
|
11
11
|
upsertSyncMetadata
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
14
|
-
import "./chunk-
|
|
15
|
-
import "./chunk-
|
|
16
|
-
import "./chunk-
|
|
17
|
-
import "./chunk-
|
|
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-
|
|
30
|
+
//# sourceMappingURL=sync-CENKDN53.js.map
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
getTradesPage,
|
|
14
14
|
searchTrades,
|
|
15
15
|
updateTradesForBlock
|
|
16
|
-
} from "./chunk-
|
|
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-
|
|
33
|
+
//# sourceMappingURL=trades-store-RTTEYMXH.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { randomUUID } from
|
|
2
|
-
import type { OAuthRegisteredClientsStore } from
|
|
3
|
-
import type { OAuthClientInformationFull } from
|
|
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,
|
|
13
|
+
clientMetadata: Omit<OAuthClientInformationFull, "client_id" | "client_id_issued_at">,
|
|
14
14
|
): OAuthClientInformationFull {
|
|
15
15
|
const clientId = randomUUID();
|
|
16
16
|
const client: OAuthClientInformationFull = {
|
package/src/auth/code-store.ts
CHANGED
package/src/auth/config.ts
CHANGED
|
@@ -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 ===
|
|
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:
|
|
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 ||
|
|
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
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
42
|
+
"TRADEBLOCKS_JWT_SECRET is required for HTTP mode.\n" +
|
|
43
|
+
"Generate one with: openssl rand -hex 32",
|
|
45
44
|
);
|
|
46
45
|
}
|
|
47
46
|
|
package/src/auth/login-page.ts
CHANGED
|
@@ -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,
|
|
87
|
-
.replace(/"/g,
|
|
88
|
-
.replace(/</g,
|
|
89
|
-
.replace(/>/g,
|
|
84
|
+
.replace(/&/g, "&")
|
|
85
|
+
.replace(/"/g, """)
|
|
86
|
+
.replace(/</g, "<")
|
|
87
|
+
.replace(/>/g, ">");
|
|
90
88
|
}
|
package/src/auth/provider.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { randomUUID, scryptSync, timingSafeEqual } from
|
|
2
|
-
import type { Response } from
|
|
3
|
-
import type { OAuthServerProvider } from
|
|
4
|
-
import type { OAuthRegisteredClientsStore } from
|
|
5
|
-
import type { AuthInfo } from
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import
|
|
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(
|
|
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:
|
|
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 ||
|
|
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(
|
|
85
|
+
targetUrl.searchParams.set("code", code);
|
|
83
86
|
if (body.state) {
|
|
84
|
-
targetUrl.searchParams.set(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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(
|
|
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 =
|
|
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
|
|
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:
|
|
25
|
-
.setSubject(
|
|
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) ||
|
|
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
|
|
54
|
-
|
|
55
|
-
case
|
|
56
|
-
|
|
57
|
-
|
|
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 {
|
|
50
|
+
try {
|
|
51
|
+
await fs.unlink(backtestsDbPath);
|
|
52
|
+
} catch {
|
|
53
|
+
/* file may not exist */
|
|
54
|
+
}
|
|
51
55
|
// Also try removing WAL file
|
|
52
|
-
try {
|
|
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
|
|
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
|
|
76
|
-
*
|
|
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
|
|
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 = [
|
|
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.
|