@pipeworx/mcp-edgar 0.3.8 → 0.3.9

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 1763+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.
5
+ Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1784+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.
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 1763+ data sources. The
109
+ Both URLs reach the same gateway and the same 1784+ 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.8",
3
+ "version": "0.3.9",
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-9871b5732265c259f8fc2a821a7721a32960cabdc9041cc1d5ea4a1e0b8d3f37",
30
- "sourceCommit": "051ee2039fc96224cb5b7358fd69d525bced13a6"
29
+ "sourceHash": "v1-d35d4a284237ace3314859c5df49ad1b77a0a9e2e7696d3f0b601f112b20e42a",
30
+ "sourceCommit": "3723e46c6d115bd4a1fd2175e6bf758555e7cb50"
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.8",
6
+ "version": "0.3.9",
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
@@ -1854,6 +1854,23 @@ function significantTokens(name: string): string[] {
1854
1854
  return normName(name).split(' ').filter((t) => t.length > 1 && !GENERIC_NAME_TOKENS.has(t));
1855
1855
  }
1856
1856
 
1857
+ // 2026-10-09: shared/src/resolve.ts's rankMatches only strips '.' and ',', so
1858
+ // "BRISTOL-MYERS SQUIBB COMPANY" (hyphen kept, full "COMPANY") never matched
1859
+ // SEC's own listed title "BRISTOL MYERS SQUIBB CO" — resolved via full-text
1860
+ // fallback as no_us_registrant instead of the direct CIK 14272 hit. Do not fix
1861
+ // this in shared (used by other packs); derive a second, edgar-only candidate
1862
+ // query that also hits the hyphen (normName already strips it) and the SEC
1863
+ // house style for a trailing corporate suffix.
1864
+ const SUFFIX_TO_SEC_STYLE: Record<string, string> = {
1865
+ COMPANY: 'CO', CORPORATION: 'CORP', INCORPORATED: 'INC', LIMITED: 'LTD',
1866
+ };
1867
+ function secStyleQuery(name: string): string {
1868
+ const tokens = normName(name).split(' ').filter(Boolean);
1869
+ const last = tokens[tokens.length - 1];
1870
+ if (last && SUFFIX_TO_SEC_STYLE[last]) tokens[tokens.length - 1] = SUFFIX_TO_SEC_STYLE[last];
1871
+ return tokens.join(' ');
1872
+ }
1873
+
1857
1874
  function distinctiveToken(name: string): string {
1858
1875
  return significantTokens(name)[0] ?? normName(name).split(' ')[0] ?? '';
1859
1876
  }
