@cyanheads/secedgar-mcp-server 0.13.1 → 0.15.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.
Files changed (74) hide show
  1. package/README.md +63 -7
  2. package/dist/index.js +9 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/mcp-server/prompts/definitions/company-analysis.prompt.d.ts.map +1 -1
  5. package/dist/mcp-server/prompts/definitions/company-analysis.prompt.js +11 -7
  6. package/dist/mcp-server/prompts/definitions/company-analysis.prompt.js.map +1 -1
  7. package/dist/mcp-server/resources/definitions/filing-types.resource.d.ts.map +1 -1
  8. package/dist/mcp-server/resources/definitions/filing-types.resource.js +24 -2
  9. package/dist/mcp-server/resources/definitions/filing-types.resource.js.map +1 -1
  10. package/dist/mcp-server/tools/definitions/compare-companies.tool.d.ts.map +1 -1
  11. package/dist/mcp-server/tools/definitions/compare-companies.tool.js +25 -11
  12. package/dist/mcp-server/tools/definitions/compare-companies.tool.js.map +1 -1
  13. package/dist/mcp-server/tools/definitions/find-holders.tool.d.ts +61 -0
  14. package/dist/mcp-server/tools/definitions/find-holders.tool.d.ts.map +1 -0
  15. package/dist/mcp-server/tools/definitions/find-holders.tool.js +396 -0
  16. package/dist/mcp-server/tools/definitions/find-holders.tool.js.map +1 -0
  17. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.d.ts +93 -0
  18. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.d.ts.map +1 -0
  19. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.js +457 -0
  20. package/dist/mcp-server/tools/definitions/get-beneficial-owners.tool.js.map +1 -0
  21. package/dist/mcp-server/tools/definitions/get-financials.tool.d.ts.map +1 -1
  22. package/dist/mcp-server/tools/definitions/get-financials.tool.js +34 -17
  23. package/dist/mcp-server/tools/definitions/get-financials.tool.js.map +1 -1
  24. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.d.ts +88 -0
  25. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.d.ts.map +1 -0
  26. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.js +674 -0
  27. package/dist/mcp-server/tools/definitions/get-fund-holdings.tool.js.map +1 -0
  28. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.d.ts +3 -2
  29. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.d.ts.map +1 -1
  30. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.js +11 -6
  31. package/dist/mcp-server/tools/definitions/get-institutional-holdings.tool.js.map +1 -1
  32. package/dist/mcp-server/tools/definitions/get-material-events.tool.d.ts +63 -0
  33. package/dist/mcp-server/tools/definitions/get-material-events.tool.d.ts.map +1 -0
  34. package/dist/mcp-server/tools/definitions/get-material-events.tool.js +380 -0
  35. package/dist/mcp-server/tools/definitions/get-material-events.tool.js.map +1 -0
  36. package/dist/mcp-server/tools/definitions/get-snapshot.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/definitions/get-snapshot.tool.js +30 -8
  38. package/dist/mcp-server/tools/definitions/get-snapshot.tool.js.map +1 -1
  39. package/dist/mcp-server/tools/definitions/search-filings.tool.d.ts +10 -13
  40. package/dist/mcp-server/tools/definitions/search-filings.tool.d.ts.map +1 -1
  41. package/dist/mcp-server/tools/definitions/search-filings.tool.js +295 -133
  42. package/dist/mcp-server/tools/definitions/search-filings.tool.js.map +1 -1
  43. package/dist/services/edgar/beneficial-ownership-parser.d.ts +70 -0
  44. package/dist/services/edgar/beneficial-ownership-parser.d.ts.map +1 -0
  45. package/dist/services/edgar/beneficial-ownership-parser.js +157 -0
  46. package/dist/services/edgar/beneficial-ownership-parser.js.map +1 -0
  47. package/dist/services/edgar/concept-map.d.ts +3 -1
  48. package/dist/services/edgar/concept-map.d.ts.map +1 -1
  49. package/dist/services/edgar/concept-map.js +45 -5
  50. package/dist/services/edgar/concept-map.js.map +1 -1
  51. package/dist/services/edgar/concept-series.d.ts +94 -22
  52. package/dist/services/edgar/concept-series.d.ts.map +1 -1
  53. package/dist/services/edgar/concept-series.js +171 -33
  54. package/dist/services/edgar/concept-series.js.map +1 -1
  55. package/dist/services/edgar/edgar-api-service.d.ts +85 -0
  56. package/dist/services/edgar/edgar-api-service.d.ts.map +1 -1
  57. package/dist/services/edgar/edgar-api-service.js +153 -1
  58. package/dist/services/edgar/edgar-api-service.js.map +1 -1
  59. package/dist/services/edgar/eight-k-items.d.ts +62 -0
  60. package/dist/services/edgar/eight-k-items.d.ts.map +1 -0
  61. package/dist/services/edgar/eight-k-items.js +131 -0
  62. package/dist/services/edgar/eight-k-items.js.map +1 -0
  63. package/dist/services/edgar/nport-parser.d.ts +68 -0
  64. package/dist/services/edgar/nport-parser.d.ts.map +1 -0
  65. package/dist/services/edgar/nport-parser.js +102 -0
  66. package/dist/services/edgar/nport-parser.js.map +1 -0
  67. package/dist/services/edgar/types.d.ts +39 -0
  68. package/dist/services/edgar/types.d.ts.map +1 -1
  69. package/dist/services/edgar/xml-nodes.d.ts +31 -0
  70. package/dist/services/edgar/xml-nodes.d.ts.map +1 -0
  71. package/dist/services/edgar/xml-nodes.js +74 -0
  72. package/dist/services/edgar/xml-nodes.js.map +1 -0
  73. package/package.json +1 -1
  74. package/server.json +3 -3
