@cyanheads/secedgar-mcp-server 0.8.1 → 0.8.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/Dockerfile CHANGED
@@ -75,6 +75,22 @@ RUN if [ "$OTEL_ENABLED" = "true" ]; then \
75
75
  # Copy the compiled application code from the build stage
76
76
  COPY --from=build /usr/src/app/dist ./dist
77
77
 
78
+ # Copy the mirror lifecycle scripts. `bun run mirror:init` / `mirror:refresh` /
79
+ # `mirror:verify` invoke these directly (Bun runs `.ts` natively) — they must
80
+ # exist inside the container or an operator can't bootstrap, inspect, or refresh
81
+ # the local mirror via `docker exec`.
82
+ COPY --from=build /usr/src/app/scripts/edgar-mirror-init.ts \
83
+ /usr/src/app/scripts/edgar-mirror-refresh.ts \
84
+ /usr/src/app/scripts/edgar-mirror-verify.ts \
85
+ /usr/src/app/scripts/_mirror-context.ts \
86
+ ./scripts/
87
+
88
+ # Emit a minimal `tsconfig.json` so Bun can resolve the `@/...` path alias the
89
+ # mirror scripts import through. The source `tsconfig.json` maps `@/*` to `./src/*`,
90
+ # but production only carries `./dist/*` — without this file, `bun run mirror:init`
91
+ # fails with `Cannot find module '@/config/server-config.js'`.
92
+ RUN echo '{"compilerOptions":{"baseUrl":".","paths":{"@/*":["./dist/*"]}}}' > tsconfig.json
93
+
78
94
  # The 'oven/bun' image already provides a non-root user named 'bun'.
79
95
  # We will use this existing user for enhanced security.
80
96
 
package/README.md CHANGED
@@ -316,6 +316,14 @@ docker build -t secedgar-mcp-server .
316
316
  docker run -e EDGAR_USER_AGENT="MyApp my@email.com" -p 3010:3010 secedgar-mcp-server
