@ateam-ai/mcp 0.3.51 → 0.3.53
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 +1 -1
- package/src/tools.js +179 -23
package/package.json
CHANGED
package/src/tools.js
CHANGED
|
@@ -241,7 +241,11 @@ export const tools = [
|
|
|
241
241
|
name: "ateam_test_skill",
|
|
242
242
|
core: true,
|
|
243
243
|
description:
|
|
244
|
-
"Send a test message to a deployed skill and get the
|
|
244
|
+
"Send a test message to a deployed skill and get the execution result.\n\n" +
|
|
245
|
+
"Wait modes (wait_for):\n" +
|
|
246
|
+
" • 'root' (default, back-compat) — wait until the message's root job completes, return single-job result. Fast, ignores any sub-skills the root delegated to via askAnySkill.\n" +
|
|
247
|
+
" • 'chain' — wait until EVERY job in the chain (root + handoffs + askAnySkill subcalls, recursively) reaches a terminal state, then return the full chain tree. Use when testing multi-skill flows (orchestrator → workers, builders → sub-builders, etc.). The response.chain field carries chainJobs[] with parentJobId/relation/depth and executionSteps[] with tool-nesting (opId/parentOpId/_toolDepth).\n\n" +
|
|
248
|
+
"Legacy: wait:false is equivalent to wait_for:'never' — returns job_id immediately for polling via ateam_test_status. wait:true is the same as the default wait_for:'root'.",
|
|
245
249
|
inputSchema: {
|
|
246
250
|
type: "object",
|
|
247
251
|
properties: {
|
|
@@ -260,7 +264,18 @@ export const tools = [
|
|
|
260
264
|
wait: {
|
|
261
265
|
type: "boolean",
|
|
262
266
|
description:
|
|
263
|
-
"
|
|
267
|
+
"Legacy: if false, return job_id immediately for polling. If true or omitted, behaves like wait_for:'root'. Prefer wait_for going forward.",
|
|
268
|
+
},
|
|
269
|
+
wait_for: {
|
|
270
|
+
type: "string",
|
|
271
|
+
enum: ["root", "chain", "never"],
|
|
272
|
+
description:
|
|
273
|
+
"What to wait for before returning. 'root' (default) = root job done; 'chain' = every chain job terminal (use for multi-skill flows); 'never' = return job_id immediately (poll via ateam_test_status). When 'chain', the response includes the chain tree under response.chain.",
|
|
274
|
+
},
|
|
275
|
+
chain_timeout_ms: {
|
|
276
|
+
type: "number",
|
|
277
|
+
description:
|
|
278
|
+
"Optional. Max total ms to wait when wait_for:'chain'. Default 300000 (5 min). Long-running chains (skill-factory, large bundle builds) may need higher. Clamped to [10000, 900000].",
|
|
264
279
|
},
|
|
265
280
|
actor_id: {
|
|
266
281
|
type: "string",
|
|
@@ -280,12 +295,13 @@ export const tools = [
|
|
|
280
295
|
"Use for:\n" +
|
|
281
296
|
" • Channel fan-out smoke (does telegram/push/app actually receive it?)\n" +
|
|
282
297
|
" • Delivery-result verification (per-channel ok/failed in the response).\n\n" +
|
|
283
|
-
"
|
|
298
|
+
"Auth: forwards your authed api_key to Core (no master-secret involvement). Tenant is pinned by the key itself — cross-tenant targeting is structurally impossible.\n\n" +
|
|
299
|
+
"⚠️ SAFETY:\n" +
|
|
284
300
|
" • The text is prefixed with [TEST] in the actual notification — visible to the user, anti-phishing.\n" +
|
|
285
301
|
" • Rate-limited: 10 calls/min per session.\n" +
|
|
286
302
|
" • Every call is audited (caller, tenant, actor, content hash) regardless of outcome.\n" +
|
|
287
|
-
" • actor_id is scoped to your tenant — cross-tenant targeting is rejected by Core.\n" +
|
|
288
|
-
" • reply_handler is
|
|
303
|
+
" • actor_id is scoped to your tenant — cross-tenant targeting is rejected by Core's per-tenant Mongo isolation.\n" +
|
|
304
|
+
" • reply_handler is NOT supported via api-key auth (Core ignores it). Routing the user's next reply to an arbitrary skill is a privilege-escalation surface. For routing/engagement tests, use ateam_test_skill.",
|
|
289
305
|
inputSchema: {
|
|
290
306
|
type: "object",
|
|
291
307
|
properties: {
|
|
@@ -912,7 +928,8 @@ export const tools = [
|
|
|
912
928
|
name: "ateam_test_status",
|
|
913
929
|
core: true,
|
|
914
930
|
description:
|
|
915
|
-
"Poll the progress of an async skill test. Returns iteration count, tool call steps, status (running/completed/failed), and result when done
|
|
931
|
+
"Poll the progress of an async skill test. Returns iteration count, tool call steps, status (running/completed/failed), and result when done.\n\n" +
|
|
932
|
+
"Set include_chain:true to ALSO include the full chain tree (every job in the chain, rooted at this job_id, with parent/child linkage). Use when this job dispatched askAnySkill subcalls and you want a single snapshot of the whole multi-skill state instead of polling each child job_id separately.",
|
|
916
933
|
inputSchema: {
|
|
917
934
|
type: "object",
|
|
918
935
|
properties: {
|
|
@@ -928,10 +945,40 @@ export const tools = [
|
|
|
928
945
|
type: "string",
|
|
929
946
|
description: "The job ID returned by ateam_test_skill",
|
|
930
947
|
},
|
|
948
|
+
include_chain: {
|
|
949
|
+
type: "boolean",
|
|
950
|
+
description:
|
|
951
|
+
"If true, includes response.chain — the full chain tree rooted at this job_id (chainJobs[] with parentJobId/relation/depth, executionSteps[] with tool-nesting). Costs one extra Core call. Default false (back-compat).",
|
|
952
|
+
},
|
|
931
953
|
},
|
|
932
954
|
required: ["solution_id", "skill_id", "job_id"],
|
|
933
955
|
},
|
|
934
956
|
},
|
|
957
|
+
{
|
|
958
|
+
name: "ateam_get_chain",
|
|
959
|
+
core: true,
|
|
960
|
+
description:
|
|
961
|
+
"Inspect the full chain tree for any job — rooted at the given job_id, walking down through every handoff and askAnySkill subcall.\n\n" +
|
|
962
|
+
"Use when a chain has already run and you want to analyze the structure: which skill called which, how deep the call tree went, which tool inside which job invoked which sub-tool. The two main shapes:\n" +
|
|
963
|
+
" • response.chain.chainJobs[] — one entry per job in the chain. Fields: jobId, skill, status, iteration, depth (0 = root, +1 per askAnySkill subcall hop), relation ('root' | 'subcall' | 'handoff'), parentJobId, parentSkill, goal.\n" +
|
|
964
|
+
" • response.chain.executionSteps[] — every tool call across all chain jobs, tagged with _skill, _jobId, _depth (= job depth), _relation, _parentSkill, _parentJobId, _toolDepth (tool-in-tool nesting via opId/parentOpId).\n\n" +
|
|
965
|
+
"Differs from ateam_test_status by purpose: status is for live polling of a job you just kicked off; get_chain is for post-hoc tree analysis (debugging multi-skill flows, regression testing, comparing two runs).\n\n" +
|
|
966
|
+
"Auth: forwards your authed api_key. Tenant scoped by the key itself. Actor scoping: you can only inspect chains rooted at jobs your actor has access to.",
|
|
967
|
+
inputSchema: {
|
|
968
|
+
type: "object",
|
|
969
|
+
properties: {
|
|
970
|
+
job_id: {
|
|
971
|
+
type: "string",
|
|
972
|
+
description: "The root job ID of the chain to inspect (or any job inside the chain — Core walks up to the root).",
|
|
973
|
+
},
|
|
974
|
+
skill_slug: {
|
|
975
|
+
type: "string",
|
|
976
|
+
description: "Optional. The skill slug for the job — speeds up the lookup when the job isn't in memory and must be loaded from storage. Omit if you don't have it; lookup still works but does an extra round-trip.",
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
required: ["job_id"],
|
|
980
|
+
},
|
|
981
|
+
},
|
|
935
982
|
{
|
|
936
983
|
name: "ateam_test_abort",
|
|
937
984
|
core: true,
|
|
@@ -1491,6 +1538,7 @@ const TENANT_TOOLS = new Set([
|
|
|
1491
1538
|
"ateam_test_voice",
|
|
1492
1539
|
"ateam_test_status",
|
|
1493
1540
|
"ateam_test_abort",
|
|
1541
|
+
"ateam_get_chain",
|
|
1494
1542
|
"ateam_get_connector_source",
|
|
1495
1543
|
"ateam_get_metrics",
|
|
1496
1544
|
"ateam_diff",
|
|
@@ -2792,11 +2840,77 @@ const handlers = {
|
|
|
2792
2840
|
return post(`/deploy/solutions/${solution_id}/test`, body, sid, { timeoutMs });
|
|
2793
2841
|
},
|
|
2794
2842
|
|
|
2795
|
-
ateam_test_skill: async ({ solution_id, skill_id, message, wait, actor_id }, sid) => {
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2843
|
+
ateam_test_skill: async ({ solution_id, skill_id, message, wait, wait_for, chain_timeout_ms, actor_id }, sid) => {
|
|
2844
|
+
// Resolve wait mode. Priority: wait_for (new explicit form) > wait (legacy).
|
|
2845
|
+
// wait:false → "never" (return job_id, no polling)
|
|
2846
|
+
// wait:true → "root" (poll root job to completion — current default)
|
|
2847
|
+
// wait_for set → use as-is (may also be "chain")
|
|
2848
|
+
let resolvedWait = wait_for || (wait === false ? "never" : "root");
|
|
2849
|
+
if (!["root", "chain", "never"].includes(resolvedWait)) {
|
|
2850
|
+
throw new Error(`Invalid wait_for: ${JSON.stringify(resolvedWait)}. Must be "root", "chain", or "never".`);
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
// Kick off the test (always async on the wire so the Builder doesn't time
|
|
2854
|
+
// out on long-running chains). When wait_for:"root" we then poll the
|
|
2855
|
+
// single-job status; when wait_for:"chain" we poll the chain tree until
|
|
2856
|
+
// every job is terminal; when wait_for:"never" we return the job_id and
|
|
2857
|
+
// caller polls themselves.
|
|
2858
|
+
const isWireAsync = resolvedWait !== "root";
|
|
2859
|
+
const body = { message, ...(isWireAsync ? { async: true } : {}), ...(actor_id ? { actor_id } : {}) };
|
|
2860
|
+
const kickoffTimeoutMs = isWireAsync ? 15_000 : 90_000;
|
|
2861
|
+
const kickoff = await post(`/deploy/solutions/${solution_id}/skills/${skill_id}/test`, body, sid, { timeoutMs: kickoffTimeoutMs });
|
|
2862
|
+
|
|
2863
|
+
if (resolvedWait === "never" || resolvedWait === "root") {
|
|
2864
|
+
// Back-compat path: kickoff response is the same shape callers see today.
|
|
2865
|
+
return kickoff;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
// wait_for:"chain" — poll the chain tree until every job is terminal.
|
|
2869
|
+
const rootJobId = kickoff?.job_id || kickoff?.jobId;
|
|
2870
|
+
if (!rootJobId) {
|
|
2871
|
+
// Builder returned no job_id — surface kickoff so caller can debug.
|
|
2872
|
+
return { ok: false, error: "ateam_test_skill (wait_for:'chain'): kickoff response has no job_id", kickoff };
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
const POLL_MIN_MS = 10_000;
|
|
2876
|
+
const POLL_MAX_MS = 900_000;
|
|
2877
|
+
const totalTimeoutMs = Math.min(POLL_MAX_MS, Math.max(POLL_MIN_MS, Number(chain_timeout_ms) || 300_000));
|
|
2878
|
+
const POLL_INTERVAL_MS = 2_000;
|
|
2879
|
+
const startedAt = Date.now();
|
|
2880
|
+
|
|
2881
|
+
const creds = getCredentials(sid);
|
|
2882
|
+
const apiKey = creds?.apiKey;
|
|
2883
|
+
if (!apiKey) throw new Error("No api_key in session — call ateam_auth(api_key) first.");
|
|
2884
|
+
const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
|
|
2885
|
+
|
|
2886
|
+
const isTerminal = (status) => status === "done" || status === "completed" || status === "error" || status === "failed" || status === "aborted";
|
|
2887
|
+
|
|
2888
|
+
let lastChain = null;
|
|
2889
|
+
while (Date.now() - startedAt < totalTimeoutMs) {
|
|
2890
|
+
const qs = new URLSearchParams();
|
|
2891
|
+
qs.set("skillSlug", skill_id);
|
|
2892
|
+
const res = await fetch(`${coreUrl}/api/job/${encodeURIComponent(rootJobId)}/chain?${qs}`, {
|
|
2893
|
+
method: "GET",
|
|
2894
|
+
headers: { "x-api-key": apiKey, "X-ADAS-SERVICE": "ateam-mcp.test_skill_chain" },
|
|
2895
|
+
signal: AbortSignal.timeout(15_000),
|
|
2896
|
+
}).catch(err => ({ ok: false, _err: err.message }));
|
|
2897
|
+
const data = res.ok === false && res._err ? { ok: false, error: res._err } : await res.json().catch(() => ({ ok: false, error: "non-json chain response" }));
|
|
2898
|
+
lastChain = data;
|
|
2899
|
+
const jobs = Array.isArray(data?.chainJobsList) ? data.chainJobsList : Array.isArray(data?.chainJobs) ? data.chainJobs : null;
|
|
2900
|
+
if (jobs && jobs.length > 0 && jobs.every(j => isTerminal(j.status))) {
|
|
2901
|
+
return { ok: true, job_id: rootJobId, wait_for: "chain", chain: data, kickoff, elapsed_ms: Date.now() - startedAt };
|
|
2902
|
+
}
|
|
2903
|
+
await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
|
|
2904
|
+
}
|
|
2905
|
+
return {
|
|
2906
|
+
ok: false,
|
|
2907
|
+
error: `Chain wait timed out after ${totalTimeoutMs}ms — some jobs are still running. Increase chain_timeout_ms or poll manually via ateam_test_status(include_chain:true).`,
|
|
2908
|
+
job_id: rootJobId,
|
|
2909
|
+
wait_for: "chain",
|
|
2910
|
+
chain: lastChain,
|
|
2911
|
+
kickoff,
|
|
2912
|
+
elapsed_ms: Date.now() - startedAt,
|
|
2913
|
+
};
|
|
2800
2914
|
},
|
|
2801
2915
|
|
|
2802
2916
|
ateam_test_notification: async ({ solution_id, actor_id, content, urgency, source, metadata, reply_handler, ...rest }, sid) => {
|
|
@@ -2836,18 +2950,19 @@ const handlers = {
|
|
|
2836
2950
|
entry.times.push(now);
|
|
2837
2951
|
bucket.set(sid, entry);
|
|
2838
2952
|
|
|
2839
|
-
//
|
|
2840
|
-
//
|
|
2841
|
-
//
|
|
2953
|
+
// Forward the caller's authed api_key to Core. Tenant scoping is
|
|
2954
|
+
// enforced by the key itself (Core's attachActor parses the tenant out
|
|
2955
|
+
// of adas_<tenant>_<hex> and pins req.tenant). This removes the need
|
|
2956
|
+
// for the MCP server to hold CORE_MCP_SECRET for this tool — the
|
|
2957
|
+
// caller's own credential is what authorizes the action.
|
|
2842
2958
|
const creds = getCredentials(sid);
|
|
2843
2959
|
const tenant = creds?.tenant;
|
|
2844
|
-
|
|
2960
|
+
const apiKey = creds?.apiKey;
|
|
2961
|
+
if (!tenant || !apiKey) {
|
|
2962
|
+
throw new Error("No api_key in session — call ateam_auth(api_key: \"adas_<tenant>_<hex>\") first. ateam_test_notification requires a tenant API key (master_key auth is not supported for this tool).");
|
|
2963
|
+
}
|
|
2845
2964
|
|
|
2846
2965
|
const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
|
|
2847
|
-
const coreSecret = process.env.CORE_MCP_SECRET;
|
|
2848
|
-
if (!coreSecret) {
|
|
2849
|
-
throw new Error("Server config error: CORE_MCP_SECRET not set. ateam_test_notification requires the platform shared secret (sibling-service auth). Contact platform admin.");
|
|
2850
|
-
}
|
|
2851
2966
|
|
|
2852
2967
|
// Force [TEST] prefix on the user-visible content. Anti-phishing rail:
|
|
2853
2968
|
// even if a tenant admin api key were misused, the recipient sees
|
|
@@ -2880,8 +2995,8 @@ const handlers = {
|
|
|
2880
2995
|
method: "POST",
|
|
2881
2996
|
headers: {
|
|
2882
2997
|
"Content-Type": "application/json",
|
|
2883
|
-
|
|
2884
|
-
"
|
|
2998
|
+
// api-key auth — tenant pinned by Core's attachActor from the key itself.
|
|
2999
|
+
"x-api-key": apiKey,
|
|
2885
3000
|
"X-ADAS-SERVICE": "ateam-mcp.test_notification",
|
|
2886
3001
|
},
|
|
2887
3002
|
body: JSON.stringify(body),
|
|
@@ -2923,8 +3038,49 @@ const handlers = {
|
|
|
2923
3038
|
return post(`/deploy/voice-test`, body, sid, { timeoutMs: timeoutTotal });
|
|
2924
3039
|
},
|
|
2925
3040
|
|
|
2926
|
-
ateam_test_status: async ({ solution_id, skill_id, job_id }, sid) =>
|
|
2927
|
-
|
|
3041
|
+
ateam_test_status: async ({ solution_id, skill_id, job_id, include_chain }, sid) => {
|
|
3042
|
+
// Existing single-job snapshot via Builder (unchanged shape for back-compat).
|
|
3043
|
+
const single = await get(`/deploy/solutions/${solution_id}/skills/${skill_id}/test/${job_id}`, sid);
|
|
3044
|
+
if (!include_chain) return single;
|
|
3045
|
+
|
|
3046
|
+
// Caller asked for the chain tree too. Fetch via Core's /api/job/:id/chain
|
|
3047
|
+
// and merge under response.chain. Single-job fields stay at the top level.
|
|
3048
|
+
const creds = getCredentials(sid);
|
|
3049
|
+
const apiKey = creds?.apiKey;
|
|
3050
|
+
if (!apiKey) return { ...single, chain: { ok: false, error: "include_chain requires api-key auth (call ateam_auth)" } };
|
|
3051
|
+
const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
|
|
3052
|
+
const qs = new URLSearchParams();
|
|
3053
|
+
if (skill_id) qs.set("skillSlug", skill_id);
|
|
3054
|
+
const res = await fetch(`${coreUrl}/api/job/${encodeURIComponent(job_id)}/chain?${qs}`, {
|
|
3055
|
+
method: "GET",
|
|
3056
|
+
headers: { "x-api-key": apiKey, "X-ADAS-SERVICE": "ateam-mcp.test_status_chain" },
|
|
3057
|
+
signal: AbortSignal.timeout(15_000),
|
|
3058
|
+
}).catch(err => ({ ok: false, _err: err.message }));
|
|
3059
|
+
const chain = res.ok === false && res._err ? { ok: false, error: res._err } : await res.json().catch(() => ({ ok: false, error: "non-json chain response" }));
|
|
3060
|
+
return { ...single, chain };
|
|
3061
|
+
},
|
|
3062
|
+
|
|
3063
|
+
ateam_get_chain: async ({ job_id, skill_slug }, sid) => {
|
|
3064
|
+
if (!job_id) throw new Error("job_id required");
|
|
3065
|
+
const creds = getCredentials(sid);
|
|
3066
|
+
const apiKey = creds?.apiKey;
|
|
3067
|
+
if (!apiKey) throw new Error("No api_key in session — call ateam_auth(api_key) first.");
|
|
3068
|
+
const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
|
|
3069
|
+
const qs = new URLSearchParams();
|
|
3070
|
+
if (skill_slug) qs.set("skillSlug", skill_slug);
|
|
3071
|
+
const res = await fetch(`${coreUrl}/api/job/${encodeURIComponent(job_id)}/chain?${qs}`, {
|
|
3072
|
+
method: "GET",
|
|
3073
|
+
headers: { "x-api-key": apiKey, "X-ADAS-SERVICE": "ateam-mcp.get_chain" },
|
|
3074
|
+
signal: AbortSignal.timeout(15_000),
|
|
3075
|
+
});
|
|
3076
|
+
const text = await res.text();
|
|
3077
|
+
let data;
|
|
3078
|
+
try { data = JSON.parse(text); } catch { data = { ok: false, error: text.slice(0, 400) }; }
|
|
3079
|
+
if (!res.ok) {
|
|
3080
|
+
throw new Error(`Core /api/job/${job_id}/chain returned ${res.status}: ${data.error || JSON.stringify(data).slice(0, 200)}`);
|
|
3081
|
+
}
|
|
3082
|
+
return data;
|
|
3083
|
+
},
|
|
2928
3084
|
|
|
2929
3085
|
ateam_test_abort: async ({ solution_id, skill_id, job_id }, sid) =>
|
|
2930
3086
|
del(`/deploy/solutions/${solution_id}/skills/${skill_id}/test/${job_id}`, sid),
|