@etsquare/mcp-server-sec 0.4.0 → 0.5.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.
- package/dist/etsquare-client.d.ts +9 -0
- package/dist/etsquare-client.js +18 -0
- package/dist/index.js +156 -3
- package/package.json +2 -2
|
@@ -45,6 +45,14 @@ interface EarningsActualsApiInput {
|
|
|
45
45
|
quarters?: number;
|
|
46
46
|
metrics?: string[];
|
|
47
47
|
}
|
|
48
|
+
/** Stored KPI extractions request. */
|
|
49
|
+
interface KpiExtractionsApiInput {
|
|
50
|
+
ticker?: string;
|
|
51
|
+
sector?: string;
|
|
52
|
+
sic_code?: string;
|
|
53
|
+
fiscal_year?: number;
|
|
54
|
+
limit?: number;
|
|
55
|
+
}
|
|
48
56
|
/** Subset of DiscoverMetricsInput that the API v1 backend accepts. */
|
|
49
57
|
interface DiscoverMetricsApiInput {
|
|
50
58
|
question: string;
|
|
@@ -70,6 +78,7 @@ export declare class ETSquareClient {
|
|
|
70
78
|
getInstitutionalHoldings(input: InstitutionalHoldingsApiInput): Promise<Record<string, unknown>>;
|
|
71
79
|
getInsiderTransactions(input: InsiderTransactionsApiInput): Promise<Record<string, unknown>>;
|
|
72
80
|
getEarningsActuals(input: EarningsActualsApiInput): Promise<Record<string, unknown>>;
|
|
81
|
+
getKpiExtractions(input: KpiExtractionsApiInput): Promise<Record<string, unknown>>;
|
|
73
82
|
discoverMetrics(input: DiscoverMetricsApiInput): Promise<Record<string, unknown>>;
|
|
74
83
|
getChunk(input: GetChunkInput): Promise<Record<string, unknown>>;
|
|
75
84
|
getChunkContext(input: GetChunkContextInput): Promise<Record<string, unknown>>;
|
package/dist/etsquare-client.js
CHANGED
|
@@ -136,6 +136,24 @@ export class ETSquareClient {
|
|
|
136
136
|
body: JSON.stringify(body),
|
|
137
137
|
});
|
|
138
138
|
}
|
|
139
|
+
async getKpiExtractions(input) {
|
|
140
|
+
const body = {
|
|
141
|
+
limit: input.limit || 50,
|
|
142
|
+
};
|
|
143
|
+
if (input.ticker)
|
|
144
|
+
body.ticker = input.ticker;
|
|
145
|
+
if (input.sector)
|
|
146
|
+
body.sector = input.sector;
|
|
147
|
+
if (input.sic_code)
|
|
148
|
+
body.sic_code = input.sic_code;
|
|
149
|
+
if (input.fiscal_year !== undefined)
|
|
150
|
+
body.fiscal_year = input.fiscal_year;
|
|
151
|
+
return this.request('/api/v1/kpis', {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: this.headers,
|
|
154
|
+
body: JSON.stringify(body),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
139
157
|
async discoverMetrics(input) {
|
|
140
158
|
const body = {
|
|
141
159
|
question: input.question,
|
package/dist/index.js
CHANGED
|
@@ -152,19 +152,25 @@ function humanizeColumnName(col) {
|
|
|
152
152
|
// ─── Structured Response Builders ───────────────────────────────────────────
|
|
153
153
|
function buildSearchContext(apiResponse, originalQuery) {
|
|
154
154
|
const results = apiResponse.narrative_results || [];
|
|
155
|
+
const metricRows = Array.isArray(apiResponse.xbrl_metrics) ? apiResponse.xbrl_metrics : [];
|
|
155
156
|
const ec = apiResponse.execution_contract || {};
|
|
157
|
+
const narrativeTickers = [...new Set(results.map((r) => r.ticker).filter(Boolean))];
|
|
158
|
+
const metricTickers = [...new Set(metricRows.map((r) => r.ticker).filter(Boolean))];
|
|
159
|
+
const resolvedTickers = [...new Set([...narrativeTickers, ...metricTickers])];
|
|
156
160
|
return {
|
|
157
161
|
query_received: originalQuery,
|
|
158
162
|
query_type: apiResponse.query_type || null,
|
|
159
163
|
scope_confidence: apiResponse.scope_confidence || null,
|
|
160
|
-
tickers_resolved:
|
|
164
|
+
tickers_resolved: resolvedTickers,
|
|
165
|
+
tickers_with_narrative: narrativeTickers,
|
|
166
|
+
tickers_with_metrics: metricTickers,
|
|
161
167
|
scope_effective: ec.scope_effective || null,
|
|
162
168
|
mode_effective: ec.mode_effective || null,
|
|
163
169
|
mode_source: ec.mode_source || null,
|
|
164
170
|
scope_source: ec.scope_source || null,
|
|
165
171
|
intent_matched: apiResponse.intent_matched || null,
|
|
166
172
|
results_returned: results.length,
|
|
167
|
-
unique_tickers:
|
|
173
|
+
unique_tickers: resolvedTickers.length,
|
|
168
174
|
unique_forms: [...new Set(results.map((r) => r.form_type).filter(Boolean))],
|
|
169
175
|
template_match: ec.template_match || null,
|
|
170
176
|
relaxations_applied: ec.relaxations_applied || [],
|
|
@@ -172,6 +178,38 @@ function buildSearchContext(apiResponse, originalQuery) {
|
|
|
172
178
|
processing_time_ms: apiResponse.processing_time_ms || null,
|
|
173
179
|
};
|
|
174
180
|
}
|
|
181
|
+
function backfillMissingMetricTickerRows(rows, requestedTickers) {
|
|
182
|
+
if (!Array.isArray(rows) || rows.length === 0)
|
|
183
|
+
return rows;
|
|
184
|
+
const requested = requestedTickers.filter(Boolean);
|
|
185
|
+
if (requested.length < 2)
|
|
186
|
+
return rows;
|
|
187
|
+
const covered = new Set(rows
|
|
188
|
+
.map((row) => String(row?.ticker || '').trim().toUpperCase())
|
|
189
|
+
.filter(Boolean));
|
|
190
|
+
const missing = requested.filter((ticker) => !covered.has(String(ticker).trim().toUpperCase()));
|
|
191
|
+
if (missing.length === 0)
|
|
192
|
+
return rows;
|
|
193
|
+
const rowKeys = [];
|
|
194
|
+
const seenKeys = new Set();
|
|
195
|
+
for (const row of rows) {
|
|
196
|
+
if (!row || typeof row !== 'object')
|
|
197
|
+
continue;
|
|
198
|
+
for (const key of Object.keys(row)) {
|
|
199
|
+
if (key === 'ticker' || seenKeys.has(key))
|
|
200
|
+
continue;
|
|
201
|
+
seenKeys.add(key);
|
|
202
|
+
rowKeys.push(key);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const placeholders = missing.map((ticker) => {
|
|
206
|
+
const placeholder = { ticker };
|
|
207
|
+
for (const key of rowKeys)
|
|
208
|
+
placeholder[key] = null;
|
|
209
|
+
return placeholder;
|
|
210
|
+
});
|
|
211
|
+
return [...rows, ...placeholders];
|
|
212
|
+
}
|
|
175
213
|
function deriveLayoutHint(apiResponse) {
|
|
176
214
|
const results = apiResponse.narrative_results || [];
|
|
177
215
|
const tickers = new Set(results.map((r) => r.ticker));
|
|
@@ -920,6 +958,120 @@ server.registerTool('etsquare_earnings_actuals', {
|
|
|
920
958
|
};
|
|
921
959
|
}
|
|
922
960
|
});
|
|
961
|
+
server.registerTool('etsquare_kpi_extractions', {
|
|
962
|
+
title: 'Get Sector KPI Extractions',
|
|
963
|
+
description: 'Query stored KPI extractions from SEC filings for supported sectors such as restaurants, airlines, banks, REITs, pharma, and SaaS.\n\n' +
|
|
964
|
+
'This returns previously extracted KPI rows with provenance like filing date, fiscal period, and source chunk IDs when available.\n\n' +
|
|
965
|
+
'Use this for stored non-XBRL operating metrics such as guest traffic, same-store sales, load factor, NIM, deposit trends, or sector-specific KPIs.\n\n' +
|
|
966
|
+
'Coverage is sector-dependent and may be partial. If coverage is sparse or you need fresh narrative detail, use etsquare_search in NARRATIVE mode.',
|
|
967
|
+
inputSchema: {
|
|
968
|
+
ticker: z.string().min(1).max(10).optional()
|
|
969
|
+
.describe('Single ticker filter (e.g., "TXRH")'),
|
|
970
|
+
sector: z.string().min(2).max(50).optional()
|
|
971
|
+
.describe('Sector key such as "restaurants", "airlines", "banks", "reits", "pharma", or "saas"'),
|
|
972
|
+
sic_code: z.string().length(4).optional()
|
|
973
|
+
.describe('Optional SIC code filter'),
|
|
974
|
+
fiscal_year: z.number().min(1990).max(2100).optional()
|
|
975
|
+
.describe('Optional fiscal year filter'),
|
|
976
|
+
limit: z.number().min(1).max(500).default(50)
|
|
977
|
+
.describe('Maximum rows to return (default: 50)'),
|
|
978
|
+
response_format: z.enum(['text', 'structured']).default('text')
|
|
979
|
+
.describe('"text" returns a readable summary (default). "structured" returns the raw extraction payload as JSON.'),
|
|
980
|
+
},
|
|
981
|
+
}, async (input) => {
|
|
982
|
+
if (containsGuardrailBypass(input))
|
|
983
|
+
return guardrailViolationResponse();
|
|
984
|
+
try {
|
|
985
|
+
const result = await client.getKpiExtractions(input);
|
|
986
|
+
const extractions = Array.isArray(result.extractions)
|
|
987
|
+
? result.extractions
|
|
988
|
+
: [];
|
|
989
|
+
const filters = result.filters && typeof result.filters === 'object'
|
|
990
|
+
? result.filters
|
|
991
|
+
: {};
|
|
992
|
+
const count = typeof result.count === 'number'
|
|
993
|
+
? result.count
|
|
994
|
+
: extractions.length;
|
|
995
|
+
if (input.response_format === 'structured') {
|
|
996
|
+
return {
|
|
997
|
+
content: [{
|
|
998
|
+
type: 'text',
|
|
999
|
+
text: JSON.stringify({
|
|
1000
|
+
count,
|
|
1001
|
+
filters,
|
|
1002
|
+
extractions,
|
|
1003
|
+
}, null, 2),
|
|
1004
|
+
}],
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
if (extractions.length === 0) {
|
|
1008
|
+
const filterBits = [
|
|
1009
|
+
input.ticker ? `ticker=${input.ticker.toUpperCase()}` : null,
|
|
1010
|
+
input.sector ? `sector=${input.sector}` : null,
|
|
1011
|
+
input.sic_code ? `sic_code=${input.sic_code}` : null,
|
|
1012
|
+
input.fiscal_year ? `fiscal_year=${input.fiscal_year}` : null,
|
|
1013
|
+
].filter(Boolean);
|
|
1014
|
+
const suffix = filterBits.length > 0 ? ` for ${filterBits.join(', ')}` : '';
|
|
1015
|
+
return {
|
|
1016
|
+
content: [{
|
|
1017
|
+
type: 'text',
|
|
1018
|
+
text: `No stored KPI extractions found${suffix}. Coverage is sector-dependent. For fresh filing detail, use etsquare_search in NARRATIVE mode.`,
|
|
1019
|
+
}],
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
1022
|
+
let text = `Found ${count} KPI extraction`;
|
|
1023
|
+
text += count === 1 ? '' : 's';
|
|
1024
|
+
if (Object.keys(filters).length > 0) {
|
|
1025
|
+
const filterText = Object.entries(filters)
|
|
1026
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
1027
|
+
.join(', ');
|
|
1028
|
+
text += ` for ${filterText}`;
|
|
1029
|
+
}
|
|
1030
|
+
text += '.\n\n';
|
|
1031
|
+
for (const extraction of extractions.slice(0, 20)) {
|
|
1032
|
+
const ticker = extraction.ticker || '—';
|
|
1033
|
+
const periodBits = [
|
|
1034
|
+
extraction.form_type || null,
|
|
1035
|
+
extraction.filing_date || null,
|
|
1036
|
+
extraction.fiscal_year != null ? `FY${extraction.fiscal_year}` : null,
|
|
1037
|
+
extraction.fiscal_period || null,
|
|
1038
|
+
].filter(Boolean);
|
|
1039
|
+
text += `### ${ticker}${extraction.sector ? ` (${extraction.sector})` : ''}\n`;
|
|
1040
|
+
if (periodBits.length > 0) {
|
|
1041
|
+
text += `${periodBits.join(' | ')}\n`;
|
|
1042
|
+
}
|
|
1043
|
+
const kpiJson = extraction.kpi_json && typeof extraction.kpi_json === 'object'
|
|
1044
|
+
? extraction.kpi_json
|
|
1045
|
+
: null;
|
|
1046
|
+
if (kpiJson) {
|
|
1047
|
+
const entries = Object.entries(kpiJson)
|
|
1048
|
+
.slice(0, 12)
|
|
1049
|
+
.map(([key, value]) => `- ${key}: ${typeof value === 'object' ? JSON.stringify(value) : String(value)}`);
|
|
1050
|
+
if (entries.length > 0) {
|
|
1051
|
+
text += `${entries.join('\n')}\n`;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (extraction.notes) {
|
|
1055
|
+
text += `Notes: ${extraction.notes}\n`;
|
|
1056
|
+
}
|
|
1057
|
+
text += '\n';
|
|
1058
|
+
}
|
|
1059
|
+
if (extractions.length > 20) {
|
|
1060
|
+
text += `Showing 20 of ${count} stored KPI extractions.\n\n`;
|
|
1061
|
+
}
|
|
1062
|
+
text += 'Coverage is sector-dependent and derived from stored extractions, not live on-demand parsing.';
|
|
1063
|
+
return {
|
|
1064
|
+
content: [{ type: 'text', text }],
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
catch (error) {
|
|
1068
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1069
|
+
return {
|
|
1070
|
+
content: [{ type: 'text', text: `KPI extraction request failed: ${message}` }],
|
|
1071
|
+
isError: true,
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
923
1075
|
server.registerTool('etsquare_execute_metrics', {
|
|
924
1076
|
title: 'Execute Financial Metrics Query',
|
|
925
1077
|
description: 'Execute an XBRL metrics template by template_id to get structured financial data ' +
|
|
@@ -1307,9 +1459,10 @@ server.registerTool('etsquare_compare', {
|
|
|
1307
1459
|
const allResults = Array.isArray(searchResult.narrative_results)
|
|
1308
1460
|
? searchResult.narrative_results
|
|
1309
1461
|
: [];
|
|
1310
|
-
const
|
|
1462
|
+
const rawXbrlMetrics = Array.isArray(searchResult.xbrl_metrics)
|
|
1311
1463
|
? searchResult.xbrl_metrics
|
|
1312
1464
|
: [];
|
|
1465
|
+
const xbrlMetrics = backfillMissingMetricTickerRows(rawXbrlMetrics, input.tickers);
|
|
1313
1466
|
// Group results by ticker
|
|
1314
1467
|
const perTicker = {};
|
|
1315
1468
|
for (const ticker of input.tickers) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsquare/mcp-server-sec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "MCP server for Claude Desktop: search
|
|
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",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|