317
317
  ```
318
318
 
319
+ The image ships the mirror CLI, so the local mirror (`EDGAR_MIRROR_ENABLED`) can be bootstrapped, inspected, and refreshed inside a running container:
320
+
321
+ ```sh
322
+ docker exec <container> bun run mirror:verify # sync status + sample reads
323
+ docker exec <container> bun run mirror:init # one-time bootstrap (downloads the SEC bulk archive)
324
+ docker exec <container> bun run mirror:refresh # re-ingest when the archive has been rebuilt
325
+ ```
326
+
319
327
  ## Project structure
320
328
 
321
329
  | Directory | Purpose |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/secedgar-mcp-server",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Query SEC EDGAR filings, XBRL financials, and company data through MCP. STDIO & Streamable HTTP.",
5
5
  "mcpName": "io.github.cyanheads/secedgar-mcp-server",
6
6
  "type": "module",
@@ -12,7 +12,17 @@
12
12
  "exports": {
13
13
  ".": "./dist/index.js"
14
14
  },
15
- "files": ["dist/", "README.md", "LICENSE", "Dockerfile", "server.json"],
15
+ "files": [
16
+ "dist/",
17
+ "README.md",
18
+ "LICENSE",
19
+ "Dockerfile",
20
+ "server.json",
21
+ "scripts/edgar-mirror-init.ts",
22
+ "scripts/edgar-mirror-refresh.ts",
23
+ "scripts/edgar-mirror-verify.ts",
24
+ "scripts/_mirror-context.ts"
25
+ ],
16
26
  "scripts": {
17
27
  "build": "bun scripts/build.ts",
18
28
  "rebuild": "bun scripts/clean.ts && bun scripts/build.ts",
@@ -82,12 +92,12 @@
82
92
  "access": "public"
83
93
  },
84
94
  "dependencies": {
85
- "@cyanheads/mcp-ts-core": "^0.9.19",
95
+ "@cyanheads/mcp-ts-core": "^0.9.21",
86
96
  "@duckdb/node-api": "^1.5.3-r.3",
87
97
  "better-sqlite3": "^12.10.0",
88
98
  "domhandler": "^6.0.1",
89
99
  "domutils": "^4.0.2",
90
- "fflate": "^0.8.2",
100
+ "fflate": "^0.8.3",
91
101
  "html-to-text": "^10.0.0",
92
102
  "htmlparser2": "^12.0.0",
93
103
  "node-cron": "^4.2.1",
@@ -0,0 +1,43 @@
1
+ /**
2
+ * @fileoverview Minimal logger + abort signal for the out-of-band mirror scripts.
3
+ * Scripts run outside the MCP request pipeline, so they have no `Context`; this
4
+ * wires console output and a SIGINT/SIGTERM-driven AbortController to the
5
+ * `MirrorLogger` shape the ingesters consume — an interrupt lets the framework
6
+ * persist sync state before exit, so a re-run resumes cleanly.
7
+ * @module scripts/_mirror-context
8
+ */
9
+
10
+ type LogLevel = 'debug' | 'info' | 'notice' | 'warning' | 'error';
11
+
12
+ function makeLogger(prefix: string) {
13
+ const emit = (level: LogLevel, message: string, meta?: object): void => {
14
+ const tag = `[${new Date().toISOString()}] ${level.toUpperCase()} ${prefix}`;
15
+ const metaStr = meta && Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';
16
+ const line = `${tag} ${message}${metaStr}`;
17
+ if (level === 'error') console.error(line);
18
+ else if (level === 'warning') console.warn(line);
19
+ else console.log(line);
20
+ };
21
+ return {
22
+ debug: (m: string, meta?: object) => emit('debug', m, meta),
23
+ info: (m: string, meta?: object) => emit('info', m, meta),
24
+ notice: (m: string, meta?: object) => emit('notice', m, meta),
25
+ warning: (m: string, meta?: object) => emit('warning', m, meta),
26
+ error: (m: string, meta?: object) => emit('error', m, meta),
27
+ };
28
+ }
29
+
30
+ /** Build a script context: a `MirrorLogger` plus a SIGINT/SIGTERM-aborted signal. */
31
+ export function makeScriptContext(prefix: string): {
32
+ log: ReturnType<typeof makeLogger>;
33
+ signal: AbortSignal;
34
+ } {
35
+ const controller = new AbortController();
36
+ const onSignal = (sig: string) => () => {
37
+ console.error(`\nReceived ${sig}; aborting mirror sync (state is persisted per page)...`);
38
+ controller.abort(new Error(`Aborted by ${sig}`));
39
+ };
40
+ process.once('SIGINT', onSignal('SIGINT'));
41
+ process.once('SIGTERM', onSignal('SIGTERM'));
42
+ return { log: makeLogger(prefix), signal: controller.signal };
43
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @fileoverview One-shot bootstrap of the EDGAR local mirror. Downloads
3
+ * `company_tickers.json` and the `companyfacts.zip` bulk archive into the local
4
+ * SQLite stores. Idempotent and resumable — the framework persists state per
5
+ * page, so re-running after an interrupt continues without re-applying lost work.
6
+ *
7
+ * Usage:
8
+ * bun run mirror:init
9
+ *
10
+ * Env vars (see CLAUDE.md Config table):
11
+ * EDGAR_USER_AGENT required — "AppName contact@email.com"
12
+ * EDGAR_MIRROR_PATH mirror directory (default ./data/edgar-mirror)
13
+ *
14
+ * @module scripts/edgar-mirror-init
15
+ */
16
+
17
+ import { getServerConfig } from '@/config/server-config.js';
18
+ import { EdgarMirror } from '@/services/edgar/mirror/index.js';
19
+ import { makeScriptContext } from './_mirror-context.js';
20
+
21
+ async function main(): Promise<void> {
22
+ const cfg = getServerConfig();
23
+ const ctx = makeScriptContext('mirror:init');
24
+ const mirror = new EdgarMirror({ dir: cfg.mirrorPath, userAgent: cfg.userAgent, log: ctx.log });
25
+
26
+ const start = Date.now();
27
+ let lastReport = start;
28
+ ctx.log.notice('Starting EDGAR mirror init (tickers + company-facts)', { dir: cfg.mirrorPath });
29
+
30
+ try {
31
+ const result = await mirror.runInit({
32
+ signal: ctx.signal,
33
+ onProgress: (layer, info) => {
34
+ const now = Date.now();
35
+ if (now - lastReport < 10_000) return;
36
+ lastReport = now;
37
+ const elapsedMin = ((now - start) / 60_000).toFixed(1);
38
+ console.log(
39
+ ` [${layer}] pages=${info.pages} records=${info.records} elapsed=${elapsedMin}m`,
40
+ );
41
+ },
42
+ });
43
+ const elapsedMin = ((Date.now() - start) / 60_000).toFixed(1);
44
+ console.log(
45
+ `\nInit complete in ${elapsedMin}m — tickers: ${result.tickers.total} rows, company-facts: ${result.companyFacts.total} rows`,
46
+ );
47
+ await mirror.close();
48
+ } catch (err) {
49
+ console.error('\nInit failed:', err instanceof Error ? err.message : err);
50
+ await mirror.close().catch(() => {});
51
+ process.exit(1);
52
+ }
53
+ }
54
+
55
+ void main();
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview Incremental refresh of the EDGAR local mirror. Re-downloads
3
+ * `company_tickers.json` and re-ingests `companyfacts.zip` when the bulk archive
4
+ * has been rebuilt since the last sync (a HEAD check short-circuits otherwise).
5
+ * Intended for an external cron under stdio deployments; HTTP deployments can
6
+ * schedule this in-process via `EDGAR_MIRROR_REFRESH_CRON`.
7
+ *
8
+ * Usage:
9
+ * bun run mirror:refresh
10
+ *
11
+ * @module scripts/edgar-mirror-refresh
12
+ */
13
+
14
+ import { getServerConfig } from '@/config/server-config.js';
15
+ import { EdgarMirror } from '@/services/edgar/mirror/index.js';
16
+ import { makeScriptContext } from './_mirror-context.js';
17
+
18
+ async function main(): Promise<void> {
19
+ const cfg = getServerConfig();
20
+ const ctx = makeScriptContext('mirror:refresh');
21
+ const mirror = new EdgarMirror({ dir: cfg.mirrorPath, userAgent: cfg.userAgent, log: ctx.log });
22
+
23
+ const start = Date.now();
24
+ ctx.log.notice('Starting EDGAR mirror refresh', { dir: cfg.mirrorPath });
25
+
26
+ try {
27
+ const result = await mirror.runRefresh({ signal: ctx.signal });
28
+ const elapsedMin = ((Date.now() - start) / 60_000).toFixed(1);
29
+ console.log(
30
+ `\nRefresh complete in ${elapsedMin}m — tickers: +${result.tickers.recordsApplied}, company-facts: +${result.companyFacts.recordsApplied}`,
31
+ );
32
+ await mirror.close();
33
+ } catch (err) {
34
+ console.error('\nRefresh failed:', err instanceof Error ? err.message : err);
35
+ await mirror.close().catch(() => {});
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ void main();
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview Operability check for the EDGAR local mirror. Prints the sync
3
+ * status of both layers and runs a couple of sample reads so an operator can
4
+ * confirm the mirror is populated and queryable after an init or refresh.
5
+ *
6
+ * Usage:
7
+ * bun run mirror:verify
8
+ *
9
+ * @module scripts/edgar-mirror-verify
10
+ */
11
+
12
+ import { getServerConfig } from '@/config/server-config.js';
13
+ import { EdgarMirror } from '@/services/edgar/mirror/index.js';
14
+
15
+ async function main(): Promise<void> {
16
+ const cfg = getServerConfig();
17
+ const mirror = new EdgarMirror({ dir: cfg.mirrorPath, userAgent: cfg.userAgent });
18
+
19
+ try {
20
+ const status = await mirror.status();
21
+ console.log(`Mirror directory: ${cfg.mirrorPath}`);
22
+ console.log(JSON.stringify(status, null, 2));
23
+
24
+ if (await mirror.tickersReady()) {
25
+ const rows = await mirror.getTickerRows();
26
+ console.log(`tickers: ${rows.length} rows`);
27
+ } else {
28
+ console.log('tickers: not ready (run `bun run mirror:init`)');
29
+ }
30
+
31
+ if (await mirror.companyFactsReady()) {
32
+ // Apple Inc. (CIK 0000320193) — a stable probe for the company-facts layer.
33
+ const concept = await mirror.getCompanyConcept('320193', 'us-gaap', 'Revenues');
34
+ console.log(
35
+ `company-facts probe (AAPL us-gaap/Revenues): ${
36
+ concept ? `${Object.keys(concept.units).length} unit(s)` : 'absent'
37
+ }`,
38
+ );
39
+ } else {
40
+ console.log('company-facts: not ready (run `bun run mirror:init`)');
41
+ }
42
+
43
+ await mirror.close();
44
+ } catch (err) {
45
+ console.error('Verify failed:', err instanceof Error ? err.message : err);
46
+ await mirror.close().catch(() => {});
47
+ process.exit(1);
48
+ }
49
+ }
50
+
51
+ void main();
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/cyanheads/secedgar-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.8.1",
9
+ "version": "0.8.3",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
@@ -19,7 +19,7 @@
19
19
  "registryBaseUrl": "https://registry.npmjs.org",
20
20
  "identifier": "@cyanheads/secedgar-mcp-server",
21
21
  "runtimeHint": "bun",
22
- "version": "0.8.1",
22
+ "version": "0.8.3",
23
23
  "environmentVariables": [
24
24
  {
25
25
  "name": "EDGAR_USER_AGENT",
@@ -44,7 +44,7 @@
44
44
  "registryBaseUrl": "https://registry.npmjs.org",
45
45
  "identifier": "@cyanheads/secedgar-mcp-server",
46
46
  "runtimeHint": "bun",
47
- "version": "0.8.1",
47
+ "version": "0.8.3",
48
48
  "environmentVariables": [
49
49
  {
50
50
  "name": "EDGAR_USER_AGENT",