equisense-research-mcp 0.1.1 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +3 -3
  2. package/index.js +41 -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
 
@@ -72,7 +72,7 @@ Should show `equisense-research - ✓ Connected`.
72
72
 
73
73
  In any Claude Code session:
74
74
 
75
- > Use ask_research: bull case for OLAELEC
75
+ > Use ask_equisense: bull case for OLAELEC
76
76
 
77
77
  You should get back `{ answer, companyName, isin, detectedIntent, followUpQuestions, responseTimeMs }`.
78
78
 
@@ -91,7 +91,7 @@ You should get back `{ answer, companyName, isin, detectedIntent, followUpQuesti
91
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
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).
93
93
  - 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.
94
+ - Each `ask_equisense` call counts against the represented user's `AI_EQUITY_RESEARCH` daily quota — same metering as the web UI.
95
95
 
96
96
  ---
97
97
 
package/index.js CHANGED
@@ -1,18 +1,24 @@
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 Spring Boot
5
- // app authenticates the caller via a Cookie: ES_AUTH=<token> header. The token
6
- // is minted via POST /api/v1/admin/auth/mint-mcp-token (admin-only) and passed
7
- // in here through the EQUISENSE_MCP_TOKEN env var.
4
+ // Single tool: ask_equisense — proxies POST /api/v1/research/ask. The user mints a
5
+ // scoped, revocable token in the web app (Settings Claude) and passes it here
6
+ // via the EQUISENSE_MCP_TOKEN env var.
8
7
  //
9
- // Treat the token like a password it grants full session access to that user
10
- // for SESSION_TTL_SECONDS (90 days). Never log it. Never commit it.
8
+ // Auth: we send the token BOTH as `Authorization: Bearer <token>` and as a
9
+ // `Cookie: ES_AUTH=<token>` header. New self-serve tokens are RESEARCH-scoped and
10
+ // authenticate via the Bearer path (McpAuthFilter); legacy admin-minted full-session
11
+ // tokens authenticate via the cookie path (PersistentAuthFilter). Sending both means
12
+ // either kind of token works with no client-side branching — the backend ignores the
13
+ // header that doesn't apply.
14
+ //
15
+ // Treat the token like a password. A scoped token grants read-only research access;
16
+ // revoke it any time in Settings → Claude. Never log it. Never commit it.
11
17
  //
12
18
  // Transport: stdio. Register in Claude Code via:
13
19
  // claude mcp add equisense-research --scope local \
14
- // --env EQUISENSE_BASE_URL=http://localhost:8080 \
15
- // --env EQUISENSE_MCP_TOKEN='<minted>' \
20
+ // --env EQUISENSE_BASE_URL=https://equisense.ai \
21
+ // --env EQUISENSE_MCP_TOKEN='<token from Settings → Claude>' \
16
22
  // -- node /abs/path/to/index.js
17
23
 
18
24
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
@@ -31,15 +37,21 @@ const MCP_TOKEN = process.env.EQUISENSE_MCP_TOKEN;
31
37
 
32
38
  if (!MCP_TOKEN || MCP_TOKEN.trim() === "") {
33
39
  console.error(
34
- "FATAL: EQUISENSE_MCP_TOKEN env var is required. Mint one via " +
35
- "POST /api/v1/admin/auth/mint-mcp-token?phoneNumber=<userPhone>.",
40
+ "FATAL: EQUISENSE_MCP_TOKEN env var is required. Generate one in the " +
41
+ "EquiSense web app under Settings → Claude.",
36
42
  );
37
43
  process.exit(1);
38
44
  }
39
45
 
46
+ // Client identity from the MCP initialize handshake (e.g. "claude-ai",
47
+ // "Claude Code"). Captured once, forwarded as X-MCP-Client so the backend can
48
+ // auto-name the connection in Settings → Claude. Null until the handshake lands
49
+ // and when askEquisense is exercised directly in unit tests.
50
+ let clientName = null;
51
+
40
52
  export const TOOLS = [
41
53
  {
42
- name: "ask_research",
54
+ name: "ask_equisense",
43
55
  description:
44
56
  "Ask EquiSense's AI equity-research engine a natural-language question " +
45
57
  "about an Indian-listed company (NSE/BSE). Returns the answer, the " +
@@ -83,7 +95,10 @@ async function callRest(path, options = {}) {
83
95
  headers: {
84
96
  "Content-Type": "application/json",
85
97
  Accept: "application/json",
98
+ // Bearer for new scoped tokens; Cookie for legacy session tokens. See header note.
99
+ Authorization: `Bearer ${MCP_TOKEN}`,
86
100
  Cookie: `ES_AUTH=${MCP_TOKEN}`,
101
+ ...(clientName ? { "X-MCP-Client": clientName } : {}),
87
102
  ...(options.headers || {}),
88
103
  },
89
104
  });
@@ -103,8 +118,8 @@ function formatHttpError(path, status, bodyText) {
103
118
  return (
104
119
  "Auth failed (HTTP 401) calling " +
105
120
  path +
106
- ". EQUISENSE_MCP_TOKEN is expired or invalid. Re-mint via " +
107
- "POST /api/v1/admin/auth/mint-mcp-token and update the env var."
121
+ ". EQUISENSE_MCP_TOKEN is expired, revoked, or invalid. Generate a fresh one " +
122
+ "in the EquiSense web app under Settings → Claude and update the env var."
108
123
  );
109
124
  }
110
125
  if (status === 402) {
@@ -135,7 +150,7 @@ function formatHttpError(path, status, bodyText) {
135
150
  return `EquiSense REST ${path} returned HTTP ${status}: ${snippet}`;
136
151
  }
137
152
 
138
- export async function askResearch(args) {
153
+ export async function askEquisense(args) {
139
154
  const body = {
140
155
  query: args.query,
141
156
  isin: args.isin ?? null,
@@ -156,14 +171,14 @@ export async function askResearch(args) {
156
171
  }
157
172
 
158
173
  const TOOL_HANDLERS = {
159
- ask_research: askResearch,
174
+ ask_equisense: askEquisense,
160
175
  };
161
176
 
162
177
  function createServer() {
163
178
  const server = new Server(
164
179
  {
165
180
  name: "equisense-research",
166
- version: "0.1.0",
181
+ version: "0.3.0",
167
182
  },
168
183
  {
169
184
  capabilities: {
@@ -172,6 +187,16 @@ function createServer() {
172
187
  },
173
188
  );
174
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
+
175
200
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
176
201
  tools: TOOLS,
177
202
  }));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "equisense-research-mcp",
3
- "version": "0.1.1",
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 minted ES_AUTH bearer token.",
3
+ "version": "0.3.0",
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": {