@@ -1861,8 +1878,14 @@ function distinctiveToken(name: string): string {
1861
1878
  // SEC company names use "&", not "and". Produce query variants: &-normalized
1862
1879
  // full name first, then progressively drop trailing generic words (browse-edgar
1863
1880
  // matches on a name prefix and a too-specific full name can return nothing).
1881
+ // 2026-10-13: browse-edgar's `company=` param is a literal prefix match and
1882
+ // does NOT fold punctuation itself -- company=Bristol-Myers%20Squibb returns
1883
+ // 0 candidates while company=Bristol%20Myers%20Squibb returns CIK 14272 (the
1884
+ // filer's own listed title has no hyphen). Fold -/() to spaces before
1885
+ // querying, same characters normName() already strips for the ranking path,
1886
+ // so every query we send is already in SEC's unpunctuated house style.
1864
1887
  function secQueryVariants(name: string): string[] {
1865
- const amp = name.replace(/\band\b/gi, '&').replace(/\s+/g, ' ').trim();
1888
+ const amp = name.replace(/\band\b/gi, '&').replace(/[-/()]/g, ' ').replace(/\s+/g, ' ').trim();
1866
1889
  const out = [amp];
1867
1890
  const words = amp.split(' ');
1868
1891
  for (let end = words.length; end > 1; end--) {
@@ -1880,28 +1903,99 @@ async function fetchCompanyTickers(): Promise<SecTickerRow[]> {
1880
1903
  return Object.values(data);
1881
1904
  }
1882
1905
 
1906
+ // Legal-form designators. A filer name carrying none of these (and no
1907
+ // descriptor word like PHARMACEUTICA / PRODUCTS / PARTNERS) in a 2-4 word
1908
+ // "Last First M" shape is an individual Form 3/4/5 reporting owner, never a
1909
+ // company's own CIK. #2998: "JANSSEN PHARMACEUTICALS INC" fell back to the
1910
+ // bare "JANSSEN" query, whose browse-edgar list is alphabetical by person
1911
+ // name, and took "Janssen Angela K" (CIK 1664284) as J&J's subsidiary.
1912
+ const LEGAL_FORM_TOKENS = new Set([
1913
+ 'INC', 'CORP', 'CO', 'COMPANY', 'CORPORATION', 'INCORPORATED', 'LTD', 'LIMITED',
1914
+ 'LLC', 'LP', 'LLP', 'PLC', 'SA', 'AG', 'NV', 'BV', 'GMBH', 'AB', 'AS', 'OY', 'SPA',
1915
+ 'SE', 'KK', 'PTE', 'PTY', 'SARL', 'SAS', 'TRUST', 'FUND', 'PARTNERSHIP',
1916
+ ]);
1917
+ const COMPANY_WORDS = /\b(PHARMA\w*|THERAP\w*|BIO\w*|PRODUCTS?|PARTNERS|ASSOCIATES|CAPITAL|VENTURES|HOLDINGS?|GROUP|LABORATOR\w*|LABS|SCIENCES?|MEDICAL|HEALTH\w*|INTERNATIONAL|GLOBAL|SERVICES|SYSTEMS|TECHNOLOG\w*|INDUSTRIES|ENTERPRISES|MANAGEMENT|INVESTMENTS?|FOUNDATION|UNIVERSITY|BANK|FINANCIAL|ENERGY|RESEARCH|AMERICA|USA|US)\b/;
1918
+
1919
+ /** Person-shaped filer name ("Janssen Angela K", "JANSSEN MICHAEL J"). */
1920
+ function isPersonShapedName(name: string): boolean {
1921
+ const n = name.toUpperCase().replace(/\./g, '').replace(/[,&/()-]/g, ' ').replace(/\s+/g, ' ').trim();
1922
+ const toks = n.split(' ').filter(Boolean);
1923
+ if (toks.length < 2 || toks.length > 4) return false;
1924
+ if (toks.some((t) => LEGAL_FORM_TOKENS.has(t) || /\d/.test(t))) return false;
1925
+ return !COMPANY_WORDS.test(n);
1926
+ }
1927
+
1928
+ const STOP_TOKENS = new Set(['THE', 'OF', 'AND']);
1929
+
1930
+ // Two tokens agree when equal, or one is a >=6-char prefix of the other
1931
+ // ("PHARMACEUTICA" ~ "PHARMACEUTICALS").
1932
+ const tokAgree = (a: string, b: string): boolean =>
1933
+ a === b || (Math.min(a.length, b.length) >= 6 && (a.startsWith(b) || b.startsWith(a)));
1934
+
1935
+ /** Similarity of a candidate filer name to the FULL sponsor query, ignoring
1936
+ * legal-form suffixes (INC matching INC says nothing). Significant tokens
1937
+ * weigh 10, descriptor words (PHARMACEUTICALS, BIOTECH...) weigh 1. */
1938
+ function nameSimilarity(query: string, candidate: string): { sig: number; desc: number; score: number } {
1939
+ const keep = (t: string) => t.length > 1 && !LEGAL_FORM_TOKENS.has(t) && !STOP_TOKENS.has(t);
1940
+ const qAll = normName(query).split(' ').filter(keep);
1941
+ const cAll = normName(candidate).split(' ').filter(keep);
1942
+ const sigSet = new Set(significantTokens(query));
1943
+ let sig = 0, desc = 0;
1944
+ for (const q of qAll) {
1945
+ if (!cAll.some((c) => tokAgree(q, c))) continue;
1946
+ if (sigSet.has(q)) sig++; else desc++;
1947
+ }
1948
+ return { sig, desc, score: sig * 10 + desc };
1949
+ }
1950
+
1951
+ const decodeHtml = (s: string): string =>
1952
+ s.replace(/&amp;/g, '&').replace(/&#39;|&apos;/g, "'").replace(/&quot;/g, '"').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
1953
+
1954
+ /** browse-edgar HTML company search -> [{cik, name}]. The atom output carries
1955
+ * no names (SEC renders them as "ARRAY(0x...)"), so a ranking by name needs
1956
+ * the HTML list; an exact single hit renders the company page instead. */
1957
+ function parseBrowseEdgarCompanies(html: string): { cik: string; name: string }[] {
1958
+ const out: { cik: string; name: string }[] = [];
1959
+ const single = html.match(/<span class="companyName">([\s\S]*?)<acronym[^>]*>CIK<\/acronym>#:\s*<a[^>]*>(\d+)/);
1960
+ if (single) return [{ cik: String(Number(single[2])), name: decodeHtml(single[1]).trim() }];
1961
+ for (const m of html.matchAll(/CIK=(\d+)&amp;owner=include[^"]*">\d+<\/a><\/td>\s*<td scope="row">([\s\S]*?)<\/td>/g)) {
1962
+ out.push({ cik: String(Number(m[1])), name: decodeHtml(m[2].split(/<br/i)[0]).trim() });
1963
+ }
1964
+ return out;
1965
+ }
1966
+
1883
1967
  /** Does the sponsor name have its OWN SEC filer CIK (registered entity — a
1884
- * subsidiary, or a private co that filed a Form D / draft S-1)? browse-edgar
1885
- * orders by relevance so the leading <cik> tags are the closest matches; each
1886
- * candidate is validated against its real name from the submissions API
1887
- * (which doubles as the entity profile). Returns {cik, profile} or null. */
1968
+ * subsidiary, or a private co that filed a Form D / draft S-1)? Candidates
1969
+ * come from browse-edgar's company search (count=40 -- its order is
1970
+ * alphabetical, not relevance, so the first hits of a short query are often
1971
+ * people); individual reporting owners are dropped, the rest ranked by
1972
+ * similarity to the FULL sponsor name, and the best are validated against
1973
+ * their real name from the submissions API (which doubles as the entity
1974
+ * profile). When the query carries descriptor words beyond its one
1975
+ * distinctive token ("JANSSEN PHARMACEUTICALS"), a candidate must agree on
1976
+ * at least one of them. Returns {cik, profile} or null. */
1888
1977
  async function edgarOwnCik(sponsor: string): Promise<{ cik: string; profile: EntityProfile } | null> {
1889
1978
  const sig = significantTokens(sponsor);
1890
1979
  const need = Math.min(2, sig.length || 1);
1980
+ const queryDesc = nameSimilarity(sponsor, sponsor).desc;
1981
+ const needDesc = sig.length <= 1 && queryDesc > 0 ? 1 : 0;
1982
+ const passes = (s: { sig: number; desc: number }) => s.sig >= need && s.desc >= needDesc;
1891
1983
  for (const q of secQueryVariants(sponsor)) {
1892
- const url = `https://www.sec.gov/cgi-bin/browse-edgar?company=${encodeURIComponent(q)}&action=getcompany&type=&dateb=&owner=include&count=10&output=atom`;
1984
+ const url = `https://www.sec.gov/cgi-bin/browse-edgar?company=${encodeURIComponent(q)}&action=getcompany&type=&dateb=&owner=include&count=40`;
1893
1985
  const res = await pwFetch(url, { headers: { 'User-Agent': SEC_HEADERS['User-Agent'] } }).catch(() => null);
1894
1986
  if (!res || !res.ok) continue;
1895
- const xml = await res.text();
1896
- const ciks = [...xml.matchAll(/<cik>(\d+)<\/cik>/gi)].map((m) => String(Number(m[1])));
1897
- for (const cik of [...new Set(ciks)].slice(0, 3)) {
1898
- const profile = await edgarEntityProfile(cik);
1899
- if (!profile?.name) continue;
1900
- const nameToks = new Set(significantTokens(profile.name));
1901
- const overlap = sig.filter((t) => nameToks.has(t)).length;
1902
- if (overlap >= need) return { cik, profile };
1987
+ const found = parseBrowseEdgarCompanies(await res.text());
1988
+ const ranked = found
1989
+ .filter((c) => !isPersonShapedName(c.name))
1990
+ .map((c, i) => ({ ...c, i, sim: nameSimilarity(sponsor, c.name) }))
1991
+ .filter((c) => passes(c.sim))
1992
+ .sort((a, b) => b.sim.score - a.sim.score || a.i - b.i);
1993
+ for (const c of ranked.slice(0, 3)) {
1994
+ const profile = await edgarEntityProfile(c.cik);
1995
+ if (!profile?.name || isPersonShapedName(profile.name)) continue;
1996
+ if (passes(nameSimilarity(sponsor, profile.name))) return { cik: c.cik, profile };
1903
1997
  }
1904
- if (ciks.length) break; // first variant that returned candidates is authoritative
1998
+ if (found.length) break; // first variant that returned candidates is authoritative
1905
1999
  }
1906
2000
  return null;
1907
2001
  }
@@ -1972,16 +2066,24 @@ async function sponsorToFiler(sponsor: string): Promise<unknown> {
1972
2066
  const tickers = await fetchCompanyTickers();
1973
2067
 
1974
2068
  // 1) Direct listed match (exact / prefix / whole-word — not loose substring).
2069
+ // Also rank a hyphen-normalized, suffix-normalized form ("Bristol-Myers
2070
+ // Squibb Company" -> "BRISTOL MYERS SQUIBB CO") and keep whichever scores
2071
+ // higher, so inputs that already match keep behaving exactly as before.
1975
2072
  const ranked = rankMatches(name, tickers, (r) => r.title);
1976
- if (ranked.length && ranked[0].score >= 2) {
1977
- const m = ranked[0].item;
2073
+ const secStyled = secStyleQuery(name);
2074
+ const rankedSecStyle = secStyled === name.toUpperCase() ? [] : rankMatches(secStyled, tickers, (r) => r.title);
2075
+ const best = rankedSecStyle.length && (!ranked.length || rankedSecStyle[0].score > ranked[0].score)
2076
+ ? rankedSecStyle
2077
+ : ranked;
2078
+ if (best.length && best[0].score >= 2) {
2079
+ const m = best[0].item;
1978
2080
  return {
1979
2081
  status: 'resolved',
1980
2082
  method: 'direct',
1981
- confidence: ranked[0].score >= 3 ? 'high' : 'medium',
2083
+ confidence: best[0].score >= 3 ? 'high' : 'medium',
1982
2084
  sponsor: name,
1983
2085
  filer: filerOut(m),
1984
- alternatives: ranked.slice(1, 4).map((x) => filerOut(x.item)),
2086
+ alternatives: best.slice(1, 4).map((x) => filerOut(x.item)),
1985
2087
  };
1986
2088
  }
1987
2089
 
@@ -1994,24 +2096,59 @@ async function sponsorToFiler(sponsor: string): Promise<unknown> {
1994
2096
  }
1995
2097
  }
1996
2098
 
1997
- // 3) Full-text: listed filers referencing the sponsor; confirm a parent by
1998
- // requiring the sponsor's distinctive token in the filer's name (kills
1999
- // "merely mentioned by an unrelated filer" false positives).
2099
+ // 3) Full-text: listed filers referencing the sponsor; confirm a parent.
2100
+ // Rule A (shared_token_fulltext): the sponsor's distinctive token appears
2101
+ // in the candidate's OWN name (kills "merely mentioned by an unrelated
2102
+ // filer" false positives) — catches e.g. "Capricor Inc." -> "Capricor
2103
+ // Therapeutics". This does NOT catch a brand subsidiary whose name shares
2104
+ // no word with its parent ("Janssen" / "Johnson & Johnson", "Merck Sharp
2105
+ // and Dohme" is the one case that happens to share "Merck") — for that we
2106
+ // never promote on mention-count alone (a press/litigation mention is not
2107
+ // ownership); we ask SEC's own authoritative source instead: does the
2108
+ // dominant candidate's latest 10-K Exhibit 21 subsidiary list (Item
2109
+ // 601(b)(21)) name the sponsor? Rule B (ex21_subsidiary_match) fires only
2110
+ // then, and only against the single dominant candidate, so a genuine miss
2111
+ // never pays for an extra filing+exhibit fetch. #2971.
2000
2112
  const ft = await fullTextListedFilers(name).catch(() => ({ candidates: [] as ListedFiler[], total: 0 }));
2001
- const confirmed = ft.candidates.find((c) => normName(c.name).split(' ').includes(disc));
2113
+ let confirmed = ft.candidates.find((c) => normName(c.name).split(' ').includes(disc));
2114
+ let rule: 'shared_token_fulltext' | 'ex21_subsidiary_match' | null = confirmed ? 'shared_token_fulltext' : null;
2115
+ let ex21Match: string | null = null;
2116
+
2117
+ if (!confirmed && ft.candidates.length) {
2118
+ const top = ft.candidates[0];
2119
+ const second = ft.candidates[1];
2120
+ // Dominance gate before spending the EX-21 fetch: top mention count must
2121
+ // be meaningful on its own AND clearly ahead of the runner-up.
2122
+ const dominant = top.hits >= 5 && (!second || top.hits >= 3 * second.hits);
2123
+ if (dominant) {
2124
+ ex21Match = await exhibit21NamesSponsor(top.cik, name).catch(() => null);
2125
+ if (ex21Match) {
2126
+ confirmed = top;
2127
+ rule = 'ex21_subsidiary_match';
2128
+ }
2129
+ }
2130
+ }
2131
+
2002
2132
  if (confirmed) {
2133
+ const directMatch = own ? { cik: own.cik, name: own.profile?.name ?? null, listed: false } : null;
2003
2134
  return {
2004
2135
  status: 'resolved_via_parent',
2005
- method: 'subsidiary_fulltext',
2136
+ method: rule === 'ex21_subsidiary_match' ? 'subsidiary_exhibit21' : 'subsidiary_fulltext',
2137
+ rule,
2006
2138
  confidence: confirmed.hits >= 10 ? 'high' : 'medium',
2007
2139
  sponsor: name,
2008
2140
  filer: { company_name: confirmed.name, ticker: confirmed.ticker, cik: confirmed.cik, cik_padded: confirmed.cik.padStart(10, '0') },
2141
+ parent_filer: { company_name: confirmed.name, ticker: confirmed.ticker, cik: confirmed.cik, cik_padded: confirmed.cik.padStart(10, '0') },
2142
+ direct_match: directMatch,
2009
2143
  evidence: {
2010
2144
  parent_mention_filings: confirmed.hits,
2011
2145
  distinctive_token: disc,
2012
2146
  own_subsidiary_cik: own?.cik ?? null,
2013
2147
  own_subsidiary_name: own?.profile?.name ?? null,
2014
- note: 'Sponsor appears to be an operating subsidiary. Resolved to the listed parent whose SEC filings reference it and whose name shares the sponsor\'s distinctive token.',
2148
+ exhibit_21_subsidiary_name: ex21Match,
2149
+ note: rule === 'ex21_subsidiary_match'
2150
+ ? `"${name}" is named as a subsidiary in ${confirmed.name}'s own SEC Exhibit 21 filing (as "${ex21Match}") -- the authoritative ownership disclosure, used because the two names share no common word.`
2151
+ : 'Sponsor appears to be an operating subsidiary. Resolved to the listed parent whose SEC filings reference it and whose name shares the sponsor\'s distinctive token.',
2015
2152
  },
2016
2153
  alternatives: ft.candidates.slice(0, 3).map((c) => ({ company_name: c.name, ticker: c.ticker, cik: c.cik, mention_filings: c.hits })),
2017
2154
  };
@@ -4466,6 +4603,45 @@ function parseSubsidiaryTable(html: string): Subsidiary[] {
4466
4603
  return out;
4467
4604
  }
4468
4605
 
4606
+ // Used by sponsor_to_filer (#2971) to confirm a dominant full-text-mention
4607
+ // candidate as the sponsor's real listed parent via SEC's own authoritative
4608
+ // ownership disclosure, for the case where the subsidiary's name shares no
4609
+ // word with its parent's (Janssen / Johnson & Johnson). Walks the SAME path
4610
+ // as filer_to_sponsors (latest 10-K -> -index.html -> EX-21 -> parsed table)
4611
+ // but stops at the first subsidiary whose name shares a significant token
4612
+ // with the sponsor, rather than returning the whole list. One filing fetch +
4613
+ // one exhibit fetch, bounded by the caller's dominance gate so it only runs
4614
+ // against a serious candidate. Returns the matching subsidiary's own EX-21
4615
+ // name, or null (no 10-K / no EX-21 / didn't parse / sponsor not listed).
4616
+ async function exhibit21NamesSponsor(cik: string, sponsorName: string): Promise<string | null> {
4617
+ const paddedCik = padCik(cik);
4618
+ const subRes = await pwFetch(`${DATA_BASE}/submissions/CIK${paddedCik}.json`, { headers: SEC_HEADERS });
4619
+ if (!subRes.ok) return null;
4620
+ const sub = (await subRes.json()) as {
4621
+ filings?: { recent?: { accessionNumber?: string[]; form?: string[] } };
4622
+ };
4623
+ const forms = sub.filings?.recent?.form ?? [];
4624
+ const accs = sub.filings?.recent?.accessionNumber ?? [];
4625
+ const idx10k = forms.findIndex((f) => f === '10-K' || f === '10-K/A');
4626
+ if (idx10k < 0) return null;
4627
+ const accession = accs[idx10k];
4628
+ if (!accession) return null;
4629
+ const accessionPath = accession.replace(/-/g, '');
4630
+ const cikNoZeros = String(Number(cik));
4631
+ const folder = `https://www.sec.gov/Archives/edgar/data/${cikNoZeros}/${accessionPath}`;
4632
+ const idxRes = await pwFetch(`${folder}/${accession}-index.html`, { headers: SEC_HEADERS });
4633
+ if (!idxRes.ok) return null;
4634
+ const exUrl = findExhibit21Url(await idxRes.text());
4635
+ if (!exUrl) return null;
4636
+ const exRes = await pwFetch(exUrl, { headers: { 'User-Agent': SEC_HEADERS['User-Agent'] } });
4637
+ if (!exRes.ok) return null;
4638
+ const parsed = parseSubsidiaryTable(await exRes.text());
4639
+ const sig = new Set(significantTokens(sponsorName));
4640
+ if (!sig.size) return null;
4641
+ const hit = parsed.find((s) => significantTokens(s.name).some((t) => sig.has(t)));
4642
+ return hit ? hit.name : null;
4643
+ }
4644
+
4469
4645
  async function filerToSponsors(
4470
4646
  tickerOrCik: string | undefined,
4471
4647
  nameFilterArg: string | undefined,
package/src/server.ts CHANGED
@@ -8,8 +8,9 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
8
8
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
9
9
  import pack from './index.js';
10
10
 
11
+
11
12
  const server = new Server(
12
- { name: '@pipeworx/mcp-edgar', version: '0.3.8' },
13
+ { name: '@pipeworx/mcp-edgar', version: '0.3.9' },
13
14
  { capabilities: { tools: {} } },
14
15
  );
15
16
 
@@ -24,7 +25,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
24
25
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
25
26
  const { name, arguments: args } = request.params;
26
27
  try {
27
- const result = await pack.callTool(name, (args ?? {}) as Record<string, unknown>);
28
+ const callArgs = { ...((args ?? {}) as Record<string, unknown>) };
29
+
30
+ const result = await pack.callTool(name, callArgs);
28
31
  return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
29
32
  } catch (err) {
30
33
  return {