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.
- package/README.md +82 -68
- 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-BKQ4PM4Y.js → chunk-6S37CXUA.js} +11 -13
- package/dist/chunk-6S37CXUA.js.map +1 -0
- package/dist/{chunk-PRAYH3RT.js → chunk-AP7IHUUR.js} +29 -12
- package/dist/chunk-AP7IHUUR.js.map +1 -0
- package/dist/{chunk-QTTR7AAW.js → chunk-C6LL746C.js} +67 -84
- package/dist/{chunk-QTTR7AAW.js.map → chunk-C6LL746C.js.map} +1 -1
- package/dist/{chunk-W2PP3LEH.js → chunk-SEUZYQGQ.js} +17 -8
- package/dist/chunk-SEUZYQGQ.js.map +1 -0
- package/dist/{chunk-4BLCXNQ6.js → chunk-UBUC5A66.js} +105 -114
- package/dist/chunk-UBUC5A66.js.map +1 -0
- package/dist/{chunk-XXYOUIZY.js → chunk-VDU25Z6X.js} +63 -80
- package/dist/chunk-VDU25Z6X.js.map +1 -0
- package/dist/daily-log-processor-BY3ISY6K.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-H3ARIVZ4.js +16 -0
- package/dist/{proto-3HZTCWK4.js → proto-V23GIZG2.js} +2 -2
- package/dist/{sync-PO4IPCYV.js → sync-EVLKB3ZJ.js} +7 -7
- package/dist/test-exports.js +358 -254
- package/dist/test-exports.js.map +1 -1
- package/dist/trade-processor-EYA3I3XB.js +9 -0
- package/dist/{trades-store-LOL3FQSF.js → trades-store-R3NL25T2.js} +2 -2
- package/manifest.json +3 -11
- package/package.json +1 -1
- package/server/{chunk-NZO6PT64.js → chunk-4P7D7YZP.js} +102 -113
- package/server/chunk-4P7D7YZP.js.map +1 -0
- package/server/{chunk-72GKJE2U.js → chunk-BOPHW5M6.js} +521 -235
- package/server/chunk-BOPHW5M6.js.map +1 -0
- package/server/{chunk-WA5AAPCH.js → chunk-GH2552SE.js} +17 -8
- package/server/chunk-GH2552SE.js.map +1 -0
- package/server/{chunk-5EBXHT6C.js → chunk-OBYKFW2B.js} +11 -13
- package/server/chunk-OBYKFW2B.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/{chunk-CSDVJPBB.js → chunk-YUCOAJ4Z.js} +29 -12
- package/server/chunk-YUCOAJ4Z.js.map +1 -0
- package/server/{chunk-PNKG7RY7.js → chunk-ZBJCF4ZG.js} +63 -80
- package/server/chunk-ZBJCF4ZG.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-ENEUT22A.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 +1771 -1538
- package/server/index.js.map +1 -1
- package/server/iv-solver-worker.js +9 -1
- package/server/market-provider-B437HKLW.js +17 -0
- package/server/{proto-DWRZJO4E.js → proto-YZ4TLX2N.js} +2 -2
- package/server/{sync-QFI5L7S7.js → sync-V25UQJA3.js} +7 -7
- package/server/trade-processor-L3PIQ5TG.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 +12 -4
- package/src/db/connection.ts +107 -40
- 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 +127 -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/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 +9 -12
- 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 +68 -82
- package/src/utils/calibration-probe.ts +1 -2
- 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-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/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-BY3ISY6K.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-H3ARIVZ4.js.map} +0 -0
- /package/dist/{proto-3HZTCWK4.js.map → proto-V23GIZG2.js.map} +0 -0
- /package/dist/{sync-PO4IPCYV.js.map → sync-EVLKB3ZJ.js.map} +0 -0
- /package/dist/{trade-processor-NHU4VWRX.js.map → trade-processor-EYA3I3XB.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-ENEUT22A.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-B437HKLW.js.map} +0 -0
- /package/server/{proto-DWRZJO4E.js.map → proto-YZ4TLX2N.js.map} +0 -0
- /package/server/{sync-QFI5L7S7.js.map → sync-V25UQJA3.js.map} +0 -0
- /package/server/{trade-processor-JWVS37KM.js.map → trade-processor-L3PIQ5TG.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-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-
|
|
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-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-
|
|
30
|
+
//# sourceMappingURL=sync-V25UQJA3.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}`);
|
|
@@ -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 = [
|
|
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`);
|