tradeblocks-mcp 2.2.5 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ Model Context Protocol (MCP) server for options trading analysis. Works with Cla
16
16
 
17
17
  ### Option 1: MCPB Bundle (Claude Desktop - One Click)
18
18
 
19
- Download the latest `.mcpb` file from [Releases](https://gitlab.com/davidromeo/tradeblocks/-/releases) and double-click to install.
19
+ Download the latest `.mcpb` file from [Releases](https://github.com/davidromeo/tradeblocks/releases) and double-click to install.
20
20
 
21
21
  The installer will prompt you to select your Trading Data Directory.
22
22
 
@@ -37,7 +37,7 @@ See [Configuration by Platform](#configuration-by-platform) below for platform-s
37
37
  ### Option 3: From Source
38
38
 
39
39
  ```bash
40
- git clone https://gitlab.com/davidromeo/tradeblocks
40
+ git clone https://github.com/davidromeo/tradeblocks
41
41
  cd tradeblocks
42
42
  npm install
43
43
  npm run build -w packages/mcp-server
@@ -147,8 +147,29 @@ tradeblocks-mcp ~/backtests
147
147
  # HTTP mode
148
148
  tradeblocks-mcp --http ~/backtests
149
149
  tradeblocks-mcp --http --port 8080 ~/backtests
150
+
151
+ # Separate CSV blocks from DuckDB storage
152
+ tradeblocks-mcp --directory ./data --blocks-dir ~/backtests
150
153
  ```
151
154
 
155
+ ### Options
156
+
157
+ | Flag | Description | Default |
158
+ |------|-------------|---------|
159
+ | `--http` | Start HTTP server instead of stdio | stdio |
160
+ | `--port <n>` | HTTP server port | 3100 |
161
+ | `--blocks-dir <path>` | Directory containing CSV block folders | same as data directory |
162
+ | `--market-db <path>` | Path to market.duckdb | `<directory>/market.duckdb` |
163
+ | `--no-auth` | Disable authentication (HTTP mode) | auth enabled |
164
+
165
+ ### Environment Variables
166
+
167
+ | Variable | Description |
168
+ |----------|-------------|
169
+ | `BLOCKS_DIRECTORY` | Default data directory if not specified as argument |
170
+ | `TRADEBLOCKS_BLOCKS_DIR` | Directory for CSV block folders (overridden by `--blocks-dir`) |
171
+ | `MARKET_DB_PATH` | Path to market.duckdb (overridden by `--market-db`) |
172
+
152
173
  ## Docker Deployment
153
174
 
154
175
  Run the MCP server in a container for remote/server deployments.
@@ -175,7 +196,7 @@ docker build -t tradeblocks-mcp .
175
196
  docker compose up -d
176
197
  ```
177
198
 
178
- Place your block folders (each containing CSV files) in the `data/` directory. The container runs in HTTP mode on port 3100 by default. See [Authentication](#authentication) below for configuring credentials.
199
+ Place your block folders (each containing CSV files) in the `data/` directory, or use `--blocks-dir` to point at a separate folder. The container runs in HTTP mode on port 3100 by default. See [Authentication](#authentication) below for configuring credentials.
179
200
 
180
201
  Connect any MCP client to `http://<your-host>:3100/mcp`. How you expose this endpoint (reverse proxy, tunnel, VPN, etc.) is up to you.
181
202
 
@@ -53,6 +53,25 @@ async function ensureSyncTables(conn) {
53
53
  sync_version INTEGER DEFAULT 1
54
54
  )
55
55
  `);
56
+ const result = await conn.run(`
57
+ SELECT COUNT(*) AS cnt FROM duckdb_constraints()
58
+ WHERE schema_name = 'trades' AND table_name = '_sync_metadata'
59
+ AND constraint_type = 'PRIMARY KEY'
60
+ `);
61
+ const rows = await result.getRows();
62
+ if (Number(rows[0][0]) === 0) {
63
+ await conn.run(`DROP TABLE trades._sync_metadata`);
64
+ await conn.run(`
65
+ CREATE TABLE trades._sync_metadata (
66
+ block_id VARCHAR PRIMARY KEY,
67
+ tradelog_hash VARCHAR NOT NULL,
68
+ dailylog_hash VARCHAR,
69
+ reportinglog_hash VARCHAR,
70
+ synced_at TIMESTAMP NOT NULL,
71
+ sync_version INTEGER DEFAULT 1
72
+ )
73
+ `);
74
+ }
56
75
  }
57
76
  async function ensureTradeDataTable(conn) {
58
77
  await conn.run(`
@@ -315,6 +334,30 @@ async function ensureMarketTables(conn) {
315
334
  } catch {
316
335
  }
317
336
  }
337
+ await conn.run(`
338
+ CREATE TABLE IF NOT EXISTS market.option_chain (
339
+ underlying VARCHAR NOT NULL,
340
+ date VARCHAR NOT NULL,
341
+ ticker VARCHAR NOT NULL,
342
+ contract_type VARCHAR NOT NULL,
343
+ strike DOUBLE NOT NULL,
344
+ expiration VARCHAR NOT NULL,
345
+ dte INTEGER,
346
+ exercise_style VARCHAR,
347
+ PRIMARY KEY (underlying, date, ticker)
348
+ )
349
+ `);
350
+ await conn.run(`
351
+ CREATE TABLE IF NOT EXISTS market.data_coverage (
352
+ ticker VARCHAR NOT NULL,
353
+ date VARCHAR NOT NULL,
354
+ source VARCHAR NOT NULL, -- 'flatfile', 'api_trade', 'api_quote', 'csv_import'
355
+ bar_count INTEGER NOT NULL,
356
+ has_quotes BOOLEAN DEFAULT FALSE,
357
+ imported_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
358
+ PRIMARY KEY (ticker, date, source)
359
+ )
360
+ `);
318
361
  await conn.run(`
319
362
  CREATE TABLE IF NOT EXISTS market._sync_metadata (
320
363
  source VARCHAR NOT NULL,
@@ -754,6 +797,15 @@ async function openReadWriteConnection(dbPath, threads, memoryLimit) {
754
797
  await ensureReportingDataTable(connection);
755
798
  await ensureMarketTables(connection);
756
799
  await ensureProfilesSchema(connection);
800
+ try {
801
+ const ext = await import("./connection.ext.js");
802
+ const ctx = await ext.default(connection, {
803
+ baseDir: path.dirname(dbPath),
804
+ marketDbPath: storedMarketDbPath
805
+ });
806
+ if (ctx?.intradayWriteTable) _intradayWriteTable = ctx.intradayWriteTable;
807
+ } catch {
808
+ }
757
809
  connectionMode = "read_write";
758
810
  return connection;
759
811
  }
@@ -898,6 +950,10 @@ async function downgradeToReadOnly(dataDir) {
898
950
  function isConnected() {
899
951
  return connection !== null;
900
952
  }
953
+ var _intradayWriteTable = "market.intraday";
954
+ function getIntradayWriteTable() {
955
+ return _intradayWriteTable;
956
+ }
901
957
 
902
958
  // src/sync/block-sync.ts
903
959
  import * as fs5 from "fs/promises";
@@ -22616,13 +22672,16 @@ async function detectBlockChanges(conn, baseDir) {
22616
22672
  const folderNames = /* @__PURE__ */ new Set();
22617
22673
  for (const entry of entries) {
22618
22674
  if (!entry.isDirectory()) continue;
22619
- if (entry.name.startsWith(".")) continue;
22620
- if (entry.name === "_marketdata") continue;
22675
+ if (entry.name.startsWith(".") || entry.name.startsWith("_")) continue;
22676
+ if (entry.name.endsWith(".tmp") || entry.name.endsWith(".duckdb") || entry.name.endsWith(".duckdb.tmp")) continue;
22621
22677
  const blockId = entry.name;
22622
22678
  folderNames.add(blockId);
22623
22679
  const blockPath = path4.join(baseDir, blockId);
22624
22680
  if (!syncedBlockIds.has(blockId)) {
22625
- toSync.push(blockId);
22681
+ const tradelog = await findTradelogFile(blockPath);
22682
+ if (tradelog) {
22683
+ toSync.push(blockId);
22684
+ }
22626
22685
  continue;
22627
22686
  }
22628
22687
  const tradelogFilename = await findTradelogFile(blockPath);
@@ -22681,11 +22740,19 @@ async function cleanupDeletedBlocks(conn, deletedBlockIds) {
22681
22740
  }
22682
22741
 
22683
22742
  // src/sync/index.ts
22743
+ var _blocksDir = null;
22744
+ function setBlocksDir(dir) {
22745
+ _blocksDir = dir;
22746
+ }
22747
+ function getBlocksDir(baseDir) {
22748
+ return _blocksDir ?? baseDir;
22749
+ }
22684
22750
  async function syncAllBlocks(baseDir) {
22685
22751
  const conn = await getConnection(baseDir);
22752
+ const blocksDir = getBlocksDir(baseDir);
22686
22753
  const results = [];
22687
22754
  const errors = [];
22688
- const { toSync, toDelete } = await detectBlockChanges(conn, baseDir);
22755
+ const { toSync, toDelete } = await detectBlockChanges(conn, blocksDir);
22689
22756
  for (const blockId of toDelete) {
22690
22757
  try {
22691
22758
  await cleanupDeletedBlocks(conn, [blockId]);
@@ -22696,7 +22763,7 @@ async function syncAllBlocks(baseDir) {
22696
22763
  }
22697
22764
  }
22698
22765
  for (const blockId of toSync) {
22699
- const blockPath = path5.join(baseDir, blockId);
22766
+ const blockPath = path5.join(blocksDir, blockId);
22700
22767
  const result = await syncBlockInternal(conn, blockId, blockPath);
22701
22768
  results.push(result);
22702
22769
  if (result.status === "error" && result.error) {
@@ -22714,7 +22781,8 @@ async function syncAllBlocks(baseDir) {
22714
22781
  }
22715
22782
  async function syncBlock(blockId, baseDir) {
22716
22783
  const conn = await getConnection(baseDir);
22717
- const blockPath = path5.join(baseDir, blockId);
22784
+ const blocksDir = getBlocksDir(baseDir);
22785
+ const blockPath = path5.join(blocksDir, blockId);
22718
22786
  try {
22719
22787
  await fs6.access(blockPath);
22720
22788
  } catch {
@@ -22742,6 +22810,7 @@ export {
22742
22810
  upgradeToReadWrite,
22743
22811
  downgradeToReadOnly,
22744
22812
  isConnected,
22813
+ getIntradayWriteTable,
22745
22814
  detectCsvType,
22746
22815
  discoverCsvFiles,
22747
22816
  logCsvDiscoveryWarning,
@@ -22760,6 +22829,8 @@ export {
22760
22829
  normalizeTicker,
22761
22830
  resolveTradeTicker,
22762
22831
  marketTickerDateKey,
22832
+ setBlocksDir,
22833
+ getBlocksDir,
22763
22834
  syncAllBlocks,
22764
22835
  syncBlock
22765
22836
  };
@@ -22774,4 +22845,4 @@ decimal.js/decimal.mjs:
22774
22845
  * MIT Licence
22775
22846
  *)
22776
22847
  */
22777
- //# sourceMappingURL=chunk-UGHXXLLR.js.map
22848
+ //# sourceMappingURL=chunk-37JFR6NF.js.map