jefrichat-mcp 0.32.0 → 0.33.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.
package/dist/http.js CHANGED
@@ -60685,7 +60685,7 @@ var JefriClient = class _JefriClient {
60685
60685
  lastPresence = "online";
60686
60686
  // re-announce this on reconnect
60687
60687
  clientInfo;
60688
- // version handshake, re-sent on reconnect
60688
+ // handshake, re-sent on reconnect
60689
60689
  constructor(server) {
60690
60690
  this.server = server.replace(/\/$/, "");
60691
60691
  }
@@ -60970,6 +60970,19 @@ var JefriClient = class _JefriClient {
60970
60970
  history(conversationId, before) {
60971
60971
  this.send({ type: "history", conversationId, ...before ? { before } : {} });
60972
60972
  }
60973
+ /** Announce (or update) the HOST APP this connector is embedded in — Claude
60974
+ * Code, Codex, ChatGPT, … — typically detected from the MCP `initialize`
60975
+ * handshake AFTER the hub socket is already open. Merges into clientInfo so
60976
+ * reconnects re-send it, and pushes an immediate client_info frame if live. */
60977
+ announceHost(host) {
60978
+ const h = (host ?? "").trim();
60979
+ this.clientInfo = { ...this.clientInfo ?? { name: "jefri-sdk", version: "0" }, host: h };
60980
+ try {
60981
+ if (this.ws?.readyState === this.ws?.OPEN)
60982
+ this.ws.send(JSON.stringify({ type: "client_info", ...this.clientInfo }));
60983
+ } catch {
60984
+ }
60985
+ }
60973
60986
  close() {
60974
60987
  this.closing = true;
60975
60988
  this.stopPing();
@@ -61240,6 +61253,23 @@ function createKeyedPool() {
61240
61253
  };
61241
61254
  }
61242
61255
 
61256
+ // src/hosts.ts
61257
+ function aggregateFreshHosts(sessions2, poolKey, now, freshMs) {
61258
+ const hosts = /* @__PURE__ */ new Set();
61259
+ for (const s of sessions2) if (s.poolKey === poolKey && s.host && now - s.lastActive < freshMs) hosts.add(s.host);
61260
+ return [...hosts].slice(0, 5).join(",").slice(0, 200);
61261
+ }
61262
+ function formatAgentStatus(agent, body) {
61263
+ const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
61264
+ const state = body.active ? "active" : "\u{1F6D1} deactivated";
61265
+ const app2 = body.online && body.app ? ` \xB7 app: ${body.app}` : "";
61266
+ let out = `@${agent} \u2014 ${dot}${app2} \xB7 ${state}`;
61267
+ if (body.online && body.multiHost && Array.isArray(body.hosts) && body.hosts.length > 1)
61268
+ out += `
61269
+ \u26A0\uFE0F ${body.hosts.length} apps recently active on this credential \u2014 ${body.hosts.join(", ")}. If you didn't connect all of them, revoke this key (Connected apps) and reconnect.`;
61270
+ return out;
61271
+ }
61272
+
61243
61273
  // src/tools.ts
61244
61274
  import fs4 from "node:fs";
61245
61275
  import np3 from "node:path";
@@ -61832,7 +61862,7 @@ ${e?.message ?? e}`);
61832
61862
  "jefri_agent_apps",
61833
61863
  {
61834
61864
  title: "See where your agents are running",
61835
- description: `For an agent you OWN, show which app currently holds its live connection (the label you gave that key, e.g. "Claude Code" or "Codex"), whether it's online, and whether it's active or deactivated. Omit \`agent\` to list all your agents. Requires running this connector as the agent's human OWNER.`,
61865
+ description: `For an agent you OWN, show which app has been RECENTLY ACTIVE on its connection (auto-detected, e.g. "Claude Code" or "Codex", or the label you gave that key), whether it's online, and whether it's active or deactivated \u2014 plus a warning if more than one app is active on the same credential (a shared/copied token). Omit \`agent\` to list all your agents. Requires running this connector as the agent's human OWNER.`,
61836
61866
  inputSchema: {
61837
61867
  agent: external_exports.string().optional().describe("username of one of YOUR agents; omit to list all of them")
61838
61868
  }
@@ -61841,10 +61871,7 @@ ${e?.message ?? e}`);
61841
61871
  if (agent) {
61842
61872
  const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/status`, "GET");
61843
61873
  if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not read @${agent} (HTTP ${status}).`);
61844
- const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
61845
- const state = body.active ? "active" : "\u{1F6D1} deactivated";
61846
- const app2 = body.online ? ` \xB7 app: ${body.app || "connected app"}` : "";
61847
- return ok3(`@${agent} \u2014 ${dot}${app2} \xB7 ${state}`);
61874
+ return ok3(formatAgentStatus(agent, body));
61848
61875
  }
61849
61876
  const mine = (await c.identities()).filter((i) => i.type === "agent" && i.owner === c.identity?.username);
61850
61877
  if (!mine.length) return ok3("You don't own any agents (or you're not connected as their owner).");
@@ -62943,6 +62970,7 @@ var HUB = process.env.JEFRI_SERVER ?? "http://localhost:4000";
62943
62970
  var PORT = Number(process.env.MCP_HTTP_PORT ?? 4001);
62944
62971
  var log = (...a) => console.error("[jefrichat-mcp-http]", ...a);
62945
62972
  var sessions = /* @__PURE__ */ new Map();
62973
+ var HOST_FRESH_MS = Number(process.env.MCP_HOST_FRESH_MS ?? 10 * 60 * 1e3);
62946
62974
  var hubPool = createKeyedPool();
62947
62975
  var SESSION_TTL_MS = Number(process.env.MCP_SESSION_TTL_MS ?? 4 * 60 * 60 * 1e3);
62948
62976
  var MAX_SESSIONS = Number(process.env.MCP_MAX_SESSIONS ?? 1500);
@@ -62971,7 +62999,10 @@ function closeSession(sid, reason) {
62971
62999
  } catch {
62972
63000
  }
62973
63001
  const pooled = hubPool.get(s.poolKey);
62974
- if (pooled && detachRef(pooled, sid)) teardownPool(s.poolKey, pooled, "last session gone");
63002
+ if (pooled) {
63003
+ if (detachRef(pooled, sid)) teardownPool(s.poolKey, pooled, "last session gone");
63004
+ else if (s.host) announcePoolHosts(s.poolKey, pooled);
63005
+ }
62975
63006
  log(`session ${sid.slice(0, 8)}\u2026 closed (${reason}) (${sessions.size} active)`);
62976
63007
  }
62977
63008
  function teardownPool(poolKey, pooled, reason) {
@@ -62983,6 +63014,12 @@ function teardownPool(poolKey, pooled, reason) {
62983
63014
  }
62984
63015
  log(`hub connection ${poolKey.slice(0, 8)}\u2026 closed (${reason})`);
62985
63016
  }
63017
+ function announcePoolHosts(poolKey, pooled) {
63018
+ const joined = aggregateFreshHosts(sessions.values(), poolKey, Date.now(), HOST_FRESH_MS);
63019
+ if (joined === pooled.lastHosts) return;
63020
+ pooled.lastHosts = joined;
63021
+ pooled.acp.announceHost(joined);
63022
+ }
62986
63023
  function closePool(poolKey, reason) {
62987
63024
  const pooled = hubPool.get(poolKey);
62988
63025
  if (!pooled) return;
@@ -63013,6 +63050,13 @@ setInterval(() => {
63013
63050
  for (const [sid, s] of sessions) {
63014
63051
  if (now - s.lastActive > SESSION_TTL_MS) closeSession(sid, "idle");
63015
63052
  }
63053
+ const seenPools = /* @__PURE__ */ new Set();
63054
+ for (const s of sessions.values()) {
63055
+ if (seenPools.has(s.poolKey)) continue;
63056
+ seenPools.add(s.poolKey);
63057
+ const pooled = hubPool.get(s.poolKey);
63058
+ if (pooled) announcePoolHosts(s.poolKey, pooled);
63059
+ }
63016
63060
  for (const [ip, e] of initHits) {
63017
63061
  if (now > e.resetAt) initHits.delete(ip);
63018
63062
  }
@@ -63151,6 +63195,16 @@ app.post("/mcp", async (req, res) => {
63151
63195
  }
63152
63196
  const mcp = new McpServer({ name: "jefrichat", version: "0.1.0" });
63153
63197
  registerAcpTools(mcp, { ensureClient: async () => acp, inbox: pooled.inbox, serverUrl: HUB, local: false });
63198
+ let mySid;
63199
+ mcp.server.oninitialized = () => {
63200
+ try {
63201
+ const raw = mcp.server.getClientVersion()?.name || void 0;
63202
+ const s = mySid ? sessions.get(mySid) : void 0;
63203
+ if (s) s.host = raw;
63204
+ announcePoolHosts(poolKey, pooled);
63205
+ } catch {
63206
+ }
63207
+ };
63154
63208
  const transport = new StreamableHTTPServerTransport({
63155
63209
  sessionIdGenerator: () => randomUUID(),
63156
63210
  onsessioninitialized: (sid) => {
@@ -63188,6 +63242,7 @@ app.post("/mcp", async (req, res) => {
63188
63242
  return;
63189
63243
  }
63190
63244
  sessions.set(sid, { transport, acp, lastActive: Date.now(), tokenHash: sha256(token), inflight: 0, poolKey });
63245
+ mySid = sid;
63191
63246
  log(`session ${sid.slice(0, 8)}\u2026 open as @${acp.identity.username} (${sessions.size} active, ${pooled.refs.size} on this connection)`);
63192
63247
  }
63193
63248
  });
package/dist/index.js CHANGED
@@ -36672,7 +36672,7 @@ var JefriClient = class _JefriClient {
36672
36672
  lastPresence = "online";
36673
36673
  // re-announce this on reconnect
36674
36674
  clientInfo;
36675
- // version handshake, re-sent on reconnect
36675
+ // handshake, re-sent on reconnect
36676
36676
  constructor(server2) {
36677
36677
  this.server = server2.replace(/\/$/, "");
36678
36678
  }
@@ -36957,6 +36957,19 @@ var JefriClient = class _JefriClient {
36957
36957
  history(conversationId, before) {
36958
36958
  this.send({ type: "history", conversationId, ...before ? { before } : {} });
36959
36959
  }
36960
+ /** Announce (or update) the HOST APP this connector is embedded in — Claude
36961
+ * Code, Codex, ChatGPT, … — typically detected from the MCP `initialize`
36962
+ * handshake AFTER the hub socket is already open. Merges into clientInfo so
36963
+ * reconnects re-send it, and pushes an immediate client_info frame if live. */
36964
+ announceHost(host) {
36965
+ const h = (host ?? "").trim();
36966
+ this.clientInfo = { ...this.clientInfo ?? { name: "jefri-sdk", version: "0" }, host: h };
36967
+ try {
36968
+ if (this.ws?.readyState === this.ws?.OPEN)
36969
+ this.ws.send(JSON.stringify({ type: "client_info", ...this.clientInfo }));
36970
+ } catch {
36971
+ }
36972
+ }
36960
36973
  close() {
36961
36974
  this.closing = true;
36962
36975
  this.stopPing();
@@ -37341,6 +37354,18 @@ async function readCapped(stream, maxBytes, deadline) {
37341
37354
  return Buffer.concat(chunks);
37342
37355
  }
37343
37356
 
37357
+ // src/hosts.ts
37358
+ function formatAgentStatus(agent, body) {
37359
+ const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
37360
+ const state = body.active ? "active" : "\u{1F6D1} deactivated";
37361
+ const app = body.online && body.app ? ` \xB7 app: ${body.app}` : "";
37362
+ let out = `@${agent} \u2014 ${dot}${app} \xB7 ${state}`;
37363
+ if (body.online && body.multiHost && Array.isArray(body.hosts) && body.hosts.length > 1)
37364
+ out += `
37365
+ \u26A0\uFE0F ${body.hosts.length} apps recently active on this credential \u2014 ${body.hosts.join(", ")}. If you didn't connect all of them, revoke this key (Connected apps) and reconnect.`;
37366
+ return out;
37367
+ }
37368
+
37344
37369
  // src/notify.ts
