@promptprojectmanager/mcp-server 4.4.3 → 4.5.1
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/chunk-POSCT6WJ.js +106 -0
- package/dist/chunk-POSCT6WJ.js.map +1 -0
- package/dist/index.js +434 -76
- package/dist/index.js.map +1 -1
- package/dist/watcher/watcher_daemon.d.ts +1 -0
- package/dist/watcher/watcher_daemon.js +341 -0
- package/dist/watcher/watcher_daemon.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
isProcessRunning,
|
|
4
|
+
isWatcherRunning,
|
|
5
|
+
readPid,
|
|
6
|
+
readStatus,
|
|
7
|
+
removePid,
|
|
8
|
+
writeStatus
|
|
9
|
+
} from "./chunk-POSCT6WJ.js";
|
|
2
10
|
|
|
3
11
|
// src/index.ts
|
|
4
12
|
import minimist from "minimist";
|
|
@@ -68,19 +76,19 @@ function parseUpdateArgs(args) {
|
|
|
68
76
|
if (!ticketSlug || !content) return null;
|
|
69
77
|
return { ticketSlug, content };
|
|
70
78
|
}
|
|
71
|
-
function
|
|
79
|
+
function parsePromptsRunArgs(args) {
|
|
72
80
|
const parsed = args;
|
|
73
81
|
const slug = typeof parsed?.slug === "string" ? parsed.slug : void 0;
|
|
74
82
|
if (!slug) return null;
|
|
75
83
|
return { slug };
|
|
76
84
|
}
|
|
77
|
-
function
|
|
85
|
+
function parsePromptsListArgs(args) {
|
|
78
86
|
const parsed = args;
|
|
79
87
|
return {
|
|
80
88
|
search: typeof parsed?.search === "string" ? parsed.search : void 0
|
|
81
89
|
};
|
|
82
90
|
}
|
|
83
|
-
function
|
|
91
|
+
function parsePromptsUpdateArgs(args) {
|
|
84
92
|
const parsed = args;
|
|
85
93
|
const slug = typeof parsed?.slug === "string" ? parsed.slug : void 0;
|
|
86
94
|
if (!slug) return null;
|
|
@@ -90,6 +98,171 @@ function parseSystemPromptsUpdateArgs(args) {
|
|
|
90
98
|
changeLogMessage: typeof parsed?.changeLogMessage === "string" ? parsed.changeLogMessage : void 0
|
|
91
99
|
};
|
|
92
100
|
}
|
|
101
|
+
function parseWatcherStartArgs(args) {
|
|
102
|
+
const parsed = args;
|
|
103
|
+
return {
|
|
104
|
+
pollIntervalMs: typeof parsed?.pollIntervalMs === "number" ? parsed.pollIntervalMs : void 0,
|
|
105
|
+
maxParallel: typeof parsed?.maxParallel === "number" ? parsed.maxParallel : void 0,
|
|
106
|
+
ticketTimeout: typeof parsed?.ticketTimeout === "number" ? parsed.ticketTimeout : void 0,
|
|
107
|
+
enableNotifications: typeof parsed?.enableNotifications === "boolean" ? parsed.enableNotifications : void 0,
|
|
108
|
+
workingDirectory: typeof parsed?.workingDirectory === "string" ? parsed.workingDirectory : void 0
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/watcher/watcher_controller.ts
|
|
113
|
+
import { spawn } from "child_process";
|
|
114
|
+
import { dirname, join } from "path";
|
|
115
|
+
import { fileURLToPath } from "url";
|
|
116
|
+
async function startWatcher(baseConfig, args) {
|
|
117
|
+
const workingDirectory = args?.workingDirectory ?? baseConfig.workingDirectory;
|
|
118
|
+
if (isWatcherRunning(workingDirectory)) {
|
|
119
|
+
const existingPid = readPid(workingDirectory);
|
|
120
|
+
const existingStatus = readStatus(workingDirectory);
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
message: "Watcher is already running",
|
|
124
|
+
pid: existingPid,
|
|
125
|
+
alreadyRunning: true,
|
|
126
|
+
config: existingStatus?.config
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const config = {
|
|
130
|
+
projectSlug: baseConfig.projectSlug,
|
|
131
|
+
projectToken: baseConfig.projectToken,
|
|
132
|
+
convexUrl: baseConfig.convexUrl,
|
|
133
|
+
workingDirectory,
|
|
134
|
+
pollIntervalMs: args?.pollIntervalMs ?? 3e4,
|
|
135
|
+
maxParallel: args?.maxParallel ?? 1,
|
|
136
|
+
ticketTimeout: args?.ticketTimeout ?? 18e5,
|
|
137
|
+
enableNotifications: args?.enableNotifications ?? true
|
|
138
|
+
};
|
|
139
|
+
const configJson = JSON.stringify(config);
|
|
140
|
+
const configBase64 = Buffer.from(configJson).toString("base64");
|
|
141
|
+
const daemonPath = getDaemonPath();
|
|
142
|
+
try {
|
|
143
|
+
const child = spawn(process.execPath, [daemonPath, "--config", configBase64], {
|
|
144
|
+
detached: true,
|
|
145
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
146
|
+
cwd: workingDirectory
|
|
147
|
+
});
|
|
148
|
+
child.unref();
|
|
149
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
150
|
+
const pid = readPid(workingDirectory);
|
|
151
|
+
if (pid && isProcessRunning(pid)) {
|
|
152
|
+
return {
|
|
153
|
+
success: true,
|
|
154
|
+
message: "Watcher started successfully",
|
|
155
|
+
pid,
|
|
156
|
+
alreadyRunning: false,
|
|
157
|
+
config: {
|
|
158
|
+
projectSlug: config.projectSlug,
|
|
159
|
+
convexUrl: config.convexUrl,
|
|
160
|
+
pollIntervalMs: config.pollIntervalMs,
|
|
161
|
+
maxParallel: config.maxParallel,
|
|
162
|
+
ticketTimeout: config.ticketTimeout,
|
|
163
|
+
enableNotifications: config.enableNotifications,
|
|
164
|
+
workingDirectory: config.workingDirectory
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
} else {
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
message: "Daemon process started but failed to initialize. Check logs in .ppm/yolo/"
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
message: `Failed to start watcher: ${errorMessage}`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function stopWatcher(workingDirectory) {
|
|
182
|
+
const pid = readPid(workingDirectory);
|
|
183
|
+
if (!pid) {
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
message: "No watcher is running",
|
|
187
|
+
wasRunning: false
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (!isProcessRunning(pid)) {
|
|
191
|
+
removePid(workingDirectory);
|
|
192
|
+
const status = readStatus(workingDirectory);
|
|
193
|
+
if (status) {
|
|
194
|
+
writeStatus(workingDirectory, { ...status, state: "stopped" });
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
message: "Watcher was not running (cleaned up stale PID file)",
|
|
199
|
+
wasRunning: false
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
process.kill(pid, "SIGTERM");
|
|
204
|
+
const maxWait = 5e3;
|
|
205
|
+
const checkInterval = 100;
|
|
206
|
+
let waited = 0;
|
|
207
|
+
while (waited < maxWait && isProcessRunning(pid)) {
|
|
208
|
+
await new Promise((resolve) => setTimeout(resolve, checkInterval));
|
|
209
|
+
waited += checkInterval;
|
|
210
|
+
}
|
|
211
|
+
if (isProcessRunning(pid)) {
|
|
212
|
+
try {
|
|
213
|
+
process.kill(pid, "SIGKILL");
|
|
214
|
+
} catch {
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
removePid(workingDirectory);
|
|
218
|
+
const status = readStatus(workingDirectory);
|
|
219
|
+
if (status) {
|
|
220
|
+
writeStatus(workingDirectory, { ...status, state: "stopped" });
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
success: true,
|
|
224
|
+
message: "Watcher stopped successfully",
|
|
225
|
+
wasRunning: true
|
|
226
|
+
};
|
|
227
|
+
} catch (error) {
|
|
228
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
message: `Failed to stop watcher: ${errorMessage}`,
|
|
232
|
+
wasRunning: true
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function getWatcherStatus(workingDirectory) {
|
|
237
|
+
const status = readStatus(workingDirectory);
|
|
238
|
+
if (!status) {
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
message: "No watcher has been started for this project"
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (status.state === "running" && status.pid) {
|
|
245
|
+
if (!isProcessRunning(status.pid)) {
|
|
246
|
+
const updatedStatus = { ...status, state: "stopped" };
|
|
247
|
+
writeStatus(workingDirectory, updatedStatus);
|
|
248
|
+
removePid(workingDirectory);
|
|
249
|
+
return {
|
|
250
|
+
success: true,
|
|
251
|
+
status: updatedStatus,
|
|
252
|
+
message: "Watcher process has stopped unexpectedly"
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
status
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function getDaemonPath() {
|
|
262
|
+
const currentFilePath = fileURLToPath(import.meta.url);
|
|
263
|
+
const currentDir = dirname(currentFilePath);
|
|
264
|
+
return join(currentDir, "watcher_daemon.js");
|
|
265
|
+
}
|
|
93
266
|
|
|
94
267
|
// src/prompt-builder.ts
|
|
95
268
|
var AmbiguousPromptError = class extends Error {
|
|
@@ -173,7 +346,6 @@ async function startServer(config, convexClientRaw) {
|
|
|
173
346
|
config.projectToken
|
|
174
347
|
);
|
|
175
348
|
console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);
|
|
176
|
-
const projectsForTickets = [{ slug: tokenProjectSlug, name: tokenProjectSlug }];
|
|
177
349
|
console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);
|
|
178
350
|
console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);
|
|
179
351
|
if (accountScopedPrompts.length === 0) {
|
|
@@ -181,78 +353,100 @@ async function startServer(config, convexClientRaw) {
|
|
|
181
353
|
"[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP."
|
|
182
354
|
);
|
|
183
355
|
}
|
|
184
|
-
const
|
|
185
|
-
const dynamicTicketTools = [
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
name:
|
|
356
|
+
const projectSlug = tokenProjectSlug;
|
|
357
|
+
const dynamicTicketTools = [
|
|
358
|
+
// Unified work command (replaces both tickets_open and tickets_work)
|
|
359
|
+
{
|
|
360
|
+
name: `tickets_work`,
|
|
189
361
|
description: `Get work from the "${projectSlug}" project. With no args: gets next ticket from open queue. With ticket slug/number: opens or resumes that specific ticket.`,
|
|
190
362
|
slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,
|
|
191
363
|
projectSlug,
|
|
192
364
|
type: "work"
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
name:
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: `tickets_close`,
|
|
196
368
|
description: `Mark a working ticket as completed in the "${projectSlug}" project`,
|
|
197
369
|
slashDescription: `Mark a working ticket as completed`,
|
|
198
370
|
projectSlug,
|
|
199
371
|
type: "close"
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
name:
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
name: `tickets_create`,
|
|
203
375
|
description: `Create a new ticket in the "${projectSlug}" project queue`,
|
|
204
376
|
slashDescription: `Create a new ticket in the backlog queue`,
|
|
205
377
|
projectSlug,
|
|
206
378
|
type: "create"
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
name:
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: `tickets_search`,
|
|
210
382
|
description: `Search for tickets by content in the "${projectSlug}" project`,
|
|
211
383
|
slashDescription: `Search for tickets by content`,
|
|
212
384
|
projectSlug,
|
|
213
385
|
type: "search"
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
name:
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: `tickets_get`,
|
|
217
389
|
description: `Get a specific ticket by number or slug from "${projectSlug}" (read-only)`,
|
|
218
390
|
slashDescription: `Get a specific ticket by number or slug (read-only)`,
|
|
219
391
|
projectSlug,
|
|
220
392
|
type: "get"
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
name:
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: `tickets_list`,
|
|
224
396
|
description: `List active tickets in the "${projectSlug}" project (backlog + open + working)`,
|
|
225
397
|
slashDescription: `List active tickets (backlog + open + working)`,
|
|
226
398
|
projectSlug,
|
|
227
399
|
type: "list"
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
name:
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: `tickets_update`,
|
|
231
403
|
description: `Update a ticket in the "${projectSlug}" project by appending content with timestamp`,
|
|
232
404
|
slashDescription: `Update a ticket by appending content with timestamp`,
|
|
233
405
|
projectSlug,
|
|
234
406
|
type: "update"
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${projectSlugs.size} project(s)...`);
|
|
238
|
-
const globalSystemTools = [
|
|
407
|
+
},
|
|
408
|
+
// YOLO watcher tools - autonomous ticket execution
|
|
239
409
|
{
|
|
240
|
-
name:
|
|
241
|
-
description:
|
|
242
|
-
slashDescription:
|
|
410
|
+
name: `tickets_yolo_start`,
|
|
411
|
+
description: `Start the YOLO ticket watcher for "${projectSlug}". Polls for open tickets with yolo flag and spawns Terminal windows with Claude Code to execute them autonomously.`,
|
|
412
|
+
slashDescription: `Start YOLO watcher for autonomous ticket execution`,
|
|
413
|
+
projectSlug,
|
|
414
|
+
type: "yolo_start"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: `tickets_yolo_stop`,
|
|
418
|
+
description: `Stop the YOLO ticket watcher for "${projectSlug}"`,
|
|
419
|
+
slashDescription: `Stop the YOLO watcher daemon`,
|
|
420
|
+
projectSlug,
|
|
421
|
+
type: "yolo_stop"
|
|
243
422
|
},
|
|
244
423
|
{
|
|
245
|
-
name:
|
|
424
|
+
name: `tickets_yolo_status`,
|
|
425
|
+
description: `Get YOLO watcher status for "${projectSlug}"`,
|
|
426
|
+
slashDescription: `Check YOLO watcher status and metrics`,
|
|
427
|
+
projectSlug,
|
|
428
|
+
type: "yolo_status"
|
|
429
|
+
}
|
|
430
|
+
];
|
|
431
|
+
console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools...`);
|
|
432
|
+
const globalPromptTools = [
|
|
433
|
+
{
|
|
434
|
+
name: "prompts_run",
|
|
435
|
+
description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
|
|
436
|
+
slashDescription: "Execute a prompt by slug. Use prompts_list to list available prompts."
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
name: "prompts_list",
|
|
246
440
|
description: "List all available prompts",
|
|
247
441
|
slashDescription: "List all available prompts"
|
|
248
442
|
},
|
|
249
443
|
{
|
|
250
|
-
name: "
|
|
444
|
+
name: "prompts_update",
|
|
251
445
|
description: "Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.",
|
|
252
446
|
slashDescription: "Read or update a prompt by slug"
|
|
253
447
|
}
|
|
254
448
|
];
|
|
255
|
-
console.error(`[MCP] Registering ${
|
|
449
|
+
console.error(`[MCP] Registering ${globalPromptTools.length} global prompt tools...`);
|
|
256
450
|
const dynamicPromptTools = [];
|
|
257
451
|
for (const prompt of accountScopedPrompts) {
|
|
258
452
|
const folderPrefix = prompt.folderSlug ? `[${prompt.folderSlug}] ` : "";
|
|
@@ -295,16 +489,16 @@ async function startServer(config, convexClientRaw) {
|
|
|
295
489
|
name: tt.name,
|
|
296
490
|
description: tt.slashDescription
|
|
297
491
|
}));
|
|
298
|
-
const
|
|
492
|
+
const promptToolSchemas = globalPromptTools.map((st) => ({
|
|
299
493
|
name: st.name,
|
|
300
494
|
description: st.slashDescription
|
|
301
495
|
}));
|
|
302
496
|
return {
|
|
303
497
|
prompts: [
|
|
304
|
-
...
|
|
305
|
-
//
|
|
498
|
+
...promptToolSchemas,
|
|
499
|
+
// prompts_run, prompts_list, prompts_update (global)
|
|
306
500
|
...ticketPromptSchemas
|
|
307
|
-
//
|
|
501
|
+
// tickets_* (token-scoped)
|
|
308
502
|
]
|
|
309
503
|
};
|
|
310
504
|
});
|
|
@@ -326,26 +520,25 @@ async function startServer(config, convexClientRaw) {
|
|
|
326
520
|
};
|
|
327
521
|
}
|
|
328
522
|
const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);
|
|
329
|
-
const ticketWorkMatch = promptName.match(/^
|
|
523
|
+
const ticketWorkMatch = promptName.match(/^tickets_work\s+(.+)$/);
|
|
330
524
|
if (ticketTool || ticketWorkMatch) {
|
|
331
525
|
let promptContent;
|
|
332
526
|
let description;
|
|
333
527
|
if (ticketWorkMatch) {
|
|
334
|
-
const
|
|
335
|
-
const ticketArg = ticketWorkMatch[2].trim();
|
|
528
|
+
const ticketArg = ticketWorkMatch[1].trim();
|
|
336
529
|
description = `Work on ticket "${ticketArg}" from "${projectSlug}"`;
|
|
337
530
|
promptContent = `Get work on ticket "${ticketArg}" from the "${projectSlug}" project.
|
|
338
531
|
|
|
339
|
-
Call the
|
|
532
|
+
Call the \`tickets_work\` tool with ticketSlug: "${ticketArg}".
|
|
340
533
|
|
|
341
534
|
This will open the ticket if it's in the open queue, or resume it if already working.`;
|
|
342
535
|
} else if (ticketTool.type === "work") {
|
|
343
536
|
description = ticketTool.description;
|
|
344
537
|
promptContent = `Get work from the "${ticketTool.projectSlug}" project.
|
|
345
538
|
|
|
346
|
-
Call the
|
|
539
|
+
Call the \`tickets_work\` tool to get the next ticket from the open queue.
|
|
347
540
|
|
|
348
|
-
You can also specify a ticket: /ppm
|
|
541
|
+
You can also specify a ticket: /ppm:tickets_work <number-or-slug>
|
|
349
542
|
|
|
350
543
|
This unified command handles both opening new tickets and resuming in-progress work.`;
|
|
351
544
|
} else if (ticketTool.type === "create") {
|
|
@@ -397,7 +590,7 @@ Call the \`${ticketTool.name}\` tool with the ticket number or slug.`;
|
|
|
397
590
|
]
|
|
398
591
|
};
|
|
399
592
|
}
|
|
400
|
-
if (promptName === "
|
|
593
|
+
if (promptName === "prompts_list") {
|
|
401
594
|
return {
|
|
402
595
|
description: "List all available prompts",
|
|
403
596
|
messages: [
|
|
@@ -407,14 +600,14 @@ Call the \`${ticketTool.name}\` tool with the ticket number or slug.`;
|
|
|
407
600
|
type: "text",
|
|
408
601
|
text: `List all available prompts.
|
|
409
602
|
|
|
410
|
-
Call the \`
|
|
603
|
+
Call the \`prompts_list\` tool with optional \`search\` parameter to filter results.`
|
|
411
604
|
}
|
|
412
605
|
}
|
|
413
606
|
]
|
|
414
607
|
};
|
|
415
608
|
}
|
|
416
|
-
const runPromptMatch = promptName.match(/^
|
|
417
|
-
if (promptName === "
|
|
609
|
+
const runPromptMatch = promptName.match(/^prompts_run\s+(.+)$/);
|
|
610
|
+
if (promptName === "prompts_run" || runPromptMatch) {
|
|
418
611
|
let promptContent;
|
|
419
612
|
let description;
|
|
420
613
|
if (runPromptMatch) {
|
|
@@ -422,19 +615,19 @@ Call the \`system_prompts\` tool with optional \`search\` parameter to filter re
|
|
|
422
615
|
description = `Execute prompt "${promptSlug}"`;
|
|
423
616
|
promptContent = `Execute the "${promptSlug}" prompt.
|
|
424
617
|
|
|
425
|
-
Call the \`
|
|
618
|
+
Call the \`prompts_run\` tool with slug: "${promptSlug}".`;
|
|
426
619
|
} else {
|
|
427
620
|
description = "Execute a prompt by slug";
|
|
428
621
|
promptContent = `Execute a prompt by slug.
|
|
429
622
|
|
|
430
623
|
## Usage
|
|
431
|
-
Call the \`
|
|
624
|
+
Call the \`prompts_run\` tool with the prompt slug.
|
|
432
625
|
|
|
433
626
|
## Available Prompts
|
|
434
|
-
Use \`
|
|
627
|
+
Use \`prompts_list\` to list all available prompts.
|
|
435
628
|
|
|
436
629
|
## Example
|
|
437
|
-
/ppm:
|
|
630
|
+
/ppm:prompts_run code-review
|
|
438
631
|
|
|
439
632
|
This will execute the "code-review" prompt.`;
|
|
440
633
|
}
|
|
@@ -451,7 +644,7 @@ This will execute the "code-review" prompt.`;
|
|
|
451
644
|
]
|
|
452
645
|
};
|
|
453
646
|
}
|
|
454
|
-
throw new Error(`Unknown prompt: ${promptName}. Use
|
|
647
|
+
throw new Error(`Unknown prompt: ${promptName}. Use prompts_run to execute prompts.`);
|
|
455
648
|
});
|
|
456
649
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
457
650
|
const tools = [
|
|
@@ -545,6 +738,45 @@ This will execute the "code-review" prompt.`;
|
|
|
545
738
|
},
|
|
546
739
|
required: ["ticketSlug", "content"]
|
|
547
740
|
};
|
|
741
|
+
} else if (tt.type === "yolo_start") {
|
|
742
|
+
inputSchema = {
|
|
743
|
+
type: "object",
|
|
744
|
+
properties: {
|
|
745
|
+
pollIntervalMs: {
|
|
746
|
+
type: "number",
|
|
747
|
+
description: "Milliseconds between polls (default: 30000 = 30s)"
|
|
748
|
+
},
|
|
749
|
+
maxParallel: {
|
|
750
|
+
type: "number",
|
|
751
|
+
description: "Maximum concurrent ticket executions (default: 1)"
|
|
752
|
+
},
|
|
753
|
+
ticketTimeout: {
|
|
754
|
+
type: "number",
|
|
755
|
+
description: "Timeout for ticket execution in ms (default: 1800000 = 30 min)"
|
|
756
|
+
},
|
|
757
|
+
enableNotifications: {
|
|
758
|
+
type: "boolean",
|
|
759
|
+
description: "Show macOS notifications (default: true)"
|
|
760
|
+
},
|
|
761
|
+
workingDirectory: {
|
|
762
|
+
type: "string",
|
|
763
|
+
description: "Working directory for Claude Code sessions (default: current working directory)"
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
required: []
|
|
767
|
+
};
|
|
768
|
+
} else if (tt.type === "yolo_stop") {
|
|
769
|
+
inputSchema = {
|
|
770
|
+
type: "object",
|
|
771
|
+
properties: {},
|
|
772
|
+
required: []
|
|
773
|
+
};
|
|
774
|
+
} else if (tt.type === "yolo_status") {
|
|
775
|
+
inputSchema = {
|
|
776
|
+
type: "object",
|
|
777
|
+
properties: {},
|
|
778
|
+
required: []
|
|
779
|
+
};
|
|
548
780
|
} else {
|
|
549
781
|
inputSchema = {
|
|
550
782
|
type: "object",
|
|
@@ -558,10 +790,10 @@ This will execute the "code-review" prompt.`;
|
|
|
558
790
|
inputSchema
|
|
559
791
|
};
|
|
560
792
|
}),
|
|
561
|
-
// Global
|
|
793
|
+
// Global prompts_run tool
|
|
562
794
|
{
|
|
563
|
-
name: "
|
|
564
|
-
description: "Execute a prompt by slug. Use
|
|
795
|
+
name: "prompts_run",
|
|
796
|
+
description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
|
|
565
797
|
inputSchema: {
|
|
566
798
|
type: "object",
|
|
567
799
|
properties: {
|
|
@@ -573,9 +805,9 @@ This will execute the "code-review" prompt.`;
|
|
|
573
805
|
required: ["slug"]
|
|
574
806
|
}
|
|
575
807
|
},
|
|
576
|
-
// Global
|
|
808
|
+
// Global prompts_list tool
|
|
577
809
|
{
|
|
578
|
-
name: "
|
|
810
|
+
name: "prompts_list",
|
|
579
811
|
description: "List all available prompts",
|
|
580
812
|
inputSchema: {
|
|
581
813
|
type: "object",
|
|
@@ -587,9 +819,9 @@ This will execute the "code-review" prompt.`;
|
|
|
587
819
|
}
|
|
588
820
|
}
|
|
589
821
|
},
|
|
590
|
-
// Global
|
|
822
|
+
// Global prompts_update tool (CLI-based prompt editing)
|
|
591
823
|
{
|
|
592
|
-
name: "
|
|
824
|
+
name: "prompts_update",
|
|
593
825
|
description: "Read or update a prompt. Without content: returns prompt with context and editing guidance. With content: saves new version.",
|
|
594
826
|
inputSchema: {
|
|
595
827
|
type: "object",
|
|
@@ -625,8 +857,8 @@ This will execute the "code-review" prompt.`;
|
|
|
625
857
|
});
|
|
626
858
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
627
859
|
const toolName = request.params.name;
|
|
628
|
-
if (toolName === "
|
|
629
|
-
const parsedArgs =
|
|
860
|
+
if (toolName === "prompts_run") {
|
|
861
|
+
const parsedArgs = parsePromptsRunArgs(request.params.arguments);
|
|
630
862
|
if (!parsedArgs) {
|
|
631
863
|
return {
|
|
632
864
|
content: [
|
|
@@ -634,7 +866,7 @@ This will execute the "code-review" prompt.`;
|
|
|
634
866
|
type: "text",
|
|
635
867
|
text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').
|
|
636
868
|
|
|
637
|
-
Use \`
|
|
869
|
+
Use \`prompts_list\` to list available prompts.`
|
|
638
870
|
}
|
|
639
871
|
],
|
|
640
872
|
isError: true
|
|
@@ -668,7 +900,7 @@ Use \`system_prompts\` to list available prompts.`
|
|
|
668
900
|
|
|
669
901
|
${suggestionsList}
|
|
670
902
|
|
|
671
|
-
Example: \`
|
|
903
|
+
Example: \`prompts_run ${error.suggestions[0]}\``
|
|
672
904
|
}
|
|
673
905
|
],
|
|
674
906
|
isError: true
|
|
@@ -687,8 +919,8 @@ Example: \`system_run_prompt ${error.suggestions[0]}\``
|
|
|
687
919
|
};
|
|
688
920
|
}
|
|
689
921
|
}
|
|
690
|
-
if (toolName === "
|
|
691
|
-
const { search: searchTerm } =
|
|
922
|
+
if (toolName === "prompts_list") {
|
|
923
|
+
const { search: searchTerm } = parsePromptsListArgs(request.params.arguments);
|
|
692
924
|
let filteredPrompts = [...accountScopedPrompts];
|
|
693
925
|
if (searchTerm) {
|
|
694
926
|
const lowerSearch = searchTerm.toLowerCase();
|
|
@@ -716,8 +948,8 @@ No prompts found.`
|
|
|
716
948
|
]
|
|
717
949
|
};
|
|
718
950
|
}
|
|
719
|
-
if (toolName === "
|
|
720
|
-
const parsedArgs =
|
|
951
|
+
if (toolName === "prompts_update") {
|
|
952
|
+
const parsedArgs = parsePromptsUpdateArgs(request.params.arguments);
|
|
721
953
|
if (!parsedArgs) {
|
|
722
954
|
return {
|
|
723
955
|
content: [
|
|
@@ -726,8 +958,8 @@ No prompts found.`
|
|
|
726
958
|
text: `Error: Missing 'slug' parameter. Provide prompt slug to read or update.
|
|
727
959
|
|
|
728
960
|
Usage:
|
|
729
|
-
- Read mode:
|
|
730
|
-
- Write mode:
|
|
961
|
+
- Read mode: prompts_update { slug: "my-prompt" }
|
|
962
|
+
- Write mode: prompts_update { slug: "my-prompt", content: "new content..." }`
|
|
731
963
|
}
|
|
732
964
|
],
|
|
733
965
|
isError: true
|
|
@@ -797,7 +1029,7 @@ ${JSON.stringify(readResult.context, null, 2)}
|
|
|
797
1029
|
${readResult.editingGuidance}
|
|
798
1030
|
|
|
799
1031
|
---
|
|
800
|
-
_To update this prompt, call
|
|
1032
|
+
_To update this prompt, call prompts_update with content parameter._`
|
|
801
1033
|
}
|
|
802
1034
|
]
|
|
803
1035
|
};
|
|
@@ -823,7 +1055,7 @@ Updated: ${writeResult.updatedAt}`
|
|
|
823
1055
|
};
|
|
824
1056
|
} catch (error) {
|
|
825
1057
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
826
|
-
console.error(`[MCP]
|
|
1058
|
+
console.error(`[MCP] prompts_update error:`, error);
|
|
827
1059
|
return {
|
|
828
1060
|
content: [
|
|
829
1061
|
{
|
|
@@ -1274,6 +1506,132 @@ _Ticket content has been appended with your update._`
|
|
|
1274
1506
|
isError: true
|
|
1275
1507
|
};
|
|
1276
1508
|
}
|
|
1509
|
+
} else if (ticketTool.type === "yolo_start") {
|
|
1510
|
+
const args = parseWatcherStartArgs(request.params.arguments);
|
|
1511
|
+
const workingDirectory = args.workingDirectory ?? process.cwd();
|
|
1512
|
+
try {
|
|
1513
|
+
const result = await startWatcher(
|
|
1514
|
+
{
|
|
1515
|
+
projectSlug: ticketTool.projectSlug,
|
|
1516
|
+
projectToken: config.projectToken,
|
|
1517
|
+
convexUrl: config.convexUrl,
|
|
1518
|
+
workingDirectory
|
|
1519
|
+
},
|
|
1520
|
+
args
|
|
1521
|
+
);
|
|
1522
|
+
if (result.success) {
|
|
1523
|
+
const statusLines = [
|
|
1524
|
+
result.alreadyRunning ? `\u2139\uFE0F Watcher is already running (PID: ${result.pid})` : `\u2705 Watcher started (PID: ${result.pid})`
|
|
1525
|
+
];
|
|
1526
|
+
if (result.config) {
|
|
1527
|
+
statusLines.push("");
|
|
1528
|
+
statusLines.push("**Configuration:**");
|
|
1529
|
+
statusLines.push(` Poll Interval: ${result.config.pollIntervalMs / 1e3}s`);
|
|
1530
|
+
statusLines.push(` Max Parallel: ${result.config.maxParallel}`);
|
|
1531
|
+
statusLines.push(` Ticket Timeout: ${result.config.ticketTimeout / 1e3 / 60} min`);
|
|
1532
|
+
statusLines.push(` Notifications: ${result.config.enableNotifications ? "enabled" : "disabled"}`);
|
|
1533
|
+
statusLines.push(` Working Directory: ${result.config.workingDirectory}`);
|
|
1534
|
+
}
|
|
1535
|
+
statusLines.push("");
|
|
1536
|
+
statusLines.push("_The watcher will poll for open tickets with the YOLO flag and spawn Claude Code terminals to execute them._");
|
|
1537
|
+
statusLines.push("_Use `tickets_yolo_status` to check progress, `tickets_yolo_stop` to stop._");
|
|
1538
|
+
return {
|
|
1539
|
+
content: [{ type: "text", text: statusLines.join("\n") }]
|
|
1540
|
+
};
|
|
1541
|
+
} else {
|
|
1542
|
+
return {
|
|
1543
|
+
content: [{ type: "text", text: `\u274C ${result.message}` }],
|
|
1544
|
+
isError: true
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1549
|
+
console.error(`[MCP] tickets_yolo_start error:`, error);
|
|
1550
|
+
return {
|
|
1551
|
+
content: [{ type: "text", text: `Error starting watcher: ${errorMessage}` }],
|
|
1552
|
+
isError: true
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
} else if (ticketTool.type === "yolo_stop") {
|
|
1556
|
+
const workingDirectory = process.cwd();
|
|
1557
|
+
try {
|
|
1558
|
+
const result = await stopWatcher(workingDirectory);
|
|
1559
|
+
if (result.success) {
|
|
1560
|
+
return {
|
|
1561
|
+
content: [{
|
|
1562
|
+
type: "text",
|
|
1563
|
+
text: result.wasRunning ? `\u2705 Watcher stopped successfully` : `\u2139\uFE0F ${result.message}`
|
|
1564
|
+
}]
|
|
1565
|
+
};
|
|
1566
|
+
} else {
|
|
1567
|
+
return {
|
|
1568
|
+
content: [{ type: "text", text: `\u274C ${result.message}` }],
|
|
1569
|
+
isError: true
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1574
|
+
console.error(`[MCP] tickets_yolo_stop error:`, error);
|
|
1575
|
+
return {
|
|
1576
|
+
content: [{ type: "text", text: `Error stopping watcher: ${errorMessage}` }],
|
|
1577
|
+
isError: true
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
} else if (ticketTool.type === "yolo_status") {
|
|
1581
|
+
const workingDirectory = process.cwd();
|
|
1582
|
+
try {
|
|
1583
|
+
const result = getWatcherStatus(workingDirectory);
|
|
1584
|
+
if (!result.status) {
|
|
1585
|
+
return {
|
|
1586
|
+
content: [{ type: "text", text: `\u2139\uFE0F ${result.message ?? "No watcher status available"}` }]
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1589
|
+
const status = result.status;
|
|
1590
|
+
const lines = [
|
|
1591
|
+
`# YOLO Watcher Status: ${status.projectSlug}`,
|
|
1592
|
+
"",
|
|
1593
|
+
`**State:** ${status.state.toUpperCase()}`
|
|
1594
|
+
];
|
|
1595
|
+
if (status.pid) {
|
|
1596
|
+
lines.push(`**PID:** ${status.pid}`);
|
|
1597
|
+
}
|
|
1598
|
+
if (status.startedAt) {
|
|
1599
|
+
lines.push(`**Started:** ${status.startedAt}`);
|
|
1600
|
+
}
|
|
1601
|
+
if (status.lastPollAt) {
|
|
1602
|
+
lines.push(`**Last Poll:** ${status.lastPollAt}`);
|
|
1603
|
+
}
|
|
1604
|
+
lines.push(`**Tickets Processed:** ${status.ticketsProcessed}`);
|
|
1605
|
+
if (status.currentlyExecuting && status.currentlyExecuting.length > 0) {
|
|
1606
|
+
lines.push("");
|
|
1607
|
+
lines.push("**Currently Executing:**");
|
|
1608
|
+
for (const exec of status.currentlyExecuting) {
|
|
1609
|
+
const displayName = exec.ticketNumber ? `#${exec.ticketNumber} ${exec.ticketSlug}` : exec.ticketSlug;
|
|
1610
|
+
lines.push(` \u2022 ${displayName} (started: ${exec.startedAt})`);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
if (status.lastError) {
|
|
1614
|
+
lines.push("");
|
|
1615
|
+
lines.push(`**Last Error:** ${status.lastError}`);
|
|
1616
|
+
}
|
|
1617
|
+
if (status.config) {
|
|
1618
|
+
lines.push("");
|
|
1619
|
+
lines.push("**Configuration:**");
|
|
1620
|
+
lines.push(` Poll Interval: ${status.config.pollIntervalMs / 1e3}s`);
|
|
1621
|
+
lines.push(` Max Parallel: ${status.config.maxParallel}`);
|
|
1622
|
+
lines.push(` Working Directory: ${status.config.workingDirectory}`);
|
|
1623
|
+
}
|
|
1624
|
+
return {
|
|
1625
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1626
|
+
};
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1629
|
+
console.error(`[MCP] tickets_yolo_status error:`, error);
|
|
1630
|
+
return {
|
|
1631
|
+
content: [{ type: "text", text: `Error getting watcher status: ${errorMessage}` }],
|
|
1632
|
+
isError: true
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1277
1635
|
}
|
|
1278
1636
|
}
|
|
1279
1637
|
const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);
|
|
@@ -1297,7 +1655,7 @@ _Ticket content has been appended with your update._`
|
|
|
1297
1655
|
};
|
|
1298
1656
|
}
|
|
1299
1657
|
}
|
|
1300
|
-
throw new Error(`Unknown tool: ${toolName}. Use
|
|
1658
|
+
throw new Error(`Unknown tool: ${toolName}. Use prompts_run to execute prompts by name, or check available tools.`);
|
|
1301
1659
|
});
|
|
1302
1660
|
const transport = new StdioServerTransport();
|
|
1303
1661
|
await server.connect(transport);
|