@@ -0,0 +1,457 @@
1
+ /**
2
+ * @fileoverview Blockholder ownership for one issuer, parsed from the structured
3
+ * SCHEDULE 13D / SCHEDULE 13G XML that SEC has required since 2024-12-18. Completes the
4
+ * ownership triangle alongside secedgar_get_insider_transactions (Form 4 officers and
5
+ * directors) and secedgar_get_institutional_holdings (13F portfolios): this tool covers the
6
+ * 5%-and-over stakes, activist and passive. Discovery runs off the issuer's own submissions
7
+ * feed — SEC cross-lists a 13D/G under every CIK party to it, so the issuer's history
8
+ * already carries the filings made about it and the archive document is reachable on the
9
+ * issuer's own path.
10
+ * @module mcp-server/tools/definitions/get-beneficial-owners
11
+ */
12
+ import { tool, z } from '@cyanheads/mcp-ts-core';
13
+ import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
14
+ import { getCanvasBridge, toDatasetField } from '../../../services/canvas-bridge/canvas-bridge.js';
15
+ import { formNameToSchedule, parseBeneficialOwnershipXml, } from '../../../services/edgar/beneficial-ownership-parser.js';
16
+ import { getEdgarApiService, rawDocumentName } from '../../../services/edgar/edgar-api-service.js';
17
+ /**
18
+ * First filing date on which every beneficial-ownership filing arrived in the structured
19
+ * format. The legacy `SC 13D` / `SC 13G` names stop dead here: EDGAR full-text search counts
20
+ * 37 legacy filings on 2024-12-17 and zero on 2024-12-18, against 44 structured ones the
21
+ * same day. Filings before it are text or HTML documents with no XML to parse.
22
+ */
23
+ const STRUCTURED_FROM = '2024-12-18';
24
+ /** Legacy form names that carry the same disclosure with no parseable XML. */
25
+ const LEGACY_FORMS = ['SC 13D', 'SC 13D/A', 'SC 13G', 'SC 13G/A'];
26
+ /**
27
+ * Character ceiling on the Item 4 purpose-of-transaction prose. It is free text and runs to
28
+ * several pages on a control filing, which would put one filing's narrative ahead of every
29
+ * other filing's ownership data in the caller's context. The full text stays one
30
+ * secedgar_get_filing call away on the accession number each row carries.
31
+ */
32
+ const PURPOSE_MAX_CHARS = 1200;
33
+ /** Character ceiling on a reporting person's cover-page footnote — same reasoning, per person. */
34
+ const NOTES_MAX_CHARS = 240;
35
+ /** Trim free text to a ceiling, reporting whether anything was cut. */
36
+ function clip(text, max) {
37
+ if (text === undefined)
38
+ return { text: undefined, cut: false };
39
+ const collapsed = text.replace(/\s+/g, ' ').trim();
40
+ if (collapsed.length <= max)
41
+ return { text: collapsed, cut: false };
42
+ return { text: `${collapsed.slice(0, max)}…`, cut: true };
43
+ }
44
+ export const getBeneficialOwnersTool = tool('secedgar_get_beneficial_owners', {
45
+ title: 'Get Beneficial Owners',
46
+ description: "List the 5%-and-over beneficial owners of a public company, parsed from the structured SCHEDULE 13D and SCHEDULE 13G filings made about it. The input is the ISSUER — the company being held — which is the opposite direction from secedgar_get_institutional_holdings, where the input is the manager. 13D is the activist form and carries the filer's stated purpose of the transaction; 13G is the passive form and has no purpose field at all, which is the substantive difference between a stake that intends to influence control and one that does not. Every filing is returned with each reporting person listed separately, because voting power, dispositive power, and percent of class are reported per person even on a joint filing where several funds and their controlling principal report overlapping shares — summing those percentages double-counts the same position. Coverage starts at 2024-12-18, when SEC replaced the legacy SC 13D / SC 13G text filings with this XML format; earlier stakes are readable but not parseable, and the response reports how many of them the issuer has. The full parsed set is materialized as df_<id> when a canvas is available, one row per reporting person, so it joins against the insider and 13F dataframes on issuer CIK.",
47
+ annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
48
+ errors: [
49
+ {
50
+ reason: 'issuer_not_found',
51
+ code: JsonRpcErrorCode.NotFound,
52
+ when: 'The issuer input does not resolve to a known EDGAR company',
53
+ recovery: 'Use secedgar_company_search to find the issuer ticker or 10-digit CIK.',
54
+ },
55
+ {
56
+ reason: 'ambiguous_issuer',
57
+ code: JsonRpcErrorCode.ValidationError,
58
+ when: 'The issuer name matches several EDGAR companies',
59
+ recovery: 'Retry with the ticker or the 10-digit CIK of the intended issuer from the matches list.',
60
+ },
61
+ {
62
+ reason: 'no_filings_found',
63
+ code: JsonRpcErrorCode.NotFound,
64
+ when: 'The issuer has no structured SCHEDULE 13D/13G filings matching the requested form kind',
65
+ recovery: 'Use secedgar_search_filings with forms ["SC 13D","SC 13G"] to reach the pre-2024-12-18 text filings, then read them with secedgar_get_filing.',
66
+ },
67
+ ],
68
+ input: z.object({
69
+ issuer: z
70
+ .string()
71
+ .trim()
72
+ .min(1, 'Issuer cannot be blank')
73
+ .describe('The company whose blockholders you want — a ticker ("AAPL"), a 10-digit CIK ("0000320193"), or a company name. This is the subject company of the schedule, not the investor filing it; passing an investment manager here returns the schedules filed about that manager, which is almost always empty.'),
74
+ form_kind: z
75
+ .enum(['all', '13D', '13G'])
76
+ .default('all')
77
+ .describe('Which schedule to return. "13D" is the activist form, filed by a holder that may seek to influence control and carrying a stated purpose of transaction. "13G" is the passive form, available to institutions and holders under 20% that certify no control intent. "all" (default) returns both, newest first.'),
78
+ include_amendments: z
79
+ .boolean()
80
+ .default(true)
81
+ .describe('Whether to include amendments (SCHEDULE 13D/A, SCHEDULE 13G/A). Amendments carry the current position and are how an ongoing stake is tracked, so they are included by default. Set false to see only filings that opened a new position.'),
82
+ limit: z
83
+ .number()
84
+ .int()
85
+ .min(1)
86
+ .max(20)
87
+ .default(10)
88
+ .describe('Number of filings to fetch and parse, newest first. Each filing is a separate document fetch, so this is the cost of the call as well as its depth. Default 10; a widely-held company can have dozens of blockholder filings a year.'),
89
+ }),
90
+ output: z.object({
91
+ issuer: z.string().describe('The issuer input, echoed.'),
92
+ issuer_cik: z.string().describe('CIK of the resolved issuer, zero-padded to 10 digits.'),
93
+ issuer_name: z.string().describe('EDGAR-conformed name of the resolved issuer.'),
94
+ form_kind: z
95
+ .enum(['all', '13D', '13G'])
96
+ .describe('The schedule filter applied — the requested value, or the default "all".'),
97
+ total_structured_filings: z
98
+ .number()
99
+ .describe("Structured SCHEDULE 13D/13G filings matching the form filter in the issuer's recent submissions window, before the limit. The population the returned filings are the newest slice of."),
100
+ filings_parsed: z
101
+ .number()
102
+ .describe('Filings actually fetched and parsed — total_structured_filings capped by limit.'),
103
+ structured_coverage_from: z
104
+ .string()
105
+ .describe('First filing date on which SEC required this XML format (YYYY-MM-DD). Blockholder filings before it exist but are not parseable into this schema.'),
106
+ legacy_filings_before_coverage: z
107
+ .number()
108
+ .describe("Legacy SC 13D / SC 13G filings in the issuer's recent submissions window — pre-2024-12-18 stakes this tool cannot parse. Reach them with secedgar_search_filings and read them with secedgar_get_filing. A floor, not a lifetime count: the submissions window holds roughly the last thousand filings of every type."),
109
+ filings: z
110
+ .array(z
111
+ .object({
112
+ form: z.string().describe('EDGAR form name, e.g. "SCHEDULE 13D" or "SCHEDULE 13G/A".'),
113
+ schedule: z
114
+ .enum(['13D', '13G'])
115
+ .describe('Which schedule this is — 13D activist, 13G passive.'),
116
+ is_amendment: z.boolean().describe('True when the form name carries the /A suffix.'),
117
+ amendment_number: z
118
+ .string()
119
+ .optional()
120
+ .describe('Amendment sequence from the cover page. Absent on an original filing.'),
121
+ accession_number: z
122
+ .string()
123
+ .describe('Accession number — pass to secedgar_get_filing for the full document.'),
124
+ filing_date: z.string().describe('Date the schedule was submitted (YYYY-MM-DD).'),
125
+ event_date: z
126
+ .string()
127
+ .optional()
128
+ .describe('Date of the event that required the filing (YYYY-MM-DD) — when the position actually crossed or changed, which precedes filing_date. Absent when the cover page omits it.'),
129
+ security_class: z
130
+ .string()
131
+ .optional()
132
+ .describe('Title of the class of securities the schedule covers. A multi-class issuer has a separate schedule per class, so percentages are of this class only.'),
133
+ cusips: z
134
+ .array(z.string())
135
+ .describe('CUSIPs of the subject class from the cover page. Empty when none listed.'),
136
+ purpose_of_transaction: z
137
+ .string()
138
+ .optional()
139
+ .describe('Item 4 purpose-of-transaction prose — what the holder says it intends. Present on 13D filings only; 13G has no such field, which is what makes it the passive form. Absent on an amendment that restates no purpose.'),
140
+ purpose_truncated: z
141
+ .boolean()
142
+ .describe('True when purpose_of_transaction was clipped to fit — read the full item with secedgar_get_filing on this accession number.'),
143
+ reporting_persons: z
144
+ .array(z
145
+ .object({
146
+ name: z.string().describe('Reporting person as named on the cover page.'),
147
+ cik: z
148
+ .string()
149
+ .optional()
150
+ .describe('Reporting person CIK, when the schedule carries one. SCHEDULE 13G never does — its cover page has no CIK field — so this is populated on 13D filings only.'),
151
+ citizenship: z
152
+ .string()
153
+ .optional()
154
+ .describe('SEC citizenship or place-of-organization code — a US state ("DE"), or an SEC country code ("X1" United States, "E9" Cayman Islands).'),
155
+ percent_of_class: z
156
+ .number()
157
+ .optional()
158
+ .describe('Percent of the class this person beneficially owns (0-100), as this person reports it. Per person, not per filing: joint filers report overlapping shares, so these do not sum to a group total.'),
159
+ aggregate_amount_owned: z
160
+ .number()
161
+ .optional()
162
+ .describe('Shares beneficially owned by this person. Absent when the person reports no amount, which happens on an exit amendment reporting a zero position.'),
163
+ sole_voting_power: z
164
+ .number()
165
+ .optional()
166
+ .describe('Shares this person alone may vote.'),
167
+ shared_voting_power: z
168
+ .number()
169
+ .optional()
170
+ .describe('Shares this person votes jointly with another party.'),
171
+ sole_dispositive_power: z
172
+ .number()
173
+ .optional()
174
+ .describe('Shares this person alone may sell or transfer.'),
175
+ shared_dispositive_power: z
176
+ .number()
177
+ .optional()
178
+ .describe('Shares this person may sell or transfer jointly with another party.'),
179
+ person_types: z
180
+ .array(z.string())
181
+ .describe('SEC type-of-reporting-person codes — IN individual, CO corporation, PN partnership, IA investment adviser, HC holding company, OO other. One person can carry several.'),
182
+ excludes_certain_shares: z
183
+ .boolean()
184
+ .optional()
185
+ .describe('True when the reported aggregate deliberately excludes shares this person disclaims beneficial ownership of. Absent when the filing does not answer.'),
186
+ notes: z
187
+ .string()
188
+ .optional()
189
+ .describe("The filer's own cover-page footnote, usually the share count the percentage was computed against. Clipped when long — the full text is in the filing."),
190
+ })
191
+ .describe('One reporting person from the schedule cover page.'))
192
+ .describe('Every reporting person on this filing. A joint filing lists a fund, its adviser, and its controlling principal separately, each reporting the same underlying shares.'),
193
+ })
194
+ .describe('One SCHEDULE 13D or 13G filing made about this issuer.'))
195
+ .describe('Blockholder filings, newest first, capped at limit.'),
196
+ dataset: z
197
+ .object({
198
+ name: z
199
+ .string()
200
+ .describe('Dataframe handle (df_XXXXX_XXXXX) — pass to secedgar_dataframe_query.'),
201
+ row_count: z.number().describe('Rows materialized in the dataframe.'),
202
+ expires_at: z.string().describe('ISO 8601 expiry timestamp.'),
203
+ truncated: z
204
+ .boolean()
205
+ .describe('True when the issuer has more structured filings than limit fetched — the dataframe holds the parsed filings only, not the whole history.'),
206
+ })
207
+ .optional()
208
+ .describe("Canvas dataframe holding one row per reporting person across every parsed filing, each row carrying the issuer, form, accession, and dates alongside the person's powers. Joins against the insider and 13F dataframes on issuer_cik. Absent when canvas is unavailable or nothing parsed."),
209
+ }),
210
+ enrichment: {
211
+ notice: z
212
+ .string()
213
+ .optional()
214
+ .describe('Guidance when no filings matched — names the coverage boundary and the fallback.'),
215
+ truncated: z.boolean().optional().describe('True when filings were capped by limit.'),
216
+ shown: z.number().optional().describe('Number of filings returned.'),
217
+ cap: z.number().optional().describe('The limit cap applied.'),
218
+ },
219
+ async handler(input, ctx) {
220
+ const api = getEdgarApiService();
221
+ const resolved = await api.resolveCik(input.issuer);
222
+ const candidates = Array.isArray(resolved) ? resolved : [resolved];
223
+ if (candidates.length > 1) {
224
+ const shown = candidates.slice(0, 10);
225
+ const list = shown.map((c) => `${c.ticker ?? c.cik} (${c.name ?? 'Unknown'})`).join(', ');
226
+ throw ctx.fail('ambiguous_issuer', `'${input.issuer}' matches multiple EDGAR companies: ${list}.`, {
227
+ ...ctx.recoveryFor('ambiguous_issuer'),
228
+ matches: shown.map((c) => ({ cik: c.cik, name: c.name, ticker: c.ticker })),
229
+ });
230
+ }
231
+ const match = candidates[0];
232
+ if (!match) {
233
+ throw ctx.fail('issuer_not_found', `Issuer '${input.issuer}' not found in EDGAR.`, {
234
+ ...ctx.recoveryFor('issuer_not_found'),
235
+ });
236
+ }
237
+ const submissions = await api.getSubmissions(match.cik);
238
+ const recent = submissions.filings.recent;
239
+ // SEC indexes a 13D/G under every CIK party to it, so the issuer's own feed already
240
+ // carries the schedules filed about it — no entity-scoped search needed.
241
+ const selected = [];
242
+ let totalMatching = 0;
243
+ let legacyCount = 0;
244
+ for (let i = 0; i < recent.form.length; i++) {
245
+ const form = recent.form[i] ?? '';
246
+ if (LEGACY_FORMS.includes(form)) {
247
+ legacyCount++;
248
+ continue;
249
+ }
250
+ const schedule = formNameToSchedule(form);
251
+ if (!schedule)
252
+ continue;
253
+ if (input.form_kind !== 'all' && schedule !== input.form_kind)
254
+ continue;
255
+ if (!input.include_amendments && form.endsWith('/A'))
256
+ continue;
257
+ totalMatching++;
258
+ if (selected.length < input.limit) {
259
+ selected.push({
260
+ form,
261
+ schedule,
262
+ accessionNumber: recent.accessionNumber[i] ?? '',
263
+ filingDate: recent.filingDate[i] ?? '',
264
+ document: rawDocumentName(recent.primaryDocument[i]),
265
+ });
266
+ }
267
+ }
268
+ if (totalMatching === 0) {
269
+ const kindLabel = input.form_kind === 'all' ? 'SCHEDULE 13D/13G' : `SCHEDULE ${input.form_kind}`;
270
+ const legacyHint = legacyCount > 0
271
+ ? `Its submissions window does hold ${legacyCount} legacy SC 13D/SC 13G filings from before ${STRUCTURED_FROM}; those are text filings with no XML to parse — read them with secedgar_get_filing.`
272
+ : `Blockholder filings from before ${STRUCTURED_FROM} use the legacy SC 13D / SC 13G names and are not parseable into this schema — search for them with secedgar_search_filings.`;
273
+ throw ctx.fail('no_filings_found', `No structured ${kindLabel} filings found for ${submissions.name} (CIK ${match.cik}). ${legacyHint}`, {
274
+ ...ctx.recoveryFor('no_filings_found'),
275
+ issuer_cik: match.cik,
276
+ issuer_name: submissions.name,
277
+ legacy_filings_before_coverage: legacyCount,
278
+ });
279
+ }
280
+ // Fetch and parse each selected filing. A document that 404s or does not parse as a
281
+ // schedule is dropped rather than failing the call — one malformed filing must not
282
+ // hide every other blockholder in the list.
283
+ const filings = [];
284
+ for (const ref of selected) {
285
+ const xml = await api.tryGetFilingDocument(match.cik, ref.accessionNumber, ref.document);
286
+ const parsed = xml ? parseBeneficialOwnershipXml(xml) : undefined;
287
+ if (!parsed) {
288
+ ctx.log.debug('Skipping unparseable beneficial-ownership filing', {
289
+ accessionNumber: ref.accessionNumber,
290
+ document: ref.document,
291
+ });
292
+ continue;
293
+ }
294
+ const purpose = clip(parsed.purpose_of_transaction, PURPOSE_MAX_CHARS);
295
+ filings.push({
296
+ form: ref.form,
297
+ schedule: ref.schedule,
298
+ is_amendment: ref.form.endsWith('/A'),
299
+ amendment_number: parsed.amendment_number,
300
+ accession_number: ref.accessionNumber,
301
+ filing_date: ref.filingDate,
302
+ event_date: parsed.event_date,
303
+ security_class: parsed.security_class,
304
+ cusips: parsed.issuer_cusips,
305
+ purpose_of_transaction: purpose.text,
306
+ purpose_truncated: purpose.cut,
307
+ reporting_persons: parsed.owners.map((o) => ({
308
+ name: o.name,
309
+ cik: o.cik,
310
+ citizenship: o.citizenship,
311
+ percent_of_class: o.percent_of_class,
312
+ aggregate_amount_owned: o.aggregate_amount_owned,
313
+ sole_voting_power: o.sole_voting_power,
314
+ shared_voting_power: o.shared_voting_power,
315
+ sole_dispositive_power: o.sole_dispositive_power,
316
+ shared_dispositive_power: o.shared_dispositive_power,
317
+ person_types: o.person_types,
318
+ excludes_certain_shares: o.excludes_certain_shares,
319
+ notes: clip(o.notes, NOTES_MAX_CHARS).text,
320
+ })),
321
+ });
322
+ }
323
+ const truncated = totalMatching > filings.length;
324
+ // One row per reporting person — the grain the ownership question is asked at, and the
325
+ // grain that joins to the insider and 13F dataframes on issuer_cik.
326
+ let dataset;
327
+ const bridge = getCanvasBridge();
328
+ const rows = filings.flatMap((f) => f.reporting_persons.map((p) => ({
329
+ issuer_cik: match.cik,
330
+ issuer_name: submissions.name,
331
+ form: f.form,
332
+ schedule: f.schedule,
333
+ is_amendment: f.is_amendment,
334
+ accession_number: f.accession_number,
335
+ filing_date: f.filing_date,
336
+ event_date: f.event_date ?? null,
337
+ security_class: f.security_class ?? null,
338
+ cusip: f.cusips[0] ?? null,
339
+ reporting_person: p.name,
340
+ reporting_person_cik: p.cik ?? null,
341
+ citizenship: p.citizenship ?? null,
342
+ percent_of_class: p.percent_of_class ?? null,
343
+ aggregate_amount_owned: p.aggregate_amount_owned ?? null,
344
+ sole_voting_power: p.sole_voting_power ?? null,
345
+ shared_voting_power: p.shared_voting_power ?? null,
346
+ sole_dispositive_power: p.sole_dispositive_power ?? null,
347
+ shared_dispositive_power: p.shared_dispositive_power ?? null,
348
+ person_types: p.person_types.join(',') || null,
349
+ has_stated_purpose: f.purpose_of_transaction !== undefined,
350
+ })));
351
+ if (bridge && rows.length > 0) {
352
+ const registered = await bridge.registerDataframe(ctx, {
353
+ rows,
354
+ sourceTool: 'secedgar_get_beneficial_owners',
355
+ queryParams: {
356
+ issuer: input.issuer,
357
+ issuer_cik: match.cik,
358
+ form_kind: input.form_kind,
359
+ include_amendments: input.include_amendments,
360
+ limit: input.limit,
361
+ },
362
+ truncated,
363
+ });
364
+ if (registered)
365
+ dataset = { ...toDatasetField(registered), truncated };
366
+ }
367
+ if (filings.length === 0) {
368
+ ctx.enrich.notice(`${totalMatching} structured filings are indexed for ${submissions.name}, but none of the ${selected.length} fetched parsed as a SCHEDULE 13D or 13G document. Read one directly with secedgar_get_filing on an accession number from secedgar_search_filings.`);
369
+ }
370
+ else if (truncated) {
371
+ ctx.enrich.truncated({
372
+ shown: filings.length,
373
+ cap: input.limit,
374
+ guidance: `${totalMatching} structured blockholder filings are indexed for this issuer. Raise limit to reach further back, or narrow with form_kind or include_amendments=false.`,
375
+ });
376
+ }
377
+ ctx.log.info('Beneficial owners retrieved', {
378
+ cik: match.cik,
379
+ formKind: input.form_kind,
380
+ totalMatching,
381
+ parsed: filings.length,
382
+ owners: rows.length,
383
+ datasetName: dataset?.name,
384
+ });
385
+ return {
386
+ issuer: input.issuer,
387
+ issuer_cik: match.cik,
388
+ issuer_name: submissions.name,
389
+ form_kind: input.form_kind,
390
+ total_structured_filings: totalMatching,
391
+ filings_parsed: filings.length,
392
+ structured_coverage_from: STRUCTURED_FROM,
393
+ legacy_filings_before_coverage: legacyCount,
394
+ filings,
395
+ dataset,
396
+ };
397
+ },
398
+ format: (result) => {
399
+ const lines = [
400
+ `**5%+ beneficial owners of ${result.issuer_name}** (CIK ${result.issuer_cik}) — input "${result.issuer}"`,
401
+ `Schedule filter: ${result.form_kind} | ${result.filings_parsed} of ${result.total_structured_filings} structured filings parsed`,
402
+ `Structured coverage starts ${result.structured_coverage_from}; ${result.legacy_filings_before_coverage} legacy SC 13D/SC 13G filings sit before it in the submissions window (read those with secedgar_get_filing).`,
403
+ ];
404
+ for (const f of result.filings) {
405
+ lines.push('');
406
+ const amendment = f.is_amendment
407
+ ? ` (amendment${f.amendment_number ? ` no. ${f.amendment_number}` : ''})`
408
+ : '';
409
+ lines.push(`## ${f.form}${amendment} — filed ${f.filing_date} [${f.accession_number}]`);
410
+ const bits = [`Schedule ${f.schedule}`];
411
+ if (f.event_date)
412
+ bits.push(`event ${f.event_date}`);
413
+ if (f.security_class)
414
+ bits.push(f.security_class);
415
+ if (f.cusips.length > 0)
416
+ bits.push(`CUSIP ${f.cusips.join(', ')}`);
417
+ lines.push(bits.join(' | '));
418
+ for (const p of f.reporting_persons) {
419
+ const pct = p.percent_of_class !== undefined ? `${p.percent_of_class}% of class` : 'percent N/A';
420
+ const owned = p.aggregate_amount_owned !== undefined
421
+ ? `${p.aggregate_amount_owned.toLocaleString()} shares`
422
+ : 'amount N/A';
423
+ const idBits = [];
424
+ if (p.cik)
425
+ idBits.push(`CIK ${p.cik}`);
426
+ if (p.citizenship)
427
+ idBits.push(p.citizenship);
428
+ if (p.person_types.length > 0)
429
+ idBits.push(p.person_types.join('/'));
430
+ lines.push(`- **${p.name}**${idBits.length > 0 ? ` (${idBits.join(', ')})` : ''}`);
431
+ lines.push(` ${owned} | ${pct}`);
432
+ lines.push(` voting sole/shared: ${p.sole_voting_power ?? 'N/A'} / ${p.shared_voting_power ?? 'N/A'} | dispositive sole/shared: ${p.sole_dispositive_power ?? 'N/A'} / ${p.shared_dispositive_power ?? 'N/A'}`);
433
+ if (p.excludes_certain_shares !== undefined) {
434
+ lines.push(` Excludes disclaimed shares: ${p.excludes_certain_shares ? 'yes' : 'no'}`);
435
+ }
436
+ if (p.notes)
437
+ lines.push(` Note: ${p.notes}`);
438
+ }
439
+ if (f.purpose_of_transaction) {
440
+ lines.push(`**Purpose of transaction:** ${f.purpose_of_transaction}${f.purpose_truncated ? ' (clipped — read the full Item 4 with secedgar_get_filing)' : ''}`);
441
+ }
442
+ else {
443
+ lines.push(f.schedule === '13G'
444
+ ? '**Purpose of transaction:** not applicable — 13G is the passive form and has no purpose item.'
445
+ : '**Purpose of transaction:** not stated in this filing.');
446
+ }
447
+ }
448
+ if (result.dataset) {
449
+ const note = result.dataset.truncated
450
+ ? ' (truncated — more structured filings exist beyond limit)'
451
+ : '';
452
+ lines.push(`\nDataset: ${result.dataset.name} (${result.dataset.row_count} reporting-person rows, expires ${result.dataset.expires_at})${note} — query with secedgar_dataframe_query.`);
453
+ }
454
+ return [{ type: 'text', text: lines.join('\n') }];
455
+ },
456
+ });
457
+ //# sourceMappingURL=get-beneficial-owners.tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-beneficial-owners.tool.js","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-beneficial-owners.tool.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"get-financials.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-financials.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAajE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAyd5B,CAAC"}
1
+ {"version":3,"file":"get-financials.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/get-financials.tool.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAejE,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkf5B,CAAC"}
@@ -7,7 +7,7 @@ import { tool, z } from '@cyanheads/mcp-ts-core';
7
7
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
8
8
  import { getCanvasBridge, toDatasetField } from '../../../services/canvas-bridge/canvas-bridge.js';
