@ateam-ai/mcp 0.4.78 → 0.4.79

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 (2) hide show
  1. package/package.json +2 -2
  2. package/src/api.js +30 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.4.78",
3
+ "version": "0.4.79",
4
4
  "mcpName": "io.github.ariekogan/ateam-mcp",
5
5
  "description": "A-Team MCP Server — build, validate, and deploy multi-agent solutions from any AI environment",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "start:http": "node src/index.js --http",
14
14
  "dev": "node --watch src/index.js",
15
15
  "dev:http": "node --watch src/index.js --http",
16
- "test": "node test/session-isolation.test.mjs && node test/widget-protocol.test.mjs"
16
+ "test": "node test/session-isolation.test.mjs && node test/widget-protocol.test.mjs && node test/actor-binding.test.mjs"
17
17
  },
18
18
  "keywords": [
19
19
  "mcp",
package/src/api.js CHANGED
@@ -173,6 +173,27 @@ export function isExplicitlyAuthenticated(sessionId) {
173
173
  * Record activity on a session — called on every tool call.
174
174
  * Keeps the session alive and updates context for smarter UX.
175
175
  */
176
+ /**
177
+ * Forget the session's bound actor.
178
+ *
179
+ * A session binds an actor from args or from a run-starting tool's result, and
180
+ * NOTHING ever unbound it — so a session that bound a bad value ("dev", a
181
+ * branch name a caller mistook for an actor) sent it on every later request
182
+ * forever, each one 401-ing with `Actor "dev" not found`. Restricting where a
183
+ * bind may come from narrows the entrance; it does not open an exit.
184
+ *
185
+ * Called when Core tells us the actor does not exist. Self-healing beats a
186
+ * hint telling a human to re-authenticate a key that was never the problem.
187
+ */
188
+ export function clearSessionActor(sessionId, reason = "") {
189
+ const session = sessionId ? sessions.get(sessionId) : null;
190
+ if (!session?.context?.actorId) return false;
191
+ const had = session.context.actorId;
192
+ delete session.context.actorId;
193
+ console.warn(`[Auth] Unbound session actor "${had}"${reason ? ` — ${reason}` : ""}. Later calls act as the tenant until an actor is passed again.`);
194
+ return true;
195
+ }
196
+
176
197
  export function touchSession(sessionId, { toolName, solutionId, skillId, actorId } = {}) {
177
198
  const session = sessions.get(sessionId);
178
199
  if (!session) return;
@@ -641,6 +662,15 @@ async function request(method, path, body, sessionId, opts = {}) {
641
662
 
642
663
  if (!res.ok) {
643
664
  const text = await res.text().catch(() => "");
665
+ // SELF-HEAL A BAD ACTOR BINDING. Core saying `Actor "X" not found` is
666
+ // proof the session is carrying an actor that does not exist, and every
667
+ // subsequent request would send it again. Restricting where a bind may
668
+ // come from narrows the entrance; this is the exit. Unbinding costs
669
+ // nothing when the actor was genuinely wrong, and the next call simply
670
+ // acts as the tenant until a real actor is passed.
671
+ if (res.status === 401 && /Actor\s+\\?"?[^"\\,}]+\\?"?\s+not found|unknown actor/i.test(text)) {
672
+ clearSessionActor(sessionId, `Core rejected it on ${method} ${path}`);
673
+ }
644
674
  // Attach the HTTP status so callers can distinguish a genuine 404
645
675
  // (resource absent) from a transient/5xx failure. ateam_patch relies
646
676
  // on this to NOT scaffold-clobber an existing skill on a read error.