@etsquare/sev-mcp 0.1.1 → 0.1.3

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/README.md CHANGED
@@ -10,16 +10,16 @@ SEV searches its supported SEC corpus and returns a ranked company shortlist wit
10
10
  2. Sign in at [etsquare.ai/account](https://etsquare.ai/account), accept the released Terms, and create an API key. Save its secret when shown.
11
11
  3. Add one of the configurations below, replace YOUR_SEV_API_KEY, and restart your MCP client. Keep your other server entries.
12
12
 
13
- The first start downloads this version from npm. No source checkout is required. The [Build page](https://etsquare.ai/build) shows the same setup for your platform.
13
+ Each new start uses npm with --prefer-online to check the latest published version. Restart your MCP client after an update; an already-running process does not hot-update. No source checkout is required. The [Build page](https://etsquare.ai/build) shows the same setup for your platform.
14
14
 
15
- ### Codex ? macOS / Linux
15
+ ### Codex - macOS / Linux
16
16
 
17
17
  Add to ~/.codex/config.toml:
18
18
 
19
19
  ```toml
20
20
  [mcp_servers.sev]
21
21
  command = "npx"
22
- args = ["-y","@etsquare/sev-mcp@0.1.1"]
22
+ args = ["-y","--prefer-online","@etsquare/sev-mcp@latest"]
23
23
  startup_timeout_sec = 30
24
24
  tool_timeout_sec = 25
25
25
 
@@ -28,12 +28,12 @@ SEV_BASE_URL = "https://etsquare.ai"
28
28
  SEV_API_KEY = "YOUR_SEV_API_KEY"
29
29
  ```
30
30
 
31
- ### Codex ? Windows
31
+ ### Codex - Windows
32
32
 
33
33
  ```toml
34
34
  [mcp_servers.sev]
35
35
  command = "cmd"
36
- args = ["/c","npx","-y","@etsquare/sev-mcp@0.1.1"]
36
+ args = ["/c","npx","-y","--prefer-online","@etsquare/sev-mcp@latest"]
37
37
  startup_timeout_sec = 30
38
38
  tool_timeout_sec = 25
39
39
 
@@ -42,7 +42,7 @@ SEV_BASE_URL = "https://etsquare.ai"
42
42
  SEV_API_KEY = "YOUR_SEV_API_KEY"
43
43
  ```
44
44
 
45
- ### Claude Desktop ? macOS / Linux
45
+ ### Claude Desktop - macOS / Linux
46
46
 
47
47
  Merge into your Claude Desktop configuration JSON:
48
48
 
@@ -53,7 +53,8 @@ Merge into your Claude Desktop configuration JSON:
53
53
  "command": "npx",
54
54
  "args": [
55
55
  "-y",
56
- "@etsquare/sev-mcp@0.1.1"
56
+ "--prefer-online",
57
+ "@etsquare/sev-mcp@latest"
57
58
  ],
58
59
  "env": {
59
60
  "SEV_BASE_URL": "https://etsquare.ai",
@@ -64,7 +65,7 @@ Merge into your Claude Desktop configuration JSON:
64
65
  }
65
66
  ```
66
67
 
67
- ### Claude Desktop ? Windows
68
+ ### Claude Desktop - Windows
68
69
 
69
70
  ```json
70
71
  {
@@ -75,7 +76,8 @@ Merge into your Claude Desktop configuration JSON:
75
76
  "/c",
76
77
  "npx",
77
78
  "-y",
78
- "@etsquare/sev-mcp@0.1.1"
79
+ "--prefer-online",
80
+ "@etsquare/sev-mcp@latest"
79
81
  ],
80
82
  "env": {
81
83
  "SEV_BASE_URL": "https://etsquare.ai",
@@ -91,11 +93,11 @@ Merge into your Claude Desktop configuration JSON:
91
93
  - **discover_sec_companies**: required question; optional tickers **or** four-digit sic_codes, inclusive filed_since (YYYY-MM-DD), and max_companies (default 10, maximum 50). Explicit filters are never relaxed. There is no hidden date default.
92
94
  - **get_sec_passage**: retrieve a returned passage_id and available neighboring context without restarting discovery. Clients supporting MCP resources can also read sev://passages/{passage_id}.
93
95
 
94
- Passage IDs accept uppercase, lowercase or mixed-case hexadecimal. Returned IDs may use their canonical uppercase form.
95
-
96
+ Passage IDs accept uppercase, lowercase or mixed-case hexadecimal. Returned IDs may use their canonical uppercase form.
97
+
96
98
  Returned references include company identity, accession, form, filing date, section, source URL and stable passage identifier. Optional previews are verbatim. Rankings describe research relevance, not investment merit or proof of current impact.
97
99
 
98
- Try: ?Which retailers describe overseas factories or sourcing agents and exposure to higher merchandise costs??
100
+ Try: "Which retailers describe overseas factories or sourcing agents and exposure to higher merchandise costs?"
99
101
 
100
102
  ## Access and timeouts
101
103
 
@@ -111,6 +113,6 @@ Anonymous use is optional and requires explicit acceptance of the service's rele
111
113
 
112
114
  ## Support
113
115
 
114
- [Documentation](https://etsquare.ai/documentation) ? [Terms](https://etsquare.ai/terms) ? [Privacy](https://etsquare.ai/privacy) ? [Support](https://etsquare.ai/support)
116
+ [Documentation](https://etsquare.ai/documentation) - [Terms](https://etsquare.ai/terms) - [Privacy](https://etsquare.ai/privacy) - [Support](https://etsquare.ai/support)
115
117
 
116
118
  MIT licensed thin client. This package is separate from the historical @etsquare/mcp-server-sec package.
package/client.mjs CHANGED
@@ -3,7 +3,72 @@ export class ApiError extends Error {
3
3
  constructor(code, message, status=0) {super(message);this.code=code;this.status=status;}
4
4
  }
5
5
 
6
+ // Never forward provider/backend error text or unknown error codes to MCP callers.
7
+ const PUBLIC_ERRORS = Object.freeze({
8
+ invalid_request:'Check the question, filters and requested result count.',
9
+ invalid_passage_id:'Use an original passage_id returned by SEV discovery.',
10
+ passage_not_found:'The original SEC passage is unavailable. Check the passage_id returned by discovery.',
11
+ unresolved_filter:'An explicit company or industry filter could not be resolved. Check the supplied filters.',
12
+ ambiguous_ticker:'An explicit ticker is ambiguous. Check the company filter.',
13
+ request_too_large:'The request is too large. Shorten the question or filters.',
14
+ authentication_required:'Configure your SEV API key to access this operation.',
15
+ authentication_failed:'Check that your SEV API key is valid and has not been revoked.',
16
+ insufficient_scope:'Your authorization does not permit this operation. Check your SEV connection.',
17
+ account_unavailable:'This SEV account is unavailable. Check your account at https://etsquare.ai/account.',
18
+ email_verification_required:'Verify your email address in your SEV account before continuing.',
19
+ rate_limited:'The request rate limit was reached. Try again shortly; no automatic retry was made.',
20
+ account_busy:'Your account already has research requests running. Wait for them to finish.',
21
+ service_busy:'SEV is busy. Try again later; no automatic retry was made.',
22
+ request_timeout:'SEV did not complete the request in time. No automatic retry was made; submitted work may still complete.',
23
+ quota_exhausted:'Your research allowance is exhausted. Check your SEV account allowance. Source passage retrieval does not consume a research request.',
24
+ anonymous_allowance_exhausted:'Your public trial allowance is exhausted. Sign in to SEV for account access.',
25
+ anonymous_capacity_reached:'Public trial capacity is exhausted for today. Sign in to SEV for account access.',
26
+ agreement_required:'Review and accept the current SEV terms in your account before continuing.',
27
+ legal_setup_pending:'SEV account setup is not ready. Try again later.',
28
+ subscription_inactive:'Your account entitlement is inactive. Check your SEV account.',
29
+ invalid_idempotency_key:'The request identifier is invalid. Check the client request identifier.',
30
+ idempotency_conflict:'This request identifier was used for different research. No additional discovery was started.',
31
+ request_already_recorded:'This request is already recorded. No additional discovery was started.',
32
+ origin_not_allowed:'The request origin is not permitted. Check your SEV connection configuration.',
33
+ interactive_signin_required:'Sign in to your SEV account to perform this operation.',
34
+ service_unavailable:'SEV is temporarily unavailable. Please try again later.',
35
+ });
36
+ function publicFailure(value, status, request) {
37
+ const supplied=value?.error?.code;
38
+ const fallback=status===401?'authentication_required':status===403?'insufficient_scope':
39
+ status===504?'request_timeout':status===429?'rate_limited':status===413?'request_too_large':
40
+ (status===400||status===422)?'invalid_request':'service_unavailable';
41
+ const code=typeof supplied==='string'&&Object.hasOwn(PUBLIC_ERRORS,supplied)?supplied:fallback;
42
+ let message=PUBLIC_ERRORS[code];
43
+ if(request&&(code==='unresolved_filter'||code==='ambiguous_ticker')) {
44
+ const details=value?.error?.invalid_filters;
45
+ const legacy=value?.error?.message;
46
+ let candidates=[];
47
+ // Legacy backend compatibility: only identifiers from known fixed formats.
48
+ if(typeof legacy==='string'&&legacy.length<=2048) {
49
+ const prefix='Explicit universe values were not found in the primary company catalog: ';
50
+ if(code==='unresolved_filter'&&legacy.startsWith(prefix))candidates=legacy.slice(prefix.length).split(', ');
51
+ if(code==='ambiguous_ticker') {
52
+ const match=legacy.match(/^Ticker ([A-Z0-9][A-Z0-9.\-^/]{0,19}) does not resolve to one CIK\.$/);
53
+ if(match)candidates=[match[1]];
54
+ }
55
+ }
56
+ const identified=[];
57
+ for(const [field,pattern] of [['tickers',/^[A-Z0-9][A-Z0-9.\-^/]{0,19}$/],['sic_codes',/^[0-9]{4}$/]]) {
58
+ if(code==='ambiguous_ticker'&&field!=='tickers')continue;
59
+ const supplied=Array.isArray(request[field])?request[field].slice(0,50):[];
60
+ const reported=Array.isArray(details?.[field])?details[field].slice(0,50):candidates;
61
+ const values=[...new Set(supplied.filter(v=>typeof v==='string').map(v=>v.trim().toUpperCase())
62
+ .filter(v=>pattern.test(v)&&reported.includes(v)))];
63
+ if(values.length)identified.push(field+': '+values.join(', '));
64
+ }
65
+ if(identified.length)message+=' Check '+identified.join('; ')+'.';
66
+ }
67
+ return new ApiError(code,message,status);
68
+ }
69
+
6
70
  export class SevClient {
71
+ #passageTail=Promise.resolve();
7
72
  constructor({baseURL=process.env.SEV_BASE_URL||'https://etsquare.ai', apiKey=process.env.SEV_API_KEY,
8
73
  termsVersion=process.env.SEV_ACCEPT_TERMS_VERSION, fetchImpl=globalThis.fetch, timeout=20000}={}) {
9
74
  const url=new URL(baseURL);
@@ -32,8 +97,12 @@ export class SevClient {
32
97
  const reader=response.body?.getReader();let size=0;const parts=[];
33
98
  if(reader){try{for(;;){const {done,value}=await reader.read();if(done)break;size+=value.length;if(size>4194304)throw new ApiError('invalid_response','The SEV response exceeded the supported size.');parts.push(value);}}finally{await reader.cancel();}}
34
99
  const bytes=new Uint8Array(size);let offset=0;for(const part of parts){bytes.set(part,offset);offset+=part.length;}
35
- let result;try{result=JSON.parse(new TextDecoder().decode(bytes));}catch{throw new ApiError('invalid_response','SEV returned an invalid JSON response.');}
36
- if(!response.ok||result.status==='error')throw new ApiError(result.error?.code||'http_error',result.error?.message||'The SEV request failed.',response.status);
100
+ let result;try{result=JSON.parse(new TextDecoder().decode(bytes));}catch{
101
+ if(!response.ok)throw publicFailure(null,response.status,body);
102
+ throw new ApiError('invalid_response','SEV returned an invalid JSON response. No automatic retry was made.');
103
+ }
104
+ if(!response.ok||result?.status==='error')throw publicFailure(result,response.status,body);
105
+ if(!result||typeof result!=='object'||Array.isArray(result))throw new ApiError('invalid_response','SEV returned an invalid response. No automatic retry was made.');
37
106
  return result;
38
107
  } catch(error) {
39
108
  if(error instanceof ApiError)throw error;
@@ -50,9 +119,24 @@ export class SevClient {
50
119
  }
51
120
  async passage(id,signal){
52
121
  if(!/^[a-f0-9]{32}$/i.test(id))throw new ApiError('invalid_passage_id','Use the passage_id returned by discovery.',400);
53
- const result=await this.request('/v1/sec/passages/'+encodeURIComponent(id),undefined,signal);
54
- if(typeof result.passage_id!=='string'||result.passage_id.toUpperCase()!==id.toUpperCase()||typeof result.text!=='string'||!Array.isArray(result.adjacent_passages))
55
- throw new ApiError('invalid_response','SEV returned an invalid source response.');
56
- return result;
122
+ const cancelled=()=>new ApiError('request_cancelled','Stopped waiting. Work already submitted may continue on the server.');
123
+ if(signal?.aborted)throw cancelled();
124
+ let onAbort;
125
+ const cancellation=signal?new Promise((_,reject)=>{
126
+ onAbort=()=>reject(cancelled());signal.addEventListener('abort',onAbort,{once:true});
127
+ }):null;
128
+ const run=this.#passageTail.then(async()=>{
129
+ // Once sent, request() owns cancellation and starts its existing timeout.
130
+ if(onAbort)signal.removeEventListener('abort',onAbort);
131
+ if(signal?.aborted)throw cancelled();
132
+ const result=await this.request('/v1/sec/passages/'+encodeURIComponent(id),undefined,signal);
133
+ if(typeof result.passage_id!=='string'||result.passage_id.toUpperCase()!==id.toUpperCase()||typeof result.text!=='string'||!Array.isArray(result.adjacent_passages))
134
+ throw new ApiError('invalid_response','SEV returned an invalid source response.');
135
+ return result;
136
+ });
137
+ // Absorb failure only on the queue tail; each caller still gets its own error.
138
+ this.#passageTail=run.then(()=>undefined,()=>undefined);
139
+ try{return await (cancellation?Promise.race([run,cancellation]):run);}
140
+ finally{if(onAbort)signal.removeEventListener('abort',onAbort);}
57
141
  }
58
142
  }
package/index.mjs CHANGED
@@ -19,7 +19,7 @@ const source=z.object({passage_id:z.string().regex(/^[A-Fa-f0-9]{32}$/)}).strict
19
19
  const text=value=>({content:[{type:'text',text:JSON.stringify(value)}],structuredContent:value});
20
20
 
21
21
  export function createServer(client=new SevClient()) {
22
- const server=new McpServer({name:'sev',version:'0.1.1'},{instructions:'SEV finds companies and original SEC passages worth investigating. Rankings indicate research relevance, not investment merit or proof of impact. Follow original sources. Do not automatically repeat a failed or uncertain discovery; it may already have used allowance. Use get_sec_passage for full text and adjacent context.'});
22
+ const server=new McpServer({name:'sev',version:'0.1.3'},{instructions:'SEV finds companies and original SEC passages worth investigating. Rankings indicate research relevance, not investment merit or proof of impact. Follow original sources. Do not automatically repeat a failed or uncertain discovery; it may already have used allowance. Use get_sec_passage for full text and adjacent context.'});
23
23
  const call=operation=>async(input,extra)=>{
24
24
  try{return text(await operation(input,extra.signal));}
25
25
  catch(error){const known=error instanceof ApiError;return {...text({status:'error',error:{code:known?error.code:'client_error',message:known?error.message:'The SEV client could not complete the request.'}}),isError:true};}
@@ -29,7 +29,7 @@ export function createServer(client=new SevClient()) {
29
29
  inputSchema:discovery,annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:false,openWorldHint:true},
30
30
  },call((input,signal)=>client.discover(input,signal)));
31
31
  server.registerTool('get_sec_passage',{
32
- title:'Read discovered SEC passage',description:'Retrieve an original passage and available adjacent same-section context using a passage_id from discovery. Supporting source retrieval; no new search or JEV call.',
32
+ title:'Read discovered SEC passage',description:'Retrieve an original passage and available adjacent same-section context using a passage_id from discovery. Supporting source retrieval; does not consume a research allowance unit.',
33
33
  inputSchema:source,annotations:{readOnlyHint:true,destructiveHint:false,idempotentHint:true,openWorldHint:true},
34
34
  },call((input,signal)=>client.passage(input.passage_id,signal)));
35
35
  server.registerResource('sec-passage',new ResourceTemplate('sev://passages/{passage_id}',{list:undefined}),{mimeType:'application/json'},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsquare/sev-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "SEV company discovery and original SEC passage access over hosted REST.",
5
5
  "type": "module",
6
6
  "license": "MIT",