@ateam-ai/mcp 0.3.48 → 0.3.50

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 +1 -1
  2. package/src/tools.js +199 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.48",
3
+ "version": "0.3.50",
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",
package/src/tools.js CHANGED
@@ -188,22 +188,29 @@ export const tools = [
188
188
  name: "ateam_build_and_run",
189
189
  core: true,
190
190
  description:
191
- "Build and deploy a governed AI Team solution in one step. ⚠️ HEAVIEST OPERATION (60-180s): validates solution+skills → deploys all connectors+skills to A-Team Core (regenerates MCP servers) → health-checks → optionally runs a warm test → auto-pushes to GitHub. AUTO-DETECTS GitHub repo: if you omit mcp_store and a repo exists, connector code is pulled from GitHub automatically. First deploy requires mcp_store. After that, write files via ateam_github_write, then just call build_and_run without mcp_store. For small changes to an already-deployed solution, prefer ateam_patch (faster, incremental). Requires authentication.",
191
+ "DEPLOY THE CURRENT MAIN BRANCH TO A-TEAM CORE. ⚠️ HEAVIEST OPERATION (60-180s): validates solution+skills → deploys all connectors+skills to Core (regenerates MCP servers) → health-checks → optionally runs a warm test → auto-pushes to GitHub.\n\n" +
192
+ "🌳 DEV/PROD WORKFLOW:\n" +
193
+ " 1. Edit files → ateam_github_patch (writes to `dev` branch by default)\n" +
194
+ " 2. (Optional) Preview what's about to ship → ateam_github_diff\n" +
195
+ " 3. Ship dev → main → ateam_github_promote (merges + auto-tags `prod-YYYY-MM-DD-NNN`)\n" +
196
+ " 4. Deploy main to Core → ateam_build_and_run\n\n" +
197
+ "This tool ALWAYS deploys the `main` branch — there is no `ref` parameter. To deploy in-progress dev work, first promote it.\n\n" +
198
+ "AUTO-DETECTS GitHub repo: if you omit mcp_store and a repo exists, connector code is pulled from main automatically. First deploy requires mcp_store. After that, edit via ateam_github_patch + promote, then build_and_run. For small changes prefer ateam_patch (faster, incremental). Requires authentication.",
192
199
  inputSchema: {
193
200
  type: "object",
194
201
  properties: {
195
202
  solution_id: {
196
203
  type: "string",
197
- description: "The solution ID. Use this INSTEAD of passing the full solution object — the solution definition is auto-pulled from GitHub. Required if solution object is omitted.",
204
+ description: "The solution ID. Use this INSTEAD of passing the full solution object — the solution definition is auto-pulled from main. Required if solution object is omitted.",
198
205
  },
199
206
  solution: {
200
207
  type: "object",
201
- description: "Full solution definition. Required on first deploy. After first deploy, just pass solution_id instead — everything is auto-pulled from GitHub.",
208
+ description: "Full solution definition. Required on first deploy. After first deploy, just pass solution_id instead — everything is auto-pulled from GitHub main.",
202
209
  },
203
210
  skills: {
204
211
  type: "array",
205
212
  items: { type: "object" },
206
- description: "Optional after first deploy: skill definitions. If omitted, auto-pulled from GitHub repo (skills/{id}/skill.json).",
213
+ description: "Optional after first deploy: skill definitions. If omitted, auto-pulled from main (skills/{id}/skill.json).",
207
214
  },
208
215
  connectors: {
209
216
  type: "array",
@@ -216,7 +223,7 @@ export const tools = [
216
223
  },
217
224
  github: {
218
225
  type: "boolean",
219
- description: "Optional: if true, pull connector source code from the solution's GitHub repo. AUTO-DETECTED: if you omit both mcp_store and github, the system checks if a repo exists and pulls from it automatically. You rarely need to set this explicitly.",
226
+ description: "Optional: if true, pull connector source code from main. AUTO-DETECTED: if you omit both mcp_store and github, the system checks if a repo exists and pulls from main automatically.",
220
227
  },
221
228
  test_message: {
222
229
  type: "string",
@@ -264,6 +271,62 @@ export const tools = [
264
271
  required: ["solution_id", "skill_id", "message"],
265
272
  },
266
273
  },
274
+ {
275
+ name: "ateam_test_notification",
276
+ core: true,
277
+ description:
278
+ "Fire a REAL notification at an existing actor in a deployed solution — for end-to-end testing of the system-initiated notification path (telegram/push/app channels + reply routing + engagement-flip).\n\n" +
279
+ "Unlike ateam_test_skill (synthetic test actor with no channels) and ateam_conversation (user-initiated thread), this calls the /api/internal/notify-user path that PCM and other sibling services use — so the actor's real enabled channels actually receive the message.\n\n" +
280
+ "Use for:\n" +
281
+ " • Routing-hook tests (does user's next reply route to the right skill given reply_handler?)\n" +
282
+ " • Engagement-flip tests (does the receiver-skill's tool call flip engaged:true on the right notification?)\n" +
283
+ " • Channel fan-out smoke (does telegram/push/app actually receive it?)\n\n" +
284
+ "⚠️ SAFETY:\n" +
285
+ " • The text is prefixed with [TEST] in the actual notification — visible to the user, anti-phishing.\n" +
286
+ " • Rate-limited: 10 calls/min per session.\n" +
287
+ " • Every call is audited (caller, tenant, actor, content hash) regardless of outcome.\n" +
288
+ " • actor_id is scoped to your tenant — cross-tenant targeting is rejected by Core.\n" +
289
+ " • reply_handler is passed through unchanged (Core handles TTL). v2 will add a tenant allowlist + context schema validation; until then, only set reply_handler against skills you trust to receive arbitrary context.",
290
+ inputSchema: {
291
+ type: "object",
292
+ properties: {
293
+ solution_id: {
294
+ type: "string",
295
+ description: "The solution ID (required for tenant scoping + audit context).",
296
+ },
297
+ actor_id: {
298
+ type: "string",
299
+ description: "Target actor ID in your tenant (e.g. 'usr_arie_admin_0001'). Must exist; Core rejects if not found in your tenant.",
300
+ },
301
+ content: {
302
+ type: "string",
303
+ description: "Notification text. Will be sent to all of the actor's enabled channels, prefixed with [TEST] for the recipient.",
304
+ },
305
+ urgency: {
306
+ type: "string",
307
+ enum: ["low", "normal", "high"],
308
+ description: "Notification urgency. Default 'normal'.",
309
+ },
310
+ source: {
311
+ type: "string",
312
+ description: "Audit label for message.source. Default 'ateam-test'.",
313
+ },
314
+ metadata: {
315
+ type: "object",
316
+ description: "Optional metadata merged into message.metadata. Useful for correlation IDs.",
317
+ },
318
+ reply_handler: {
319
+ type: "object",
320
+ description: "OPTIONAL — install a routing hook so the user's next reply goes to a specific skill with injected context. Shape: { skill: 'skill_id', context: { ...arbitrary } }. Common contexts: dialogueId, observationId, proposalId, candidateSpecs, patternId, mode.\n\n⚠️ This semantically hijacks the user's next reply. Only use against skills designed to receive notification replies (e.g. 'ui-companion', 'pcm-companion'). v2 will enforce a tenant allowlist.",
321
+ properties: {
322
+ skill: { type: "string", description: "Skill ID to route the next reply to." },
323
+ context: { type: "object", description: "Object injected into the receiving job's triggerContext." },
324
+ },
325
+ },
326
+ },
327
+ required: ["solution_id", "actor_id", "content"],
328
+ },
329
+ },
267
330
  {
268
331
  name: "ateam_conversation",
269
332
  core: true,
@@ -1219,14 +1282,35 @@ export const tools = [
1219
1282
  name: "ateam_github_diff",
1220
1283
  core: true,
1221
1284
  description:
1222
- "Compare two branches. Default base=main, head=dev — shows what's about to ship if you call ateam_github_promote(). " +
1223
- "Returns the list of commits and files that would land on main.",
1285
+ "PRE-FLIGHT BEFORE PROMOTE. Compares `dev` (head) vs `main` (base) by default — shows exactly which commits and files are about to ship if you call ateam_github_promote() next.\n\n" +
1286
+ "Use this when you want to:\n" +
1287
+ " • Review changes before promoting to prod\n" +
1288
+ " • See if dev is ahead of main at all (returns ahead_by: 0 if nothing to promote)\n" +
1289
+ " • Inspect arbitrary branch/tag/commit comparisons (override base/head)",
1224
1290
  inputSchema: {
1225
1291
  type: "object",
1226
1292
  properties: {
1227
1293
  solution_id: { type: "string", description: "The solution ID" },
1228
- base: { type: "string", description: "Base branch (the target). Default: 'main'.", default: "main" },
1229
- head: { type: "string", description: "Head branch (the source). Default: 'dev'.", default: "dev" },
1294
+ base: { type: "string", description: "Base branch/tag/sha (the target — what you're comparing TO). Default: 'main'.", default: "main" },
1295
+ head: { type: "string", description: "Head branch/tag/sha (the source — what you're comparing FROM). Default: 'dev'.", default: "dev" },
1296
+ },
1297
+ required: ["solution_id"],
1298
+ },
1299
+ },
1300
+ {
1301
+ name: "ateam_verify_consistency",
1302
+ core: true,
1303
+ description:
1304
+ "Check that the Builder filesystem state and GitHub state are in sync for a solution. Read-only probe — does NOT trigger a deploy.\n\n" +
1305
+ "Returns:\n" +
1306
+ " • ok: true + drifts: [] if everything matches\n" +
1307
+ " • ok: false + drifts: [{path, kind}] listing files that differ (kinds: fs_missing, gh_missing, content_differs)\n\n" +
1308
+ "Drift can creep in when GitHub writes happen but Builder FS doesn't get the mirror update (network blip, container restart mid-write). Boot sync heals most of it on next backend restart; this tool surfaces drift earlier.\n\n" +
1309
+ "Run after a series of ateam_github_patch calls to confirm the Builder backend is consistent with GitHub before you ateam_build_and_run.",
1310
+ inputSchema: {
1311
+ type: "object",
1312
+ properties: {
1313
+ solution_id: { type: "string", description: "The solution ID to verify" },
1230
1314
  },
1231
1315
  required: ["solution_id"],
1232
1316
  },
@@ -1411,6 +1495,7 @@ const TENANT_TOOLS = new Set([
1411
1495
  "ateam_get_execution_logs",
1412
1496
  "ateam_conversation",
1413
1497
  "ateam_test_skill",
1498
+ "ateam_test_notification",
1414
1499
  "ateam_test_pipeline",
1415
1500
  "ateam_test_voice",
1416
1501
  "ateam_test_status",
@@ -2723,6 +2808,108 @@ const handlers = {
2723
2808
  return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/test`, body, sid, { timeoutMs });
2724
2809
  },
2725
2810
 
2811
+ ateam_test_notification: async ({ solution_id, actor_id, content, urgency, source, metadata, reply_handler }, sid) => {
2812
+ if (!solution_id) throw new Error("solution_id required");
2813
+ if (!actor_id) throw new Error("actor_id required");
2814
+ if (!content || typeof content !== "string") throw new Error("content required (string)");
2815
+
2816
+ // Rate limit: 10 calls / minute / session. In-memory; bounded leak fine
2817
+ // for a test tool. Survives until process restart, which is acceptable
2818
+ // (the bound is per-session, not per-tenant).
2819
+ const RATE_LIMIT = 10;
2820
+ const RATE_WINDOW_MS = 60_000;
2821
+ if (!globalThis.__notifyRateLimit) globalThis.__notifyRateLimit = new Map();
2822
+ const bucket = globalThis.__notifyRateLimit;
2823
+ const now = Date.now();
2824
+ const entry = bucket.get(sid) || { times: [] };
2825
+ entry.times = entry.times.filter(t => now - t < RATE_WINDOW_MS);
2826
+ if (entry.times.length >= RATE_LIMIT) {
2827
+ const waitMs = RATE_WINDOW_MS - (now - entry.times[0]);
2828
+ throw new Error(`Rate limited: max ${RATE_LIMIT} ateam_test_notification calls per minute per session. Retry in ${Math.ceil(waitMs / 1000)}s.`);
2829
+ }
2830
+ entry.times.push(now);
2831
+ bucket.set(sid, entry);
2832
+
2833
+ // Get the authed tenant — used for X-ADAS-TENANT header (Core scopes
2834
+ // the actor lookup by tenant, so cross-tenant targeting is rejected at
2835
+ // Core regardless of what we pass).
2836
+ const creds = getCredentials(sid);
2837
+ const tenant = creds?.tenant;
2838
+ if (!tenant) throw new Error("No tenant in session — call ateam_auth first.");
2839
+
2840
+ const coreUrl = process.env.ADAS_CORE_URL || "http://adas-backend:4000";
2841
+ const coreSecret = process.env.CORE_MCP_SECRET;
2842
+ if (!coreSecret) {
2843
+ 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.");
2844
+ }
2845
+
2846
+ // Force [TEST] prefix on the user-visible content. Anti-phishing rail:
2847
+ // even if a tenant admin api key were misused, the recipient sees
2848
+ // [TEST] on the actual message — they can't be fooled into thinking
2849
+ // it's a system-initiated production notification.
2850
+ const safeContent = content.startsWith("[TEST]") ? content : `[TEST] ${content}`;
2851
+
2852
+ // Audit log (cheap — console). Replace with structured audit when one exists.
2853
+ const contentHash = (await import("node:crypto")).createHash("sha256").update(content).digest("hex").slice(0, 12);
2854
+ console.log(JSON.stringify({
2855
+ audit: "ateam_test_notification",
2856
+ tenant,
2857
+ solution_id,
2858
+ actor_id,
2859
+ caller_session: sid?.slice(0, 8),
2860
+ content_preview: content.slice(0, 60),
2861
+ content_hash: contentHash,
2862
+ reply_handler_skill: reply_handler?.skill || null,
2863
+ urgency: urgency || "normal",
2864
+ at: new Date().toISOString(),
2865
+ }));
2866
+
2867
+ if (reply_handler) {
2868
+ console.warn(`[ateam_test_notification] reply_handler set → next user reply will route to skill="${reply_handler.skill}" with caller-supplied context. v2 will enforce a tenant allowlist + context schema validation.`);
2869
+ }
2870
+
2871
+ const body = {
2872
+ actorId: actor_id,
2873
+ content: safeContent,
2874
+ urgency: urgency || "normal",
2875
+ metadata: { ...(metadata || {}), source: source || "ateam-test", _test: true },
2876
+ ...(reply_handler ? { reply_handler } : {}),
2877
+ };
2878
+
2879
+ const res = await fetch(`${coreUrl}/api/internal/notify-user`, {
2880
+ method: "POST",
2881
+ headers: {
2882
+ "Content-Type": "application/json",
2883
+ "X-ADAS-TOKEN": coreSecret,
2884
+ "X-ADAS-TENANT": tenant,
2885
+ "X-ADAS-SERVICE": "ateam-mcp.test_notification",
2886
+ },
2887
+ body: JSON.stringify(body),
2888
+ signal: AbortSignal.timeout(15_000),
2889
+ });
2890
+
2891
+ const text = await res.text();
2892
+ let data;
2893
+ try { data = JSON.parse(text); } catch { data = { ok: false, error: text.slice(0, 400) }; }
2894
+
2895
+ if (!res.ok) {
2896
+ // Surface Core's actual reason — "actor not found in tenant" is the
2897
+ // most common (caller mistyped the actor_id), 502 = notif-router down.
2898
+ throw new Error(`Core /api/internal/notify-user returned ${res.status}: ${data.error || JSON.stringify(data).slice(0, 200)}`);
2899
+ }
2900
+
2901
+ return {
2902
+ ok: true,
2903
+ tenant,
2904
+ actor_id,
2905
+ dispatchId: data.dispatchId || null,
2906
+ notification_id: data.dispatchId || null, // alias matching the spec
2907
+ results: data.results || [],
2908
+ content_preview: safeContent.slice(0, 80),
2909
+ ...(reply_handler && { reply_handler_armed: { skill: reply_handler.skill } }),
2910
+ };
2911
+ },
2912
+
2726
2913
  ateam_test_pipeline: async ({ solution_id, skill_id, message }, sid) =>
2727
2914
  post(`/deploy/solutions/${solution_id}/skills/${skill_id}/test-pipeline`, { message }, sid, { timeoutMs: 30_000 }),
2728
2915
 
@@ -2875,6 +3062,9 @@ const handlers = {
2875
3062
  return get(`/deploy/solutions/${solution_id}/github/diff${q ? '?' + q : ''}`, sid);
2876
3063
  },
2877
3064
 
3065
+ ateam_verify_consistency: async ({ solution_id }, sid) =>
3066
+ get(`/deploy/solutions/${solution_id}/verify`, sid),
3067
+
2878
3068
  ateam_github_promote: async ({ solution_id, label, dry_run, skip_tag }, sid) =>
2879
3069
  post(`/deploy/solutions/${solution_id}/promote`, { label, dry_run, skip_tag }, sid),
2880
3070