@pipeworx/mcp-edgar 0.3.3 → 0.3.5

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  The Securities and Exchange Commission's filings database. Every public company in the US files here, every filing is timestamped and immutable, and every disclosure (revenue, debt, risk factors, executive compensation, M&A activity, insider trading) is structured for machine retrieval. Free and authoritative.
4
4
 
5
- Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1679+ live data sources.
5
+ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1683+ live data sources.
6
6
 
7
7
  ## Why this matters for AI agents
8
8
 
@@ -106,7 +106,7 @@ directly, instead of just this one's:
106
106
  }
107
107
  ```
108
108
 
109
- Both URLs reach the same gateway and the same 1679+ data sources. The
109
+ Both URLs reach the same gateway and the same 1683+ data sources. The
110
110
  only difference is which pack's tools are listed **directly**; `ask_pipeworx`
111
111
  reaches all of them from either one.
112
112
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pipeworx/mcp-edgar",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "EDGAR MCP — SEC EDGAR public APIs (free, no auth)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -26,7 +26,7 @@
26
26
  "@cloudflare/workers-types": "^4.20260405.1"
27
27
  },
28
28
  "pipeworx": {
29
- "sourceHash": "v1-c758c9386febbe0e91470cf7aa45ee292d3edb8f1b165a1404f258617be43004",
30
- "sourceCommit": "76aa8f5d9b5dc2f665563030647c68e661478864"
29
+ "sourceHash": "v1-8ebd704e53458e829aea1033aa14ef78911a50cd60211956318c1574f8b1eb4c",
30
+ "sourceCommit": "1064560fd9dcd0d6fc2606298c2ba244b3a1fbeb"
31
31
  }
32
32
  }
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.pipeworx-io/edgar",
4
4
  "title": "Edgar",
5
5
  "description": "EDGAR MCP — SEC EDGAR public APIs (free, no auth)",
6
- "version": "0.3.3",
6
+ "version": "0.3.5",
7
7
  "websiteUrl": "https://pipeworx.io/packs/edgar",
8
8
  "repository": {
9
9
  "url": "https://github.com/pipeworx-io/mcp-edgar",
package/src/index.ts CHANGED
@@ -452,7 +452,42 @@ async function fetchWithTimeout(
452
452
  ),
453
453
  );
454
454
  }
455
- throw err;
455
+ // Fleet #2382. Everything that isn't a timeout/abort here is a genuine
456
+ // NETWORK-LEVEL failure — DNS resolution, connection refused, TLS handshake,
457
+ // Cloudflare's own "Network connection lost." — meaning `fetch()` itself
458
+ // threw and no HTTP response of any kind was ever received. Until this fix
459
+ // that raw exception was rethrown VERBATIM: a bare `TypeError: fetch failed`
460
+ // (or the Workers-runtime equivalent) names no upstream, carries no class
461
+ // token, and reads exactly like a defect in OUR code — because it says
462
+ // nothing about the call at all. It landed in `error`, the tier that means
463
+ // "Pipeworx has a defect", for every one of the (at the time of writing)
464
+ // ~470 packs that call this helper directly with no wrapper of their own.
465
+ //
466
+ // `dexscreener` hit this independently (fleet #1579) and fixed it with a
467
+ // bespoke per-pack try/catch around `fetchWithTimeout`. That fix is correct
468
+ // but only covers one pack; every other caller of this shared helper still
469
+ // leaked the raw exception. Moving the same fix HERE — the one place that
470
+ // already carries the timeout case — covers every pack that uses
471
+ // `fetchWithTimeout` without a wrapper, for free, and without widening
472
+ // `classifyToolError`'s regex list: the fix is giving the message a proper
473
+ // `upstream_down:` token at the point the two facts (no response was ever
474
+ // received, and which host we were trying to reach) are actually in hand,
475
+ // not teaching the classifier to guess from prose after the fact.
476
+ //
477
+ // Safe on the same grounds as the timeout branch above: no argument a
478
+ // caller passes can make `fetch()` itself throw a connection-level error,
479
+ // so this is always an availability failure, never a caller mistake. Same
480
+ // `markInternalOrigin` treatment — an origin we run that never answered is
481
+ // still ours, not a third party's outage.
482
+ const raw = err instanceof Error ? err.message : String(err);
483
+ throw new Error(
484
+ markInternalOrigin(
485
+ `upstream_down: could not reach ${name} at all (${raw.slice(0, 160)}). ` +
486
+ `No request reached ${name}, so this says NOTHING about whether the arguments you passed ` +
487
+ 'are valid — do not re-check them on the strength of this error. Retry shortly.',
488
+ url,
489
+ ),
490
+ );
456
491
  }
457
492
  }
458
493
 
@@ -1534,7 +1569,7 @@ const tools: McpToolExport['tools'] = [
1534
1569
  },
1535
1570
  form_type: {
1536
1571
  type: 'string',
1537
- description: 'When accession is omitted, the form type of the latest filing to fetch, e.g. "10-K", "10-Q", "8-K", "DEF 14A". Omit both accession and form_type to get the single most recent filing of any type.',
1572
+ description: 'When accession is omitted, the form type of the latest filing to fetch, e.g. "10-K", "10-Q", "8-K", "DEF 14A" — or a `|`-separated SET ("10-K|10-Q") for "the most recent of either, whichever is newer". Omit both accession and form_type to get the single most recent filing of ANY type at all (routine 8-Ks/Form 4s/Form 144s included, not just annual/quarterly reports) — use the `|` set instead when you specifically want the newest 10-K or 10-Q.',
1538
1573
  },
1539
1574
  include_primary_text: {
1540
1575
  type: 'boolean',
@@ -1551,7 +1586,7 @@ const tools: McpToolExport['tools'] = [
1551
1586
  {
1552
1587
  name: 'edgar_filing_text',
1553
1588
  description:
1554
- 'AUTHORITATIVE full text of a SEC filing\'s primary document (10-K / 10-Q / 8-K body), HTML stripped to clean plaintext — the source for disclosures that live in prose, not XBRL: going-concern language, ATM / at-the-market equity facilities, committed-equity share caps, public-float figures, subsequent events, and the liquidity footnote. Pass an accession (from edgar_search_filings / edgar_company_filings) plus the filer\'s ticker or CIK; OR omit accession and pass ticker + form_type to auto-resolve the latest matching filing. Optionally set `section` to return just one part (going_concern | liquidity | capital_resources | subsequent_events). Large docs (a 10-Q is ~100k+ chars of text) are PAGED, not spilled: the result caps at `max_chars` (default 50000) from `offset`, and returns `truncated` + `next_offset` — pass next_offset back as `offset` to read the next window. An especially large filing (e.g. an S-1 with heavy inline-XBRL tagging can exceed 10MB of raw HTML) is also capped on the READ side — the response sets `raw_truncated:true` when only the first portion of the document was read at all, which bounds how far `offset` can page and can make a late `section` (e.g. subsequent_events) come back not-found even though it exists further in. Use for "does $TICKER disclose substantial doubt / going concern", "what ATM facility does $TICKER have", "read the liquidity section of the latest 10-Q". For the list of documents/exhibits in a filing use edgar_filing_documents; for structured financial numbers use edgar_company_concept.',
1589
+ 'AUTHORITATIVE full text of a SEC filing\'s primary document (10-K / 10-Q / 8-K body), HTML stripped to clean plaintext — the source for disclosures that live in prose, not XBRL: going-concern language, ATM / at-the-market equity facilities, committed-equity share caps, public-float figures, subsequent events, the liquidity footnote, and MD&A KPIs XBRL never tags (test volume, units shipped, subscriber counts, same-store sales). Pass an accession (from edgar_search_filings / edgar_company_filings) plus the filer\'s ticker or CIK; OR omit accession and pass ticker + form_type to auto-resolve the latest matching filing. **For a specific fact inside a long filing, pass `search`** (a word or exact phrase, e.g. "tests processed" or "processed approximately") instead of paging blind — it scans the WHOLE document (before any offset/max_chars windowing) and returns every matching passage with surrounding context and its own `offset` in the document, so a KPI ~100k characters in is found in one call instead of paging through `max_chars` windows by hand. A zero-match `search` is a real answer (the filing does not use that exact wording) — retry with a shorter or different phrase rather than assuming the tool failed. Optionally set `section` to return just one part (going_concern | liquidity | capital_resources | subsequent_events); `search` runs within that slice when both are given. Large docs (a 10-Q is ~100k+ chars of text) are PAGED, not spilled, when `search` is not used: the result caps at `max_chars` (default 50000) from `offset`, and returns `truncated` + `next_offset` — pass next_offset back as `offset` to read the next window. An especially large filing (e.g. an S-1 with heavy inline-XBRL tagging can exceed 10MB of raw HTML) is also capped on the READ side — the response sets `raw_truncated:true` when only the first portion of the document was read at all, which bounds how far `offset` can page (and how far `search` can scan) and can make a late section or search term come back not-found even though it exists further in. Use for "does $TICKER disclose substantial doubt / going concern", "what ATM facility does $TICKER have", "read the liquidity section of the latest 10-Q", "how many tests did $TICKER process this quarter". For the list of documents/exhibits in a filing use edgar_filing_documents; for structured financial numbers use edgar_company_concept.',
1555
1590
  summary: 'The full text of an SEC filing\'s main document, HTML stripped to clean plaintext.',
1556
1591
  inputSchema: {
1557
1592
  type: 'object' as const,
@@ -1570,7 +1605,7 @@ const tools: McpToolExport['tools'] = [
1570
1605
  },
1571
1606
  form_type: {
1572
1607
  type: 'string',
1573
- description: 'When accession is omitted, the form type of the latest filing to fetch — "10-K", "10-Q", "8-K", "DEF 14A", etc.',
1608
+ description: 'When accession is omitted, the form type of the latest filing to fetch — "10-K", "10-Q", "8-K", "DEF 14A", etc. For "the most recent 10-K OR 10-Q" (or any "whichever of these is newer" question), pass a `|`-separated SET, e.g. "10-K|10-Q" — do NOT guess a single type ("10-K" by habit skips a newer 10-Q) and do NOT omit this field to get "any type", since a company files far more 8-Ks/Form 4s/Form 144s between annual or quarterly reports than it files the reports themselves and an empty form_type returns the single most recent filing of ANY kind (verified live 2026-09-25, fleet #2450: NTRA\'s single most recent SEC filing was a Form 144 insider-sale notice, filed weeks after its real 10-Q and completely unrelated to the question asked).',
1574
1609
  },
1575
1610
  section: {
1576
1611
  type: 'string',
@@ -1585,6 +1620,22 @@ const tools: McpToolExport['tools'] = [
1585
1620
  type: 'number',
1586
1621
  description: 'Character offset to start from (default 0). Pass the prior result\'s next_offset to page forward.',
1587
1622
  },
1623
+ search: {
1624
+ type: 'string',
1625
+ description: 'Find a specific fact instead of paging blind. This is a SUBSTRING match, not a relevance search — pass the exact 2-4 word phrase most likely to appear VERBATIM in the prose ("tests processed", "processed approximately", "going concern"), never a compound of every concept in the question. A phrase that ANDs several unrelated question-words together ("oncology Signatera revenue test volume") returns ZERO matches even in the CORRECT filing, because the filing\'s own sentence never contains all of those words together — verified live 2026-09-25 (fleet #2450) on a real Natera 10-Q that DOES report the exact number asked for: "Signatera revenue" and the 4-word compound above both found nothing, while the filing\'s own wording ("processed approximately", "tests processed") is what actually appears. When unsure of the filing\'s exact phrasing, prefer the SHORTEST distinctive 2-3 word fragment of the metric name itself (a unit, a verb+noun like "processed approximately") over restating the question. Case-insensitive substring match over the WHOLE document text (or the whole `section` slice, if both are given), run BEFORE max_chars/offset windowing. Returns every matching passage (surrounding context + its own offset in the document) instead of the normal paged `text` — use the returned offsets with a follow-up call (no `search`, `offset` set to one of them) if you need more surrounding text than the passage gives. Zero matches means try again with a SHORTER, more literal phrase before concluding the filing does not disclose it — this is a real answer about wording, not a tool failure. Accepted aliases: `contains`, `find`, `phrase`.',
1626
+ },
1627
+ contains: {
1628
+ type: 'string',
1629
+ description: 'Alias for `search`.',
1630
+ },
1631
+ find: {
1632
+ type: 'string',
1633
+ description: 'Alias for `search`.',
1634
+ },
1635
+ phrase: {
1636
+ type: 'string',
1637
+ description: 'Alias for `search`.',
1638
+ },
1588
1639
  ticker_or_cik: {
1589
1640
  type: 'string',
1590
1641
  description: 'Alias for `ticker` / `cik` — the single-argument spelling edgar_company_filings and edgar_insider_transactions use. Takes either a ticker or a CIK.',
@@ -2317,6 +2368,34 @@ function htmlToText(html: string): string {
2317
2368
  .trim();
2318
2369
  }
2319
2370
 
2371
+ /**
2372
+ * Index into SEC's own recent-first `forms` array of the latest filing
2373
+ * matching `wantFormTypeArg` — a single form ("10-Q") or a `|`-separated SET
2374
+ * ("10-K|10-Q" for "the most recent annual or quarterly report, whichever is
2375
+ * newer"), same pipe convention this catalog already uses for status fields
2376
+ * (grants-gov's "posted|forecasted"). Omitted/empty returns 0 — the single
2377
+ * most recent filing of ANY type.
2378
+ *
2379
+ * Fleet #2450: a question asking for "the most recent 10-K or 10-Q" cannot
2380
+ * be answered by omitting form_type — that resolves to the single most
2381
+ * recent filing of ANY type, and a company files far more 8-Ks, Form 4s and
2382
+ * Form 144s between annual/quarterly reports than it files the reports
2383
+ * themselves. Verified live: NTRA's single most recent SEC filing on
2384
+ * 2026-09-23 was a Form 144 insider-sale notice, not an annual or quarterly
2385
+ * report at all — form_type left empty silently returns THAT instead of the
2386
+ * 10-Q the question meant. Nor does picking one type and hoping work: a
2387
+ * 10-Q is very often newer than the last 10-K. "10-K|10-Q" is the answer to
2388
+ * "I don't care which, just the newest of these".
2389
+ */
2390
+ function findLatestFormIndex(forms: string[], wantFormTypeArg: string | undefined): number {
2391
+ const wanted = (wantFormTypeArg ?? '')
2392
+ .split('|')
2393
+ .map((f) => f.trim().toUpperCase())
2394
+ .filter(Boolean);
2395
+ if (!wanted.length) return 0;
2396
+ return forms.findIndex((f) => wanted.includes(f.toUpperCase()));
2397
+ }
2398
+
2320
2399
  async function filingDocuments(
2321
2400
  accession: string | undefined,
2322
2401
  tickerOrCik: string,
@@ -2347,7 +2426,7 @@ async function filingDocuments(
2347
2426
  const r = sub.filings?.recent;
2348
2427
  const forms = r?.form ?? [];
2349
2428
  const accs = r?.accessionNumber ?? [];
2350
- const idx = wantForm ? forms.findIndex((f) => f.toUpperCase() === wantForm) : 0;
2429
+ const idx = findLatestFormIndex(forms, wantFormTypeArg);
2351
2430
  if (idx < 0 || !accs[idx]) {
2352
2431
  throw new Error(
2353
2432
  `No ${wantForm ?? 'recent'} filing found for ${tickerOrCik} in the recent index. Use edgar_company_filings({ticker:"${tickerOrCik}"${wantForm ? `, form_type:"${wantForm}"` : ''}}) to list available filings, then pass an accession.`,
@@ -2464,6 +2543,72 @@ const FILING_SECTION_PATTERNS: Record<string, { anchor: RegExp; pick: 'first' |
2464
2543
  const FILING_TEXT_DEFAULT_MAX = 50000;
2465
2544
  const FILING_TEXT_CAP = 100000;
2466
2545
 
2546
+ // fleet #2446: a KPI question ("how many tests did $TICKER process") routed
2547
+ // correctly to edgar_filing_text but the fact sat ~100k characters into a
2548
+ // 10-Q's MD&A, which meant paging max_chars windows by hand to find it. This
2549
+ // is a plain case-insensitive substring scan of the ALREADY-EXTRACTED plain-
2550
+ // text document (not a second fetch) — cheap, and it runs before the normal
2551
+ // offset/max_chars windowing so it sees the whole doc (or the whole `section`
2552
+ // slice) rather than one page of it.
2553
+ const SEARCH_CONTEXT_CHARS = 400;
2554
+ const SEARCH_MAX_PASSAGES = 10;
2555
+
2556
+ // A KPI figure looks like "2,056,800" or "1,708,200" — thousands-grouped, or
2557
+ // otherwise a bare run of 4+ digits (a raw count with no separator).
2558
+ const NEARBY_NUMBER_RE = /\d{1,3}(?:,\d{3})+|\d{4,}/;
2559
+
2560
+ function findPassages(
2561
+ text: string,
2562
+ term: string,
2563
+ contextChars = SEARCH_CONTEXT_CHARS,
2564
+ maxPassages = SEARCH_MAX_PASSAGES,
2565
+ ): { total: number; distinct_locations: number; passages: Array<{ offset: number; text: string }> } {
2566
+ const needle = term.toLowerCase();
2567
+ if (!needle) return { total: 0, distinct_locations: 0, passages: [] };
2568
+ const hay = text.toLowerCase();
2569
+ const offsets: number[] = [];
2570
+ let from = 0;
2571
+ for (;;) {
2572
+ const idx = hay.indexOf(needle, from);
2573
+ if (idx === -1) break;
2574
+ offsets.push(idx);
2575
+ from = idx + needle.length;
2576
+ }
2577
+
2578
+ // Merge occurrences close enough that their context windows would overlap
2579
+ // into ONE passage — a KPI sentence ("we processed approximately X tests,
2580
+ // compared to Y tests processed") can repeat the search term 2-3 times a
2581
+ // few words apart, and without this a common word burns most of the
2582
+ // (capped) passage budget on one sentence instead of covering the document.
2583
+ const groups: Array<{ first: number; last: number }> = [];
2584
+ for (const idx of offsets) {
2585
+ const g = groups[groups.length - 1];
2586
+ if (g && idx - g.last <= contextChars * 2) g.last = idx;
2587
+ else groups.push({ first: idx, last: idx });
2588
+ }
2589
+
2590
+ // Rank groups whose window carries a nearby NUMBER first. A KPI question
2591
+ // ("how many tests/units/subscribers") is answered by a figure next to the
2592
+ // word, not by every mention of the word — measured live (fleet #2446):
2593
+ // searching "tests" in Natera's Q2 2026 10-Q hits 78 times, mostly
2594
+ // forward-looking-statements boilerplate ("expectations... for our
2595
+ // tests"), with the actual "processed approximately 2,056,800 tests" KPI
2596
+ // buried past the first ~80 of them by document order. Ranking by nearby
2597
+ // digits (no second fetch — same text already in hand) reliably surfaces
2598
+ // it inside a small `maxPassages` cap instead of requiring the caller to
2599
+ // already know the more distinctive phrase to search for. Groups without a
2600
+ // nearby number keep DOCUMENT ORDER among themselves (stable sort).
2601
+ const scored = groups.map((g) => {
2602
+ const start = Math.max(0, g.first - contextChars);
2603
+ const end = Math.min(text.length, g.last + needle.length + contextChars);
2604
+ return { start, end, hasNumber: NEARBY_NUMBER_RE.test(text.slice(start, end)) ? 1 : 0 };
2605
+ });
2606
+ scored.sort((a, b) => b.hasNumber - a.hasNumber);
2607
+
2608
+ const passages = scored.slice(0, maxPassages).map((s) => ({ offset: s.start, text: text.slice(s.start, s.end) }));
2609
+ return { total: offsets.length, distinct_locations: groups.length, passages };
2610
+ }
2611
+
2467
2612
  // Fetch ONE filing's primary-document text, HTML-stripped, paged. Splitting this
2468
2613
  // out (rather than piggybacking edgar_filing_documents' include_primary_text,
2469
2614
  // which caps at 40k and can't page) lets the register pull deep disclosures —
@@ -2475,6 +2620,7 @@ async function filingText(
2475
2620
  maxChars?: number,
2476
2621
  offset?: number,
2477
2622
  wantFormTypeArg?: string,
2623
+ searchTerm?: string,
2478
2624
  ) {
2479
2625
  if (typeof tickerOrCik !== 'string' || !tickerOrCik.trim()) {
2480
2626
  throw new Error('A company is required: pass `ticker` ("ACTU"), `cik` or their alias `ticker_or_cik` — plus either an accession or a form_type (e.g. edgar_filing_text({ticker:"ACTU", form_type:"10-Q"})).');
@@ -2501,7 +2647,7 @@ async function filingText(
2501
2647
 
2502
2648
  if (!acc) {
2503
2649
  const wantForm = wantFormTypeArg?.trim().toUpperCase();
2504
- const idx = wantForm ? forms.findIndex((f) => f.toUpperCase() === wantForm) : 0;
2650
+ const idx = findLatestFormIndex(forms, wantFormTypeArg);
2505
2651
  if (idx < 0 || !accs[idx]) {
2506
2652
  throw new Error(`No ${wantForm ?? 'recent'} filing found for ${tickerOrCik}. Use edgar_company_filings({ticker_or_cik:"${tickerOrCik}"${wantForm ? `, form_type:"${wantForm}"` : ''}}) to list filings, then pass an accession.`);
2507
2653
  }
@@ -2559,6 +2705,44 @@ async function filingText(
2559
2705
  }
2560
2706
  }
2561
2707
 
2708
+ // `search` short-circuits the normal offset/max_chars windowing: it scans
2709
+ // the whole (possibly section-sliced) document and returns matching
2710
+ // passages with their own offsets, rather than one page of raw text the
2711
+ // caller has to eyeball for the fact they wanted (fleet #2446).
2712
+ const search = (searchTerm ?? '').trim();
2713
+ if (search) {
2714
+ const { total: totalMatches, distinct_locations: distinctLocations, passages } = findPassages(fullText, search);
2715
+ return {
2716
+ accession: acc.dashed,
2717
+ cik: cikNoZeros,
2718
+ company: sub.name ?? null,
2719
+ form: formType,
2720
+ filed: filingDate,
2721
+ document: primaryDocument,
2722
+ document_url: docUrl,
2723
+ section: sectionApplied,
2724
+ ...(sectionFound !== null ? { section_found: sectionFound } : {}),
2725
+ total_chars: fullText.length,
2726
+ search: {
2727
+ term: search,
2728
+ total_matches: totalMatches,
2729
+ distinct_locations: distinctLocations,
2730
+ returned: passages.length,
2731
+ truncated: distinctLocations > passages.length,
2732
+ passages,
2733
+ note: totalMatches === 0
2734
+ ? 'No case-insensitive match for this exact wording in the document (or section, if one was given). That is a real answer, not a failure — retry with a shorter phrase or drop `section` to search the whole document.'
2735
+ : `Passages carrying a nearby number (a likely KPI figure) are ranked first; the rest keep document order. Each passage's offset is a position in the document text — pass it as \`offset\` (without \`search\`) to read more surrounding text via the normal paged text field.`,
2736
+ },
2737
+ ...(rawTruncated
2738
+ ? {
2739
+ raw_truncated: true,
2740
+ raw_truncated_note: `This document's raw HTML exceeds the ${(FILING_DOC_MAX_BYTES / 1_000_000).toFixed(0)}MB this tool reads in one call, so the search above only scanned the portion read — NOT the whole filing. Treat total_matches:0 as inconclusive, not "absent", for a raw_truncated document.`,
2741
+ }
2742
+ : {}),
2743
+ };
2744
+ }
2745
+
2562
2746
  const total = fullText.length;
2563
2747
  const off = Math.max(0, Math.floor(Number(offset) || 0));
2564
2748
  const cap = Math.min(FILING_TEXT_CAP, Math.max(1000, Math.floor(Number(maxChars) || FILING_TEXT_DEFAULT_MAX)));
@@ -4439,6 +4623,7 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<un
4439
4623
  args.max_chars as number | undefined,
4440
4624
  args.offset as number | undefined,
4441
4625
  args.form_type as string | undefined,
4626
+ (args.search ?? args.contains ?? args.find ?? args.phrase) as string | undefined,
4442
4627
  );
4443
4628
  case 'edgar_ticker_to_cik':
4444
4629
  return tickerToCik((args.ticker ?? args.ticker_or_cik ?? args.company) as string);
package/src/server.ts CHANGED
@@ -9,7 +9,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprot
9
9
  import pack from './index.js';
10
10
 
11
11
  const server = new Server(
12
- { name: '@pipeworx/mcp-edgar', version: '0.3.3' },
12
+ { name: '@pipeworx/mcp-edgar', version: '0.3.5' },
13
13
  { capabilities: { tools: {} } },
14
14
  );
15
15