@ateam-ai/mcp 0.3.50 → 0.3.51

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 +21 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ateam-ai/mcp",
3
- "version": "0.3.50",
3
+ "version": "0.3.51",
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
@@ -275,18 +275,17 @@ export const tools = [
275
275
  name: "ateam_test_notification",
276
276
  core: true,
277
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" +
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).\n\n" +
279
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
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" +
281
+ " • Channel fan-out smoke (does telegram/push/app actually receive it?)\n" +
282
+ " • Delivery-result verification (per-channel ok/failed in the response).\n\n" +
283
+ "⚠️ SAFETY (v1):\n" +
285
284
  " • The text is prefixed with [TEST] in the actual notification — visible to the user, anti-phishing.\n" +
286
285
  " • Rate-limited: 10 calls/min per session.\n" +
287
286
  " • Every call is audited (caller, tenant, actor, content hash) regardless of outcome.\n" +
288
287
  " • 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.",
288
+ " • reply_handler is INTENTIONALLY NOT SUPPORTED in v1. Routing the user's next reply to an arbitrary skill is a privilege-escalation surface (caller-supplied skill + context). v2 will add a tenant skill allowlist + context schema validation. Until then, use ateam_test_skill for routing/engagement tests.",
290
289
  inputSchema: {
291
290
  type: "object",
292
291
  properties: {
@@ -315,14 +314,6 @@ export const tools = [
315
314
  type: "object",
316
315
  description: "Optional metadata merged into message.metadata. Useful for correlation IDs.",
317
316
  },
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
317
  },
327
318
  required: ["solution_id", "actor_id", "content"],
328
319
  },
@@ -2808,11 +2799,26 @@ const handlers = {
2808
2799
  return post(`/deploy/solutions/${solution_id}/skills/${skill_id}/test`, body, sid, { timeoutMs });
2809
2800
  },
2810
2801
 
2811
- ateam_test_notification: async ({ solution_id, actor_id, content, urgency, source, metadata, reply_handler }, sid) => {
2802
+ ateam_test_notification: async ({ solution_id, actor_id, content, urgency, source, metadata, reply_handler, ...rest }, sid) => {
2812
2803
  if (!solution_id) throw new Error("solution_id required");
2813
2804
  if (!actor_id) throw new Error("actor_id required");
2814
2805
  if (!content || typeof content !== "string") throw new Error("content required (string)");
2815
2806
 
2807
+ // v1: reply_handler is intentionally NOT supported (privilege-escalation
2808
+ // surface — caller could route user's next reply to any skill with
2809
+ // arbitrary context). Reject the field rather than silently dropping it,
2810
+ // so callers know to stop relying on it. v2 will add allowlist + schema.
2811
+ if (reply_handler !== undefined) {
2812
+ throw new Error("reply_handler is not supported in v1 of ateam_test_notification (security: caller-supplied skill + context = privilege escalation). v2 will add a tenant skill allowlist + context schema. For routing/engagement tests, use ateam_test_skill instead.");
2813
+ }
2814
+ // Defense-in-depth: also reject any unknown field that might smuggle a
2815
+ // reply_handler via case variants or aliases.
2816
+ for (const k of Object.keys(rest || {})) {
2817
+ if (/reply/i.test(k) || /handler/i.test(k)) {
2818
+ throw new Error(`Unsupported field "${k}" in ateam_test_notification (likely a reply_handler alias — see v1 safety note).`);
2819
+ }
2820
+ }
2821
+
2816
2822
  // Rate limit: 10 calls / minute / session. In-memory; bounded leak fine
2817
2823
  // for a test tool. Survives until process restart, which is acceptable
2818
2824
  // (the bound is per-session, not per-tenant).
@@ -2859,21 +2865,15 @@ const handlers = {
2859
2865
  caller_session: sid?.slice(0, 8),
2860
2866
  content_preview: content.slice(0, 60),
2861
2867
  content_hash: contentHash,
2862
- reply_handler_skill: reply_handler?.skill || null,
2863
2868
  urgency: urgency || "normal",
2864
2869
  at: new Date().toISOString(),
2865
2870
  }));
2866
2871
 
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
2872
  const body = {
2872
2873
  actorId: actor_id,
2873
2874
  content: safeContent,
2874
2875
  urgency: urgency || "normal",
2875
2876
  metadata: { ...(metadata || {}), source: source || "ateam-test", _test: true },
2876
- ...(reply_handler ? { reply_handler } : {}),
2877
2877
  };
2878
2878
 
2879
2879
  const res = await fetch(`${coreUrl}/api/internal/notify-user`, {
@@ -2906,7 +2906,6 @@ const handlers = {
2906
2906
  notification_id: data.dispatchId || null, // alias matching the spec
2907
2907
  results: data.results || [],
2908
2908
  content_preview: safeContent.slice(0, 80),
2909
- ...(reply_handler && { reply_handler_armed: { skill: reply_handler.skill } }),
2910
2909
  };
2911
2910
  },
2912
2911