@etsquare/sev-mcp 0.1.1 → 0.1.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/README.md +15 -13
- package/client.mjs +70 -2
- package/index.mjs +2 -2
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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@
|
|
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
|
|
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@
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
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:
|
|
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)
|
|
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,6 +3,70 @@ 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 {
|
|
7
71
|
constructor({baseURL=process.env.SEV_BASE_URL||'https://etsquare.ai', apiKey=process.env.SEV_API_KEY,
|
|
8
72
|
termsVersion=process.env.SEV_ACCEPT_TERMS_VERSION, fetchImpl=globalThis.fetch, timeout=20000}={}) {
|
|
@@ -32,8 +96,12 @@ export class SevClient {
|
|
|
32
96
|
const reader=response.body?.getReader();let size=0;const parts=[];
|
|
33
97
|
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
98
|
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{
|
|
36
|
-
|
|
99
|
+
let result;try{result=JSON.parse(new TextDecoder().decode(bytes));}catch{
|
|
100
|
+
if(!response.ok)throw publicFailure(null,response.status,body);
|
|
101
|
+
throw new ApiError('invalid_response','SEV returned an invalid JSON response. No automatic retry was made.');
|
|
102
|
+
}
|
|
103
|
+
if(!response.ok||result?.status==='error')throw publicFailure(result,response.status,body);
|
|
104
|
+
if(!result||typeof result!=='object'||Array.isArray(result))throw new ApiError('invalid_response','SEV returned an invalid response. No automatic retry was made.');
|
|
37
105
|
return result;
|
|
38
106
|
} catch(error) {
|
|
39
107
|
if(error instanceof ApiError)throw error;
|
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.
|
|
22
|
+
const server=new McpServer({name:'sev',version:'0.1.2'},{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;
|
|
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'},
|