@loopops/mcp-server 3.26.0 → 3.29.0

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/dist/index.js CHANGED
File without changes
@@ -396,18 +396,57 @@ export function registerDeployTools(server, allowed) {
396
396
  }, safeTool(async (input) => trpcMutation("mcp.reviseUserAssignment", input)));
397
397
  }
398
398
  // ── Phase 3 — Account → Territory placement ────────────────────────
399
+ if (allowed.has("snapshot_planning_scope")) {
400
+ server.tool("snapshot_planning_scope", [
401
+ "Ops only. Three modes:",
402
+ "(1) DEFAULT — materialize live TAL membership into the cycle's",
403
+ "frozen scope. TAL slug comes from",
404
+ "config/deploy/account_placement.yaml's planning_scope.tal_slug",
405
+ "(override via talSlugOverride). Must run BEFORE",
406
+ "propose_account_assignments. Idempotent on the same cycle.",
407
+ "(2) FORCE REFRESH — pass force:true (with reason ≥10 chars) to",
408
+ "delete the existing snapshot and re-read live TAL membership.",
409
+ "Replaces everything; invalidates ongoing manager review work.",
410
+ "(3) APPEND — pass appendTalSlug (with reason) to UNION members",
411
+ "from a second TAL into the existing scope. Surgical mid-cycle",
412
+ "additions. Existing rows untouched (first-snapshotted-wins on",
413
+ "source_tal_slug). Doesn't disturb prior manager review; the",
414
+ "newly-appended accounts get fresh proposal rows on next propose.",
415
+ "force and appendTalSlug are mutually exclusive.",
416
+ ].join(" "), {
417
+ planningCycleId: z.string().uuid().optional().describe("Planning cycle. Defaults to the org's currently-open cycle."),
418
+ talSlugOverride: z.string().optional().describe("Override TAL slug from account_placement.yaml."),
419
+ force: z.boolean().optional().describe("Default false. Pass true to delete + re-snapshot. Mutually exclusive with appendTalSlug."),
420
+ appendTalSlug: z.string().optional().describe("Append membership from this additional TAL into the existing scope. Surgical mid-cycle additions; existing rows untouched. Requires an existing snapshot. Mutually exclusive with force."),
421
+ reason: z.string().min(10).optional().describe("Required when force=true OR appendTalSlug is set. Why you're modifying scope mid-cycle."),
422
+ branch: z.string().optional(),
423
+ }, safeTool(async (input) => trpcMutation("mcp.snapshotPlanningScope", input)));
424
+ }
425
+ if (allowed.has("show_planning_scope")) {
426
+ server.tool("show_planning_scope", [
427
+ "Read-only. Show the snapshotted scope for a planning cycle:",
428
+ "source TAL, snapshot date, total + inactive counts, and a",
429
+ "sample of accounts. Empty when no snapshot has been taken yet",
430
+ "(run snapshot_planning_scope first).",
431
+ ].join(" "), {
432
+ planningCycleId: z.string().uuid().optional().describe("Planning cycle. Defaults to the org's currently-open cycle."),
433
+ sampleSize: z.number().int().min(0).max(100).optional().describe("Sample rows. Default 20."),
434
+ }, safeTool(async (input) => trpcQuery("mcp.showPlanningScope", input)));
435
+ }
399
436
  if (allowed.has("propose_account_assignments")) {
400
437
  server.tool("propose_account_assignments", [
401
438
  "Ops/eng. Run the Phase 3 account-placement model for an open",
402
439
  "planning cycle. Continuity-preservation policy from",
403
440
  "config/deploy/account_placement.yaml is honored unless overridden.",
404
- "Two refusal gates: (1) Phase 1 (user → territory) must be fully",
441
+ "Three refusal gates: (1) Phase 1 (user → territory) must be fully",
405
442
  "committed/superseded; (2) capacity_config.active_scenario must",
406
- "be set. Default behavior: accounts with active current owners",
407
- "auto-ratify their current territory (continuity); only new or",
408
- "owner-departed accounts go through full match-rule + composition",
409
- "+ balance modeling. Pass preserveCurrentOwnershipOverride:false",
410
- "for full re-plans (M&A, geographic restructure).",
443
+ "be set; (3) a planning scope snapshot must exist for the cycle",
444
+ "(run snapshot_planning_scope first). Default behavior: accounts",
445
+ "with active current owners auto-ratify their current territory",
446
+ "(continuity); only new or owner-departed accounts go through",
447
+ "full match-rule + composition + balance modeling. Pass",
448
+ "preserveCurrentOwnershipOverride:false for full re-plans (M&A,",
449
+ "geographic restructure).",
411
450
  ].join(" "), {
412
451
  planningCycleId: z.string().uuid().optional().describe("Planning cycle. Defaults to the org's currently-open cycle."),
413
452
  preserveCurrentOwnershipOverride: z.boolean().optional().describe("Override config/deploy/account_placement.yaml's preserve_current_ownership policy."),
@@ -494,6 +533,33 @@ export function registerDeployTools(server, allowed) {
494
533
  branch: z.string().optional(),
495
534
  }, safeTool(async (input) => trpcMutation("mcp.commitAccountAssignments", input)));
496
535
  }
536
+ if (allowed.has("rollback_account_commit")) {
537
+ server.tool("rollback_account_commit", [
538
+ "Ops only. Reverse a prior commit_account_assignments by reading",
539
+ "the per-proposal audit rows under the given rollback_id and",
540
+ "restoring each account's territory_slug to its pre_snapshot",
541
+ "value. Force two-step (default dryRun:true). Writes a per-row",
542
+ "audit entry with action='rollback' and metadata linking to the",
543
+ "original commit's audit_id. Proposal rows stay at",
544
+ "state='committed' — this is a data-plane reversal, not a",
545
+ "state-machine reversal. The response surfaces a fresh rollback",
546
+ "id you can pass back through this tool to revert the rollback.",
547
+ "AM's SFDC sync orchestrator (every 15m) propagates to SF.",
548
+ ].join(" "), {
549
+ rollbackId: z
550
+ .string()
551
+ .uuid()
552
+ .describe("rollback_id surfaced by the original commit_account_assignments call."),
553
+ dryRun: z
554
+ .boolean()
555
+ .optional()
556
+ .describe("Default true. Pass false to apply the reversal."),
557
+ reason: z
558
+ .string()
559
+ .min(20)
560
+ .describe("Why you're rolling back — required, min 20 chars. Captured in every per-proposal audit row."),
561
+ }, safeTool(async (input) => trpcMutation("mcp.rollbackAccountCommit", input)));
562
+ }
497
563
  if (allowed.has("close_cycle")) {
498
564
  server.tool("close_cycle", [
499
565
  "Ops only. Mark a planning cycle done. State `open → closed`.",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loopops/mcp-server",
3
- "version": "3.26.0",
3
+ "version": "3.29.0",
4
4
  "description": "Loop Operations MCP Server — AI skills for RevOps",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -20,6 +20,12 @@
20
20
  "publishConfig": {
21
21
  "access": "public"
22
22
  },
23
+ "scripts": {
24
+ "build": "tsc",
25
+ "dev": "tsx src/index.ts",
26
+ "start": "node dist/index.js",
27
+ "prepublishOnly": "pnpm build"
28
+ },
23
29
  "dependencies": {
24
30
  "@modelcontextprotocol/sdk": "^1.12.1",
25
31
  "zod": "^3.24.4"
@@ -28,10 +34,5 @@
28
34
  "@types/node": "^22.15.21",
29
35
  "tsx": "^4.19.4",
30
36
  "typescript": "^5.8.3"
31
- },
32
- "scripts": {
33
- "build": "tsc",
34
- "dev": "tsx src/index.ts",
35
- "start": "node dist/index.js"
36
37
  }
37
- }
38
+ }