@etsquare/mcp-server-sec 0.5.0 → 0.5.2
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 +18 -0
- package/dist/etsquare-client.js +33 -1
- package/dist/index.js +175 -5
- package/package.json +1 -1
|
@@ -53,6 +53,23 @@ interface KpiExtractionsApiInput {
|
|
|
53
53
|
fiscal_year?: number;
|
|
54
54
|
limit?: number;
|
|
55
55
|
}
|
|
56
|
+
/** Server-side KPI query with metric filtering. */
|
|
57
|
+
interface QueryKpisApiInput {
|
|
58
|
+
select?: string[];
|
|
59
|
+
filter?: Array<{
|
|
60
|
+
metric: string;
|
|
61
|
+
op: string;
|
|
62
|
+
value: number;
|
|
63
|
+
}>;
|
|
64
|
+
sector?: string;
|
|
65
|
+
tickers?: string[];
|
|
66
|
+
period?: string;
|
|
67
|
+
fiscal_year?: number;
|
|
68
|
+
sort_by?: string;
|
|
69
|
+
sort_order?: string;
|
|
70
|
+
limit?: number;
|
|
71
|
+
action?: string;
|
|
72
|
+
}
|
|
56
73
|
/** Subset of DiscoverMetricsInput that the API v1 backend accepts. */
|
|
57
74
|
interface DiscoverMetricsApiInput {
|
|
58
75
|
question: string;
|
|
@@ -79,6 +96,7 @@ export declare class ETSquareClient {
|
|
|
79
96
|
getInsiderTransactions(input: InsiderTransactionsApiInput): Promise<Record<string, unknown>>;
|
|
80
97
|
getEarningsActuals(input: EarningsActualsApiInput): Promise<Record<string, unknown>>;
|
|
81
98
|
getKpiExtractions(input: KpiExtractionsApiInput): Promise<Record<string, unknown>>;
|
|
99
|
+
queryKpis(input: QueryKpisApiInput): Promise<Record<string, unknown>>;
|
|
82
100
|
discoverMetrics(input: DiscoverMetricsApiInput): Promise<Record<string, unknown>>;
|
|
83
101
|
getChunk(input: GetChunkInput): Promise<Record<string, unknown>>;
|
|
84
102
|
getChunkContext(input: GetChunkContextInput): Promise<Record<string, unknown>>;
|
package/dist/etsquare-client.js
CHANGED
|
@@ -32,7 +32,11 @@ export class ETSquareClient {
|
|
|
32
32
|
async request(path, init) {
|
|
33
33
|
const url = `${this.baseUrl}${path}`;
|
|
34
34
|
try {
|
|
35
|
-
const
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
const timeoutMs = parseInt(process.env.ETSQUARE_FETCH_TIMEOUT_MS || '600000', 10);
|
|
37
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
38
|
+
const res = await fetch(url, { ...init, signal: controller.signal });
|
|
39
|
+
clearTimeout(timer);
|
|
36
40
|
return this.handleResponse(res);
|
|
37
41
|
}
|
|
38
42
|
catch (error) {
|
|
@@ -154,6 +158,34 @@ export class ETSquareClient {
|
|
|
154
158
|
body: JSON.stringify(body),
|
|
155
159
|
});
|
|
156
160
|
}
|
|
161
|
+
async queryKpis(input) {
|
|
162
|
+
const body = {
|
|
163
|
+
limit: input.limit || 50,
|
|
164
|
+
};
|
|
165
|
+
if (input.select && input.select.length > 0)
|
|
166
|
+
body.select = input.select;
|
|
167
|
+
if (input.filter && input.filter.length > 0)
|
|
168
|
+
body.filter = input.filter;
|
|
169
|
+
if (input.sector)
|
|
170
|
+
body.sector = input.sector;
|
|
171
|
+
if (input.tickers && input.tickers.length > 0)
|
|
172
|
+
body.tickers = input.tickers;
|
|
173
|
+
if (input.period)
|
|
174
|
+
body.period = input.period;
|
|
175
|
+
if (input.fiscal_year !== undefined)
|
|
176
|
+
body.fiscal_year = input.fiscal_year;
|
|
177
|
+
if (input.sort_by)
|
|
178
|
+
body.sort_by = input.sort_by;
|
|
179
|
+
if (input.sort_order)
|
|
180
|
+
body.sort_order = input.sort_order;
|
|
181
|
+
if (input.action)
|
|
182
|
+
body.action = input.action;
|
|
183
|
+
return this.request('/api/v1/kpis/query', {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
headers: this.headers,
|
|
186
|
+
body: JSON.stringify(body),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
157
189
|
async discoverMetrics(input) {
|
|
158
190
|
const body = {
|
|
159
191
|
question: input.question,
|
package/dist/index.js
CHANGED
|
@@ -959,11 +959,15 @@ server.registerTool('etsquare_earnings_actuals', {
|
|
|
959
959
|
}
|
|
960
960
|
});
|
|
961
961
|
server.registerTool('etsquare_kpi_extractions', {
|
|
962
|
-
title: 'Get
|
|
963
|
-
description: '
|
|
964
|
-
'
|
|
965
|
-
'
|
|
966
|
-
'
|
|
962
|
+
title: 'Get KPI Extractions (Full Detail + Source Chunk IDs)',
|
|
963
|
+
description: 'Returns full KPI extraction detail including source_chunk_ids for provenance.\n\n' +
|
|
964
|
+
'USE THIS TOOL for the provenance chain:\n' +
|
|
965
|
+
' 1. etsquare_query_kpis -> find interesting metric + extraction_id\n' +
|
|
966
|
+
' 2. etsquare_kpi_extractions(ticker, sector) -> get source_chunk_ids array\n' +
|
|
967
|
+
' 3. etsquare_get_chunk(chunk_id) -> read actual filing paragraph\n\n' +
|
|
968
|
+
'Also use when you need the complete kpi_json payload for deep analysis of a single ticker.\n\n' +
|
|
969
|
+
'RETURNS: extraction_id, kpi_json (full), source_chunk_ids (array of chunk IDs), notes, extraction_model.\n\n' +
|
|
970
|
+
'For compact tabular queries across many tickers, prefer etsquare_query_kpis instead.',
|
|
967
971
|
inputSchema: {
|
|
968
972
|
ticker: z.string().min(1).max(10).optional()
|
|
969
973
|
.describe('Single ticker filter (e.g., "TXRH")'),
|
|
@@ -1072,6 +1076,172 @@ server.registerTool('etsquare_kpi_extractions', {
|
|
|
1072
1076
|
};
|
|
1073
1077
|
}
|
|
1074
1078
|
});
|
|
1079
|
+
server.registerTool('etsquare_query_kpis', {
|
|
1080
|
+
title: 'Query KPI Extractions (Compare, Screen, Catalog)',
|
|
1081
|
+
description: 'Server-side filtered KPI query. Returns compact tabular data with values + YoY changes + summary stats.\n\n' +
|
|
1082
|
+
'WORKFLOW: action="list_metrics" -> discover metrics -> action="query" with select/filter\n\n' +
|
|
1083
|
+
'EXAMPLES:\n' +
|
|
1084
|
+
' Peer comp: select="net_interest_margin,cet1_ratio,return_on_tangible_common_equity", sector="banks", period="FY"\n' +
|
|
1085
|
+
' Screen: filter="net_interest_margin>3;cet1_ratio>11", sector="banks", period="FY"\n' +
|
|
1086
|
+
' SaaS: select="arr,net_dollar_retention,free_cash_flow_margin", sector="saas", sort_by="arr"\n' +
|
|
1087
|
+
' Catalog: action="list_metrics", sector="banks"\n' +
|
|
1088
|
+
' Time series: select="net_interest_margin", tickers="JPM", limit=20\n\n' +
|
|
1089
|
+
'OUTPUT: Each row includes ticker, company_name, extraction_id, metric_value, metric_chg (YoY), metric_unit.\n' +
|
|
1090
|
+
' Summary stats (min/max/avg/median) per metric included.\n\n' +
|
|
1091
|
+
'PROVENANCE CHAIN: Results include extraction_id. To read source MD&A text:\n' +
|
|
1092
|
+
' 1. etsquare_query_kpis -> get extraction_id\n' +
|
|
1093
|
+
' 2. etsquare_kpi_extractions(ticker) -> get source_chunk_ids\n' +
|
|
1094
|
+
' 3. etsquare_get_chunk(chunk_id) -> read actual filing paragraph\n' +
|
|
1095
|
+
'Use when a number looks surprising or needs management context.',
|
|
1096
|
+
inputSchema: {
|
|
1097
|
+
select: z.string().optional()
|
|
1098
|
+
.describe('Comma-separated metric keys to return (e.g., "net_interest_margin,cet1_ratio,diluted_eps")'),
|
|
1099
|
+
filter: z.string().optional()
|
|
1100
|
+
.describe('Filter conditions as "metric>value" pairs separated by semicolons (e.g., "net_interest_margin>3;cet1_ratio>11")'),
|
|
1101
|
+
sector: z.string().optional()
|
|
1102
|
+
.describe('Sector filter (e.g., "banks", "restaurants", "saas", "airlines")'),
|
|
1103
|
+
tickers: z.string().optional()
|
|
1104
|
+
.describe('Comma-separated tickers (e.g., "JPM,BAC,WFC")'),
|
|
1105
|
+
period: z.string().optional()
|
|
1106
|
+
.describe('Fiscal period: "FY", "Q1", "Q2", "Q3", "Q4"'),
|
|
1107
|
+
fiscal_year: z.number().optional()
|
|
1108
|
+
.describe('Fiscal year filter (e.g., 2025)'),
|
|
1109
|
+
sort_by: z.string().optional()
|
|
1110
|
+
.describe('Metric key to sort by'),
|
|
1111
|
+
sort_order: z.string().optional()
|
|
1112
|
+
.describe('Sort direction: "ASC" or "DESC" (default: DESC)'),
|
|
1113
|
+
limit: z.number().min(1).max(500).default(50)
|
|
1114
|
+
.describe('Maximum rows (default: 50)'),
|
|
1115
|
+
action: z.string().optional()
|
|
1116
|
+
.describe('"query" returns data (default). "list_metrics" returns available metric names for a sector.'),
|
|
1117
|
+
response_format: z.string().optional()
|
|
1118
|
+
.describe('"text" returns a markdown table (default). "structured" returns raw JSON for programmatic use.'),
|
|
1119
|
+
},
|
|
1120
|
+
}, async (input) => {
|
|
1121
|
+
if (containsGuardrailBypass(input))
|
|
1122
|
+
return guardrailViolationResponse();
|
|
1123
|
+
try {
|
|
1124
|
+
// Parse string inputs into arrays for the API
|
|
1125
|
+
const apiInput = {
|
|
1126
|
+
limit: input.limit || 50,
|
|
1127
|
+
action: input.action || 'query',
|
|
1128
|
+
};
|
|
1129
|
+
if (input.select) {
|
|
1130
|
+
apiInput.select = input.select.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1131
|
+
}
|
|
1132
|
+
if (input.filter) {
|
|
1133
|
+
// Parse "metric>value;metric2<value2" format
|
|
1134
|
+
const filterParts = input.filter.split(';').map((s) => s.trim()).filter(Boolean);
|
|
1135
|
+
const filters = [];
|
|
1136
|
+
for (const part of filterParts) {
|
|
1137
|
+
const match = part.match(/^([a-z][a-z0-9_]*)\s*(>=|<=|!=|>|<|=)\s*(-?[\d.]+)$/);
|
|
1138
|
+
if (match) {
|
|
1139
|
+
filters.push({ metric: match[1], op: match[2], value: parseFloat(match[3]) });
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (filters.length > 0)
|
|
1143
|
+
apiInput.filter = filters;
|
|
1144
|
+
}
|
|
1145
|
+
if (input.tickers) {
|
|
1146
|
+
apiInput.tickers = input.tickers.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean);
|
|
1147
|
+
}
|
|
1148
|
+
if (input.sector)
|
|
1149
|
+
apiInput.sector = input.sector;
|
|
1150
|
+
if (input.period)
|
|
1151
|
+
apiInput.period = input.period;
|
|
1152
|
+
if (input.fiscal_year !== undefined)
|
|
1153
|
+
apiInput.fiscal_year = input.fiscal_year;
|
|
1154
|
+
if (input.sort_by)
|
|
1155
|
+
apiInput.sort_by = input.sort_by;
|
|
1156
|
+
if (input.sort_order)
|
|
1157
|
+
apiInput.sort_order = input.sort_order;
|
|
1158
|
+
const result = await client.queryKpis(apiInput);
|
|
1159
|
+
const data = Array.isArray(result.data)
|
|
1160
|
+
? result.data
|
|
1161
|
+
: [];
|
|
1162
|
+
const count = typeof result.count === 'number'
|
|
1163
|
+
? result.count
|
|
1164
|
+
: data.length;
|
|
1165
|
+
// list_metrics action
|
|
1166
|
+
if ((input.action || 'query') === 'list_metrics') {
|
|
1167
|
+
const metrics = result.metrics || [];
|
|
1168
|
+
const otherMetrics = result.other_kpis_metrics || [];
|
|
1169
|
+
let text = `Available KPI metrics`;
|
|
1170
|
+
if (result.sector)
|
|
1171
|
+
text += ` for sector "${result.sector}"`;
|
|
1172
|
+
if (result.ticker)
|
|
1173
|
+
text += ` ticker ${result.ticker}`;
|
|
1174
|
+
text += `:\n\n`;
|
|
1175
|
+
text += `**Top-level metrics (${metrics.length}):**\n`;
|
|
1176
|
+
text += metrics.map(m => `- ${m}`).join('\n');
|
|
1177
|
+
if (otherMetrics.length > 0) {
|
|
1178
|
+
text += `\n\n**other_kpis metrics (${otherMetrics.length}):**\n`;
|
|
1179
|
+
text += otherMetrics.slice(0, 30).map(m => `- ${m}`).join('\n');
|
|
1180
|
+
if (otherMetrics.length > 30)
|
|
1181
|
+
text += `\n... and ${otherMetrics.length - 30} more`;
|
|
1182
|
+
}
|
|
1183
|
+
return { content: [{ type: 'text', text }] };
|
|
1184
|
+
}
|
|
1185
|
+
// query action
|
|
1186
|
+
if (data.length === 0) {
|
|
1187
|
+
return {
|
|
1188
|
+
content: [{
|
|
1189
|
+
type: 'text',
|
|
1190
|
+
text: 'No KPI data found for the specified query. Try broader filters or use action="list_metrics" to see available metrics.',
|
|
1191
|
+
}],
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
// Structured format — return raw JSON
|
|
1195
|
+
if ((input.response_format || 'text') === 'structured') {
|
|
1196
|
+
return {
|
|
1197
|
+
content: [{
|
|
1198
|
+
type: 'text',
|
|
1199
|
+
text: JSON.stringify({ count, data }, null, 2),
|
|
1200
|
+
}],
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
// Build markdown table
|
|
1204
|
+
const columns = Object.keys(data[0]);
|
|
1205
|
+
let text = `Found ${count} results.\n\n`;
|
|
1206
|
+
text += '| ' + columns.join(' | ') + ' |\n';
|
|
1207
|
+
text += '| ' + columns.map(() => '---').join(' | ') + ' |\n';
|
|
1208
|
+
for (const row of data) {
|
|
1209
|
+
const values = columns.map(col => {
|
|
1210
|
+
const v = row[col];
|
|
1211
|
+
if (v === null || v === undefined)
|
|
1212
|
+
return '—';
|
|
1213
|
+
if (typeof v === 'number')
|
|
1214
|
+
return Number.isInteger(v) ? String(v) : v.toFixed(2);
|
|
1215
|
+
return String(v);
|
|
1216
|
+
});
|
|
1217
|
+
text += '| ' + values.join(' | ') + ' |\n';
|
|
1218
|
+
}
|
|
1219
|
+
if (count > data.length) {
|
|
1220
|
+
text += `\nShowing ${data.length} of ${count} results.`;
|
|
1221
|
+
}
|
|
1222
|
+
// Append summary stats if available
|
|
1223
|
+
const summary = result.summary;
|
|
1224
|
+
if (summary && typeof summary === 'object' && Object.keys(summary).length > 0) {
|
|
1225
|
+
text += '\n\n**Summary Stats:**\n';
|
|
1226
|
+
text += '| Metric | Min | Max | Avg | Median | Count |\n';
|
|
1227
|
+
text += '| --- | --- | --- | --- | --- | --- |\n';
|
|
1228
|
+
for (const [metric, stats] of Object.entries(summary)) {
|
|
1229
|
+
const s = stats;
|
|
1230
|
+
text += `| ${metric} | ${s.min} | ${s.max} | ${s.avg} | ${s.median} | ${s.count} |\n`;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
// 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.*';
|
|
1235
|
+
return { content: [{ type: 'text', text }] };
|
|
1236
|
+
}
|
|
1237
|
+
catch (error) {
|
|
1238
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1239
|
+
return {
|
|
1240
|
+
content: [{ type: 'text', text: `KPI query failed: ${message}` }],
|
|
1241
|
+
isError: true,
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1075
1245
|
server.registerTool('etsquare_execute_metrics', {
|
|
1076
1246
|
title: 'Execute Financial Metrics Query',
|
|
1077
1247
|
description: 'Execute an XBRL metrics template by template_id to get structured financial data ' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etsquare/mcp-server-sec",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
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",
|