@etsquare/mcp-server-sec 0.2.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.
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ETSquare SEC Intelligence API client.
3
+ * Wraps /api/v1/* endpoints with X-API-Key authentication.
4
+ */
5
+ import type { SearchInput, LookupCompanyInput, ExecuteMetricsInput, DiscoverMetricsInput } from './types.js';
6
+ export interface ETSquareClientOptions {
7
+ baseUrl: string;
8
+ apiKey: string;
9
+ }
10
+ export declare class ETSquareClient {
11
+ private readonly baseUrl;
12
+ private readonly apiKey;
13
+ constructor(options: ETSquareClientOptions);
14
+ private get headers();
15
+ private handleResponse;
16
+ search(input: SearchInput): Promise<Record<string, unknown>>;
17
+ lookupCompany(input: LookupCompanyInput): Promise<Record<string, unknown>>;
18
+ executeMetrics(input: ExecuteMetricsInput): Promise<Record<string, unknown>>;
19
+ discoverMetrics(input: DiscoverMetricsInput): Promise<Record<string, unknown>>;
20
+ }
@@ -0,0 +1,93 @@
1
+ export class ETSquareClient {
2
+ constructor(options) {
3
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
4
+ this.apiKey = options.apiKey;
5
+ }
6
+ get headers() {
7
+ return {
8
+ 'Content-Type': 'application/json',
9
+ 'X-API-Key': this.apiKey,
10
+ };
11
+ }
12
+ async handleResponse(res) {
13
+ if (!res.ok) {
14
+ let detail = await res.text();
15
+ try {
16
+ const json = JSON.parse(detail);
17
+ if (json.detail) {
18
+ detail = typeof json.detail === 'string' ? json.detail : JSON.stringify(json.detail);
19
+ }
20
+ else if (json.message) {
21
+ detail = json.message;
22
+ }
23
+ }
24
+ catch {
25
+ // ignore parse error
26
+ }
27
+ throw new Error(`ETSquare API error (${res.status}): ${detail}`);
28
+ }
29
+ return (await res.json());
30
+ }
31
+ async search(input) {
32
+ const body = {
33
+ query: input.query,
34
+ mode_lock: input.mode_lock,
35
+ scope_lock: input.scope_lock,
36
+ top_k: input.top_k ?? 5,
37
+ include_synthesis: false,
38
+ skip_micro_rewrite: true,
39
+ };
40
+ if (input.tickers)
41
+ body.tickers = input.tickers;
42
+ if (input.sic_codes)
43
+ body.sic_codes = input.sic_codes;
44
+ if (input.doc_types)
45
+ body.doc_types = input.doc_types;
46
+ const res = await fetch(`${this.baseUrl}/api/v1/search`, {
47
+ method: 'POST',
48
+ headers: this.headers,
49
+ body: JSON.stringify(body),
50
+ });
51
+ return this.handleResponse(res);
52
+ }
53
+ async lookupCompany(input) {
54
+ const params = new URLSearchParams({ query: input.query });
55
+ if (input.limit)
56
+ params.set('limit', String(input.limit));
57
+ const res = await fetch(`${this.baseUrl}/api/v1/companies/lookup?${params}`, {
58
+ headers: this.headers,
59
+ });
60
+ return this.handleResponse(res);
61
+ }
62
+ async executeMetrics(input) {
63
+ const body = {
64
+ template_id: input.template_id,
65
+ bind_params: input.bind_params || {},
66
+ };
67
+ if (input.row_limit)
68
+ body.row_limit = input.row_limit;
69
+ const res = await fetch(`${this.baseUrl}/api/v1/metrics/execute`, {
70
+ method: 'POST',
71
+ headers: this.headers,
72
+ body: JSON.stringify(body),
73
+ });
74
+ return this.handleResponse(res);
75
+ }
76
+ async discoverMetrics(input) {
77
+ const body = {
78
+ question: input.question,
79
+ };
80
+ if (input.scenario)
81
+ body.scenario = input.scenario;
82
+ if (input.metric_family)
83
+ body.metric_family = input.metric_family;
84
+ if (input.max_results)
85
+ body.max_results = input.max_results;
86
+ const res = await fetch(`${this.baseUrl}/api/v1/metrics/discover`, {
87
+ method: 'POST',
88
+ headers: this.headers,
89
+ body: JSON.stringify(body),
90
+ });
91
+ return this.handleResponse(res);
92
+ }
93
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,360 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @etsquare/mcp-server-sec
4
+ * MCP server for SEC Intelligence: search SEC filings,
5
+ * resolve company tickers, and execute financial metrics templates.
6
+ */
7
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
8
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
9
+ import { z } from 'zod';
10
+ import { ETSquareClient } from './etsquare-client.js';
11
+ // Guardrail keywords to detect prompt injection attempts
12
+ const GUARDRAIL_KEYWORDS = [
13
+ 'reset guardrails',
14
+ 'disable guardrails',
15
+ 'ignore guardrails',
16
+ 'bypass guardrails',
17
+ 'disable safety',
18
+ 'bypass safety',
19
+ 'disable protections',
20
+ 'turn off protections',
21
+ ];
22
+ function containsGuardrailBypass(payload) {
23
+ if (payload === null || payload === undefined)
24
+ return false;
25
+ if (typeof payload === 'string') {
26
+ const lower = payload.toLowerCase();
27
+ return GUARDRAIL_KEYWORDS.some(kw => lower.includes(kw));
28
+ }
29
+ if (Array.isArray(payload)) {
30
+ return payload.some(item => containsGuardrailBypass(item));
31
+ }
32
+ if (typeof payload === 'object') {
33
+ return Object.values(payload).some(value => containsGuardrailBypass(value));
34
+ }
35
+ return false;
36
+ }
37
+ function guardrailViolationResponse() {
38
+ return {
39
+ content: [{ type: 'text', text: 'Request rejected: system guardrails cannot be bypassed.' }],
40
+ isError: true,
41
+ };
42
+ }
43
+ // Environment configuration
44
+ const baseUrl = process.env.ETSQUARE_BASE_URL || 'https://www.etsquare.ai';
45
+ const apiKey = process.env.ETSQUARE_API_KEY;
46
+ const DEBUG = process.env.DEBUG === 'true';
47
+ function log(level, message, data) {
48
+ if (level === 'debug' && !DEBUG)
49
+ return;
50
+ const timestamp = new Date().toISOString();
51
+ const logData = data ? ` ${JSON.stringify(data)}` : '';
52
+ console.error(`[${timestamp}] [${level.toUpperCase()}] ${message}${logData}`);
53
+ }
54
+ // Validate API key
55
+ if (!apiKey) {
56
+ console.error('ERROR: ETSQUARE_API_KEY environment variable is required');
57
+ process.exit(1);
58
+ }
59
+ // Initialize client
60
+ const client = new ETSquareClient({ baseUrl, apiKey });
61
+ log('info', `ETSquare MCP Server starting with backend: ${baseUrl}`);
62
+ // Initialize MCP Server
63
+ const server = new McpServer({
64
+ name: 'etsquare-mcp-sec',
65
+ version: '0.2.0',
66
+ });
67
+ // Tool 1: Company Lookup
68
+ server.registerTool('etsquare_lookup_company', {
69
+ title: 'Look Up Company Ticker',
70
+ description: 'Resolve a company name or partial ticker to its official SEC ticker symbol and CIK number. ' +
71
+ 'Call this when a user mentions a company by name (e.g., "Apple" -> AAPL, "Texas Roadhouse" -> TXRH). ' +
72
+ 'Returns ticker, company name, CIK, and SIC industry code.',
73
+ inputSchema: {
74
+ query: z.string().min(2).describe('Company name or partial ticker to look up (e.g., "Apple", "Texas Road", "NVDA")'),
75
+ limit: z.number().min(1).max(10).default(5).describe('Max results to return'),
76
+ },
77
+ }, async ({ query, limit }) => {
78
+ if (containsGuardrailBypass(query))
79
+ return guardrailViolationResponse();
80
+ try {
81
+ log('debug', 'Looking up company', { query });
82
+ const result = await client.lookupCompany({ query, limit });
83
+ const results = result.results || [];
84
+ if (results.length === 0) {
85
+ return { content: [{ type: 'text', text: `No companies found for: "${query}"` }] };
86
+ }
87
+ const formatted = results
88
+ .map((r) => {
89
+ let line = `${r.ticker || 'N/A'} - ${r.company_name || 'Unknown'}`;
90
+ if (r.cik)
91
+ line += ` (CIK: ${r.cik})`;
92
+ if (r.sic_code)
93
+ line += ` [SIC: ${r.sic_code}]`;
94
+ return line;
95
+ })
96
+ .join('\n');
97
+ log('info', `Company lookup returned ${results.length} results`);
98
+ return {
99
+ content: [{ type: 'text', text: `Company matches for "${query}":\n${formatted}` }],
100
+ };
101
+ }
102
+ catch (error) {
103
+ log('error', 'Company lookup failed', { error: error instanceof Error ? error.message : error });
104
+ return {
105
+ content: [{ type: 'text', text: `Lookup failed: ${error instanceof Error ? error.message : 'Unknown error'}` }],
106
+ isError: true,
107
+ };
108
+ }
109
+ });
110
+ // Tool 2: SEC Filing Search
111
+ server.registerTool('etsquare_search', {
112
+ title: 'Search SEC Filings',
113
+ description: 'Search 1.2M+ SEC filing sections (10-K, 10-Q, 8-K) with hybrid retrieval. ' +
114
+ 'Returns verbatim filing text with citations — best for qualitative research.\n\n' +
115
+ 'Execution contract (required):\n' +
116
+ '- mode_lock: NARRATIVE (recommended) | HYBRID\n' +
117
+ '- scope_lock: COMPANY (specific tickers) | INDUSTRY (SIC sector) | MACRO (cross-market)\n\n' +
118
+ 'For structured financial data (revenue, margins, ratios), use etsquare_discover_metrics + etsquare_execute_metrics instead.\n\n' +
119
+ 'Use tickers to scope to specific companies (resolve names first with etsquare_lookup_company). ' +
120
+ 'Results include verbatim filing text, similarity scores, and SEC source URLs.',
121
+ inputSchema: {
122
+ query: z.string().min(3).describe('What to search for in SEC filings (e.g., "customer concentration risk", "Show NVDA revenue trend")'),
123
+ mode_lock: z.enum(['NARRATIVE', 'HYBRID']).describe('NARRATIVE for text search, HYBRID for text + any available metrics'),
124
+ scope_lock: z.enum(['COMPANY', 'INDUSTRY', 'MACRO']).describe('COMPANY for specific tickers, INDUSTRY for SIC sector-wide, MACRO for cross-market'),
125
+ top_k: z.number().min(1).max(50).default(5).describe('Number of results to return (default: 5)'),
126
+ tickers: z.array(z.string()).optional().describe('Limit to specific companies by ticker (e.g., ["AAPL", "MSFT"])'),
127
+ doc_types: z.array(z.string()).optional().describe('Limit by SEC form type: "10-k", "10-q", "8-k"'),
128
+ sic_codes: z.array(z.string()).optional().describe('Limit by SIC industry code (e.g., "7372", "3674")'),
129
+ },
130
+ }, async (input) => {
131
+ if (containsGuardrailBypass(input))
132
+ return guardrailViolationResponse();
133
+ try {
134
+ log('debug', 'Executing search', {
135
+ query: input.query,
136
+ mode_lock: input.mode_lock,
137
+ scope_lock: input.scope_lock,
138
+ top_k: input.top_k,
139
+ });
140
+ const result = await client.search(input);
141
+ const searchResults = Array.isArray(result.narrative_results)
142
+ ? result.narrative_results
143
+ : Array.isArray(result.results)
144
+ ? result.results
145
+ : [];
146
+ const xbrlMetrics = Array.isArray(result.xbrl_metrics)
147
+ ? result.xbrl_metrics
148
+ : [];
149
+ const resultCount = searchResults.length;
150
+ if (resultCount === 0 && xbrlMetrics.length === 0) {
151
+ let text = `No results found for: "${input.query}"`;
152
+ text += '\nTry broadening your query or removing filters.';
153
+ return { content: [{ type: 'text', text }] };
154
+ }
155
+ // Format narrative citations
156
+ const sections = [];
157
+ if (resultCount > 0) {
158
+ const formatted = searchResults
159
+ .slice(0, input.top_k || 5)
160
+ .map((r, i) => {
161
+ const formType = r.form_type || r.doc_subtype || '';
162
+ const score = r.final_score ?? r.similarity_score;
163
+ const lines = [
164
+ `[${i + 1}] ${r.ticker || 'N/A'} — ${r.company_name || 'Unknown'} (${formType})`,
165
+ ` Filed: ${r.filing_date || 'N/A'} | Section: ${r.item_code || 'N/A'} | Score: ${typeof score === 'number' ? score.toFixed(3) : 'N/A'}`,
166
+ ];
167
+ if (r.fiscal_year && r.fiscal_period) {
168
+ lines[1] += ` | ${r.fiscal_period} FY${r.fiscal_year}`;
169
+ }
170
+ if (r.chunk_text) {
171
+ const snippet = r.chunk_text.substring(0, 600);
172
+ lines.push(` ${snippet}${r.chunk_text.length > 600 ? '...' : ''}`);
173
+ }
174
+ const url = r.sec_url || r.source_url;
175
+ if (url) {
176
+ lines.push(` SEC URL: ${url}`);
177
+ }
178
+ return lines.join('\n');
179
+ })
180
+ .join('\n\n');
181
+ sections.push(`SEC Filing Citations (${resultCount}):\n\n${formatted}`);
182
+ }
183
+ // Format XBRL metrics if present (handles both wide-format and long-format rows)
184
+ if (xbrlMetrics.length > 0) {
185
+ const colNames = Object.keys(xbrlMetrics[0] || {}).filter((k) => !k.startsWith('_'));
186
+ const header = colNames.join(' | ');
187
+ const separator = colNames.map(() => '---').join(' | ');
188
+ const displayRows = xbrlMetrics.slice(0, 20);
189
+ const tableRows = displayRows.map((row) => colNames.map((col) => {
190
+ const val = row[col];
191
+ if (val === null || val === undefined)
192
+ return 'N/A';
193
+ if (typeof val === 'number')
194
+ return Number.isInteger(val) ? String(val) : val.toFixed(2);
195
+ return String(val);
196
+ }).join(' | '));
197
+ const table = [header, separator, ...tableRows].join('\n');
198
+ const suffix = xbrlMetrics.length > 20
199
+ ? `\n(Showing 20 of ${xbrlMetrics.length} rows)`
200
+ : '';
201
+ sections.push(`XBRL Metrics (${xbrlMetrics.length}):\n\n${table}${suffix}`);
202
+ }
203
+ log('info', `Search returned ${resultCount} citations, ${xbrlMetrics.length} metrics`);
204
+ return {
205
+ content: [{
206
+ type: 'text',
207
+ text: sections.join('\n\n---\n\n'),
208
+ }],
209
+ };
210
+ }
211
+ catch (error) {
212
+ log('error', 'Search failed', { error: error instanceof Error ? error.message : error });
213
+ return {
214
+ content: [{ type: 'text', text: `Search failed: ${error instanceof Error ? error.message : 'Unknown error'}` }],
215
+ isError: true,
216
+ };
217
+ }
218
+ });
219
+ // Tool 3: Execute Metrics (XBRL structured data)
220
+ server.registerTool('etsquare_execute_metrics', {
221
+ title: 'Execute Financial Metrics Query',
222
+ description: 'Execute an XBRL metrics template by template_id to get structured financial data ' +
223
+ '(numbers, ratios, time series). Returns tabular rows with columns like revenue, margins, EPS, etc.\n\n' +
224
+ 'Common bind_params: p_ticker (company ticker), p_sic_code (industry), ' +
225
+ 'p_start_year / p_end_year (time range, e.g., 2023-2025).',
226
+ inputSchema: {
227
+ template_id: z.string().min(10).describe('Template ID for metrics execution'),
228
+ bind_params: z.record(z.unknown()).optional().describe('Template parameters as key-value pairs'),
229
+ row_limit: z.number().min(1).max(500).optional().describe('Max rows to return (default: 100)'),
230
+ },
231
+ }, async (input) => {
232
+ if (containsGuardrailBypass(input))
233
+ return guardrailViolationResponse();
234
+ try {
235
+ log('debug', 'Executing metrics template', { template_id: input.template_id });
236
+ const result = await client.executeMetrics(input);
237
+ const rows = result.rows || [];
238
+ const rowCount = result.row_count || rows.length;
239
+ const truncated = result.truncated || false;
240
+ const columns = result.columns || [];
241
+ if (rowCount === 0) {
242
+ return {
243
+ content: [{
244
+ type: 'text',
245
+ text: `No data returned for template ${input.template_id}. Check bind_params (e.g., p_ticker, p_start_year, p_end_year).`,
246
+ }],
247
+ };
248
+ }
249
+ const colNames = columns.length > 0 ? columns.map((c) => c.name) : Object.keys(rows[0] || {});
250
+ const header = colNames.join(' | ');
251
+ const separator = colNames.map(() => '---').join(' | ');
252
+ const displayRows = rows.slice(0, 20);
253
+ const tableRows = displayRows.map((row) => colNames.map((col) => {
254
+ const val = row[col];
255
+ if (val === null || val === undefined)
256
+ return 'N/A';
257
+ if (typeof val === 'number')
258
+ return Number.isInteger(val) ? String(val) : val.toFixed(2);
259
+ return String(val);
260
+ }).join(' | '));
261
+ const table = [header, separator, ...tableRows].join('\n');
262
+ const suffix = truncated || rows.length > 20
263
+ ? `\n\n(Showing ${displayRows.length} of ${rowCount} rows${truncated ? ', results truncated' : ''})`
264
+ : '';
265
+ log('info', `Metrics query returned ${rowCount} rows`);
266
+ return {
267
+ content: [{
268
+ type: 'text',
269
+ text: `Metrics data (${rowCount} rows):\n\n${table}${suffix}`,
270
+ }],
271
+ };
272
+ }
273
+ catch (error) {
274
+ log('error', 'Metrics execution failed', { error: error instanceof Error ? error.message : error });
275
+ return {
276
+ content: [{ type: 'text', text: `Metrics query failed: ${error instanceof Error ? error.message : 'Unknown error'}` }],
277
+ isError: true,
278
+ };
279
+ }
280
+ });
281
+ // Tool 4: Discover Metrics Templates
282
+ server.registerTool('etsquare_discover_metrics', {
283
+ title: 'Discover Financial Metrics Templates',
284
+ description: 'Find available XBRL metrics templates by business question. ' +
285
+ 'Returns template IDs and metadata — use the template_id with etsquare_execute_metrics.\n\n' +
286
+ 'Example: "revenue trend by quarter" → returns matching templates with their required bind_params.\n\n' +
287
+ 'Workflow: discover_metrics → pick template_id → execute_metrics with bind_params.',
288
+ inputSchema: {
289
+ question: z.string().min(3).describe('Business question (e.g., "revenue trend by quarter", "profit margins comparison")'),
290
+ scenario: z.enum(['snapshot', 'trends', 'peer_benchmark']).optional().describe('Filter by scenario type'),
291
+ metric_family: z.string().optional().describe('Filter by metric family: REVENUE, EARNINGS, PROFITABILITY_MARGIN, LEVERAGE_DEBT, FREE_CASH_FLOW, LIQUIDITY'),
292
+ max_results: z.number().min(1).max(10).default(5).optional().describe('Max templates to return'),
293
+ },
294
+ }, async (input) => {
295
+ if (containsGuardrailBypass(input))
296
+ return guardrailViolationResponse();
297
+ try {
298
+ log('debug', 'Discovering metrics templates', { question: input.question });
299
+ const result = await client.discoverMetrics(input);
300
+ const templates = Array.isArray(result.templates)
301
+ ? result.templates
302
+ : [];
303
+ if (templates.length === 0) {
304
+ return {
305
+ content: [{
306
+ type: 'text',
307
+ text: `No metrics templates found for: "${input.question}"\nTry a different question or remove filters.`,
308
+ }],
309
+ };
310
+ }
311
+ const formatted = templates
312
+ .map((t, i) => {
313
+ const lines = [
314
+ `[${i + 1}] ${t.title || 'Untitled'}`,
315
+ ` template_id: ${t.template_id}`,
316
+ ];
317
+ if (t.description)
318
+ lines.push(` ${t.description}`);
319
+ if (t.scenario)
320
+ lines.push(` Scenario: ${t.scenario}`);
321
+ if (t.category)
322
+ lines.push(` Category: ${t.category}`);
323
+ if (t.required_params && Object.keys(t.required_params).length > 0) {
324
+ const params = Object.entries(t.required_params)
325
+ .map(([k, v]) => {
326
+ if (v && typeof v === 'object') {
327
+ const ex = v.example !== undefined ? `, e.g. ${JSON.stringify(v.example)}` : '';
328
+ return `${k} (${v.type || 'string'}${ex})`;
329
+ }
330
+ return `${k} (${v})`;
331
+ })
332
+ .join(', ');
333
+ lines.push(` Required params: ${params}`);
334
+ }
335
+ if (Array.isArray(t.columns) && t.columns.length > 0) {
336
+ lines.push(` Output columns: ${t.columns.join(', ')}`);
337
+ }
338
+ return lines.join('\n');
339
+ })
340
+ .join('\n\n');
341
+ log('info', `Discovered ${templates.length} metrics templates`);
342
+ return {
343
+ content: [{
344
+ type: 'text',
345
+ text: `Found ${templates.length} metrics template(s) for: "${input.question}"\n\n${formatted}\n\nUse etsquare_execute_metrics with the template_id and required bind_params to get data.`,
346
+ }],
347
+ };
348
+ }
349
+ catch (error) {
350
+ log('error', 'Metrics discovery failed', { error: error instanceof Error ? error.message : error });
351
+ return {
352
+ content: [{ type: 'text', text: `Template discovery failed: ${error instanceof Error ? error.message : 'Unknown error'}` }],
353
+ isError: true,
354
+ };
355
+ }
356
+ });
357
+ // Start server
358
+ const transport = new StdioServerTransport();
359
+ await server.connect(transport);
360
+ log('info', 'ETSquare MCP Server ready and listening on stdio');
@@ -0,0 +1,69 @@
1
+ import { z } from 'zod';
2
+ export declare const searchSchema: z.ZodObject<{
3
+ query: z.ZodString;
4
+ mode_lock: z.ZodEnum<["NARRATIVE", "HYBRID"]>;
5
+ scope_lock: z.ZodEnum<["COMPANY", "INDUSTRY", "MACRO"]>;
6
+ top_k: z.ZodOptional<z.ZodNumber>;
7
+ tickers: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
8
+ sic_codes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ doc_types: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ query: string;
12
+ mode_lock: "NARRATIVE" | "HYBRID";
13
+ scope_lock: "COMPANY" | "INDUSTRY" | "MACRO";
14
+ top_k?: number | undefined;
15
+ tickers?: string[] | undefined;
16
+ sic_codes?: string[] | undefined;
17
+ doc_types?: string[] | undefined;
18
+ }, {
19
+ query: string;
20
+ mode_lock: "NARRATIVE" | "HYBRID";
21
+ scope_lock: "COMPANY" | "INDUSTRY" | "MACRO";
22
+ top_k?: number | undefined;
23
+ tickers?: string[] | undefined;
24
+ sic_codes?: string[] | undefined;
25
+ doc_types?: string[] | undefined;
26
+ }>;
27
+ export declare const lookupCompanySchema: z.ZodObject<{
28
+ query: z.ZodString;
29
+ limit: z.ZodOptional<z.ZodNumber>;
30
+ }, "strip", z.ZodTypeAny, {
31
+ query: string;
32
+ limit?: number | undefined;
33
+ }, {
34
+ query: string;
35
+ limit?: number | undefined;
36
+ }>;
37
+ export declare const executeMetricsSchema: z.ZodObject<{
38
+ template_id: z.ZodString;
39
+ bind_params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
40
+ row_limit: z.ZodOptional<z.ZodNumber>;
41
+ }, "strip", z.ZodTypeAny, {
42
+ template_id: string;
43
+ bind_params?: Record<string, unknown> | undefined;
44
+ row_limit?: number | undefined;
45
+ }, {
46
+ template_id: string;
47
+ bind_params?: Record<string, unknown> | undefined;
48
+ row_limit?: number | undefined;
49
+ }>;
50
+ export declare const discoverMetricsSchema: z.ZodObject<{
51
+ question: z.ZodString;
52
+ scenario: z.ZodOptional<z.ZodEnum<["snapshot", "trends", "peer_benchmark"]>>;
53
+ metric_family: z.ZodOptional<z.ZodString>;
54
+ max_results: z.ZodOptional<z.ZodNumber>;
55
+ }, "strip", z.ZodTypeAny, {
56
+ question: string;
57
+ scenario?: "snapshot" | "trends" | "peer_benchmark" | undefined;
58
+ metric_family?: string | undefined;
59
+ max_results?: number | undefined;
60
+ }, {
61
+ question: string;
62
+ scenario?: "snapshot" | "trends" | "peer_benchmark" | undefined;
63
+ metric_family?: string | undefined;
64
+ max_results?: number | undefined;
65
+ }>;
66
+ export type SearchInput = z.infer<typeof searchSchema>;
67
+ export type LookupCompanyInput = z.infer<typeof lookupCompanySchema>;
68
+ export type ExecuteMetricsInput = z.infer<typeof executeMetricsSchema>;
69
+ export type DiscoverMetricsInput = z.infer<typeof discoverMetricsSchema>;
package/dist/types.js ADDED
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ export const searchSchema = z.object({
3
+ query: z.string().min(3, 'query must be at least 3 characters'),
4
+ mode_lock: z.enum(['NARRATIVE', 'HYBRID']),
5
+ scope_lock: z.enum(['COMPANY', 'INDUSTRY', 'MACRO']),
6
+ top_k: z.number().min(1).max(50).optional(),
7
+ tickers: z.array(z.string()).optional(),
8
+ sic_codes: z.array(z.string()).optional(),
9
+ doc_types: z.array(z.string()).optional(),
10
+ });
11
+ export const lookupCompanySchema = z.object({
12
+ query: z.string().min(2, 'query must be at least 2 characters'),
13
+ limit: z.number().min(1).max(10).optional(),
14
+ });
15
+ export const executeMetricsSchema = z.object({
16
+ template_id: z.string().min(10, 'template_id is required'),
17
+ bind_params: z.record(z.unknown()).optional(),
18
+ row_limit: z.number().min(1).max(500).optional(),
19
+ });
20
+ export const discoverMetricsSchema = z.object({
21
+ question: z.string().min(3, 'question must be at least 3 characters'),
22
+ scenario: z.enum(['snapshot', 'trends', 'peer_benchmark']).optional(),
23
+ metric_family: z.string().optional(),
24
+ max_results: z.number().min(1).max(10).optional(),
25
+ });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@etsquare/mcp-server-sec",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "description": "MCP server for Claude Desktop: search 1.2M+ SEC filing sections with hybrid retrieval, XBRL templates, and company lookup.",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "etsquare-mcp-sec": "./dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "keywords": [
15
+ "mcp",
16
+ "mcp-server",
17
+ "claude",
18
+ "sec",
19
+ "sec-filings",
20
+ "xbrl",
21
+ "financial-analysis",
22
+ "search",
23
+ "hybrid-search"
24
+ ],
25
+ "author": "ETSquare",
26
+ "license": "MIT",
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "start": "node dist/index.js",
30
+ "dev": "ts-node src/index.ts"
31
+ },
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.0.0",
34
+ "zod": "^3.23.8"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^20.11.30",
38
+ "typescript": "^5.4.0",
39
+ "ts-node": "^10.9.2"
40
+ }
41
+ }