androjack-mcp 1.6.3 โ†’ 1.6.4

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
@@ -19,21 +19,21 @@
19
19
 
20
20
  [![npm version](https://img.shields.io/npm/v/androjack-mcp?color=0A7AFF&style=flat-square&logo=npm&label=npm)](https://www.npmjs.com/package/androjack-mcp)
21
21
  [![VS Code](https://img.shields.io/visual-studio-marketplace/v/VIKAS9793.androjack-vscode?color=0A7AFF&style=flat-square&logo=visual-studio-code&label=VS%20Code)](https://marketplace.visualstudio.com/items?itemName=VIKAS9793.androjack-vscode)
22
- [![GitHub stars](https://img.shields.io/github/stars/VIKAS9793/AndroJack-mcp?style=flat-square&logo=github&color=0A7AFF)](https://github.com/VIKAS9793/AndroJack-mcp/stargazers)
23
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen?style=flat-square&logo=node.js)](https://nodejs.org)
24
- [![MCP Spec](https://img.shields.io/badge/MCP-2025--11--25-blueviolet?style=flat-square)](https://modelcontextprotocol.io)
25
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-3178C6?style=flat-square&logo=typescript)](https://typescriptlang.org)
26
- [![Tools](https://img.shields.io/badge/tools-21-orange?style=flat-square)](#-the-21-tools)
27
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)
28
- [![Security Policy](https://img.shields.io/badge/security-policy-0A7AFF?style=flat-square&logo=github)](SECURITY.md)
29
- [![Android API](https://img.shields.io/badge/Android-API%2021--36-34A853?style=flat-square&logo=android)](https://developer.android.com)
22
+ [![GitHub stars](https://img.shields.io/github/stars/VIKAS9793/AndroJack-mcp?style=flat-square&logo=github&color=0A7AFF)](https://github.com/VIKAS9793/AndroJack-mcp/stargazers)
23
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen?style=flat-square&logo=node.js)](https://nodejs.org)
24
+ [![MCP Spec](https://img.shields.io/badge/MCP-2025--11--25-blueviolet?style=flat-square)](https://modelcontextprotocol.io)
25
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-3178C6?style=flat-square&logo=typescript)](https://typescriptlang.org)
26
+ [![Tools](https://img.shields.io/badge/tools-21-orange?style=flat-square)](#-the-21-tools)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](LICENSE)
28
+ [![Security Policy](https://img.shields.io/badge/security-policy-0A7AFF?style=flat-square&logo=github)](SECURITY.md)
29
+ [![Android API](https://img.shields.io/badge/Android-API%2021--36-34A853?style=flat-square&logo=android)](https://developer.android.com)
30
30
 
31
31
  ### ๐Ÿš€ One-Click Install
32
32
 
33
33
  [![Install in VS Code](https://img.shields.io/badge/Install%20in-VS%20Code-007ACC?style=for-the-badge&logo=visual-studio-code&logoColor=white)](https://marketplace.visualstudio.com/items?itemName=VIKAS9793.androjack-vscode)
34
- [![Install in Claude Desktop](https://img.shields.io/badge/Install%20in-Claude%20Desktop-D97706?style=for-the-badge&logo=anthropic&logoColor=white)](https://claude.ai/integrations/install-mcp?params=eyJuYW1lIjoiYW5kcm9qYWNrIiwiY29tbWFuZCI6Im5weCIsImFyZ3MiOlsiLXkiLCJhbmRyb2phY2stbWNwQDEuNi4zIl19)
35
- [![Install in Cursor](https://img.shields.io/badge/Install%20in-Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=androjack&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImFuZHJvamFjay1tY3BAMS42LjMiXX0=)
36
- [![Add to Kiro](https://img.shields.io/badge/Add%20to-AWS%20Kiro-FF9900?style=for-the-badge&logo=amazonaws&logoColor=white)](https://kiro.dev/launch/mcp/add?name=androjack&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22androjack-mcp%401.6.3%22%5D%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D)
34
+ [![Install in Claude Desktop](https://img.shields.io/badge/Install%20in-Claude%20Desktop-D97706?style=for-the-badge&logo=anthropic&logoColor=white)](https://claude.ai/integrations/install-mcp?params=eyJuYW1lIjoiYW5kcm9qYWNrIiwiY29tbWFuZCI6Im5weCIsImFyZ3MiOlsiLXkiLCJhbmRyb2phY2stbWNwQDEuNi4zIl19)
35
+ [![Install in Cursor](https://img.shields.io/badge/Install%20in-Cursor-000000?style=for-the-badge&logo=cursor&logoColor=white)](https://cursor.com/install-mcp?name=androjack&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImFuZHJvamFjay1tY3BAMS42LjMiXX0=)
36
+ [![Add to Kiro](https://img.shields.io/badge/Add%20to-AWS%20Kiro-FF9900?style=for-the-badge&logo=amazonaws&logoColor=white)](https://kiro.dev/launch/mcp/add?name=androjack&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22androjack-mcp%401.6.4%22%5D%2C%22disabled%22%3Afalse%2C%22autoApprove%22%3A%5B%5D%7D)
37
37
  [![View on npm](https://img.shields.io/badge/View%20on-npm-CB3837?style=for-the-badge&logo=npm&logoColor=white)](https://www.npmjs.com/package/androjack-mcp)
38
38
 
39
39
  **VS Code distribution:** AndroJack MCP is also live on the VS Code Marketplace as [AndroJack MCP for VS Code](https://marketplace.visualstudio.com/items?itemName=VIKAS9793.androjack-vscode). The `VS Code` badge above always reflects the currently published Marketplace version.
@@ -258,28 +258,49 @@ That's the job AndroJack exists to do โ€” and nothing else in the current ecosys
258
258
 
259
259
  ## ๐Ÿš€ Quick Start โ€” Zero Install Required
260
260
 
261
- ### Option 1 โ€” Interactive CLI (v1.6.3) โœจ Recommended
262
- ```bash
263
- npx androjack-mcp@1.6.3 install
264
- ```
261
+ ### Option 1 โ€” Interactive CLI (v1.6.4) โœจ Recommended
262
+ ```bash
263
+ npx -y androjack-mcp@1.6.4 install
264
+ ```
265
265
  Launches a full animated terminal wizard with auto-detection for **VS Code, Cursor, Claude, Windsurf, JetBrains, Kiro, and Antigravity.**
266
266
 
267
267
  ### Option 2 โ€” Targeted installs
268
- ```bash
269
- # Auto-detect all
270
- npx androjack-mcp@1.6.3 install --auto
271
-
272
- # Install to specific IDE
273
- npx androjack-mcp@1.6.3 install --ide=cursor
274
- npx androjack-mcp@1.6.3 install --ide=claude
275
- npx androjack-mcp@1.6.3 install --ide=vscode
276
- npx androjack-mcp@1.6.3 install --ide=jetbrains
277
- ```
268
+ ```bash
269
+ # Preview detected IDEs and config paths
270
+ npx -y androjack-mcp@1.6.4 install --list
271
+
272
+ # Auto-detect all
273
+ npx -y androjack-mcp@1.6.4 install --auto
274
+
275
+ # Install to specific IDE
276
+ npx -y androjack-mcp@1.6.4 install --ide=cursor
277
+ npx -y androjack-mcp@1.6.4 install --ide=claude
278
+ npx -y androjack-mcp@1.6.4 install --ide=vscode
279
+ npx -y androjack-mcp@1.6.4 install --ide=windsurf
280
+ npx -y androjack-mcp@1.6.4 install --ide=jetbrains
281
+ npx -y androjack-mcp@1.6.4 install --ide=kiro
282
+ npx -y androjack-mcp@1.6.4 install --ide=antigravity
283
+ ```
278
284
 
279
285
  ### Option 3 โ€” Test without IDE
280
- ```bash
281
- npx @modelcontextprotocol/inspector npx -y androjack-mcp@1.6.3
282
- ```
286
+ ```bash
287
+ npx -y @modelcontextprotocol/inspector npx -y androjack-mcp@1.6.4
288
+ ```
289
+
290
+ ## ๐Ÿงฉ Manual Config / Copy / Paste
291
+
292
+ If you prefer to wire the server manually, or want to inspect the exact JSON before writing anything:
293
+
294
+ - Run `npx -y androjack-mcp@1.6.4 install --list` to preview detected IDEs and target config paths.
295
+ - Ready-to-paste examples live in [`config/`](config/).
296
+ - Use [`config/claude_desktop_config.json`](config/claude_desktop_config.json) for Claude Desktop.
297
+ - Use [`config/cursor_mcp.json`](config/cursor_mcp.json) for Cursor.
298
+ - Use [`config/vscode_mcp.json`](config/vscode_mcp.json) for VS Code / GitHub Copilot.
299
+ - Use [`config/windsurf_mcp.json`](config/windsurf_mcp.json) for Windsurf.
300
+ - Use [`config/jetbrains_mcp.json`](config/jetbrains_mcp.json) for Android Studio / IntelliJ.
301
+ - Use [`config/kiro_mcp.json`](config/kiro_mcp.json) for AWS Kiro.
302
+ - Use [`config/antigravity_mcp.json`](config/antigravity_mcp.json) for Google Antigravity.
303
+ - Every example keeps AndroJack local by default and runs the published package via `npx -y androjack-mcp@1.6.4`.
283
304
 
284
305
  ---
285
306
 
@@ -291,26 +312,26 @@ npx @modelcontextprotocol/inspector npx -y androjack-mcp@1.6.3
291
312
  | **Mechanism** | Context Retrieval | Context Enforcement |
292
313
  | **Scope** | Generalist โ€” Firebase, Cloud, Maps | Android engineering specialist |
293
314
  | **Tools** | 3 retrieval tools | 21 specialized tools |
294
- | **Setup** | Google Cloud project + API key required | `npx androjack-mcp@1.6.3` โ€” zero auth |
315
+ | **Setup** | Google Cloud project + API key required | `npx androjack-mcp@1.6.4` โ€” zero auth |
295
316
  | **Enforcement**| Passive โ€” AI decides when to retrieve | Active โ€” mandating calls by task type |
296
317
 
297
- ## ๐Ÿ”’ Security & Privacy
298
- * **Domain allowlist**: Requests only to Google/Android/Kotlin official domains.
299
- * **HTTPS only**: Outbound documentation fetches refuse non-HTTPS URLs, cap body size, and redact query strings in retry logs.
300
- * **Local by default**: `serve` binds to loopback only unless you explicitly pass `--allow-remote`.
301
- * **HTTP hardening**: Streamable HTTP validates `Origin` and `Host` headers and caps request bodies and active sessions.
302
- * **Transparent agent**: User-Agent: `AndroJack-MCP/1.6.3`.
303
- * **Read-only**: All 21 tools are annotated `readOnlyHint: true`.
304
- * **Zero credentials**: No API keys or tokens required for documentation fetching.
305
- * **Security policy**: Disclosure process and supported versions live in [SECURITY.md](SECURITY.md).
306
-
307
- ## ๐Ÿ“‹ Changelog
308
- ### v1.6.3 โ€” CLI Routing Fixes and Local Transport Hardening
309
- - **Fix:** `npx androjack-mcp install`, `install --auto`, `install --ide=...`, `help`, and `--version` now route correctly instead of falling through to the stdio server.
310
- - **Fix:** `install --auto` no longer over-detects IDEs from parent directories and create configs in a clean workspace.
311
- - **Fix:** HTTP serve mode now fails closed on non-loopback binds unless `--allow-remote` is explicit.
312
- - **Security:** Streamable HTTP now validates `Origin` and `Host` headers and enforces request body and session limits.
313
- - **Security:** Outbound fetches now enforce HTTPS, cap response size, and redact sensitive URL parts from retry logs.
318
+ ## ๐Ÿ”’ Security & Privacy
319
+ * **Domain allowlist**: Requests only to Google/Android/Kotlin official domains.
320
+ * **HTTPS only**: Outbound documentation fetches refuse non-HTTPS URLs, cap body size, and redact query strings in retry logs.
321
+ * **Local by default**: `serve` binds to loopback only unless you explicitly pass `--allow-remote`.
322
+ * **HTTP hardening**: Streamable HTTP validates `Origin` and `Host` headers and caps request bodies and active sessions.
323
+ * **Transparent agent**: User-Agent: `AndroJack-MCP/1.6.4`.
324
+ * **Read-only**: All 21 tools are annotated `readOnlyHint: true`.
325
+ * **Zero credentials**: No API keys or tokens required for documentation fetching.
326
+ * **Security policy**: Disclosure process and supported versions live in [SECURITY.md](SECURITY.md).
327
+
328
+ ## ๐Ÿ“‹ Changelog
329
+ ### v1.6.4 โ€” CLI Routing Fixes and Local Transport Hardening
330
+ - **Fix:** `npx androjack-mcp install`, `install --auto`, `install --ide=...`, `help`, and `--version` now route correctly instead of falling through to the stdio server.
331
+ - **Fix:** `install --auto` no longer over-detects IDEs from parent directories and create configs in a clean workspace.
332
+ - **Fix:** HTTP serve mode now fails closed on non-loopback binds unless `--allow-remote` is explicit.
333
+ - **Security:** Streamable HTTP now validates `Origin` and `Host` headers and enforces request body and session limits.
334
+ - **Security:** Outbound fetches now enforce HTTPS, cap response size, and redact sensitive URL parts from retry logs.
314
335
 
315
336
  ## ๐Ÿ‘ฅ Authorship & Ownership
316
337
  **Vikas Sahani** โ€” Product Lead (vikassahani17@gmail.com)
@@ -1,25 +1,16 @@
1
1
  /**
2
2
  * AndroJack MCP โ€“ Streamable HTTP Transport
3
3
  *
4
- * Runs the same MCP server over HTTP instead of stdio.
4
+ * Runs the MCP server over HTTP instead of stdio.
5
5
  * Spec: MCP 2025-11-25 Streamable HTTP transport.
6
6
  *
7
+ * Security: each new initialize request creates a FRESH McpServer +
8
+ * StreamableHTTPServerTransport pair. Sessions are never shared โ€”
9
+ * this prevents cross-session state leakage that existed in v1.6.3.
10
+ *
7
11
  * Usage:
8
12
  * node build/index.js --http # default port 3000
9
13
  * PORT=8080 node build/index.js --http # custom port
10
- *
11
- * Config for Claude Desktop / Cursor (remote team instance):
12
- * {
13
- * "mcpServers": {
14
- * "androjack": {
15
- * "type": "streamable-http",
16
- * "url": "http://localhost:3000/mcp"
17
- * }
18
- * }
19
- * }
20
- *
21
- * Security note: bind to 127.0.0.1 by default (loopback only).
22
- * To expose on LAN, set HOST=0.0.0.0 and add your own auth layer.
23
14
  */
24
15
  import http from "node:http";
25
16
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -32,8 +23,7 @@ const MCP_PATH = "/mcp";
32
23
  const WELL_KNOWN_PATH = "/.well-known/mcp";
33
24
  const MAX_REQUEST_BODY_BYTES = 1024 * 1024;
34
25
  const MAX_ACTIVE_SESSIONS = 64;
35
- // Per-session transports โ€” Streamable HTTP is session-aware (MCP-Session-Id header)
36
- const sessions = new Map();
26
+ // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
37
27
  function createHttpError(statusCode, message) {
38
28
  const error = new Error(message);
39
29
  error.statusCode = statusCode;
@@ -103,16 +93,49 @@ function validateHostHeader(hostHeader, bindHost) {
103
93
  throw createHttpError(403, `Host "${hostHeader}" is not allowed.`);
104
94
  }
105
95
  }
96
+ function readBody(req) {
97
+ return new Promise((resolve, reject) => {
98
+ const chunks = [];
99
+ let totalBytes = 0;
100
+ req.on("data", (chunk) => {
101
+ totalBytes += chunk.length;
102
+ if (totalBytes > MAX_REQUEST_BODY_BYTES) {
103
+ req.destroy(createHttpError(413, "Request body too large"));
104
+ return;
105
+ }
106
+ chunks.push(chunk);
107
+ });
108
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
109
+ req.on("error", reject);
110
+ });
111
+ }
112
+ // โ”€โ”€ Main export โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
106
113
  /**
107
- * Starts the Streamable HTTP server and connects the provided MCP server to it.
108
- * Exported so index.ts can dynamically import it only when --http is passed.
114
+ * Starts the Streamable HTTP server.
115
+ *
116
+ * @param createServer - Factory called once per MCP initialize request.
117
+ * Each call MUST return a new McpServer instance โ€” never share instances
118
+ * across sessions.
119
+ * @param opts - Optional port/host overrides (fall back to env vars / defaults).
120
+ * @returns A handle with close() and the bound address.
121
+ *
122
+ * Security controls (all preserved from v1.6.3):
123
+ * - Loopback-only bind host by default
124
+ * - Explicit --allow-remote required for non-loopback (enforced in serve.ts)
125
+ * - Host header validation
126
+ * - Origin header validation
127
+ * - Body size cap (1 MiB)
128
+ * - Active session cap (64)
129
+ * - Per-session server isolation (NEW in v1.6.4)
109
130
  */
110
- export async function startHttpServer(server) {
111
- const port = parseInt(process.env.PORT ?? String(DEFAULT_PORT), 10);
112
- const host = process.env.HOST ?? DEFAULT_HOST;
131
+ export async function startHttpServer(createServer, opts) {
132
+ const port = opts?.port ?? parseInt(process.env["PORT"] ?? String(DEFAULT_PORT), 10);
133
+ const host = opts?.host ?? process.env["HOST"] ?? DEFAULT_HOST;
113
134
  let advertisedPort = port;
114
135
  let advertisedHost = host;
115
136
  let allowedOrigins = buildAllowedOrigins(host, port);
137
+ // Per-session state: each session owns its own server + transport pair
138
+ const sessions = new Map();
116
139
  const httpServer = http.createServer(async (req, res) => {
117
140
  try {
118
141
  validateHostHeader(req.headers.host, host);
@@ -125,7 +148,7 @@ export async function startHttpServer(server) {
125
148
  return;
126
149
  }
127
150
  const url = new URL(req.url ?? "/", "http://localhost");
128
- // โ”€โ”€ .well-known/mcp โ€” capability discovery (MCP 2025-11-25) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
151
+ // โ”€โ”€ .well-known/mcp โ€” capability discovery โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
129
152
  if (url.pathname === WELL_KNOWN_PATH && req.method === "GET") {
130
153
  res.writeHead(200, { "Content-Type": "application/json" });
131
154
  res.end(JSON.stringify({
@@ -141,24 +164,25 @@ export async function startHttpServer(server) {
141
164
  }));
142
165
  return;
143
166
  }
144
- // โ”€โ”€ /mcp โ€” Streamable HTTP transport endpoint โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
167
+ // โ”€โ”€ /mcp โ€” Streamable HTTP transport endpoint โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
145
168
  if (url.pathname !== MCP_PATH) {
146
169
  res.writeHead(404, { "Content-Type": "text/plain" });
147
170
  res.end(`AndroJack MCP: endpoint is ${MCP_PATH}`);
148
171
  return;
149
172
  }
150
- // Only POST and GET are valid for Streamable HTTP
151
173
  if (req.method !== "POST" && req.method !== "GET" && req.method !== "DELETE") {
152
174
  res.writeHead(405, { Allow: "GET, POST, DELETE" });
153
175
  res.end();
154
176
  return;
155
177
  }
156
- // DELETE โ€” client signals session teardown
178
+ // โ”€โ”€ DELETE โ€” client signals session teardown โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
157
179
  if (req.method === "DELETE") {
158
180
  const sessionId = req.headers["mcp-session-id"];
159
181
  if (sessionId && sessions.has(sessionId)) {
160
- await sessions.get(sessionId).close();
182
+ const session = sessions.get(sessionId);
183
+ await session.transport.close();
161
184
  sessions.delete(sessionId);
185
+ process.stderr.write(`AndroJack HTTP: session deleted [${sessionId}]\n`);
162
186
  }
163
187
  res.writeHead(200);
164
188
  res.end();
@@ -166,13 +190,14 @@ export async function startHttpServer(server) {
166
190
  }
167
191
  try {
168
192
  const sessionId = req.headers["mcp-session-id"];
169
- let transport;
170
193
  if (sessionId && sessions.has(sessionId)) {
171
- // Resume existing session
172
- transport = sessions.get(sessionId);
194
+ // โ”€โ”€ Resume existing session โ€” reuse only this session's transport โ”€โ”€
195
+ const { transport } = sessions.get(sessionId);
196
+ await transport.handleRequest(req, res);
197
+ return;
173
198
  }
174
- else if (!sessionId) {
175
- // New session โ€” only valid if this is an Initialize request
199
+ if (!sessionId) {
200
+ // โ”€โ”€ New session โ€” only valid for MCP Initialize requests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
176
201
  if (sessions.size >= MAX_ACTIVE_SESSIONS) {
177
202
  res.writeHead(503, { "Content-Type": "text/plain" });
178
203
  res.end("Too many active MCP sessions");
@@ -193,11 +218,13 @@ export async function startHttpServer(server) {
193
218
  res.end("First request must be an MCP Initialize request");
194
219
  return;
195
220
  }
221
+ // โ”€โ”€ Create a fresh server + transport pair for this session โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
196
222
  const newSessionId = randomUUID();
197
- transport = new StreamableHTTPServerTransport({
223
+ const mcpServer = createServer(); // <-- fresh instance per session
224
+ const transport = new StreamableHTTPServerTransport({
198
225
  sessionIdGenerator: () => newSessionId,
199
226
  onsessioninitialized: (id) => {
200
- sessions.set(id, transport);
227
+ sessions.set(id, { server: mcpServer, transport });
201
228
  process.stderr.write(`AndroJack HTTP: session opened [${id}]\n`);
202
229
  },
203
230
  });
@@ -205,18 +232,15 @@ export async function startHttpServer(server) {
205
232
  sessions.delete(newSessionId);
206
233
  process.stderr.write(`AndroJack HTTP: session closed [${newSessionId}]\n`);
207
234
  };
208
- // Connect the shared McpServer to this session's transport
209
- await server.connect(transport);
235
+ // Connect this session's fresh server to its own transport
236
+ await mcpServer.connect(transport);
210
237
  // Handle the Initialize request on the new transport
211
238
  await transport.handleRequest(req, res, parsed);
212
239
  return;
213
240
  }
214
- else {
215
- res.writeHead(400, { "Content-Type": "text/plain" });
216
- res.end("Unknown session ID");
217
- return;
218
- }
219
- await transport.handleRequest(req, res);
241
+ // Unknown session ID
242
+ res.writeHead(400, { "Content-Type": "text/plain" });
243
+ res.end("Unknown session ID");
220
244
  }
221
245
  catch (err) {
222
246
  process.stderr.write(`AndroJack HTTP error: ${err}\n`);
@@ -245,26 +269,17 @@ export async function startHttpServer(server) {
245
269
  for (const sig of ["SIGINT", "SIGTERM"]) {
246
270
  process.once(sig, async () => {
247
271
  process.stderr.write(`\nAndroJack HTTP: shutting down (${sig})โ€ฆ\n`);
248
- for (const t of sessions.values())
249
- await t.close().catch(() => { });
272
+ for (const { transport } of sessions.values())
273
+ await transport.close().catch(() => { });
250
274
  httpServer.close(() => process.exit(0));
251
275
  });
252
276
  }
253
- }
254
- // โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
255
- function readBody(req) {
256
- return new Promise((resolve, reject) => {
257
- const chunks = [];
258
- let totalBytes = 0;
259
- req.on("data", (chunk) => {
260
- totalBytes += chunk.length;
261
- if (totalBytes > MAX_REQUEST_BODY_BYTES) {
262
- req.destroy(createHttpError(413, "Request body too large"));
263
- return;
264
- }
265
- chunks.push(chunk);
266
- });
267
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
268
- req.on("error", reject);
269
- });
277
+ return {
278
+ close: () => {
279
+ for (const { transport } of sessions.values())
280
+ transport.close().catch(() => { });
281
+ httpServer.close();
282
+ },
283
+ address: { host: advertisedHost, port: advertisedPort },
284
+ };
270
285
  }
package/build/install.js CHANGED
@@ -6,10 +6,10 @@
6
6
  * Supports both automated (--auto) and guided interactive installation.
7
7
  *
8
8
  * Usage:
9
- * npx androjack-mcp@1.6.3 install รขโ€ โ€™ interactive guided mode
10
- * npx androjack-mcp@1.6.3 install --auto รขโ€ โ€™ auto-detect and install to all found IDEs
11
- * npx androjack-mcp@1.6.3 install --ide cursor รขโ€ โ€™ target a specific IDE
12
- * npx androjack-mcp@1.6.3 install --list รขโ€ โ€™ list all supported IDEs and their status
9
+ * npx androjack-mcp@1.6.4 install รขโ€ โ€™ interactive guided mode
10
+ * npx androjack-mcp@1.6.4 install --auto รขโ€ โ€™ auto-detect and install to all found IDEs
11
+ * npx androjack-mcp@1.6.4 install --ide cursor รขโ€ โ€™ target a specific IDE
12
+ * npx androjack-mcp@1.6.4 install --list รขโ€ โ€™ list all supported IDEs and their status
13
13
  */
14
14
  import * as fs from "fs";
15
15
  import * as path from "path";
@@ -105,6 +105,15 @@ function getConfigPaths(platform) {
105
105
  path.join(process.cwd(), ".kiro", "settings", "mcp.json"), // project-level
106
106
  path.join(HOME, ".kiro", "settings", "mcp.json"), // global
107
107
  ],
108
+ // Kiro creates ~/.kiro only after first launch + MCP plugin setup.
109
+ // Detect the Kiro app itself via its known install location.
110
+ ideInstalledPaths: [
111
+ ...(platform === "win32"
112
+ ? [path.join(process.env.LOCALAPPDATA ?? path.join(HOME, "AppData", "Local"), "Programs", "Kiro", "Kiro.exe")]
113
+ : platform === "darwin"
114
+ ? ["/Applications/Kiro.app"]
115
+ : [path.join(HOME, ".local", "share", "kiro")]),
116
+ ],
108
117
  configKey: "mcpServers",
109
118
  format: "standard",
110
119
  oneClickUrl: (() => {
@@ -145,6 +154,21 @@ function getConfigPaths(platform) {
145
154
  path.join(HOME, ".config", "JetBrains", "IdeaIC2024.3", "mcp.json"),
146
155
  ]),
147
156
  ],
157
+ // JetBrains config dir only exists after first launch with AI plugin installed.
158
+ // Detect IDE via its known application paths.
159
+ ideInstalledPaths: [
160
+ ...(platform === "darwin"
161
+ ? ["/Applications/Android Studio.app", "/Applications/IntelliJ IDEA.app", "/Applications/IntelliJ IDEA CE.app"]
162
+ : platform === "win32"
163
+ ? [
164
+ path.join(process.env.LOCALAPPDATA ?? path.join(HOME, "AppData", "Local"), "Programs", "Android Studio", "bin", "studio64.exe"),
165
+ path.join("C:\\", "Program Files", "Android", "Android Studio", "bin", "studio64.exe"),
166
+ ]
167
+ : [
168
+ path.join(HOME, ".local", "share", "JetBrains"),
169
+ "/opt/android-studio",
170
+ ]),
171
+ ],
148
172
  configKey: "mcpServers",
149
173
  format: "standard",
150
174
  notes: "Or add manually: Android Studio รขโ€ โ€™ Settings รขโ€ โ€™ Tools รขโ€ โ€™ AI Assistant รขโ€ โ€™ MCP Servers รขโ€ โ€™ +",
@@ -210,6 +234,18 @@ function detectInstalledIdes(targets) {
210
234
  return target.configPaths.some((configPath) => fs.existsSync(path.dirname(configPath)));
211
235
  });
212
236
  }
237
+ /**
238
+ * Returns true when the IDE application is installed on this machine, but the
239
+ * MCP config directory hasn't been created yet (IDE was never launched or
240
+ * the AI/MCP plugin hasn't been set up). Used in v1.6.4 to surface a
241
+ * more helpful message than silently showing "not found".
242
+ */
243
+ function isIdeApplicationInstalled(target) {
244
+ if (!target.ideInstalledPaths || detectInstalledIdes([target]).length > 0) {
245
+ return false; // already detected via config dir โ€” no extra message needed
246
+ }
247
+ return target.ideInstalledPaths.some((p) => fs.existsSync(p));
248
+ }
213
249
  function alreadyInstalled(target) {
214
250
  for (const p of target.configPaths) {
215
251
  if (!fs.existsSync(p))
@@ -283,18 +319,23 @@ function printStatusTable(targets) {
283
319
  for (const t of targets) {
284
320
  const installed = alreadyInstalled(t);
285
321
  const detected = detectInstalledIdes([t]).length > 0;
322
+ const ideInstalled = isIdeApplicationInstalled(t);
286
323
  let icon;
287
324
  let label;
288
325
  if (installed) {
289
- icon = chalk.green(" รขล“โ€œ");
290
- label = chalk.green("already installed") + chalk.dim(` รขโ€ โ€™ ${installed}`);
326
+ icon = chalk.green(" โœ“");
327
+ label = chalk.green("already installed") + chalk.dim(` โ†’ ${installed}`);
291
328
  }
292
329
  else if (detected) {
293
- icon = chalk.cyan(" รขโ€”โ€ฐ");
330
+ icon = chalk.cyan(" โ—‰");
294
331
  label = chalk.cyan("detected, not configured");
295
332
  }
333
+ else if (ideInstalled) {
334
+ icon = chalk.yellow(" โ—ˆ");
335
+ label = chalk.yellow("detected (MCP not yet configured โ€” open the IDE once to initialize)");
336
+ }
296
337
  else {
297
- icon = chalk.dim(" รขโ€”โ€น");
338
+ icon = chalk.dim(" โ—‹");
298
339
  label = chalk.dim("not found");
299
340
  }
300
341
  console.log(`${icon} ${chalk.bold(t.name)} ${label}`);
@@ -521,6 +562,7 @@ export async function main(rawArgs = process.argv.slice(2)) {
521
562
  options: targets.map((t) => {
522
563
  const installed = alreadyInstalled(t);
523
564
  const detected = detectInstalledIdes([t]).length > 0;
565
+ const ideInstalled = isIdeApplicationInstalled(t);
524
566
  return {
525
567
  value: t.id,
526
568
  label: t.name,
@@ -528,7 +570,9 @@ export async function main(rawArgs = process.argv.slice(2)) {
528
570
  ? chalk.green("already installed")
529
571
  : detected
530
572
  ? chalk.cyan("detected")
531
- : chalk.dim("not found"),
573
+ : ideInstalled
574
+ ? chalk.yellow("IDE installed โ€” run once to initialize MCP")
575
+ : chalk.dim("not found"),
532
576
  };
533
577
  }),
534
578
  required: true,