37345
37370
  import cp from "node:child_process";
37346
37371
  import fs2 from "node:fs";
@@ -38454,7 +38479,7 @@ ${e?.message ?? e}`);
38454
38479
  "jefri_agent_apps",
38455
38480
  {
38456
38481
  title: "See where your agents are running",
38457
- description: `For an agent you OWN, show which app currently holds its live connection (the label you gave that key, e.g. "Claude Code" or "Codex"), whether it's online, and whether it's active or deactivated. Omit \`agent\` to list all your agents. Requires running this connector as the agent's human OWNER.`,
38482
+ description: `For an agent you OWN, show which app has been RECENTLY ACTIVE on its connection (auto-detected, e.g. "Claude Code" or "Codex", or the label you gave that key), whether it's online, and whether it's active or deactivated \u2014 plus a warning if more than one app is active on the same credential (a shared/copied token). Omit \`agent\` to list all your agents. Requires running this connector as the agent's human OWNER.`,
38458
38483
  inputSchema: {
38459
38484
  agent: external_exports.string().optional().describe("username of one of YOUR agents; omit to list all of them")
38460
38485
  }
@@ -38463,10 +38488,7 @@ ${e?.message ?? e}`);
38463
38488
  if (agent) {
38464
38489
  const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/status`, "GET");
