@ateam-ai/mcp 0.4.77 → 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.
- package/package.json +2 -2
- package/src/api.js +54 -0
- package/src/tools.js +24 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ateam-ai/mcp",
|
|
3
|
-
"version": "0.4.
|
|
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;
|
|
@@ -487,6 +508,30 @@ function formatError(method, path, status, body, baseUrl) {
|
|
|
487
508
|
503: "A-Team API is temporarily unavailable. Try again in a minute.",
|
|
488
509
|
};
|
|
489
510
|
|
|
511
|
+
// A 401 IS NOT ALWAYS AN AUTH PROBLEM, AND SAYING SO COSTS A RUN.
|
|
512
|
+
//
|
|
513
|
+
// The table below attaches "your API key may be invalid or expired — get a
|
|
514
|
+
// new key and call ateam_auth" to EVERY 401, by status code, never by cause.
|
|
515
|
+
// Core also returns 401 for `Actor "X" not found`, where the key is perfectly
|
|
516
|
+
// valid and the actor is the problem. A PROD agent believed that hint this
|
|
517
|
+
// morning, stopped, and asked the admin to paste an API key — for an error
|
|
518
|
+
// that had nothing to do with keys. An error naming the wrong remedy does not
|
|
519
|
+
// just fail; it sends someone competent in the wrong direction.
|
|
520
|
+
if (status === 401) {
|
|
521
|
+
const t = typeof body === "string"
|
|
522
|
+
? body
|
|
523
|
+
: (() => { try { return JSON.stringify(body || ""); } catch { return ""; } })();
|
|
524
|
+
const m = /Actor\s+\\?"([^"\\]+)\\?"\s+not found|unknown actor\s+\\?"?([^"\\,}]+)/i.exec(t);
|
|
525
|
+
if (m) {
|
|
526
|
+
const who = (m[1] || m[2] || "").trim();
|
|
527
|
+
hints[401] =
|
|
528
|
+
`NOT an auth problem — your key is fine. Core does not recognise the ACTOR "${who}" in this tenant. ` +
|
|
529
|
+
`Re-authenticating will not help. Either pass a real actor id (the one ateam_conversation returned for the thread), ` +
|
|
530
|
+
`or omit the actor entirely to act as the tenant. If you never sent an actor, the session is bound to a stale one: ` +
|
|
531
|
+
`call ateam_auth again to reset the session binding.`;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
490
535
|
// A 500 THAT NAMES A MISSING CONFIGURATION IS NOT "TRY AGAIN IN A MINUTE".
|
|
491
536
|
//
|
|
492
537
|
// /chat answered 500 {"message":"OPENAI_API_KEY is not set"} and this table
|
|
@@ -617,6 +662,15 @@ async function request(method, path, body, sessionId, opts = {}) {
|
|
|
617
662
|
|
|
618
663
|
if (!res.ok) {
|
|
619
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
|
+
}
|
|
620
674
|
// Attach the HTTP status so callers can distinguish a genuine 404
|
|
621
675
|
// (resource absent) from a transient/5xx failure. ateam_patch relies
|
|
622
676
|
// on this to NOT scaffold-clobber an existing skill on a read error.
|
package/src/tools.js
CHANGED
|
@@ -19,6 +19,17 @@ import {
|
|
|
19
19
|
// (tenant + the app URL to view the change). ateam-mcp is a PUBLIC MCP used
|
|
20
20
|
// from non-desktop clients too, so the location must live IN the tool result
|
|
21
21
|
// — not only in a desktop plugin's SKILL.md. Read-only/global tools skip it.
|
|
22
|
+
// Tools that START a run and therefore MINT the actor that ran it. Only these
|
|
23
|
+
// may rebind the session's actor — see the call site for what accepting it from
|
|
24
|
+
// any result cost.
|
|
25
|
+
const ACTOR_MINTING_TOOLS = new Set([
|
|
26
|
+
"ateam_conversation",
|
|
27
|
+
"ateam_test_skill",
|
|
28
|
+
"ateam_solution_chat",
|
|
29
|
+
"ateam_test_pipeline",
|
|
30
|
+
"ateam_test_voice",
|
|
31
|
+
]);
|
|
32
|
+
|
|
22
33
|
const STAMP_WHERE_TOOLS = new Set([
|
|
23
34
|
"ateam_build_and_run", "ateam_patch", "ateam_upload_connector", "ateam_redeploy",
|
|
24
35
|
"ateam_create_skill", "ateam_create_connector", "ateam_create_plugin",
|
|
@@ -6129,7 +6140,19 @@ export async function handleToolCall(name, args, sessionId) {
|
|
|
6129
6140
|
// it on the way out so the follow-up ateam_get_execution_logs /
|
|
6130
6141
|
// ateam_get_metrics on that very job is not refused for not knowing who ran
|
|
6131
6142
|
// it — the single most common dead end when debugging a run.
|
|
6132
|
-
|
|
6143
|
+
//
|
|
6144
|
+
// ONLY FROM TOOLS THAT ACTUALLY MINT ONE. This used to accept `actor_id` off
|
|
6145
|
+
// ANY tool's result, so a single unrelated payload carrying that field
|
|
6146
|
+
// silently repointed the whole session — observed on a clean e2e where every
|
|
6147
|
+
// later call 401'd with `Actor "dev" not found`, from an agent that had
|
|
6148
|
+
// never sent an actor at all. A session that binds to a non-existent actor
|
|
6149
|
+
// never recovers on its own, because nothing ever unbinds it.
|
|
6150
|
+
//
|
|
6151
|
+
// The generated-thread-key filter in api.js does not help here: it rejects
|
|
6152
|
+
// test_<ts>_<rand>, and a short literal like "dev" walks straight past it.
|
|
6153
|
+
// An allow-list of minting tools is the honest boundary — "who ran this job"
|
|
6154
|
+
// is knowledge only the tools that START a job possess.
|
|
6155
|
+
if (result && typeof result === "object" && result.actor_id && ACTOR_MINTING_TOOLS.has(name)) {
|
|
6133
6156
|
touchSession(sessionId, { actorId: result.actor_id });
|
|
6134
6157
|
}
|
|
6135
6158
|
|