9
9
  import { resolveConceptTarget } from '../../../services/edgar/concept-map.js';
10
- import { deprecatedTagCaveats, matchesPeriodType, resolveFrameSeries, } from '../../../services/edgar/concept-series.js';
10
+ import { matchesPeriodType, preferredTagIndex, resolveFrameSeries, seriesStalenessCaveats, } from '../../../services/edgar/concept-series.js';
11
11
  import { getEdgarApiService } from '../../../services/edgar/edgar-api-service.js';
12
12
  import { missingQuarterCaveats } from '../../../services/edgar/fiscal-periods.js';
13
13
  export const getFinancialsTool = tool('secedgar_get_financials', {
@@ -128,7 +128,7 @@ export const getFinancialsTool = tool('secedgar_get_financials', {
128
128
  caveats: z
129
129
  .array(z.string())
130
130
  .optional()
131
- .describe("Data-completeness warnings about the returned series. Two kinds. On quarterly results, one entry when one or two calendar quarters are absent from every recent qualifying year — SEC reports a filer's fiscal Q4 as the 10-K residual rather than a discrete quarterly fact, so the calendar quarter fiscal Q4 spans has no frame-tagged value, and a filer whose other fiscal quarters span non-calendar durations loses a second quarter the same way. Applies to calendar-year filers (no discrete Q4) as much as to off-calendar ones. On any result, one entry when the concept resolved to an XBRL tag SEC has retired from the taxonomy, which means the current tags reported nothing and the series may stop years short. Absent when the series has nothing to flag."),
131
+ .describe("Data-completeness warnings about the returned series. Two kinds. On quarterly results, one entry when one or two calendar quarters are absent from every recent qualifying year — SEC reports a filer's fiscal Q4 as the 10-K residual rather than a discrete quarterly fact, so the calendar quarter fiscal Q4 spans has no frame-tagged value, and a filer whose other fiscal quarters span non-calendar durations loses a second quarter the same way. Applies to calendar-year filers (no discrete Q4) as much as to off-calendar ones. On any result, one entry when the series stops well short of today — either because the concept resolved to an XBRL tag SEC has retired from the taxonomy (the current tags reported nothing), or because a current tag's series ends more than two years plus a filing window back, which is what a filer migrating to a different element or dropping the disclosure looks like. Absent when the series has nothing to flag."),
132
132
  }),
133
133
  async handler(input, ctx) {
134
134
  const api = getEdgarApiService();
@@ -166,12 +166,13 @@ export const getFinancialsTool = tool('secedgar_get_financials', {
166
166
  // Resolve concept to taxonomy + XBRL tag(s). The mapping's own taxonomy wins
167
167
  // over the `us-gaap` default (e.g. `dei` for shares_outstanding); ifrs-full
168
168
  // uses the confirmed IFRS variants when the mapping has them.
169
- const { label, tags, taxonomy, unit: mappedUnit, } = resolveConceptTarget(input.concept, input.taxonomy);
169
+ const { label, tagSelection, tags, taxonomy, unit: mappedUnit, } = resolveConceptTarget(input.concept, input.taxonomy);
170
170
  // Default to "annual" for unset period_type; post-fetch fallback handles instant concepts (#48).
171
171
  const effectivePeriodType = input.period_type ?? 'annual';
172
172
  // Try each tag until we get data. `tryGetCompanyConcept` returns null for 404
173
173
  // (tag not reported by this company); other errors propagate.
174
- let conceptResponse;
174
+ /** Responding tags keyed by array position, in declared order. */
175
+ const responses = new Map();
175
176
  const tagsTried = [];
176
177
  /**
177
178
  * Each unit is augmented with its source-tag index so the frame dedup can
@@ -183,20 +184,24 @@ export const getFinancialsTool = tool('secedgar_get_financials', {
183
184
  const resp = await api.tryGetCompanyConcept(match.cik, taxonomy, tag);
184
185
  if (!resp)
185
186
  continue;
186
- if (!conceptResponse) {
187
- conceptResponse = {
188
- units: resp.units,
189
- label: resp.label,
190
- description: resp.description ?? undefined,
191
- tag: resp.tag,
192
- };
193
- }
187
+ responses.set(tagIndex, {
188
+ units: resp.units,
189
+ label: resp.label,
190
+ description: resp.description ?? undefined,
191
+ tag: resp.tag,
192
+ });
194
193
  for (const units of Object.values(resp.units)) {
195
194
  for (const u of units) {
196
195
  allUnits.push({ ...u, tagIndex });
197
196
  }
198
197
  }
199
198
  }
199
+ /**
200
+ * The concept is described by the tag that won for this filer, which under a
201
+ * `coverage` selection is not the first one to respond (#101).
202
+ */
203
+ const winningTagIndex = preferredTagIndex(allUnits, tagSelection);
204
+ const conceptResponse = winningTagIndex !== undefined ? responses.get(winningTagIndex) : undefined;
200
205
  if (!conceptResponse || allUnits.length === 0) {
201
206
  // Probe companyfacts to discover what namespaces and tags this filer actually reports.
202
207
  // Only on the error path — one extra request, never on the happy path.
@@ -238,7 +243,7 @@ export const getFinancialsTool = tool('secedgar_get_financials', {
238
243
  * only, same-frame collisions resolved by tag priority then latest `filed`
239
244
  * (#44). An empty map means the concept exists but has no frame-aligned entries.
240
245
  */
241
- const byFrameClean = resolveFrameSeries(allUnits);
246
+ const byFrameClean = resolveFrameSeries(allUnits, tagSelection);
242
247
  if (byFrameClean.size === 0) {
243
248
  throw ctx.fail('no_frame_data', `'${conceptResponse.tag}' exists for this company but has no standard-period data.`, {
244
249
  ...ctx.recoveryFor('no_frame_data'),
@@ -278,6 +283,12 @@ export const getFinancialsTool = tool('secedgar_get_financials', {
278
283
  }
279
284
  // Sort newest first
280
285
  filtered.sort((a, b) => b.end.localeCompare(a.end));
286
+ /**
287
+ * Read from the full deduped set, not the period-filtered slice: an annual
288
+ * view that stops a year behind a still-current quarterly series is a
289
+ * property of the filter, not of the concept.
290
+ */
291
+ const newestFramed = [...byFrameClean.values()].reduce((newest, unit) => (!newest || unit.end > newest.end ? unit : newest), undefined);
281
292
  /**
282
293
  * Off-calendar filers lose a whole calendar quarter from the frame-tagged
283
294
  * series — SEC reports fiscal Q4 as the 10-K residual, never as a discrete
@@ -290,11 +301,17 @@ export const getFinancialsTool = tool('secedgar_get_financials', {
290
301
  const caveats = [
291
302
  ...(resolvedPeriodType === 'annual' ? [] : missingQuarterCaveats(byFrameClean.keys())),
292
303
  /**
293
- * The tag that won the priority walk can be one SEC retired years ago,
294
- * which happens exactly when no current tag reports for this filer. The
295
- * values look ordinary; the taxonomy label is the only tell (#98).
304
+ * The tag that won the walk can be one SEC retired years ago, which
305
+ * happens exactly when no current tag reports for this filer; the values
306
+ * look ordinary and the taxonomy label is the only tell (#98). A current
307
+ * tag whose series simply stops carries no tell at all, so the gap to
308
+ * today is the signal — one companyconcept payload is all this tool reads,
309
+ * and it holds no filer-wide period to compare against (#102).
296
310
  */
297
- ...deprecatedTagCaveats(conceptResponse.tag, conceptResponse.label),
311
+ ...seriesStalenessCaveats(conceptResponse.tag, conceptResponse.label, newestFramed, {
312
+ date: new Date().toISOString().slice(0, 10),
313
+ kind: 'current-date',
314
+ }),
298
315
  ];
299
316
  // Determine unit string
300
317
  const unitKey = Object.keys(conceptResponse.units)[0] ?? mappedUnit ?? 'USD';