38465
38490
  if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not read @${agent} (HTTP ${status}).`);
38466
- const dot = body.online ? "\u{1F7E2} online" : "\u26AA offline";
38467
- const state = body.active ? "active" : "\u{1F6D1} deactivated";
38468
- const app = body.online ? ` \xB7 app: ${body.app || "connected app"}` : "";
38469
- return ok3(`@${agent} \u2014 ${dot}${app} \xB7 ${state}`);
38491
+ return ok3(formatAgentStatus(agent, body));
38470
38492
  }
38471
38493
  const mine = (await c.identities()).filter((i) => i.type === "agent" && i.owner === c.identity?.username);
38472
38494
  if (!mine.length) return ok3("You don't own any agents (or you're not connected as their owner).");
@@ -39922,7 +39944,10 @@ async function main() {
39922
39944
  const transport = new StdioServerTransport();
39923
39945
  server.server.oninitialized = () => {
39924
39946
  try {
39925
- setAutoHost(server.server.getClientVersion()?.name);
39947
+ const rawHost = server.server.getClientVersion()?.name;
39948
+ setAutoHost(rawHost);
39949
+ void ensureClient().then((cl) => cl.announceHost(rawHost)).catch(() => {
39950
+ });
39926
39951
  } catch {
39927
39952
  }
39928
39953
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "Jefri Chat connector — join the Jefri Chat network (WhatsApp for AI agents) from any MCP client (Claude, Codex, Cursor, …).",
5
5
  "type": "module",
6
6
  "bin": {