@etsquare/mcp-server-sec 0.5.2 → 0.6.1

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.
@@ -53,6 +53,14 @@ interface KpiExtractionsApiInput {
53
53
  fiscal_year?: number;
54
54
  limit?: number;
55
55
  }
56
+ /** Company research packet request. */
57
+ interface CompanyResearchApiInput {
58
+ ticker: string;
59
+ sections?: Array<'snapshot' | 'financials' | 'earnings' | 'narrative' | 'ownership' | 'kpis'>;
60
+ detail_level?: 'summary' | 'standard' | 'full';
61
+ peers?: string[];
62
+ n_periods?: number;
63
+ }
56
64
  /** Server-side KPI query with metric filtering. */
57
65
  interface QueryKpisApiInput {
58
66
  select?: string[];
@@ -96,6 +104,7 @@ export declare class ETSquareClient {
96
104
  getInsiderTransactions(input: InsiderTransactionsApiInput): Promise<Record<string, unknown>>;
97
105
  getEarningsActuals(input: EarningsActualsApiInput): Promise<Record<string, unknown>>;
98
106
  getKpiExtractions(input: KpiExtractionsApiInput): Promise<Record<string, unknown>>;
107
+ getCompanyResearch(input: CompanyResearchApiInput): Promise<Record<string, unknown>>;
99
108
  queryKpis(input: QueryKpisApiInput): Promise<Record<string, unknown>>;
100
109
  discoverMetrics(input: DiscoverMetricsApiInput): Promise<Record<string, unknown>>;
101
110
  getChunk(input: GetChunkInput): Promise<Record<string, unknown>>;
@@ -158,6 +158,24 @@ export class ETSquareClient {
158
158
  body: JSON.stringify(body),
159
159
  });
160
160
  }
