@loopops/mcp-server 2.1.0 → 2.4.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.
@@ -151,6 +151,162 @@ export function registerConfigTools(server, allowed) {
151
151
  .describe("Git branch to read from and write back to. Default: main. Writing to main = live promotion."),
152
152
  }, safeTool(async ({ scenarioId, dryRun, branch }) => trpcMutation("mcp.promoteScenario", { scenarioId, dryRun, branch })));
153
153
  }
154
+ if (allowed.has("gap_analysis")) {
155
+ server.tool("gap_analysis", "Use a Claude agent to find the smallest change set that closes a scenario's capacity gap on the target measure. The agent iterates simulate_capacity calls (mutating roster + target_productivity in memory, calling the existing /capacity endpoint) up to a budget cap, then submits a structured proposal. Writes the converged proposal as a draft scenario at `config/plan/scenarios/proposed-gap-{measure}-{ts}.yaml` (override with `writeFile: false`). Use `compare_scenarios` to verify the proposal, then `promote_scenario` to make it active.", {
156
+ scenarioId: z
157
+ .string()
158
+ .describe("Scenario whose capacity gap to close (e.g. 'stretch'). Must exist in config/plan/scenarios/ and be complete."),
159
+ targetMeasure: z
160
+ .string()
161
+ .default("new_acv")
162
+ .describe("Measure id whose gap to close. Default: 'new_acv'. Other options: 'new_arr', 'new_logo_volume', 'new_logo_acv'."),
163
+ targetTerritory: z
164
+ .string()
165
+ .optional()
166
+ .describe("Optional territory slug to focus on (e.g. 'ams'). If omitted, agent closes the org-wide gap (sum across all territories for the measure)."),
167
+ maxNewHires: z
168
+ .number()
169
+ .int()
170
+ .nonnegative()
171
+ .default(20)
172
+ .describe("Max new planned-hire rows the agent may propose. Default: 20."),
173
+ maxProductivityBumpPct: z
174
+ .number()
175
+ .nonnegative()
176
+ .default(20)
177
+ .describe("Max % bump on any (profile, segment, measure) target productivity vs. base. Default: 20."),
178
+ maxIterations: z
179
+ .number()
180
+ .int()
181
+ .min(1)
182
+ .max(20)
183
+ .default(5)
184
+ .describe("Max simulate_capacity calls before the agent must submit. Default: 5. Hard cap: 20."),
185
+ writeFile: z
186
+ .boolean()
187
+ .default(true)
188
+ .describe("When true (default), commit the proposal as a draft scenario. Pass false to inspect inline only."),
189
+ branch: z
190
+ .string()
191
+ .optional()
192
+ .describe("Git branch to read from and (if writeFile) write to. Default: main."),
193
+ }, safeTool(async ({ scenarioId, targetMeasure, targetTerritory, maxNewHires, maxProductivityBumpPct, maxIterations, writeFile, branch, }) => trpcMutation("mcp.gapAnalysis", {
194
+ scenarioId,
195
+ targetMeasure,
196
+ targetTerritory,
197
+ maxNewHires,
198
+ maxProductivityBumpPct,
199
+ maxIterations,
200
+ writeFile,
201
+ branch,
202
+ })));
203
+ }
204
+ if (allowed.has("scenario_what_if")) {
205
+ server.tool("scenario_what_if", "Apply operator-specified changes to a scenario (added hires, shifted dates, productivity changes — increases OR decreases) and render a side-by-side per-measure delta vs. the source. Single capacity simulation, no agent loop. Use this when YOU know the change and want to evaluate it; use `gap_analysis` when you want the agent to find the change. By default, exploratory only — pass `writeFile:true` to materialize as a draft scenario.", {
206
+ scenarioId: z
207
+ .string()
208
+ .describe("Source scenario id (e.g. 'base'). Must exist and be complete."),
209
+ addHires: z
210
+ .array(z.object({
211
+ id: z.string(),
212
+ profile_ref: z.enum(["acquisition", "expansion", "hybrid"]),
213
+ segment_ref: z.string(),
214
+ territory_slug: z.string(),
215
+ start_date: z.string(),
216
+ notes: z.string().optional(),
217
+ }))
218
+ .default([])
219
+ .describe("New planned-hire rows to append to the roster."),
220
+ shiftHireDates: z
221
+ .array(z.object({ hire_id: z.string(), new_start_date: z.string() }))
222
+ .default([])
223
+ .describe("Move existing planned hires to new start dates."),
224
+ bumpProductivity: z
225
+ .array(z.object({
226
+ profile: z.enum(["acquisition", "expansion", "hybrid"]),
227
+ segment: z.string(),
228
+ measure: z.string(),
229
+ new_value: z.number(),
230
+ }))
231
+ .default([])
232
+ .describe("Override default target productivity for (profile, segment, measure) cells. Increases OR decreases — useful for sensitivity ('what if Hybrid productivity dropped 10%?')."),
233
+ label: z
234
+ .string()
235
+ .optional()
236
+ .describe("Optional short label for the what-if (e.g. 'add-1-ams-q2'). Used in the proposed scenario filename when writeFile=true. Filesystem-unsafe characters are stripped."),
237
+ measureIds: z
238
+ .array(z.string())
239
+ .optional()
240
+ .describe("Filter the comparison output to a subset of measures. Default: all target_settable measures."),
241
+ writeFile: z
242
+ .boolean()
243
+ .default(false)
244
+ .describe("When true, write the modified scenario as a draft for compare_scenarios + promote_scenario. Default: false (exploratory)."),
245
+ branch: z
246
+ .string()
247
+ .optional()
248
+ .describe("Git branch to read from and (if writeFile) write to. Default: main."),
249
+ }, safeTool(async ({ scenarioId, addHires, shiftHireDates, bumpProductivity, label, measureIds, writeFile, branch, }) => trpcMutation("mcp.scenarioWhatIf", {
250
+ scenarioId,
251
+ addHires,
252
+ shiftHireDates,
253
+ bumpProductivity,
254
+ label,
255
+ measureIds,
256
+ writeFile,
257
+ branch,
258
+ })));
259
+ }
260
+ if (allowed.has("opportunity_search")) {
261
+ server.tool("opportunity_search", "Use a Claude agent to find the deployment of a fixed budget that maximizes capacity gain on a target measure. Sibling to gap_analysis with opposite objective: where gap_analysis closes a gap to zero, opportunity_search maximizes upside under a budget constraint. Same tool surface (simulate_capacity + submit_final_proposal). Writes the agent's best deployment as a draft scenario at `config/plan/scenarios/proposed-opportunity-{measure}-{ts}.yaml` (override with `writeFile: false`). Use `compare_scenarios` to verify, then `promote_scenario` to make it active.", {
262
+ scenarioId: z
263
+ .string()
264
+ .describe("Source scenario id (e.g. 'base'). Must exist and be complete."),
265
+ targetMeasure: z
266
+ .string()
267
+ .default("new_acv")
268
+ .describe("Measure id to maximize. Default: 'new_acv'. Other options: 'new_arr', 'new_logo_volume', 'new_logo_acv'."),
269
+ targetTerritory: z
270
+ .string()
271
+ .optional()
272
+ .describe("Optional territory slug to focus on. If omitted, agent maximizes org-wide capacity for the measure."),
273
+ maxNewHires: z
274
+ .number()
275
+ .int()
276
+ .nonnegative()
277
+ .default(20)
278
+ .describe("Max new planned-hire rows the agent may propose. Budget is at-most — agent may underspend if extra hires don't materially help. Default: 20."),
279
+ maxProductivityBumpPct: z
280
+ .number()
281
+ .nonnegative()
282
+ .default(20)
283
+ .describe("Max % bump on any (profile, segment, measure) target productivity vs. base. Increases only — decreases reduce capacity. Default: 20."),
284
+ maxIterations: z
285
+ .number()
286
+ .int()
287
+ .min(1)
288
+ .max(20)
289
+ .default(5)
290
+ .describe("Max simulate_capacity calls before the agent must submit. Default: 5. Hard cap: 20."),
291
+ writeFile: z
292
+ .boolean()
293
+ .default(true)
294
+ .describe("When true (default), commit the deployment as a draft scenario. Pass false to inspect inline only."),
295
+ branch: z
296
+ .string()
297
+ .optional()
298
+ .describe("Git branch to read from and (if writeFile) write to. Default: main."),
299
+ }, safeTool(async ({ scenarioId, targetMeasure, targetTerritory, maxNewHires, maxProductivityBumpPct, maxIterations, writeFile, branch, }) => trpcMutation("mcp.opportunitySearch", {
300
+ scenarioId,
301
+ targetMeasure,
302
+ targetTerritory,
303
+ maxNewHires,
304
+ maxProductivityBumpPct,
305
+ maxIterations,
306
+ writeFile,
307
+ branch,
308
+ })));
309
+ }
154
310
  if (allowed.has("preview_routing")) {
155
311
  server.tool("preview_routing", "Simulate Route loop resolution for a hypothetical lead. Walks config/route/rules.yaml, dispatches to territory/pool/queue/user/target_account handler, and returns the resolved owner + audit trail. Useful for testing rule changes before merging. Requires at least billing geography or email.", {
156
312
  email: z.string().optional().describe("Lead email — also extracts the domain for target_account lookup."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loopops/mcp-server",
3
- "version": "2.1.0",
3
+ "version": "2.4.0",
4
4
  "description": "Loop Operations MCP Server — AI skills for RevOps",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",