equisense-research-mcp 0.2.0 → 0.3.1

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.
Files changed (3) hide show
  1. package/README.md +13 -30
  2. package/index.js +15 -16
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,7 +4,7 @@ Thin Node.js MCP wrapper that exposes the EquiSense AI equity-research engine as
4
4
 
5
5
  | Tool | Endpoint | What it does |
6
6
  |---|---|---|
7
- | `ask_research` | `POST /api/v1/research/ask` | Natural-language Q&A over Indian-listed companies. Returns answer, detected company, intent, follow-up suggestions. |
7
+ | `ask_equisense` | `POST /api/v1/research/ask` | Natural-language Q&A over Indian-listed companies. Returns answer, detected company, intent, follow-up suggestions. |
8
8
 
9
9
  Same brain the WhatsApp chat uses, just over MCP instead of WhatsApp.
10
10
 
@@ -14,30 +14,13 @@ Published on npm: [`equisense-research-mcp`](https://www.npmjs.com/package/equis
14
14
 
15
15
  ## Quick start
16
16
 
17
- ### 1. Mint a token
17
+ ### 1. Generate a token (self-serve)
18
18
 
19
- The MCP authenticates via a 90-day HMAC bearer token, byte-identical to a logged-in `ES_AUTH` session cookie. Mint it via the admin endpoint (admin-IP-restricted in prod via nginx; reachable on localhost in dev):
19
+ Log in to the EquiSense web app and open **Settings Claude → Connect Claude**. You get a **research-scoped, individually-revocable** token, shown once. Copy it (the web UI even pre-fills the full config block for you).
20
20
 
21
- ```bash
22
- # Prod (must be on an admin-allowlisted IP)
23
- curl -X POST "https://equisense.ai/api/v1/admin/auth/mint-mcp-token?phoneNumber=9876543210"
24
-
25
- # Dev
26
- curl -X POST "http://localhost:8080/api/v1/admin/auth/mint-mcp-token?phoneNumber=9876543210"
27
- ```
28
-
29
- Response:
30
- ```json
31
- {
32
- "token": "<payload>.<sig>",
33
- "expiresAt": 1777777777,
34
- "userId": "user-abc",
35
- "phone": "9876543210",
36
- "ttlSeconds": 7776000
37
- }
38
- ```
21
+ The token authenticates as `Authorization: Bearer <token>` and is limited to the research API — it cannot trade, change account settings, or mint further tokens. Revoke it any time from the same Settings page; revocation takes effect immediately.
39
22
 
40
- Copy the `token`. Treat it like a password.
23
+ > The old admin endpoint `POST /api/v1/admin/auth/mint-mcp-token` is **deprecated** — it issued full-account session tokens with no per-token revocation. Use Settings → Claude instead.
41
24
 
42
25
  ### 2. Register the MCP with Claude Code
43
26
 
@@ -46,7 +29,7 @@ Copy the `token`. Treat it like a password.
46
29
  ```bash
47
30
  claude mcp add equisense-research --scope local \
48
31
  --env EQUISENSE_BASE_URL=https://equisense.ai \
49
- --env EQUISENSE_MCP_TOKEN='<token from step 1>' \
32
+ --env EQUISENSE_MCP_TOKEN='<token from Settings → Claude>' \
50
33
  -- npx -y equisense-research-mcp
51
34
  ```
52
35
 
@@ -72,7 +55,7 @@ Should show `equisense-research - ✓ Connected`.
72
55
 
73
56
  In any Claude Code session:
74
57
 
75
- > Use ask_research: bull case for OLAELEC
58
+ > Use ask_equisense: bull case for OLAELEC
76
59
 
77
60
  You should get back `{ answer, companyName, isin, detectedIntent, followUpQuestions, responseTimeMs }`.
78
61
 
@@ -84,14 +67,15 @@ You should get back `{ answer, companyName, isin, detectedIntent, followUpQuesti
84
67
  |---|---|---|
85
68
  | `EQUISENSE_BASE_URL` | `http://localhost:8080` | Backend root URL |
86
69
  | `EQUISENSE_TIMEOUT_MS` | `90000` | Per-request timeout (research queries can take 30–90s) |
87
- | `EQUISENSE_MCP_TOKEN` | **required** | HMAC bearer token. Treat like a password. |
70
+ | `EQUISENSE_MCP_TOKEN` | **required** | Scoped bearer token from Settings → Claude. Treat like a password. |
88
71
 
89
72
  ## Security notes
90
73
 
91
- - The minted token grants **full session access** for 90 days, scope-equivalent to a logged-in browser session for that user. Not scoped to research-only.
92
- - There is no per-token revocation in v1. The only way to invalidate a token early is to rotate the global `auth.token.secret` (which logs out every user).
74
+ - The token is **research-scoped** it can only call the research API (`/api/v1/research/*`), enforced by the backend registering its authenticator only on those paths. It cannot trade, change account settings, or mint further tokens.
75
+ - The token is **individually revocable** from Settings → Claude. Revocation is immediate: a revoked token gets a `401` on the next call, with no signature-only fallback.
76
+ - Tokens last 90 days. The wrapper also sends the token as a legacy `Cookie: ES_AUTH` header so older admin-minted session tokens keep working during migration; new scoped tokens authenticate via `Authorization: Bearer`.
93
77
  - Never commit the token. Never log it. Never paste it into chat.
94
- - Each `ask_research` call counts against the represented user's `AI_EQUITY_RESEARCH` daily quota — same metering as the web UI.
78
+ - Each `ask_equisense` call counts against the user's `AI_EQUITY_RESEARCH` daily quota — same metering as the web UI.
95
79
 
96
80
  ---
97
81
 
@@ -128,12 +112,11 @@ The Java app (Spring Boot 3.1) is too old for the Spring AI MCP server starter (
128
112
  | Symptom | Likely cause | Fix |
129
113
  |---|---|---|
130
114
  | `FATAL: EQUISENSE_MCP_TOKEN env var is required` on startup | Env var unset or empty | Pass `--env EQUISENSE_MCP_TOKEN=...` to `claude mcp add` |
131
- | `Auth failed (HTTP 401) ... re-mint via /api/v1/admin/auth/mint-mcp-token` | Token > 90 days old OR `auth.token.secret` rotated | Re-mint and update the env |
115
+ | `Auth failed (HTTP 401)` | Token expired, revoked, or `auth.token.secret` rotated | Generate a fresh token in Settings → Claude and update the env |
132
116
  | `AI_EQUITY_RESEARCH quota exhausted (HTTP 402)` | Daily quota hit | Wait until tomorrow OR upgrade the user's plan |
133
117
  | `Rate limited (HTTP 429)` | Transient | Retry in a few seconds |
134
118
  | `Forbidden (HTTP 403)` | User doesn't have `AI_EQUITY_RESEARCH` feature | Check the user's license/plan |
135
119
  | Hangs > 90s with no response | Backend research query timed out | Bump `EQUISENSE_TIMEOUT_MS`; check backend logs |
136
- | `403` minting in prod | Your IP isn't admin-allowlisted | SSH to prod box and mint from there, or have an admin mint for you |
137
120
 
138
121
  ## License
139
122
 
package/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // EquiSense Research MCP server.
3
3
  //
4
- // Single tool: ask_research — proxies POST /api/v1/research/ask. The user mints a
4
+ // Single tool: ask_equisense — proxies POST /api/v1/research/ask. The user mints a
5
5
  // scoped, revocable token in the web app (Settings → Claude) and passes it here
6
6
  // via the EQUISENSE_MCP_TOKEN env var.
7
7
  //
@@ -46,12 +46,12 @@ if (!MCP_TOKEN || MCP_TOKEN.trim() === "") {
46
46
  // Client identity from the MCP initialize handshake (e.g. "claude-ai",
47
47
  // "Claude Code"). Captured once, forwarded as X-MCP-Client so the backend can
48
48
  // auto-name the connection in Settings → Claude. Null until the handshake lands
49
- // and when askResearch is exercised directly in unit tests.
49
+ // and when askEquisense is exercised directly in unit tests.
50
50
  let clientName = null;
51
51
 
52
52
  export const TOOLS = [
53
53
  {
54
- name: "ask_research",
54
+ name: "ask_equisense",
55
55
  description:
56
56
  "Ask EquiSense's AI equity-research engine a natural-language question " +
57
57
  "about an Indian-listed company (NSE/BSE). Returns the answer, the " +
@@ -150,7 +150,7 @@ function formatHttpError(path, status, bodyText) {
150
150
  return `EquiSense REST ${path} returned HTTP ${status}: ${snippet}`;
151
151
  }
152
152
 
153
- export async function askResearch(args) {
153
+ export async function askEquisense(args) {
154
154
  const body = {
155
155
  query: args.query,
156
156
  isin: args.isin ?? null,
@@ -171,14 +171,14 @@ export async function askResearch(args) {
171
171
  }
172
172
 
173
173
  const TOOL_HANDLERS = {
174
- ask_research: askResearch,
174
+ ask_equisense: askEquisense,
175
175
  };
176
176
 
177
177
  function createServer() {
178
178
  const server = new Server(
179
179
  {
180
180
  name: "equisense-research",
181
- version: "0.2.0",
181
+ version: "0.3.1",
182
182
  },
183
183
  {
184
184
  capabilities: {
@@ -187,21 +187,20 @@ function createServer() {
187
187
  },
188
188
  );
189
189
 
190
- // Capture the client identity once the initialize handshake completes, so
191
- // callRest can forward it as X-MCP-Client for auto-naming the connection.
192
- server.oninitialized = () => {
193
- try {
194
- clientName = server.getClientVersion()?.name || null;
195
- } catch {
196
- clientName = null;
197
- }
198
- };
199
-
200
190
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
201
191
  tools: TOOLS,
202
192
  }));
203
193
 
204
194
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
195
+ // Capture the client identity here rather than via the `initialized`
196
+ // notification: a tool call can only arrive after initialize has completed,
197
+ // so getClientVersion() is reliably populated by now. The notification
198
+ // callback raced the first call (it could fire after the call's fetch had
199
+ // already built its headers), leaving the connection un-named on first use.
200
+ try {
201
+ clientName = server.getClientVersion()?.name || clientName;
202
+ } catch { /* leave clientName as-is */ }
203
+
205
204
  const { name, arguments: args } = request.params;
206
205
  const handler = TOOL_HANDLERS[name];
207
206
  if (!handler) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "equisense-research-mcp",
3
- "version": "0.2.0",
4
- "description": "MCP server wrapper for the EquiSense AI equity-research API. Exposes a single ask_research tool that proxies POST /api/v1/research/ask, authenticated via a scoped, revocable token minted in Settings → Claude.",
3
+ "version": "0.3.1",
4
+ "description": "MCP server wrapper for the EquiSense AI equity-research API. Exposes a single ask_equisense tool that proxies POST /api/v1/research/ask, authenticated via a scoped, revocable token minted in Settings → Claude.",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {