@loopops/mcp-server 3.24.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 +0 -0
- package/dist/tools/deploy.js +165 -0
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/deploy.js
CHANGED
|
@@ -395,6 +395,171 @@ export function registerDeployTools(server, allowed) {
|
|
|
395
395
|
.describe("Why you're sending this back — required, captured in the audit log so the manager sees the intent."),
|
|
396
396
|
}, safeTool(async (input) => trpcMutation("mcp.reviseUserAssignment", input)));
|
|
397
397
|
}
|
|
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
|
+
}
|
|
436
|
+
if (allowed.has("propose_account_assignments")) {
|
|
437
|
+
server.tool("propose_account_assignments", [
|
|
438
|
+
"Ops/eng. Run the Phase 3 account-placement model for an open",
|
|
439
|
+
"planning cycle. Continuity-preservation policy from",
|
|
440
|
+
"config/deploy/account_placement.yaml is honored unless overridden.",
|
|
441
|
+
"Three refusal gates: (1) Phase 1 (user → territory) must be fully",
|
|
442
|
+
"committed/superseded; (2) capacity_config.active_scenario must",
|
|
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).",
|
|
450
|
+
].join(" "), {
|
|
451
|
+
planningCycleId: z.string().uuid().optional().describe("Planning cycle. Defaults to the org's currently-open cycle."),
|
|
452
|
+
preserveCurrentOwnershipOverride: z.boolean().optional().describe("Override config/deploy/account_placement.yaml's preserve_current_ownership policy."),
|
|
453
|
+
accountIds: z.array(z.string().uuid()).optional().describe("Optional restriction; only model these accounts."),
|
|
454
|
+
scenarioId: z.string().optional().describe("Override active scenario."),
|
|
455
|
+
branch: z.string().optional(),
|
|
456
|
+
}, safeTool(async (input) => trpcMutation("mcp.proposeAccountAssignments", input)));
|
|
457
|
+
}
|
|
458
|
+
if (allowed.has("review_account_assignments")) {
|
|
459
|
+
server.tool("review_account_assignments", [
|
|
460
|
+
"List Phase 3 account placement proposals for a cycle. Subtree-",
|
|
461
|
+
"scoped for managers; full org for ops/eng/leadership. Default",
|
|
462
|
+
"sort = confidence ascending (lowest first — these need attention).",
|
|
463
|
+
"Confidence band filter for triage: 'low' (<0.75) for the rows",
|
|
464
|
+
"that need real review; 'high' (≥0.9) for bulk-approve candidates.",
|
|
465
|
+
"Continuity-preserved rows show 'preserved' in their continuity",
|
|
466
|
+
"column. Composition violations get a ⚠️ marker.",
|
|
467
|
+
].join(" "), {
|
|
468
|
+
planningCycleId: z.string().uuid().optional(),
|
|
469
|
+
state: z.array(z.enum(["proposed", "in_review", "edited", "approved", "committed", "superseded"])).optional(),
|
|
470
|
+
territorySlug: z.string().optional(),
|
|
471
|
+
confidenceBand: z.enum(["low", "mid", "high", "all"]).optional().describe("low: <0.75; mid: 0.75-0.9; high: >=0.9. Default: all."),
|
|
472
|
+
cursor: z.string().optional(),
|
|
473
|
+
limit: z.number().int().positive().max(500).optional(),
|
|
474
|
+
branch: z.string().optional(),
|
|
475
|
+
}, safeTool(async (input) => trpcQuery("mcp.reviewAccountAssignments", input)));
|
|
476
|
+
}
|
|
477
|
+
if (allowed.has("approve_account_assignments")) {
|
|
478
|
+
server.tool("approve_account_assignments", [
|
|
479
|
+
"Manager+. Bulk-approve Phase 3 account placement proposals.",
|
|
480
|
+
"State proposed/in_review/edited → approved. Default = all in your",
|
|
481
|
+
"subtree; pass proposalIds for a subset (e.g., bulk-approve all",
|
|
482
|
+
"high-confidence rows after reviewing). Speed equivalent of",
|
|
483
|
+
"today's auto-commit, but gated by an explicit operator action.",
|
|
484
|
+
].join(" "), {
|
|
485
|
+
planningCycleId: z.string().uuid().optional(),
|
|
486
|
+
proposalIds: z.array(z.string().uuid()).optional(),
|
|
487
|
+
notes: z.string().optional(),
|
|
488
|
+
branch: z.string().optional(),
|
|
489
|
+
}, safeTool(async (input) => trpcMutation("mcp.approveAccountAssignments", input)));
|
|
490
|
+
}
|
|
491
|
+
if (allowed.has("edit_account_assignment")) {
|
|
492
|
+
server.tool("edit_account_assignment", [
|
|
493
|
+
"Manager+. Move an account between territories within your",
|
|
494
|
+
"hierarchy subtree. State proposed/in_review/edited → edited.",
|
|
495
|
+
"Both source AND destination territories must be in your subtree.",
|
|
496
|
+
"Returns synchronous recompute of composition + balance for the",
|
|
497
|
+
"affected territories so you see the impact of the move before",
|
|
498
|
+
"approving. Composition violations surface as warnings (not",
|
|
499
|
+
"blocking — manager judgment overrides). Match-rule violations",
|
|
500
|
+
"(e.g., NY account moved to SF Bay) are also warned-but-allowed",
|
|
501
|
+
"with audit captured.",
|
|
502
|
+
].join(" "), {
|
|
503
|
+
proposalId: z.string().uuid(),
|
|
504
|
+
newTerritorySlug: z.string().describe("Target patch slug. Must be in your hierarchy subtree."),
|
|
505
|
+
notes: z.string().min(5).describe("Required, min 5 chars. Captured in audit log."),
|
|
506
|
+
branch: z.string().optional(),
|
|
507
|
+
}, safeTool(async (input) => trpcMutation("mcp.editAccountAssignment", input)));
|
|
508
|
+
}
|
|
509
|
+
if (allowed.has("revise_account_proposal")) {
|
|
510
|
+
server.tool("revise_account_proposal", [
|
|
511
|
+
"Ops only. Demote an approved account proposal back to edited",
|
|
512
|
+
"so the manager can revise. State `approved → edited`.",
|
|
513
|
+
"final_territory_slug preserved. Audit row records who sent it",
|
|
514
|
+
"back and why. Mirrors revise_user_assignment + revise_quota_proposal.",
|
|
515
|
+
].join(" "), {
|
|
516
|
+
proposalId: z.string().uuid(),
|
|
517
|
+
notes: z.string().min(5),
|
|
518
|
+
}, safeTool(async (input) => trpcMutation("mcp.reviseAccountProposal", input)));
|
|
519
|
+
}
|
|
520
|
+
if (allowed.has("commit_account_assignments")) {
|
|
521
|
+
server.tool("commit_account_assignments", [
|
|
522
|
+
"Ops only. Force two-step (default dryRun:true). Writes",
|
|
523
|
+
"account.territory_slug (Account Master); the existing AM SFDC",
|
|
524
|
+
"sync orchestrator (cron every 15m) pushes to Salesforce on its",
|
|
525
|
+
"next run. Pre-snapshot of every affected account's prior",
|
|
526
|
+
"territory_slug lands in audit_log under a stable rollback_id",
|
|
527
|
+
"the response surfaces — keep it for any reversal need. Re-run",
|
|
528
|
+
"with dryRun:false to commit; the response includes rollback",
|
|
529
|
+
"instructions.",
|
|
530
|
+
].join(" "), {
|
|
531
|
+
planningCycleId: z.string().uuid().optional(),
|
|
532
|
+
dryRun: z.boolean().optional().describe("Default true. Pass false to commit + advance state."),
|
|
533
|
+
branch: z.string().optional(),
|
|
534
|
+
}, safeTool(async (input) => trpcMutation("mcp.commitAccountAssignments", input)));
|
|
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
|
+
}
|
|
398
563
|
if (allowed.has("close_cycle")) {
|
|
399
564
|
server.tool("close_cycle", [
|
|
400
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.
|
|
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
|
+
}
|