jefrichat-mcp 0.31.0 → 0.32.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/dist/http.js +218 -45
  2. package/dist/index.js +112 -29
  3. package/package.json +1 -1
package/dist/http.js CHANGED
@@ -60681,7 +60681,7 @@ var JefriClient = class _JefriClient {
60681
60681
  isAlive = true;
60682
60682
  // heartbeat: did we get a pong since the last ping?
60683
60683
  hasConnected = false;
60684
- // armed for auto-reconnect only after the first open
60684
+ // armed for auto-reconnect only after the hub `registered` us
60685
60685
  lastPresence = "online";
60686
60686
  // re-announce this on reconnect
60687
60687
  clientInfo;
@@ -60713,31 +60713,46 @@ var JefriClient = class _JefriClient {
60713
60713
  }
60714
60714
  client.token = token;
60715
60715
  client.clientInfo = opts.clientInfo;
60716
- await client.openSocket();
60717
- if (!client.identity) {
60718
- await new Promise((resolve, reject) => {
60719
- let done = false;
60720
- const finish = (fn) => {
60721
- if (done) return;
60722
- done = true;
60723
- client.off("registered", onReg);
60724
- client.off("error", onErr);
60725
- clearTimeout(timer);
60726
- fn();
60727
- };
60728
- const onReg = () => finish(resolve);
60729
- const onErr = (ev) => finish(
60730
- () => reject(
60731
- /auth/i.test(ev?.message ?? "") ? new JefriAuthError("invalid or expired Jefri Chat token \u2014 get a fresh connect command") : new Error(ev?.message ?? "hub rejected the connection")
60732
- )
60733
- );
60734
- const timer = setTimeout(
60735
- () => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
60736
- 8e3
60737
- );
60738
- client.on("registered", onReg);
60739
- client.on("error", onErr);
60740
- });
60716
+ let onReg = () => {
60717
+ };
60718
+ let onErr = () => {
60719
+ };
60720
+ let timer;
60721
+ const registered = new Promise((resolve, reject) => {
60722
+ let done = false;
60723
+ const finish = (fn) => {
60724
+ if (done) return;
60725
+ done = true;
60726
+ client.off("registered", onReg);
60727
+ client.off("error", onErr);
60728
+ clearTimeout(timer);
60729
+ fn();
60730
+ };
60731
+ onReg = () => finish(resolve);
60732
+ onErr = (ev) => finish(
60733
+ () => reject(
60734
+ /auth/i.test(ev?.message ?? "") ? new JefriAuthError("invalid or expired Jefri Chat token \u2014 get a fresh connect command") : new Error(ev?.message ?? "hub rejected the connection")
60735
+ )
60736
+ );
60737
+ const regTimeoutMs = typeof process !== "undefined" && Number(process.env?.JEFRI_REGISTER_TIMEOUT_MS) || 8e3;
60738
+ timer = setTimeout(
60739
+ () => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
60740
+ regTimeoutMs
60741
+ );
60742
+ client.on("registered", onReg);
60743
+ client.on("error", onErr);
60744
+ });
60745
+ try {
60746
+ await Promise.all([client.openSocket(), registered]);
60747
+ } catch (e) {
60748
+ client.off("registered", onReg);
60749
+ client.off("error", onErr);
60750
+ clearTimeout(timer);
60751
+ try {
60752
+ client.close();
60753
+ } catch {
60754
+ }
60755
+ throw e;
60741
60756
  }
60742
60757
  if (opts.status) client.presence(opts.status);
60743
60758
  await client.ensureE2EKey().catch(() => {
@@ -60750,8 +60765,6 @@ var JefriClient = class _JefriClient {
60750
60765
  return new Promise((resolve, reject) => {
60751
60766
  let settled = false;
60752
60767
  this.ws.on("open", () => {
60753
- this.reconnectAttempts = 0;
60754
- this.hasConnected = true;
60755
60768
  this.startPing();
60756
60769
  try {
60757
60770
  this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
@@ -60781,7 +60794,11 @@ var JefriClient = class _JefriClient {
60781
60794
  } catch {
60782
60795
  return;
60783
60796
  }
60784
- if (ev.event === "registered") this.identity = ev.identity;
60797
+ if (ev.event === "registered") {
60798
+ this.identity = ev.identity;
60799
+ this.hasConnected = true;
60800
+ this.reconnectAttempts = 0;
60801
+ }
60785
60802
  if (ev.event === "error" && /auth/i.test(ev.message ?? "")) {
60786
60803
  this.authFailed = true;
60787
60804
  }
@@ -61181,6 +61198,48 @@ var JefriClient = class _JefriClient {
61181
61198
  };
61182
61199
  var serveDir = path2.join(os.homedir(), ".acp", "serve");
61183
61200
 
61201
+ // src/conn-pool.ts
61202
+ function attachRef(c, id) {
61203
+ if (c.closed) return false;
61204
+ c.refs.add(id);
61205
+ return true;
61206
+ }
61207
+ function detachRef(c, id) {
61208
+ c.refs.delete(id);
61209
+ return c.refs.size === 0;
61210
+ }
61211
+ function createKeyedPool() {
61212
+ const ready = /* @__PURE__ */ new Map();
61213
+ const pending = /* @__PURE__ */ new Map();
61214
+ return {
61215
+ async acquire(key, create) {
61216
+ const existing = ready.get(key);
61217
+ if (existing !== void 0) return existing;
61218
+ const inflight = pending.get(key);
61219
+ if (inflight) return inflight;
61220
+ const p = (async () => {
61221
+ const value = await create();
61222
+ ready.set(key, value);
61223
+ return value;
61224
+ })();
61225
+ pending.set(key, p);
61226
+ try {
61227
+ return await p;
61228
+ } finally {
61229
+ pending.delete(key);
61230
+ }
61231
+ },
61232
+ get: (key) => ready.get(key),
61233
+ drop: (key) => {
61234
+ const v = ready.get(key);
61235
+ ready.delete(key);
61236
+ return v;
61237
+ },
61238
+ has: (key) => ready.has(key),
61239
+ size: () => ready.size
61240
+ };
61241
+ }
61242
+
61184
61243
  // src/tools.ts
61185
61244
  import fs4 from "node:fs";
61186
61245
  import np3 from "node:path";
@@ -61756,6 +61815,72 @@ ${e?.message ?? e}`);
61756
61815
  return ok3(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
61757
61816
  })
61758
61817
  );
61818
+ const NOT_OWNER = "This only works when you run the Jefri connector as your OWN (human) account and own this agent. An agent session can't manage agents \u2014 use the web app or an owner session.";
61819
+ const ownerApi = async (c, path3, method) => {
61820
+ const res = await fetch(`${ctx.serverUrl}${path3}`, {
61821
+ method,
61822
+ headers: { Authorization: `Bearer ${c.token}` }
61823
+ });
61824
+ let body = {};
61825
+ try {
61826
+ body = await res.json();
61827
+ } catch {
61828
+ }
61829
+ return { status: res.status, body };
61830
+ };
61831
+ server.registerTool(
61832
+ "jefri_agent_apps",
61833
+ {
61834
+ 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.`,
61836
+ inputSchema: {
61837
+ agent: external_exports.string().optional().describe("username of one of YOUR agents; omit to list all of them")
61838
+ }
61839
+ },
61840
+ async ({ agent }) => withClient(async (c) => {
61841
+ if (agent) {
61842
+ const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/status`, "GET");
61843
+ 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}`);
61848
+ }
61849
+ const mine = (await c.identities()).filter((i) => i.type === "agent" && i.owner === c.identity?.username);
61850
+ if (!mine.length) return ok3("You don't own any agents (or you're not connected as their owner).");
61851
+ const lines = mine.map((i) => `${i.status && i.status !== "offline" ? "\u{1F7E2}" : "\u26AA"} @${i.username} \u2014 ${i.displayName}`);
61852
+ return ok3(`Your agents:
61853
+ ${lines.join("\n")}
61854
+
61855
+ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
61856
+ })
61857
+ );
61858
+ server.registerTool(
61859
+ "jefri_deactivate",
61860
+ {
61861
+ title: "Deactivate an agent",
61862
+ description: "Deactivate an agent you OWN: it is immediately disconnected and CANNOT reconnect from any app (Claude, Codex, SDK, \u2026) until you reactivate it. Reversible via jefri_activate. Requires running this connector as the agent's human OWNER \u2014 an agent session cannot deactivate agents.",
61863
+ inputSchema: { agent: external_exports.string().describe("username of YOUR agent to deactivate") }
61864
+ },
61865
+ async ({ agent }) => withClient(async (c) => {
61866
+ const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/deactivate`, "POST");
61867
+ if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not deactivate @${agent} (HTTP ${status}).`);
61868
+ return ok3(`\u{1F6D1} Deactivated @${agent}. It's disconnected and can't reconnect from any app until you reactivate it.`);
61869
+ })
61870
+ );
61871
+ server.registerTool(
61872
+ "jefri_activate",
61873
+ {
61874
+ title: "Reactivate an agent",
61875
+ description: "Reactivate an agent you OWN that was previously deactivated, so it can connect again. Requires running this connector as the agent's human OWNER.",
61876
+ inputSchema: { agent: external_exports.string().describe("username of YOUR agent to reactivate") }
61877
+ },
61878
+ async ({ agent }) => withClient(async (c) => {
61879
+ const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/activate`, "POST");
61880
+ if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not reactivate @${agent} (HTTP ${status}).`);
61881
+ return ok3(`\u2705 Reactivated @${agent}. It can connect again now.`);
61882
+ })
61883
+ );
61759
61884
  server.registerTool(
61760
61885
  "jefri_send",
61761
61886
  {
@@ -62818,6 +62943,7 @@ var HUB = process.env.JEFRI_SERVER ?? "http://localhost:4000";
62818
62943
  var PORT = Number(process.env.MCP_HTTP_PORT ?? 4001);
62819
62944
  var log = (...a) => console.error("[jefrichat-mcp-http]", ...a);
62820
62945
  var sessions = /* @__PURE__ */ new Map();
62946
+ var hubPool = createKeyedPool();
62821
62947
  var SESSION_TTL_MS = Number(process.env.MCP_SESSION_TTL_MS ?? 4 * 60 * 60 * 1e3);
62822
62948
  var MAX_SESSIONS = Number(process.env.MCP_MAX_SESSIONS ?? 1500);
62823
62949
  var MAX_SESSIONS_PER_IDENTITY = Number(process.env.MCP_MAX_SESSIONS_PER_IDENTITY ?? 20);
@@ -62841,14 +62967,41 @@ function closeSession(sid, reason) {
62841
62967
  if (!s) return;
62842
62968
  sessions.delete(sid);
62843
62969
  try {
62844
- s.acp.close();
62970
+ s.transport.close?.();
62845
62971
  } catch {
62846
62972
  }
62973
+ const pooled = hubPool.get(s.poolKey);
62974
+ if (pooled && detachRef(pooled, sid)) teardownPool(s.poolKey, pooled, "last session gone");
62975
+ log(`session ${sid.slice(0, 8)}\u2026 closed (${reason}) (${sessions.size} active)`);
62976
+ }
62977
+ function teardownPool(poolKey, pooled, reason) {
62978
+ pooled.closed = true;
62979
+ if (hubPool.get(poolKey) === pooled) hubPool.drop(poolKey);
62847
62980
  try {
62848
- s.transport.close?.();
62981
+ pooled.acp.close();
62849
62982
  } catch {
62850
62983
  }
62851
- log(`session ${sid.slice(0, 8)}\u2026 closed (${reason}) (${sessions.size} active)`);
62984
+ log(`hub connection ${poolKey.slice(0, 8)}\u2026 closed (${reason})`);
62985
+ }
62986
+ function closePool(poolKey, reason) {
62987
+ const pooled = hubPool.get(poolKey);
62988
+ if (!pooled) return;
62989
+ pooled.closed = true;
62990
+ for (const sid of [...pooled.refs]) closeSession(sid, reason);
62991
+ if (hubPool.get(poolKey) === pooled) teardownPool(poolKey, pooled, reason);
62992
+ }
62993
+ async function acquirePooledConn(token, poolKey) {
62994
+ return hubPool.acquire(poolKey, async () => {
62995
+ const acp = await JefriClient.connect({ server: HUB, token, status: "online" });
62996
+ const inbox = [];
62997
+ if (acp.identity) attachInbox(acp, inbox, acp.identity.username);
62998
+ const pooled = { acp, inbox, refs: /* @__PURE__ */ new Set(), closed: false };
62999
+ acp.on("force_disconnect", () => closePool(poolKey, "credential revoked"));
63000
+ acp.on("error", (ev) => {
63001
+ if (/auth|revoked|unauthor|disabled/i.test(String(ev?.message ?? ""))) closePool(poolKey, "auth revoked");
63002
+ });
63003
+ return pooled;
63004
+ });
62852
63005
  }
62853
63006
  function touch(sid) {
62854
63007
  if (!sid) return;
@@ -62967,9 +63120,10 @@ app.post("/mcp", async (req, res) => {
62967
63120
  });
62968
63121
  return;
62969
63122
  }
62970
- let acp;
63123
+ const poolKey = sha256(token).toString("hex");
63124
+ let pooled;
62971
63125
  try {
62972
- acp = await JefriClient.connect({ server: HUB, token, status: "online" });
63126
+ pooled = await acquirePooledConn(token, poolKey);
62973
63127
  } catch (e) {
62974
63128
  if (e instanceof JefriAuthError || e?.code === "ACP_AUTH_FAILED") {
62975
63129
  res.status(401).json({
@@ -62985,7 +63139,9 @@ app.post("/mcp", async (req, res) => {
62985
63139
  res.status(502).json({ jsonrpc: "2.0", error: { code: -32002, message: `Hub unreachable: ${e?.message ?? e}` }, id: null });
62986
63140
  return;
62987
63141
  }
63142
+ const acp = pooled.acp;
62988
63143
  if (!acp.identity) {
63144
+ closePool(poolKey, "no identity");
62989
63145
  res.status(401).json({
62990
63146
  jsonrpc: "2.0",
62991
63147
  error: { code: -32001, message: "Invalid or expired Jefri Chat token \u2014 rotate it in the Jefri Chat web app and reconnect." },
@@ -62993,13 +63149,19 @@ app.post("/mcp", async (req, res) => {
62993
63149
  });
62994
63150
  return;
62995
63151
  }
62996
- const inbox = [];
62997
- attachInbox(acp, inbox, acp.identity.username);
62998
63152
  const mcp = new McpServer({ name: "jefrichat", version: "0.1.0" });
62999
- registerAcpTools(mcp, { ensureClient: async () => acp, inbox, serverUrl: HUB, local: false });
63153
+ registerAcpTools(mcp, { ensureClient: async () => acp, inbox: pooled.inbox, serverUrl: HUB, local: false });
63000
63154
  const transport = new StreamableHTTPServerTransport({
63001
63155
  sessionIdGenerator: () => randomUUID(),
63002
63156
  onsessioninitialized: (sid) => {
63157
+ if (pooled.closed) {
63158
+ try {
63159
+ transport.close?.();
63160
+ } catch {
63161
+ }
63162
+ log(`session ${sid.slice(0, 8)}\u2026 refused (connection torn down during init)`);
63163
+ return;
63164
+ }
63003
63165
  const meName = acp.identity.username;
63004
63166
  const mine = [...sessions.entries()].filter(([, s]) => s.acp.identity?.username === meName);
63005
63167
  if (mine.length >= MAX_SESSIONS_PER_IDENTITY) {
@@ -63017,20 +63179,31 @@ app.post("/mcp", async (req, res) => {
63017
63179
  }
63018
63180
  if (oldest) closeSession(oldest, "evicted (cap)");
63019
63181
  }
63020
- sessions.set(sid, { transport, acp, lastActive: Date.now(), tokenHash: sha256(token), inflight: 0 });
63021
- log(`session ${sid.slice(0, 8)}\u2026 open as @${acp.identity.username} (${sessions.size} active)`);
63022
- acp.on("force_disconnect", () => closeSession(sid, "credential revoked"));
63023
- acp.on("error", (ev) => {
63024
- if (/auth|revoked|unauthor|disabled/i.test(String(ev?.message ?? ""))) closeSession(sid, "auth revoked");
63025
- });
63182
+ if (!attachRef(pooled, sid)) {
63183
+ try {
63184
+ transport.close?.();
63185
+ } catch {
63186
+ }
63187
+ log(`session ${sid.slice(0, 8)}\u2026 refused (connection torn down during init)`);
63188
+ return;
63189
+ }
63190
+ sessions.set(sid, { transport, acp, lastActive: Date.now(), tokenHash: sha256(token), inflight: 0, poolKey });
63191
+ log(`session ${sid.slice(0, 8)}\u2026 open as @${acp.identity.username} (${sessions.size} active, ${pooled.refs.size} on this connection)`);
63026
63192
  }
63027
63193
  });
63028
63194
  transport.onclose = () => {
63029
63195
  const sid = transport.sessionId;
63030
63196
  if (sid) closeSession(sid, "client closed");
63031
63197
  };
63032
- await mcp.connect(transport);
63033
- await transport.handleRequest(req, res, req.body);
63198
+ const reservation = "init:" + randomUUID();
63199
+ pooled.refs.add(reservation);
63200
+ try {
63201
+ await mcp.connect(transport);
63202
+ await transport.handleRequest(req, res, req.body);
63203
+ } finally {
63204
+ pooled.refs.delete(reservation);
63205
+ if (pooled.refs.size === 0 && hubPool.get(poolKey) === pooled) teardownPool(poolKey, pooled, "init failed, no session");
63206
+ }
63034
63207
  return;
63035
63208
  }
63036
63209
  res.status(404).json({
package/dist/index.js CHANGED
@@ -36668,7 +36668,7 @@ var JefriClient = class _JefriClient {
36668
36668
  isAlive = true;
36669
36669
  // heartbeat: did we get a pong since the last ping?
36670
36670
  hasConnected = false;
36671
- // armed for auto-reconnect only after the first open
36671
+ // armed for auto-reconnect only after the hub `registered` us
36672
36672
  lastPresence = "online";
36673
36673
  // re-announce this on reconnect
36674
36674
  clientInfo;
@@ -36700,31 +36700,46 @@ var JefriClient = class _JefriClient {
36700
36700
  }
36701
36701
  client.token = token;
36702
36702
  client.clientInfo = opts.clientInfo;
36703
- await client.openSocket();
36704
- if (!client.identity) {
36705
- await new Promise((resolve, reject) => {
36706
- let done = false;
36707
- const finish = (fn) => {
36708
- if (done) return;
36709
- done = true;
36710
- client.off("registered", onReg);
36711
- client.off("error", onErr);
36712
- clearTimeout(timer);
36713
- fn();
36714
- };
36715
- const onReg = () => finish(resolve);
36716
- const onErr = (ev) => finish(
36717
- () => reject(
36718
- /auth/i.test(ev?.message ?? "") ? new JefriAuthError("invalid or expired Jefri Chat token \u2014 get a fresh connect command") : new Error(ev?.message ?? "hub rejected the connection")
36719
- )
36720
- );
36721
- const timer = setTimeout(
36722
- () => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
36723
- 8e3
36724
- );
36725
- client.on("registered", onReg);
36726
- client.on("error", onErr);
36727
- });
36703
+ let onReg = () => {
36704
+ };
36705
+ let onErr = () => {
36706
+ };
36707
+ let timer;
36708
+ const registered = new Promise((resolve, reject) => {
36709
+ let done = false;
36710
+ const finish = (fn) => {
36711
+ if (done) return;
36712
+ done = true;
36713
+ client.off("registered", onReg);
36714
+ client.off("error", onErr);
36715
+ clearTimeout(timer);
36716
+ fn();
36717
+ };
36718
+ onReg = () => finish(resolve);
36719
+ onErr = (ev) => finish(
36720
+ () => reject(
36721
+ /auth/i.test(ev?.message ?? "") ? new JefriAuthError("invalid or expired Jefri Chat token \u2014 get a fresh connect command") : new Error(ev?.message ?? "hub rejected the connection")
36722
+ )
36723
+ );
36724
+ const regTimeoutMs = typeof process !== "undefined" && Number(process.env?.JEFRI_REGISTER_TIMEOUT_MS) || 8e3;
36725
+ timer = setTimeout(
36726
+ () => finish(() => reject(new Error("timed out waiting for the hub to register the connection"))),
36727
+ regTimeoutMs
36728
+ );
36729
+ client.on("registered", onReg);
36730
+ client.on("error", onErr);
36731
+ });
36732
+ try {
36733
+ await Promise.all([client.openSocket(), registered]);
36734
+ } catch (e) {
36735
+ client.off("registered", onReg);
36736
+ client.off("error", onErr);
36737
+ clearTimeout(timer);
36738
+ try {
36739
+ client.close();
36740
+ } catch {
36741
+ }
36742
+ throw e;
36728
36743
  }
36729
36744
  if (opts.status) client.presence(opts.status);
36730
36745
  await client.ensureE2EKey().catch(() => {
@@ -36737,8 +36752,6 @@ var JefriClient = class _JefriClient {
36737
36752
  return new Promise((resolve, reject) => {
36738
36753
  let settled = false;
36739
36754
  this.ws.on("open", () => {
36740
- this.reconnectAttempts = 0;
36741
- this.hasConnected = true;
36742
36755
  this.startPing();
36743
36756
  try {
36744
36757
  this.ws.send(JSON.stringify({ type: "presence", status: this.lastPresence }));
@@ -36768,7 +36781,11 @@ var JefriClient = class _JefriClient {
36768
36781
  } catch {
36769
36782
  return;
36770
36783
  }
36771
- if (ev.event === "registered") this.identity = ev.identity;
36784
+ if (ev.event === "registered") {
36785
+ this.identity = ev.identity;
36786
+ this.hasConnected = true;
36787
+ this.reconnectAttempts = 0;
36788
+ }
36772
36789
  if (ev.event === "error" && /auth/i.test(ev.message ?? "")) {
36773
36790
  this.authFailed = true;
36774
36791
  }
@@ -38420,6 +38437,72 @@ ${e?.message ?? e}`);
38420
38437
  return ok3(lines.length ? header + lines.join("\n") : "No other identities on the network yet.");
38421
38438
  })
38422
38439
  );
38440
+ const NOT_OWNER = "This only works when you run the Jefri connector as your OWN (human) account and own this agent. An agent session can't manage agents \u2014 use the web app or an owner session.";
38441
+ const ownerApi = async (c, path4, method) => {
38442
+ const res = await fetch(`${ctx.serverUrl}${path4}`, {
38443
+ method,
38444
+ headers: { Authorization: `Bearer ${c.token}` }
38445
+ });
38446
+ let body = {};
38447
+ try {
38448
+ body = await res.json();
38449
+ } catch {
38450
+ }
38451
+ return { status: res.status, body };
38452
+ };
38453
+ server2.registerTool(
38454
+ "jefri_agent_apps",
38455
+ {
38456
+ 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.`,
38458
+ inputSchema: {
38459
+ agent: external_exports.string().optional().describe("username of one of YOUR agents; omit to list all of them")
38460
+ }
38461
+ },
38462
+ async ({ agent }) => withClient(async (c) => {
38463
+ if (agent) {
38464
+ const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/status`, "GET");
38465
+ 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}`);
38470
+ }
38471
+ const mine = (await c.identities()).filter((i) => i.type === "agent" && i.owner === c.identity?.username);
38472
+ if (!mine.length) return ok3("You don't own any agents (or you're not connected as their owner).");
38473
+ const lines = mine.map((i) => `${i.status && i.status !== "offline" ? "\u{1F7E2}" : "\u26AA"} @${i.username} \u2014 ${i.displayName}`);
38474
+ return ok3(`Your agents:
38475
+ ${lines.join("\n")}
38476
+
38477
+ Use jefri_agent_apps with agent=<name> to see its live app + active state.`);
38478
+ })
38479
+ );
38480
+ server2.registerTool(
38481
+ "jefri_deactivate",
38482
+ {
38483
+ title: "Deactivate an agent",
38484
+ description: "Deactivate an agent you OWN: it is immediately disconnected and CANNOT reconnect from any app (Claude, Codex, SDK, \u2026) until you reactivate it. Reversible via jefri_activate. Requires running this connector as the agent's human OWNER \u2014 an agent session cannot deactivate agents.",
38485
+ inputSchema: { agent: external_exports.string().describe("username of YOUR agent to deactivate") }
38486
+ },
38487
+ async ({ agent }) => withClient(async (c) => {
38488
+ const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/deactivate`, "POST");
38489
+ if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not deactivate @${agent} (HTTP ${status}).`);
38490
+ return ok3(`\u{1F6D1} Deactivated @${agent}. It's disconnected and can't reconnect from any app until you reactivate it.`);
38491
+ })
38492
+ );
38493
+ server2.registerTool(
38494
+ "jefri_activate",
38495
+ {
38496
+ title: "Reactivate an agent",
38497
+ description: "Reactivate an agent you OWN that was previously deactivated, so it can connect again. Requires running this connector as the agent's human OWNER.",
38498
+ inputSchema: { agent: external_exports.string().describe("username of YOUR agent to reactivate") }
38499
+ },
38500
+ async ({ agent }) => withClient(async (c) => {
38501
+ const { status, body } = await ownerApi(c, `/api/agents/${encodeURIComponent(agent)}/activate`, "POST");
38502
+ if (status !== 200) return fail(body?.error === "not allowed" ? NOT_OWNER : `Could not reactivate @${agent} (HTTP ${status}).`);
38503
+ return ok3(`\u2705 Reactivated @${agent}. It can connect again now.`);
38504
+ })
38505
+ );
38423
38506
  server2.registerTool(
38424
38507
  "jefri_send",
38425
38508
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jefrichat-mcp",
3
- "version": "0.31.0",
3
+ "version": "0.32.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": {