161
+ async getCompanyResearch(input) {
162
+ const body = {
163
+ ticker: input.ticker,
164
+ };
165
+ if (input.sections && input.sections.length > 0)
166
+ body.sections = input.sections;
167
+ if (input.detail_level)
168
+ body.detail_level = input.detail_level;
169
+ if (input.peers && input.peers.length > 0)
170
+ body.peers = input.peers;
171
+ if (input.n_periods !== undefined)
172
+ body.n_periods = input.n_periods;
173
+ return this.request('/api/v1/company/research', {
174
+ method: 'POST',
175
+ headers: this.headers,
176
+ body: JSON.stringify(body),
177
+ });
178
+ }
161
179
  async queryKpis(input) {
162
180
  const body = {
163
181
  limit: input.limit || 50,
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @etsquare/mcp-server-sec v0.3.0
3
+ * @etsquare/mcp-server-sec v0.6.0
4
4
  * MCP server for SEC Intelligence: search SEC filings,
5
5
  * resolve company tickers, execute financial metrics templates,
6
6
  * compare companies, and access weekly briefs.
@@ -57,7 +57,7 @@ if (!apiKey) {
57
57
  process.exit(1);
58
58
  }
59
59
  const client = new ETSquareClient({ baseUrl, apiKey });
60
- log('info', `ETSquare MCP Server v0.3.0 starting with backend: ${baseUrl}`);
60
+ log('info', `ETSquare MCP Server v0.6.0 starting with backend: ${baseUrl}`);
61
61
  // ─── Item Code Labels ───────────────────────────────────────────────────────
62
62
  const ITEM_CODE_LABELS = {
63
63
  // 10-K
@@ -358,7 +358,7 @@ function buildMarkdownTable(colNames, rows, maxRows = 20) {
358
358
  // ─── MCP Server ─────────────────────────────────────────────────────────────
359
359
  const server = new McpServer({
360
360
  name: 'etsquare-mcp-sec',
361
- version: '0.3.0',
361
+ version: '0.6.0',
362
362
  });
363
363
  // ─── Tool 1: Company Lookup ─────────────────────────────────────────────────
364
364
  server.registerTool('etsquare_lookup_company', {
@@ -406,7 +406,7 @@ server.registerTool('etsquare_lookup_company', {
406
406
  // ─── Tool 2: SEC Filing Search ──────────────────────────────────────────────
407
407
  server.registerTool('etsquare_search', {
408
408
  title: 'Search SEC Filings',
409
- description: 'Search 3.4M+ SEC filing sections (10-K, 10-Q, 8-K) with hybrid retrieval. ' +
409
+ description: 'Search 4.0M+ SEC filing sections (10-K, 10-Q, 8-K) with hybrid retrieval. ' +
410
410
  'Returns verbatim filing text with citations — best for qualitative research.\n\n' +
411
411
  'Execution contract (required):\n' +
412
412
  '- mode_lock: NARRATIVE (recommended) | HYBRID\n' +
@@ -678,6 +678,13 @@ server.registerTool('etsquare_financial_statements', {
678
678
  table += `| **${labelDisplay(dk)}** | ${values.join(' | ')} |\n`;
679
679
  }
680
680
  }
681
+ // Filing source URLs per period
682
+ const periodUrls = periods
683
+ .filter((p) => p.sec_url)
684
+ .map((p) => `- FY${p.fiscal_year}${p.fiscal_period !== 'FY' ? ' ' + p.fiscal_period : ''}: ${p.sec_url}`);
685
+ if (periodUrls.length > 0) {
686
+ table += `\n**Filing Sources:**\n${periodUrls.join('\n')}\n`;
687
+ }
681
688
  // Provenance note
682
689
  const firstPeriod = periods[0];
683
690
  const sampleLine = firstPeriod?.lines?.[labels[0]];
@@ -752,14 +759,15 @@ server.registerTool('etsquare_insider_trades', {
752
759
  text += `\n\n`;
753
760
  // Transaction table — show top 20
754
761
  const shown = transactions.slice(0, 20);
755
- text += `| Date | Insider | Title | Type | Shares | Price | Value |\n`;
756
- text += `|------|---------|-------|------|-------:|------:|------:|\n`;
762
+ text += `| Date | Insider | Title | Type | Shares | Price | Value | Filing |\n`;
763
+ text += `|------|---------|-------|------|-------:|------:|------:|:------:|\n`;
757
764
  for (const t of shown) {
758
765
  const shares = t.shares != null ? t.shares.toLocaleString() : '—';
759
766
  const price = t.price_per_share != null ? `$${t.price_per_share.toFixed(2)}` : '—';
760
767
  const value = t.total_value != null ? fmtVal(t.total_value) : '—';
761
768
  const typeLabel = t.is_derivative ? `${t.transaction_type} *` : t.transaction_type;
762
- text += `| ${t.transaction_date} | ${t.owner_name} | ${t.officer_title || '—'} | ${typeLabel} | ${shares} | ${price} | ${value} |\n`;
769
+ const filing = t.sec_url ? `[SEC](${t.sec_url})` : '—';
770
+ text += `| ${t.transaction_date} | ${t.owner_name} | ${t.officer_title || '—'} | ${typeLabel} | ${shares} | ${price} | ${value} | ${filing} |\n`;
763
771
  }
764
772
  if (transactions.length > 20) {
765
773
  text += `\n*Showing 20 of ${transactions.length} transactions.*\n`;
@@ -845,6 +853,19 @@ server.registerTool('etsquare_institutional_holdings', {
845
853
  if (holders.length > 20) {
846
854
  text += `\n*Showing 20 of ${holders.length} tracked holders.*\n`;
847
855
  }
856
+ // Filing source URLs for this quarter (deduplicated by manager)
857
+ const seenMgrs = new Set();
858
+ const quarterLinks = [];
859
+ for (const h of holders) {
860
+ const key = h.manager_cik || h.manager_name;
861
+ if (h.sec_url && key && !seenMgrs.has(key)) {
862
+ seenMgrs.add(key);
863
+ quarterLinks.push(`- ${h.manager_name || 'Unknown'}: [13F Filing](${h.sec_url})`);
864
+ }
865
+ }
866
+ if (quarterLinks.length > 0) {
867
+ text += `\n**13F Filing Sources (${q}):**\n${quarterLinks.slice(0, 10).join('\n')}\n`;
868
+ }
848
869
  text += '\n';
849
870
  }
850
871
  text += `*Source: SEC 13F-HR filings via EDGAR. Tracked managers only — not full institutional ownership.*`;
@@ -928,6 +949,18 @@ server.registerTool('etsquare_earnings_actuals', {
928
949
  const conf = a.confidence != null ? `${(a.confidence * 100).toFixed(0)}%` : '—';
929
950
  text += `| ${a.filing_date || '—'} | ${a.metric_name} | ${val} | ${gaap} | ${fy} | ${fq} | ${conf} |\n`;
930
951
  }
952
+ // Filing source URLs (deduplicated by accession)
953
+ const seenAccessions = new Set();
954
+ const filingLinks = [];
955
+ for (const a of actuals) {
956
+ if (a.sec_url && a.accession_number && !seenAccessions.has(a.accession_number)) {
957
+ seenAccessions.add(a.accession_number);
958
+ filingLinks.push(`- ${a.filing_date || '—'}: ${a.sec_url}`);
959
+ }
960
+ }
961
+ if (filingLinks.length > 0) {
962
+ text += `\n**Filing Sources:**\n${filingLinks.join('\n')}\n`;
963
+ }
931
964
  text += '\n';
932
965
  }
933
966
  if (guidance.length > 0) {
@@ -943,6 +976,18 @@ server.registerTool('etsquare_earnings_actuals', {
943
976
  const fq = g.fiscal_quarter ?? '—';
944
977
  text += `| ${g.filing_date || '—'} | ${g.metric_name} | ${low} | ${high} | ${mid} | ${gaap} | ${fy} | ${fq} |\n`;
945
978
  }
979
+ // Filing source URLs for guidance (deduplicated)
980
+ const seenGuidanceAcc = new Set();
981
+ const guidanceLinks = [];
982
+ for (const g of guidance) {
983
+ if (g.sec_url && g.accession_number && !seenGuidanceAcc.has(g.accession_number)) {
984
+ seenGuidanceAcc.add(g.accession_number);
985
+ guidanceLinks.push(`- ${g.filing_date || '—'}: ${g.sec_url}`);
986
+ }
987
+ }
988
+ if (guidanceLinks.length > 0) {
989
+ text += `\n**Guidance Filing Sources:**\n${guidanceLinks.join('\n')}\n`;
990
+ }
946
991
  text += '\n';
947
992
  }
948
993
  text += `*Source: SEC 8-K Item 2.02 press releases — deterministic extraction from issuer filings.*`;
@@ -1055,6 +1100,9 @@ server.registerTool('etsquare_kpi_extractions', {
1055
1100
  text += `${entries.join('\n')}\n`;
1056
1101
  }
1057
1102
  }
1103
+ if (extraction.sec_url) {
1104
+ text += `SEC Filing: ${extraction.sec_url}\n`;
1105
+ }
1058
1106
  if (extraction.notes) {
1059
1107
  text += `Notes: ${extraction.notes}\n`;
1060
1108
  }
@@ -1200,8 +1248,9 @@ server.registerTool('etsquare_query_kpis', {
1200
1248
  }],
1201
1249
  };
1202
1250
  }
1203
- // Build markdown table
1204
- const columns = Object.keys(data[0]);
1251
+ // Build markdown table — exclude sec_url from columns (too wide for table)
1252
+ const excludeFromTable = new Set(['sec_url']);
1253
+ const columns = Object.keys(data[0]).filter(c => !excludeFromTable.has(c));
1205
1254
  let text = `Found ${count} results.\n\n`;
1206
1255
  text += '| ' + columns.join(' | ') + ' |\n';
1207
1256
  text += '| ' + columns.map(() => '---').join(' | ') + ' |\n';
@@ -1219,6 +1268,18 @@ server.registerTool('etsquare_query_kpis', {
1219
1268
  if (count > data.length) {
1220
1269
  text += `\nShowing ${data.length} of ${count} results.`;
1221
1270
  }
1271
+ // Filing source URLs (deduplicated by ticker)
1272
+ const seenTickers = new Set();
1273
+ const filingLinks = [];
1274
+ for (const row of data) {
1275
+ if (row.sec_url && row.ticker && !seenTickers.has(row.ticker)) {
1276
+ seenTickers.add(row.ticker);
1277
+ filingLinks.push(`- ${row.ticker}: ${row.sec_url}`);
1278
+ }
1279
+ }
1280
+ if (filingLinks.length > 0) {
1281
+ text += `\n\n**Filing Sources:**\n${filingLinks.join('\n')}`;
1282
+ }
1222
1283
  // Append summary stats if available
1223
1284
  const summary = result.summary;
1224
1285
  if (summary && typeof summary === 'object' && Object.keys(summary).length > 0) {
@@ -1231,7 +1292,7 @@ server.registerTool('etsquare_query_kpis', {
1231
1292
  }
1232
1293
  }
1233
1294
  // Provenance hint
1234
- text += '\n*Each row includes extraction_id for source tracing. Use etsquare_kpi_extractions(ticker) to get source_chunk_ids, then etsquare_get_chunk(chunk_id) to read the filing text.*';
1295
+ text += '\n*Each row includes extraction_id. Filing source URLs are listed above. Use response_format="structured" for per-row sec_url in JSON.*';
1235
1296
  return { content: [{ type: 'text', text }] };
1236
1297
  }
1237
1298
  catch (error) {
@@ -1353,18 +1414,35 @@ server.registerTool('etsquare_execute_metrics', {
1353
1414
  return { content: [{ type: 'text', text: JSON.stringify(structured, null, 2) }] };
1354
1415
  }
1355
1416
  // ── Text response path (existing behavior) ──
1356
- const colNames = columns.length > 0
1417
+ const allColNames = columns.length > 0
1357
1418
  ? columns.map((c) => c.name)
1358
1419
  : Object.keys(rows[0] || {});
1420
+ // Exclude sec_url from table columns (too wide for markdown)
1421
+ const colNames = allColNames.filter((c) => c !== 'sec_url');
1359
1422
  const table = buildMarkdownTable(colNames, rows);
1360
1423
  const suffix = truncated || rows.length > 20
1361
1424
  ? `\n\n(Showing ${Math.min(rows.length, 20)} of ${rowCount} rows${truncated ? ', results truncated' : ''})`
1362
1425
  : '';
1426
+ // Filing source URLs (deduplicated)
1427
+ let filingSources = '';
1428
+ const seenUrls = new Set();
1429
+ const urlLinks = [];
1430
+ for (const row of rows) {
1431
+ if (row.sec_url && !seenUrls.has(row.sec_url)) {
1432
+ seenUrls.add(row.sec_url);
1433
+ const label = row.ticker || row.TICKER || '';
1434
+ const year = row.fiscal_year || row.FISCAL_YEAR || row.year || '';
1435
+ urlLinks.push(`- ${label} ${year}: ${row.sec_url}`);
1436
+ }
1437
+ }
1438
+ if (urlLinks.length > 0) {
1439
+ filingSources = `\n\n**Filing Sources:**\n${urlLinks.slice(0, 20).join('\n')}`;
1440
+ }
1363
1441
  log('info', `Metrics query returned ${rowCount} rows`);
1364
1442
  return {
1365
1443
  content: [{
1366
1444
  type: 'text',
1367
- text: `Metrics data (${rowCount} rows):\n\n${table}${suffix}`,
1445
+ text: `Metrics data (${rowCount} rows):\n\n${table}${suffix}${filingSources}`,
1368
1446
  }],
1369
1447
  };
1370
1448
  }
@@ -1513,10 +1591,14 @@ server.registerTool('etsquare_get_chunk', {
1513
1591
  ? result.chunk_text
1514
1592
  : '';
1515
1593
  const chunkLength = result.chunk_text_length;
1594
+ const secUrl = result.sec_url;
1595
+ let header = `Chunk ${input.chunk_id}${typeof chunkLength === 'number' ? ` (${chunkLength} chars)` : ''}`;
1596
+ if (secUrl)
1597
+ header += `\nSEC Filing: ${secUrl}`;
1516
1598
  return {
1517
1599
  content: [{
1518
1600
  type: 'text',
1519
- text: `Chunk ${input.chunk_id}${typeof chunkLength === 'number' ? ` (${chunkLength} chars)` : ''}:\n\n${chunkText}`,
1601
+ text: `${header}\n\n${chunkText}`,
1520
1602
  }],
1521
1603
  };
1522
1604
  }
@@ -1782,7 +1864,58 @@ server.registerTool('etsquare_weekly_brief', {
1782
1864
  };
1783
1865
  }
1784
1866
  });
1867
+ // ─── Tool 9: Company Research ──────────────────────────────────────────────
1868
+ server.registerTool('etsquare_company_research', {
1869
+ title: 'Company Research Packet',
1870
+ description: 'Returns a citation-grounded research packet for one company in a single call. ' +
1871
+ 'Server-side orchestration assembles financials, earnings, narrative, ownership, and KPIs with SEC provenance.',
1872
+ inputSchema: {
1873
+ ticker: z.string().min(1).max(10)
1874
+ .describe('Company ticker (e.g., "AAPL", "NVDA"). Resolve names first with etsquare_lookup_company.'),
1875
+ sections: z.array(z.enum(['snapshot', 'financials', 'earnings', 'narrative', 'ownership', 'kpis']))
1876
+ .optional()
1877
+ .describe('Sections to include. Returns all if omitted.'),
1878
+ detail_level: z.enum(['summary', 'standard', 'full']).default('standard')
1879
+ .describe('Controls response size: summary (~4K tokens), standard (~8K), full (~15K).'),
1880
+ peers: z.array(z.string().min(1).max(10)).max(3).optional()
1881
+ .describe('Peer tickers for comparison (financial spine + earnings only). Max 3.'),
1882
+ n_periods: z.number().min(1).max(8).default(4)
1883
+ .describe('Financial statement periods (default: 4).'),
1884
+ },
1885
+ }, async (input) => {
1886
+ if (containsGuardrailBypass(input))
1887
+ return guardrailViolationResponse();
1888
+ const ticker = input.ticker.trim().toUpperCase();
1889
+ try {
1890
+ log('info', `Company research: ${ticker}`, {
1891
+ sections: input.sections,
1892
+ detail_level: input.detail_level,
1893
+ peers: input.peers,
1894
+ n_periods: input.n_periods,
1895
+ });
1896
+ const packet = await client.getCompanyResearch({
1897
+ ticker,
1898
+ sections: input.sections,
1899
+ detail_level: input.detail_level,
1900
+ peers: input.peers?.map((peer) => peer.trim().toUpperCase()),
1901
+ n_periods: input.n_periods,
1902
+ });
1903
+ log('info', `Company research: ${ticker} complete`, {
1904
+ elapsed_ms: packet?.meta?.elapsed_ms,
1905
+ sections_returned: packet?.meta?.sections_returned,
1906
+ sections_failed: packet?.meta?.sections_failed,
1907
+ });
1908
+ return { content: [{ type: 'text', text: JSON.stringify(packet, null, 2) }] };
1909
+ }
1910
+ catch (error) {
1911
+ log('error', `Company research failed: ${ticker}`, { error: error instanceof Error ? error.message : error });
1912
+ return {
1913
+ content: [{ type: 'text', text: `Company research failed for ${ticker}: ${error instanceof Error ? error.message : 'Unknown error'}` }],
1914
+ isError: true,
1915
+ };
1916
+ }
1917
+ });
1785
1918
  // ─── Start Server ───────────────────────────────────────────────────────────
1786
1919
  const transport = new StdioServerTransport();
1787
1920
  await server.connect(transport);
1788
- log('info', 'ETSquare MCP Server v0.3.0 ready and listening on stdio (8 tools)');
1921
+ log('info', 'ETSquare MCP Server v0.6.0 ready and listening on stdio (15 tools)');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsquare/mcp-server-sec",
3
- "version": "0.5.2",
3
+ "version": "0.6.1",
4
4
  "type": "module",
5
5
  "description": "MCP server for Claude Desktop: search SEC filing sections, financial statements, insider trades, institutional ownership, earnings actuals, XBRL metrics, and company lookup.",
6
6
  "main": "dist/index.js",