@coderule/mcp 1.4.0 → 1.6.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/dist/mcp-cli.cjs CHANGED
@@ -48,6 +48,8 @@ var DEFAULT_QUEUE_POLL_INTERVAL_MS = 500;
48
48
  var DEFAULT_HASH_BATCH_SIZE = 32;
49
49
  var DEFAULT_MAX_SNAPSHOT_ATTEMPTS = 5;
50
50
  var DEFAULT_HTTP_TIMEOUT_MS = 12e4;
51
+ var DEFAULT_UPLOAD_CHUNK_SIZE = 1;
52
+ var DEFAULT_MAX_QUERY_WAIT_MS = 5e4;
51
53
 
52
54
  // src/config/Configurator.ts
53
55
  var DEFAULT_RETRIEVAL_FORMATTER = "standard";
@@ -75,6 +77,14 @@ function parseInteger(value, fallback) {
75
77
  }
76
78
  return parsed;
77
79
  }
80
+ function parseSecondsToMs(value, fallbackMs) {
81
+ if (!value) return fallbackMs;
82
+ const seconds = Number.parseInt(value, 10);
83
+ if (Number.isNaN(seconds) || seconds <= 0) {
84
+ throw new Error(`Invalid seconds value: ${value}`);
85
+ }
86
+ return seconds * 1e3;
87
+ }
78
88
  function parseFormatter(value) {
79
89
  if (!value) return DEFAULT_RETRIEVAL_FORMATTER;
80
90
  const normalized = value.toLowerCase();
@@ -121,7 +131,9 @@ async function resolveConfig({
121
131
  maxSnapshotAttempts: DEFAULTS.maxSnapshotAttempts,
122
132
  retrievalFormatter: parseFormatter(
123
133
  process.env.CODERULE_RETRIEVAL_FORMATTER
124
- )
134
+ ),
135
+ uploadChunkSize: DEFAULT_UPLOAD_CHUNK_SIZE,
136
+ maxQueryWaitMs: DEFAULT_MAX_QUERY_WAIT_MS
125
137
  };
126
138
  if (process.env.CODERULE_SNAPSHOT_DEBOUNCE_MS) {
127
139
  baseConfig.snapshotDebounceMs = parseInteger(
@@ -163,6 +175,16 @@ async function resolveConfig({
163
175
  process.env.CODERULE_HTTP_TIMEOUT,
164
176
  DEFAULT_HTTP_TIMEOUT_MS
165
177
  );
178
+ if (process.env.CODERULE_UPLOAD_CHUNK_SIZE) {
179
+ baseConfig.uploadChunkSize = parseInteger(
180
+ process.env.CODERULE_UPLOAD_CHUNK_SIZE,
181
+ baseConfig.uploadChunkSize
182
+ );
183
+ }
184
+ baseConfig.maxQueryWaitMs = parseSecondsToMs(
185
+ process.env.CODERULE_MAX_WAIT_TIME,
186
+ baseConfig.maxQueryWaitMs
187
+ );
166
188
  logger.debug(
167
189
  {
168
190
  rootPath,
@@ -1096,7 +1118,7 @@ async function withRetries(op, logger2, context, maxAttempts) {
1096
1118
  }
1097
1119
  }
1098
1120
  }
1099
- async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts, chunkSize = 64) {
1121
+ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts, chunkSize = 1) {
1100
1122
  if (!missing || missing.length === 0) return;
1101
1123
  const total = missing.length;
1102
1124
  const chunks = [];
@@ -1138,7 +1160,7 @@ async function uploadMissing(rootPath, missing, syncClient, logger2, maxAttempts
1138
1160
  async function ensureSnapshotCreated(rootPath, computation, syncClient, logger2, options) {
1139
1161
  const { snapshotHash, files } = computation;
1140
1162
  const maxAttempts = options?.maxAttempts ?? 5;
1141
- const uploadChunkSize = options?.uploadChunkSize ?? 64;
1163
+ const uploadChunkSize = options?.uploadChunkSize ?? 1;
1142
1164
  let status = await withRetries(
1143
1165
  () => syncClient.checkSnapshotStatus(snapshotHash),
1144
1166
  logger2,
@@ -1243,7 +1265,10 @@ async function runInitialSyncPipeline(runtime) {
1243
1265
  runtime.snapshotsRepo,
1244
1266
  runtime.clients.sync,
1245
1267
  syncLogger,
1246
- { maxAttempts: runtime.config.maxSnapshotAttempts }
1268
+ {
1269
+ maxAttempts: runtime.config.maxSnapshotAttempts,
1270
+ uploadChunkSize: runtime.config.uploadChunkSize
1271
+ }
1247
1272
  );
1248
1273
  return result;
1249
1274
  }
@@ -1647,7 +1672,10 @@ var ServiceRunner = class {
1647
1672
  this.runtime.snapshotsRepo,
1648
1673
  this.runtime.clients.sync,
1649
1674
  log,
1650
- { maxAttempts: this.runtime.config.maxSnapshotAttempts }
1675
+ {
1676
+ maxAttempts: this.runtime.config.maxSnapshotAttempts,
1677
+ uploadChunkSize: this.runtime.config.uploadChunkSize
1678
+ }
1651
1679
  );
1652
1680
  this.runtime.outbox.ack(job.id, this.fsControlLeaseOwner);
1653
1681
  this.state.updateSnapshotReady(result.createdAt);
@@ -1705,6 +1733,63 @@ function collectIndexingStatus(runtime, runner) {
1705
1733
  service: runner.getServiceStateSnapshot()
1706
1734
  };
1707
1735
  }
1736
+ function formatStatus(status) {
1737
+ const lines = [];
1738
+ lines.push("=== Coderule Indexing Status ===");
1739
+ lines.push("");
1740
+ lines.push(`Timestamp: ${new Date(status.timestamp).toISOString()}`);
1741
+ lines.push("");
1742
+ lines.push("Repository:");
1743
+ lines.push(` ID: ${status.root.id}`);
1744
+ lines.push(` Path: ${status.root.path}`);
1745
+ lines.push("");
1746
+ lines.push("Files:");
1747
+ lines.push(` Total: ${status.files.total}`);
1748
+ lines.push(" States:");
1749
+ lines.push(` Clean: ${status.files.byState.clean}`);
1750
+ lines.push(` Dirty: ${status.files.byState.dirty}`);
1751
+ lines.push(` Hashing: ${status.files.byState.hashing}`);
1752
+ lines.push(` Missing: ${status.files.byState.missing}`);
1753
+ lines.push("");
1754
+ lines.push("Queue:");
1755
+ lines.push(` Pending: ${status.queue.pending}`);
1756
+ lines.push(` Processing: ${status.queue.processing}`);
1757
+ lines.push(` Done: ${status.queue.done}`);
1758
+ lines.push(` Failed: ${status.queue.failed}`);
1759
+ lines.push("");
1760
+ if (status.latestSnapshot) {
1761
+ lines.push("Latest Snapshot:");
1762
+ lines.push(` Hash: ${status.latestSnapshot.snapshot_hash}`);
1763
+ lines.push(` Files: ${status.latestSnapshot.files_count}`);
1764
+ lines.push(` Size: ${formatBytes(status.latestSnapshot.total_size)}`);
1765
+ lines.push(
1766
+ ` Created: ${new Date(status.latestSnapshot.created_at).toISOString()}`
1767
+ );
1768
+ } else {
1769
+ lines.push("Latest Snapshot: None");
1770
+ }
1771
+ lines.push("");
1772
+ lines.push("Service State:");
1773
+ lines.push(
1774
+ ` Last Change: ${new Date(status.service.lastChangeAt).toISOString()}`
1775
+ );
1776
+ lines.push(
1777
+ ` Last Snapshot Ready: ${new Date(status.service.lastSnapshotReadyAt).toISOString()}`
1778
+ );
1779
+ lines.push(
1780
+ ` Last Heartbeat: ${status.service.lastHeartbeatEnqueuedAt > 0 ? new Date(status.service.lastHeartbeatEnqueuedAt).toISOString() : "Never"}`
1781
+ );
1782
+ lines.push(` Watcher Ready: ${status.service.watcherReady ? "Yes" : "No"}`);
1783
+ lines.push(` Buffering: ${status.service.buffering ? "Yes" : "No"}`);
1784
+ return lines.join("\n");
1785
+ }
1786
+ function formatBytes(bytes) {
1787
+ if (bytes === 0) return "0 B";
1788
+ const units = ["B", "KB", "MB", "GB", "TB"];
1789
+ const k = 1024;
1790
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1791
+ return `${(bytes / Math.pow(k, i)).toFixed(2)} ${units[i]}`;
1792
+ }
1708
1793
 
1709
1794
  // src/mcp/server.ts
1710
1795
  var SERVER_NAME = "coderule-scanner-mcp";
@@ -1713,6 +1798,37 @@ function createMcpServer({
1713
1798
  runtime,
1714
1799
  runner
1715
1800
  }) {
1801
+ async function sleep3(ms) {
1802
+ return new Promise((resolve) => setTimeout(resolve, ms));
1803
+ }
1804
+ async function waitForLocalSnapshot(deadlineMs) {
1805
+ let latest = runtime.snapshotsRepo.getLatest();
1806
+ while (!latest && Date.now() < deadlineMs) {
1807
+ await sleep3(250);
1808
+ latest = runtime.snapshotsRepo.getLatest();
1809
+ }
1810
+ return latest;
1811
+ }
1812
+ async function waitForServerReady(initialHash, deadlineMs) {
1813
+ let currentHash = initialHash;
1814
+ while (Date.now() < deadlineMs) {
1815
+ try {
1816
+ const status = await runtime.clients.sync.checkSnapshotStatus(currentHash);
1817
+ if (status.status === "READY") {
1818
+ return currentHash;
1819
+ }
1820
+ if (status.status === "FAILED") {
1821
+ }
1822
+ } catch {
1823
+ }
1824
+ await sleep3(500);
1825
+ const latest = runtime.snapshotsRepo.getLatest();
1826
+ if (latest && latest.snapshot_hash !== currentHash) {
1827
+ currentHash = latest.snapshot_hash;
1828
+ }
1829
+ }
1830
+ return void 0;
1831
+ }
1716
1832
  const server = new mcp_js.McpServer({
1717
1833
  name: SERVER_NAME,
1718
1834
  version: SERVER_VERSION,
@@ -1727,7 +1843,7 @@ function createMcpServer({
1727
1843
  },
1728
1844
  async () => {
1729
1845
  const status = collectIndexingStatus(runtime, runner);
1730
- const text = JSON.stringify(status, null, 2);
1846
+ const text = formatStatus(status);
1731
1847
  return {
1732
1848
  content: [{ type: "text", text }]
1733
1849
  };
@@ -1748,38 +1864,37 @@ function createMcpServer({
1748
1864
  query,
1749
1865
  budgetTokens
1750
1866
  }) => {
1751
- const latest = runtime.snapshotsRepo.getLatest();
1867
+ const deadline = Date.now() + runtime.config.maxQueryWaitMs;
1868
+ const latest = await waitForLocalSnapshot(deadline);
1752
1869
  if (!latest) {
1753
- const message = "No snapshots available yet. Run indexing first.";
1754
- return {
1755
- content: [{ type: "text", text: message }],
1756
- isError: true
1757
- };
1870
+ const statusText = formatStatus(collectIndexingStatus(runtime, runner));
1871
+ const text = `We are not ready....
1872
+ ${statusText}`;
1873
+ return { content: [{ type: "text", text }] };
1874
+ }
1875
+ const readyHash = await waitForServerReady(
1876
+ latest.snapshot_hash,
1877
+ deadline
1878
+ );
1879
+ if (!readyHash) {
1880
+ const statusText = formatStatus(collectIndexingStatus(runtime, runner));
1881
+ const text = `We are not ready....
1882
+ ${statusText}`;
1883
+ return { content: [{ type: "text", text }] };
1758
1884
  }
1759
1885
  const effectiveBudget = Math.max(100, budgetTokens ?? 3e3);
1760
1886
  try {
1761
1887
  const result = await runtime.clients.retrieval.query(
1762
- latest.snapshot_hash,
1888
+ readyHash,
1763
1889
  query,
1764
1890
  effectiveBudget,
1765
- {
1766
- formatter: runtime.config.retrievalFormatter
1767
- }
1891
+ { formatter: runtime.config.retrievalFormatter }
1768
1892
  );
1769
- const summary = {
1770
- snapshotHash: latest.snapshot_hash,
1771
- budgetTokens: effectiveBudget,
1772
- formatter: runtime.config.retrievalFormatter
1773
- };
1774
1893
  return {
1775
1894
  content: [
1776
1895
  {
1777
1896
  type: "text",
1778
1897
  text: result.formatted_output ?? "(no formatted output)"
1779
- },
1780
- {
1781
- type: "text",
1782
- text: JSON.stringify({ summary, result }, null, 2)
1783
1898
  }
1784
1899
  ]
1785
1900
  };
@@ -1812,7 +1927,9 @@ var ENV_FLAG_MAP = {
1812
1927
  "queue-poll": "CODERULE_QUEUE_POLL_INTERVAL_MS",
1813
1928
  "hash-batch": "CODERULE_HASH_BATCH_SIZE",
1814
1929
  "hash-lease": "CODERULE_HASH_LEASE_MS",
1815
- "max-snapshot-attempts": "CODERULE_MAX_SNAPSHOT_ATTEMPTS"
1930
+ "max-snapshot-attempts": "CODERULE_MAX_SNAPSHOT_ATTEMPTS",
1931
+ "upload-chunk-size": "CODERULE_UPLOAD_CHUNK_SIZE",
1932
+ "max-wait-time": "CODERULE_MAX_WAIT_TIME"
1816
1933
  };
1817
1934
  function printUsage() {
1818
1935
  console.log(`Usage: coderule-mcp-server [token] [options]
@@ -1854,6 +1971,12 @@ function printUsage() {
1854
1971
  console.log(
1855
1972
  " --max-snapshot-attempts <n> Override CODERULE_MAX_SNAPSHOT_ATTEMPTS"
1856
1973
  );
1974
+ console.log(
1975
+ " --upload-chunk-size <n> Override CODERULE_UPLOAD_CHUNK_SIZE (default 1)"
1976
+ );
1977
+ console.log(
1978
+ " --max-wait-time <sec> Override CODERULE_MAX_WAIT_TIME (default 50s)"
1979
+ );
1857
1980
  console.log(
1858
1981
  " KEY=value Set arbitrary environment variable"
1859
1982
  );
@@ -1975,6 +2098,10 @@ async function main() {
1975
2098
  const runner = new ServiceRunner(runtime);
1976
2099
  try {
1977
2100
  await runner.prepareWatcher(true);
2101
+ const server = createMcpServer({ runtime, runner });
2102
+ const transport = new stdio_js.StdioServerTransport();
2103
+ await server.connect(transport);
2104
+ runtime.logger.info("MCP server connected via stdio");
1978
2105
  let initialCreatedAt;
1979
2106
  try {
1980
2107
  const initial = await runInitialSyncPipeline(runtime);
@@ -1983,7 +2110,7 @@ async function main() {
1983
2110
  snapshotHash: initial.snapshotHash,
1984
2111
  filesCount: initial.filesCount
1985
2112
  },
1986
- "Initial sync completed; starting MCP server"
2113
+ "Initial sync completed; entering continuous mode"
1987
2114
  );
1988
2115
  initialCreatedAt = initial.createdAt;
1989
2116
  } catch (error) {
@@ -1998,10 +2125,6 @@ async function main() {
1998
2125
  }
1999
2126
  await runner.startLoops();
2000
2127
  await runner.enableWatcherProcessing();
2001
- const server = createMcpServer({ runtime, runner });
2002
- const transport = new stdio_js.StdioServerTransport();
2003
- await server.connect(transport);
2004
- runtime.logger.info("MCP server connected via stdio");
2005
2128
  const signal = await awaitShutdownSignals();
2006
2129
  runtime.logger.info({ signal }, "Shutdown signal received");
2007
2130
  if (typeof transport.close === "function") {