@promptprojectmanager/mcp-server 4.9.4 → 4.9.6
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 +1445 -1557
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,6 +22,313 @@ import {
|
|
|
22
22
|
ListToolsRequestSchema
|
|
23
23
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
24
24
|
|
|
25
|
+
// src/auth/agent-resolver.ts
|
|
26
|
+
function buildAuthArgs(config) {
|
|
27
|
+
return { projectToken: config.projectToken };
|
|
28
|
+
}
|
|
29
|
+
function resolveAgent(paramAgent) {
|
|
30
|
+
if (paramAgent) return paramAgent;
|
|
31
|
+
const envAgent = process.env.PPM_AGENT_NAME;
|
|
32
|
+
if (envAgent) return envAgent;
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/auth/token-validator.ts
|
|
37
|
+
async function validateProjectToken(client, token) {
|
|
38
|
+
try {
|
|
39
|
+
const typedClient = client;
|
|
40
|
+
const result = await typedClient.query(
|
|
41
|
+
"projects:validateProjectToken",
|
|
42
|
+
{ token }
|
|
43
|
+
);
|
|
44
|
+
if (result && result.valid) {
|
|
45
|
+
return {
|
|
46
|
+
valid: true,
|
|
47
|
+
projectId: result.projectId,
|
|
48
|
+
projectSlug: result.projectSlug,
|
|
49
|
+
ownerId: result.ownerId
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { valid: false, error: "Invalid project token" };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
valid: false,
|
|
56
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/tools/registry.ts
|
|
62
|
+
function getTicketToolDefinitions(projectSlug) {
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
name: "tickets_work",
|
|
66
|
+
type: "work",
|
|
67
|
+
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.`,
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: "object",
|
|
70
|
+
properties: {
|
|
71
|
+
ticketSlug: {
|
|
72
|
+
type: "string",
|
|
73
|
+
description: "Optional: Ticket number (e.g., '102') or full slug. If not provided, gets next ticket from open queue."
|
|
74
|
+
},
|
|
75
|
+
agent: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "Optional: Agent slug to filter queue. Overrides PPM_AGENT_NAME env var. Omit for owner mode (no filtering)."
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
required: []
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: "tickets_close",
|
|
85
|
+
type: "close",
|
|
86
|
+
description: `Mark a working ticket as completed in the "${projectSlug}" project. If a hook is configured, first call returns instructions - then call again with metadata.`,
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: {
|
|
90
|
+
ticketSlug: {
|
|
91
|
+
type: "string",
|
|
92
|
+
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
93
|
+
},
|
|
94
|
+
metadata: {
|
|
95
|
+
type: "object",
|
|
96
|
+
description: "Optional: Key-value metadata to store with the closed ticket (e.g., evaluation scores, completion notes).",
|
|
97
|
+
additionalProperties: {
|
|
98
|
+
oneOf: [{ type: "string" }, { type: "number" }, { type: "boolean" }]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
required: ["ticketSlug"]
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "tickets_pause",
|
|
107
|
+
type: "pause",
|
|
108
|
+
description: `Pause a working ticket and return to queue. Before calling, review the full session context and generate comprehensive checkpoint content that enables seamless handoff to another agent. Include: completed tasks with specifics, key decisions and rationale, sticking points and resolutions, user preferences discovered, remaining work with file:line references, and open questions.`,
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: "object",
|
|
111
|
+
properties: {
|
|
112
|
+
ticketSlug: {
|
|
113
|
+
type: "string",
|
|
114
|
+
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
115
|
+
},
|
|
116
|
+
content: {
|
|
117
|
+
type: "string",
|
|
118
|
+
description: "Comprehensive checkpoint for handoff. Review the session and include: ## Progress (completed tasks with specifics, decisions made and why) ## Context (user preferences, edge cases discovered, key feedback received) ## Remaining (specific next steps, file:line references if applicable) ## Blockers (unresolved issues, open questions). This enables the next agent to continue seamlessly."
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
required: ["ticketSlug", "content"]
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "tickets_create",
|
|
126
|
+
type: "create",
|
|
127
|
+
description: `Create a new ticket in the "${projectSlug}" project queue`,
|
|
128
|
+
inputSchema: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
content: {
|
|
132
|
+
type: "string",
|
|
133
|
+
description: "The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed."
|
|
134
|
+
},
|
|
135
|
+
agent: {
|
|
136
|
+
type: "string",
|
|
137
|
+
description: "Optional: Agent slug to assign ticket to. Overrides PPM_AGENT_NAME env var. Omit for unassigned."
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
required: ["content"]
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "tickets_search",
|
|
145
|
+
type: "search",
|
|
146
|
+
description: `Search for tickets by content in the "${projectSlug}" project`,
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: "object",
|
|
149
|
+
properties: {
|
|
150
|
+
query: {
|
|
151
|
+
type: "string",
|
|
152
|
+
description: "Search query (min 3 characters)"
|
|
153
|
+
},
|
|
154
|
+
agent: {
|
|
155
|
+
type: "string",
|
|
156
|
+
description: "Optional: Agent slug to filter results. Overrides PPM_AGENT_NAME env var. Omit for all tickets."
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
required: ["query"]
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "tickets_get",
|
|
164
|
+
type: "get",
|
|
165
|
+
description: `Get a specific ticket by number or slug from "${projectSlug}" (read-only)`,
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
ticketSlug: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Ticket number (e.g., '102') or full slug"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
required: ["ticketSlug"]
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "tickets_list",
|
|
179
|
+
type: "list",
|
|
180
|
+
description: `List active tickets in the "${projectSlug}" project (backlog + open + working)`,
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
agent: {
|
|
185
|
+
type: "string",
|
|
186
|
+
description: "Optional: Agent slug to filter tickets. Overrides PPM_AGENT_NAME env var. Omit for all tickets."
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
required: []
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "tickets_update",
|
|
194
|
+
type: "update",
|
|
195
|
+
description: `Update a ticket in the "${projectSlug}" project by appending content with timestamp`,
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: "object",
|
|
198
|
+
properties: {
|
|
199
|
+
ticketSlug: {
|
|
200
|
+
type: "string",
|
|
201
|
+
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
202
|
+
},
|
|
203
|
+
content: {
|
|
204
|
+
type: "string",
|
|
205
|
+
description: "Update content to append to the ticket (markdown supported)"
|
|
206
|
+
},
|
|
207
|
+
agent: {
|
|
208
|
+
type: "string",
|
|
209
|
+
description: "Optional: Reassign ticket to this agent. Use empty string '' to unassign."
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
required: ["ticketSlug", "content"]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
function getMemoryToolDefinitions() {
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
name: "memories_load",
|
|
221
|
+
type: "load",
|
|
222
|
+
description: `Load project memories: returns all rules (always) + relevant history (by query or recent). Use [MEMORY_LOAD] in prompts for automatic loading.`,
|
|
223
|
+
inputSchema: {
|
|
224
|
+
type: "object",
|
|
225
|
+
properties: {
|
|
226
|
+
query: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Optional: search query to filter agent memories. If not provided, returns recent memories."
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
required: []
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: "memories_load_extreme",
|
|
236
|
+
type: "load_extreme",
|
|
237
|
+
description: `Load project memories with FULL ticket context. Returns all matching memories, and for each memory linked to a ticket, includes the complete ticket content (tasks, updates, decisions). Use when you need deep historical context about past work.`,
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: "object",
|
|
240
|
+
properties: {
|
|
241
|
+
query: {
|
|
242
|
+
type: "string",
|
|
243
|
+
description: "Optional: search query to filter agent memories. If not provided, returns recent memories."
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
required: []
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: "memories_add",
|
|
251
|
+
type: "add",
|
|
252
|
+
description: `Record a project memory. Optionally links to working ticket if exactly one exists. Use for key decisions, patterns discovered, or important context.`,
|
|
253
|
+
inputSchema: {
|
|
254
|
+
type: "object",
|
|
255
|
+
properties: {
|
|
256
|
+
content: {
|
|
257
|
+
type: "string",
|
|
258
|
+
description: "Memory content to record. Be concise and focused on key decisions, patterns, or context."
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
required: ["content"]
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "memories_list_all",
|
|
266
|
+
type: "list_all",
|
|
267
|
+
description: `List ALL project memories (rules and agent history). Returns complete memory list with IDs for review/cleanup. Use for memory audits or before bulk operations.`,
|
|
268
|
+
inputSchema: {
|
|
269
|
+
type: "object",
|
|
270
|
+
properties: {
|
|
271
|
+
typeFilter: {
|
|
272
|
+
type: "string",
|
|
273
|
+
enum: ["all", "user", "agent"],
|
|
274
|
+
description: "Optional: Filter by memory type. 'user' = rules, 'agent' = history. Default: 'all'"
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
required: []
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
];
|
|
281
|
+
}
|
|
282
|
+
function getPromptToolDefinitions() {
|
|
283
|
+
return [
|
|
284
|
+
{
|
|
285
|
+
name: "prompts_run",
|
|
286
|
+
description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
|
|
287
|
+
inputSchema: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
slug: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "Prompt slug to execute (e.g., 'code-review', 'plan')"
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
required: ["slug"]
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "prompts_list",
|
|
300
|
+
description: "List all available prompts",
|
|
301
|
+
inputSchema: {
|
|
302
|
+
type: "object",
|
|
303
|
+
properties: {
|
|
304
|
+
search: {
|
|
305
|
+
type: "string",
|
|
306
|
+
description: "Optional search term to filter prompts by name or description (case-insensitive)"
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "prompts_update",
|
|
313
|
+
description: "Read or update a prompt, snippet, or template. Without content: returns resource with context. With content: saves new version.",
|
|
314
|
+
inputSchema: {
|
|
315
|
+
type: "object",
|
|
316
|
+
properties: {
|
|
317
|
+
slug: {
|
|
318
|
+
type: "string",
|
|
319
|
+
description: "Resource slug to read or update. Searches prompts first, then snippets, then templates. Supports fuzzy matching."
|
|
320
|
+
},
|
|
321
|
+
content: {
|
|
322
|
+
type: "string",
|
|
323
|
+
description: "New resource content (omit for read-only mode)"
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
required: ["slug"]
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
];
|
|
330
|
+
}
|
|
331
|
+
|
|
25
332
|
// src/types.ts
|
|
26
333
|
function isAccessDenied(result) {
|
|
27
334
|
return typeof result === "object" && result !== null && "accessDenied" in result && result.accessDenied === true;
|
|
@@ -205,1636 +512,1176 @@ async function fetchAndExecuteAccountScopedPrompt(promptSlug, config, convexClie
|
|
|
205
512
|
};
|
|
206
513
|
}
|
|
207
514
|
|
|
208
|
-
// src/
|
|
209
|
-
function
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
]
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.error("[MCP] Validating project token...");
|
|
224
|
-
const validation = await validateProjectToken(convexClient, config.projectToken);
|
|
225
|
-
if (!validation.valid) {
|
|
226
|
-
throw new Error(`Invalid project token: ${validation.error}`);
|
|
227
|
-
}
|
|
228
|
-
const tokenProjectSlug = validation.projectSlug;
|
|
229
|
-
console.error(`[MCP] Project token validated for project "${tokenProjectSlug}"`);
|
|
230
|
-
console.error("[MCP] Fetching prompt metadata...");
|
|
231
|
-
const accountScopedPrompts = await fetchAccountScopedPromptMetadataByToken(
|
|
232
|
-
convexClient,
|
|
233
|
-
config.projectToken
|
|
234
|
-
);
|
|
235
|
-
console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);
|
|
236
|
-
console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);
|
|
237
|
-
console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);
|
|
238
|
-
if (accountScopedPrompts.length === 0) {
|
|
239
|
-
console.error(
|
|
240
|
-
"[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP."
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
const projectSlug = tokenProjectSlug;
|
|
244
|
-
const dynamicTicketTools = [
|
|
245
|
-
// Unified work command (replaces both tickets_open and tickets_work)
|
|
246
|
-
{
|
|
247
|
-
name: `tickets_work`,
|
|
248
|
-
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.`,
|
|
249
|
-
slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,
|
|
250
|
-
projectSlug,
|
|
251
|
-
type: "work"
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
name: `tickets_close`,
|
|
255
|
-
description: `Mark a working ticket as completed in the "${projectSlug}" project. If a hook is configured, first call returns instructions - then call again with metadata.`,
|
|
256
|
-
slashDescription: `Mark a working ticket as completed`,
|
|
257
|
-
projectSlug,
|
|
258
|
-
type: "close"
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: `tickets_pause`,
|
|
262
|
-
description: `Pause a working ticket and return to queue. Before calling, review the full session context and generate comprehensive checkpoint content that enables seamless handoff to another agent. Include: completed tasks with specifics, key decisions and rationale, sticking points and resolutions, user preferences discovered, remaining work with file:line references, and open questions.`,
|
|
263
|
-
slashDescription: `Pause working ticket with checkpoint content (working \u2192 open)`,
|
|
264
|
-
projectSlug,
|
|
265
|
-
type: "pause"
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
name: `tickets_create`,
|
|
269
|
-
description: `Create a new ticket in the "${projectSlug}" project queue`,
|
|
270
|
-
slashDescription: `Create a new ticket in the backlog queue`,
|
|
271
|
-
projectSlug,
|
|
272
|
-
type: "create"
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
name: `tickets_search`,
|
|
276
|
-
description: `Search for tickets by content in the "${projectSlug}" project`,
|
|
277
|
-
slashDescription: `Search for tickets by content`,
|
|
278
|
-
projectSlug,
|
|
279
|
-
type: "search"
|
|
280
|
-
},
|
|
281
|
-
{
|
|
282
|
-
name: `tickets_get`,
|
|
283
|
-
description: `Get a specific ticket by number or slug from "${projectSlug}" (read-only)`,
|
|
284
|
-
slashDescription: `Get a specific ticket by number or slug (read-only)`,
|
|
285
|
-
projectSlug,
|
|
286
|
-
type: "get"
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
name: `tickets_list`,
|
|
290
|
-
description: `List active tickets in the "${projectSlug}" project (backlog + open + working)`,
|
|
291
|
-
slashDescription: `List active tickets (backlog + open + working)`,
|
|
292
|
-
projectSlug,
|
|
293
|
-
type: "list"
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
name: `tickets_update`,
|
|
297
|
-
description: `Update a ticket in the "${projectSlug}" project by appending content with timestamp`,
|
|
298
|
-
slashDescription: `Update a ticket by appending content with timestamp`,
|
|
299
|
-
projectSlug,
|
|
300
|
-
type: "update"
|
|
301
|
-
}
|
|
302
|
-
];
|
|
303
|
-
console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools...`);
|
|
304
|
-
const dynamicMemoryTools = [
|
|
305
|
-
{
|
|
306
|
-
name: `memories_load`,
|
|
307
|
-
description: `Load project memories: returns all rules (always) + relevant history (by query or recent). Use [MEMORY_LOAD] in prompts for automatic loading.`,
|
|
308
|
-
slashDescription: `Load project memories (rules + history)`,
|
|
309
|
-
projectSlug,
|
|
310
|
-
type: "load"
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: `memories_load_extreme`,
|
|
314
|
-
description: `Load project memories with FULL ticket context. Returns all matching memories, and for each memory linked to a ticket, includes the complete ticket content (tasks, updates, decisions). Use when you need deep historical context about past work.`,
|
|
315
|
-
slashDescription: `Load memories with full ticket context`,
|
|
316
|
-
projectSlug,
|
|
317
|
-
type: "load_extreme"
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: `memories_add`,
|
|
321
|
-
description: `Record a project memory. Optionally links to working ticket if exactly one exists. Use for key decisions, patterns discovered, or important context.`,
|
|
322
|
-
slashDescription: `Add memory to project (auto-links to working ticket if one exists)`,
|
|
323
|
-
projectSlug,
|
|
324
|
-
type: "add"
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
name: `memories_list_all`,
|
|
328
|
-
description: `List ALL project memories (rules and agent history). Returns complete memory list with IDs for review/cleanup. Use for memory audits or before bulk operations.`,
|
|
329
|
-
slashDescription: `List all memories with IDs for review`,
|
|
330
|
-
projectSlug,
|
|
331
|
-
type: "list_all"
|
|
332
|
-
}
|
|
333
|
-
];
|
|
334
|
-
console.error(`[MCP] Registering ${dynamicMemoryTools.length} memory tools...`);
|
|
335
|
-
const globalPromptTools = [
|
|
336
|
-
{
|
|
337
|
-
name: "prompts_run",
|
|
338
|
-
description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
|
|
339
|
-
slashDescription: "Execute a prompt by slug. Use prompts_list to list available prompts."
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
name: "prompts_list",
|
|
343
|
-
description: "List all available prompts",
|
|
344
|
-
slashDescription: "List all available prompts"
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
name: "prompts_update",
|
|
348
|
-
description: "Read or update a prompt, snippet, or template. Without content: returns resource with context. With content: saves new version.",
|
|
349
|
-
slashDescription: "Read or update a prompt, snippet, or template by slug"
|
|
350
|
-
}
|
|
351
|
-
];
|
|
352
|
-
console.error(`[MCP] Registering ${globalPromptTools.length} global prompt tools...`);
|
|
353
|
-
const dynamicPromptTools = [];
|
|
354
|
-
for (const prompt of accountScopedPrompts) {
|
|
355
|
-
const folderPrefix = prompt.folderPath ? `[${prompt.folderPath}] ` : "";
|
|
356
|
-
const baseDescription = prompt.description || `Execute the "${prompt.slug}" prompt`;
|
|
357
|
-
dynamicPromptTools.push({
|
|
358
|
-
name: prompt.slug,
|
|
359
|
-
// Global tool name, no project prefix
|
|
360
|
-
description: folderPrefix + baseDescription,
|
|
361
|
-
promptSlug: prompt.slug
|
|
362
|
-
});
|
|
515
|
+
// src/handlers/prompts.ts
|
|
516
|
+
async function handlePromptsRun(args, config, convexClient) {
|
|
517
|
+
const parsedArgs = parsePromptsRunArgs(args);
|
|
518
|
+
if (!parsedArgs) {
|
|
519
|
+
return {
|
|
520
|
+
content: [
|
|
521
|
+
{
|
|
522
|
+
type: "text",
|
|
523
|
+
text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').
|
|
524
|
+
|
|
525
|
+
Use \`prompts_list\` to list available prompts.`
|
|
526
|
+
}
|
|
527
|
+
],
|
|
528
|
+
isError: true
|
|
529
|
+
};
|
|
363
530
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
}
|
|
371
|
-
promptNames.add(p.slug);
|
|
372
|
-
});
|
|
373
|
-
if (duplicates.length > 0) {
|
|
374
|
-
console.error(
|
|
375
|
-
`[MCP] WARNING: Duplicate prompt slugs detected: ${duplicates.join(", ")}. Only the first occurrence will be registered.`
|
|
531
|
+
const { slug: promptSlug } = parsedArgs;
|
|
532
|
+
try {
|
|
533
|
+
const result = await fetchAndExecuteAccountScopedPrompt(
|
|
534
|
+
promptSlug,
|
|
535
|
+
config,
|
|
536
|
+
convexClient
|
|
376
537
|
);
|
|
377
|
-
|
|
378
|
-
const server = new Server(
|
|
379
|
-
{
|
|
380
|
-
name: "ppm-mcp-server",
|
|
381
|
-
version: "3.1.0"
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
capabilities: {
|
|
385
|
-
prompts: {},
|
|
386
|
-
tools: {}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
);
|
|
390
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
391
|
-
const ticketPromptSchemas = dynamicTicketTools.map((tt) => ({
|
|
392
|
-
name: tt.name,
|
|
393
|
-
description: tt.slashDescription
|
|
394
|
-
}));
|
|
395
|
-
const memoryPromptSchemas = dynamicMemoryTools.map((mt) => ({
|
|
396
|
-
name: mt.name,
|
|
397
|
-
description: mt.slashDescription
|
|
398
|
-
}));
|
|
399
|
-
const promptToolSchemas = globalPromptTools.map((st) => ({
|
|
400
|
-
name: st.name,
|
|
401
|
-
description: st.slashDescription
|
|
402
|
-
}));
|
|
538
|
+
const promptText = result.messages.map((msg) => msg.content.text).join("\n\n");
|
|
403
539
|
return {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
...memoryPromptSchemas
|
|
410
|
-
// memories_* (token-scoped)
|
|
540
|
+
content: [
|
|
541
|
+
{
|
|
542
|
+
type: "text",
|
|
543
|
+
text: promptText
|
|
544
|
+
}
|
|
411
545
|
]
|
|
412
546
|
};
|
|
413
|
-
})
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (systemTool) {
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (error instanceof AmbiguousPromptError) {
|
|
549
|
+
const suggestionsList = error.suggestions.map((s) => ` \u2022 ${s}`).join("\n");
|
|
550
|
+
console.error(`[MCP] prompts_run ambiguous match:`, error.suggestions);
|
|
418
551
|
return {
|
|
419
|
-
|
|
420
|
-
messages: [
|
|
552
|
+
content: [
|
|
421
553
|
{
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
554
|
+
type: "text",
|
|
555
|
+
text: `Multiple prompts match "${promptSlug}". Please specify one of:
|
|
556
|
+
|
|
557
|
+
${suggestionsList}
|
|
558
|
+
|
|
559
|
+
Example: \`prompts_run ${error.suggestions[0]}\``
|
|
427
560
|
}
|
|
428
|
-
]
|
|
561
|
+
],
|
|
562
|
+
isError: true
|
|
429
563
|
};
|
|
430
564
|
}
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
565
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
566
|
+
console.error(`[MCP] prompts_run error:`, error);
|
|
567
|
+
return {
|
|
568
|
+
content: [
|
|
569
|
+
{
|
|
570
|
+
type: "text",
|
|
571
|
+
text: `Error executing prompt "${promptSlug}": ${errorMessage}`
|
|
572
|
+
}
|
|
573
|
+
],
|
|
574
|
+
isError: true
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
async function handlePromptsList(args, _config, _convexClient, accountScopedPrompts) {
|
|
579
|
+
const { search: searchTerm } = parsePromptsListArgs(args);
|
|
580
|
+
let filteredPrompts = [...accountScopedPrompts];
|
|
581
|
+
if (searchTerm) {
|
|
582
|
+
const lowerSearch = searchTerm.toLowerCase();
|
|
583
|
+
filteredPrompts = filteredPrompts.filter(
|
|
584
|
+
(p) => p.slug.toLowerCase().includes(lowerSearch) || p.description?.toLowerCase().includes(lowerSearch)
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
filteredPrompts.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
588
|
+
const userPromptList = filteredPrompts.map((p) => {
|
|
589
|
+
const desc = p.description || "No description";
|
|
590
|
+
return `\u2022 ${p.slug}
|
|
591
|
+
Description: ${desc}`;
|
|
592
|
+
}).join("\n\n");
|
|
593
|
+
const summary = searchTerm ? `Found ${filteredPrompts.length} prompt(s) matching "${searchTerm}":` : `Available prompts (${filteredPrompts.length} total):`;
|
|
594
|
+
return {
|
|
595
|
+
content: [
|
|
596
|
+
{
|
|
597
|
+
type: "text",
|
|
598
|
+
text: userPromptList ? `${summary}
|
|
440
599
|
|
|
441
|
-
|
|
600
|
+
${userPromptList}` : `${summary}
|
|
442
601
|
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
|
|
602
|
+
No prompts found.`
|
|
603
|
+
}
|
|
604
|
+
]
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
async function handlePromptsUpdate(args, config, convexClient) {
|
|
608
|
+
const parsedArgs = parsePromptsUpdateArgs(args);
|
|
609
|
+
if (!parsedArgs) {
|
|
610
|
+
return {
|
|
611
|
+
content: [
|
|
612
|
+
{
|
|
613
|
+
type: "text",
|
|
614
|
+
text: `Error: Missing 'slug' parameter. Provide prompt slug to read or update.
|
|
447
615
|
|
|
448
|
-
|
|
616
|
+
Usage:
|
|
617
|
+
- Read mode: prompts_update { slug: "my-prompt" }
|
|
618
|
+
- Write mode: prompts_update { slug: "my-prompt", content: "new content..." }`
|
|
619
|
+
}
|
|
620
|
+
],
|
|
621
|
+
isError: true
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
const typedClient = convexClient;
|
|
626
|
+
const result = await typedClient.mutation(
|
|
627
|
+
"mcp_prompts:updateMcpPrompt",
|
|
628
|
+
{
|
|
629
|
+
...buildAuthArgs(config),
|
|
630
|
+
promptSlug: parsedArgs.slug,
|
|
631
|
+
content: parsedArgs.content
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
if (!result.success) {
|
|
635
|
+
const errorResult = result;
|
|
636
|
+
if (errorResult.suggestions) {
|
|
637
|
+
const suggestionsList = errorResult.suggestions.map((s) => ` \u2022 ${s}`).join("\n");
|
|
638
|
+
return {
|
|
639
|
+
content: [
|
|
640
|
+
{
|
|
641
|
+
type: "text",
|
|
642
|
+
text: `${errorResult.error}
|
|
449
643
|
|
|
450
|
-
|
|
644
|
+
Did you mean one of these?
|
|
645
|
+
${suggestionsList}`
|
|
646
|
+
}
|
|
647
|
+
],
|
|
648
|
+
isError: true
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
content: [{ type: "text", text: errorResult.error }],
|
|
653
|
+
isError: true
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
if (result.mode === "read") {
|
|
657
|
+
const readResult = result;
|
|
658
|
+
const matchNote = readResult.matchType !== "exact" ? `
|
|
659
|
+
_Note: Matched via ${readResult.matchType} match_` : "";
|
|
660
|
+
const aiTagNotice = readResult.hasAiTags ? `
|
|
451
661
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
description = ticketTool.description;
|
|
455
|
-
promptContent = `Create a new ticket in the "${ticketTool.projectSlug}" project queue.
|
|
662
|
+
## \u26A1 AI Tags Detected
|
|
663
|
+
This ${readResult.resourceType} contains AI tags that need processing: **${readResult.aiTags?.join(", ")}**
|
|
456
664
|
|
|
457
|
-
|
|
458
|
-
|
|
665
|
+
Process these tags by reading the instructions in each tag, generating appropriate content, and saving the result with the content parameter.` : "";
|
|
666
|
+
const versionInfo = readResult.version !== void 0 ? ` (v${readResult.version})` : "";
|
|
667
|
+
const resourceLabel = readResult.resourceType.charAt(0).toUpperCase() + readResult.resourceType.slice(1);
|
|
668
|
+
const contextSection = readResult.context ? `
|
|
459
669
|
|
|
460
|
-
##
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
670
|
+
## Available Resources
|
|
671
|
+
\`\`\`json
|
|
672
|
+
${JSON.stringify(readResult.context, null, 2)}
|
|
673
|
+
\`\`\`` : "";
|
|
674
|
+
return {
|
|
675
|
+
content: [
|
|
676
|
+
{
|
|
677
|
+
type: "text",
|
|
678
|
+
text: `# ${resourceLabel}: ${readResult.slug}${versionInfo}${matchNote}${aiTagNotice}
|
|
465
679
|
|
|
466
|
-
##
|
|
467
|
-
|
|
468
|
-
[Clear descriptive title - this becomes the slug]
|
|
680
|
+
## Description
|
|
681
|
+
${readResult.description || "No description"}
|
|
469
682
|
|
|
470
|
-
|
|
683
|
+
## Content
|
|
471
684
|
\`\`\`
|
|
685
|
+
${readResult.content}
|
|
686
|
+
\`\`\`${contextSection}
|
|
472
687
|
|
|
473
|
-
|
|
474
|
-
- "create a ticket from /path/to/plan.md" \u2192 Read file, use as ticket content
|
|
475
|
-
- "summarize our brainstorm into a ticket" \u2192 Extract key points from conversation
|
|
476
|
-
- "create a ticket for the auth bug we discussed" \u2192 Generate from session context
|
|
477
|
-
- "just the tasks from this file" \u2192 Extract only task items
|
|
478
|
-
|
|
479
|
-
Call the \`${ticketTool.name}\` tool with the final generated content.`;
|
|
480
|
-
} else if (ticketTool.type === "close") {
|
|
481
|
-
description = ticketTool.description;
|
|
482
|
-
promptContent = `Close a working ticket in the "${ticketTool.projectSlug}" project.
|
|
688
|
+
${readResult.editingGuidance}
|
|
483
689
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
description = ticketTool.description;
|
|
487
|
-
promptContent = `Use the \`${ticketTool.name}\` tool.`;
|
|
488
|
-
}
|
|
489
|
-
return {
|
|
490
|
-
description,
|
|
491
|
-
messages: [
|
|
492
|
-
{
|
|
493
|
-
role: "user",
|
|
494
|
-
content: {
|
|
495
|
-
type: "text",
|
|
496
|
-
text: promptContent
|
|
497
|
-
}
|
|
690
|
+
---
|
|
691
|
+
_To update this ${readResult.resourceType}, call prompts_update with content parameter._`
|
|
498
692
|
}
|
|
499
693
|
]
|
|
500
694
|
};
|
|
501
695
|
}
|
|
502
|
-
if (
|
|
696
|
+
if (result.mode === "write") {
|
|
697
|
+
const writeResult = result;
|
|
698
|
+
const resourceLabel = writeResult.resourceType.charAt(0).toUpperCase() + writeResult.resourceType.slice(1);
|
|
699
|
+
const versionInfo = writeResult.version !== void 0 ? `
|
|
700
|
+
Version: ${writeResult.version}` : "";
|
|
701
|
+
const updatedInfo = writeResult.updatedAt ? `
|
|
702
|
+
Updated: ${writeResult.updatedAt}` : "";
|
|
503
703
|
return {
|
|
504
|
-
|
|
505
|
-
messages: [
|
|
704
|
+
content: [
|
|
506
705
|
{
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
type: "text",
|
|
510
|
-
text: `List all available prompts.
|
|
706
|
+
type: "text",
|
|
707
|
+
text: `\u2705 ${writeResult.message}
|
|
511
708
|
|
|
512
|
-
|
|
513
|
-
}
|
|
709
|
+
${resourceLabel}: ${writeResult.slug}${versionInfo}${updatedInfo}`
|
|
514
710
|
}
|
|
515
711
|
]
|
|
516
712
|
};
|
|
517
713
|
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
714
|
+
return {
|
|
715
|
+
content: [{ type: "text", text: "Unknown response format" }],
|
|
716
|
+
isError: true
|
|
717
|
+
};
|
|
718
|
+
} catch (error) {
|
|
719
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
720
|
+
console.error(`[MCP] prompts_update error:`, error);
|
|
721
|
+
return {
|
|
722
|
+
content: [
|
|
723
|
+
{
|
|
724
|
+
type: "text",
|
|
725
|
+
text: `Error updating prompt: ${errorMessage}`
|
|
726
|
+
}
|
|
727
|
+
],
|
|
728
|
+
isError: true
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
}
|
|
526
732
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
733
|
+
// src/handlers/tickets.ts
|
|
734
|
+
async function handleTicketsWork(args, config, convexClient, projectSlug) {
|
|
735
|
+
const { ticketSlug, agent } = parseWorkArgs(args);
|
|
736
|
+
const resolvedAgent = resolveAgent(agent);
|
|
737
|
+
const typedClient = convexClient;
|
|
738
|
+
try {
|
|
739
|
+
const result = await typedClient.mutation(
|
|
740
|
+
"mcp_tickets:workMcpTicket",
|
|
741
|
+
{
|
|
742
|
+
...buildAuthArgs(config),
|
|
743
|
+
projectSlug,
|
|
744
|
+
ticketSlug,
|
|
745
|
+
agentId: resolvedAgent
|
|
746
|
+
}
|
|
747
|
+
);
|
|
748
|
+
if (!result) {
|
|
749
|
+
const agentContext = resolvedAgent ? ` for agent "${resolvedAgent}"` : "";
|
|
750
|
+
const message = ticketSlug ? `Ticket "${ticketSlug}" not found or not in open/working status in project "${projectSlug}"${agentContext}.` : `No open tickets${agentContext} in project "${projectSlug}".`;
|
|
751
|
+
return {
|
|
752
|
+
content: [{ type: "text", text: message }]
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
if (isAccessDenied(result)) {
|
|
756
|
+
return {
|
|
757
|
+
content: [{ type: "text", text: `Access denied: ${result.error}` }],
|
|
758
|
+
isError: true
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const workResult = result;
|
|
762
|
+
const startedInfo = workResult.startedAt ? `
|
|
763
|
+
Started: ${new Date(workResult.startedAt).toISOString()}` : "";
|
|
764
|
+
const pausedNotice = workResult.pausedAt ? `
|
|
531
765
|
|
|
532
|
-
##
|
|
533
|
-
|
|
766
|
+
**\u26A0\uFE0F PAUSED TICKET HANDOFF**: This ticket was paused on ${new Date(workResult.pausedAt).toISOString()}. A previous agent checkpointed their progress. Review the checkpoint content below (look for "## Progress", "## Context", "## Remaining", "## Blockers" sections) and resume where they left off.
|
|
767
|
+
` : "";
|
|
768
|
+
const statusNote = workResult.wasOpened ? `_Ticket moved to working status. ${workResult.remainingTickets} ticket(s) remaining in queue._` : `_Resuming work on this ticket. ${workResult.remainingTickets} ticket(s) in queue._`;
|
|
769
|
+
return {
|
|
770
|
+
content: [
|
|
771
|
+
{
|
|
772
|
+
type: "text",
|
|
773
|
+
text: `# Ticket: ${workResult.slug} [WORKING]${startedInfo}${pausedNotice}
|
|
534
774
|
|
|
535
|
-
|
|
536
|
-
Use \`prompts_list\` to list all available prompts.
|
|
775
|
+
${workResult.content}
|
|
537
776
|
|
|
538
|
-
|
|
539
|
-
|
|
777
|
+
---
|
|
778
|
+
${statusNote}`
|
|
779
|
+
}
|
|
780
|
+
]
|
|
781
|
+
};
|
|
782
|
+
} catch (error) {
|
|
783
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
784
|
+
console.error(`[MCP] tickets_work error:`, error);
|
|
785
|
+
return {
|
|
786
|
+
content: [{ type: "text", text: `Error getting work: ${errorMessage}` }],
|
|
787
|
+
isError: true
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async function handleTicketsCreate(args, config, convexClient, projectSlug) {
|
|
792
|
+
const parsedArgs = parseCreateArgs(args);
|
|
793
|
+
if (!parsedArgs) {
|
|
794
|
+
return {
|
|
795
|
+
content: [
|
|
796
|
+
{
|
|
797
|
+
type: "text",
|
|
798
|
+
text: `Error: Missing content parameter. Provide the ticket content (first line becomes the slug).`
|
|
799
|
+
}
|
|
800
|
+
],
|
|
801
|
+
isError: true
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
const { content, agent } = parsedArgs;
|
|
805
|
+
const resolvedAgent = resolveAgent(agent);
|
|
806
|
+
const typedClient = convexClient;
|
|
807
|
+
try {
|
|
808
|
+
const result = await typedClient.mutation(
|
|
809
|
+
"mcp_tickets:createMcpTicket",
|
|
810
|
+
{
|
|
811
|
+
...buildAuthArgs(config),
|
|
812
|
+
projectSlug,
|
|
813
|
+
content,
|
|
814
|
+
agentId: resolvedAgent
|
|
815
|
+
}
|
|
816
|
+
);
|
|
817
|
+
const agentInfo = result.agentId ? `
|
|
818
|
+
Assigned to: ${result.agentId}` : "";
|
|
819
|
+
return {
|
|
820
|
+
content: [
|
|
821
|
+
{
|
|
822
|
+
type: "text",
|
|
823
|
+
text: `\u2705 Created ticket [${result.slug}] in backlog for project "${projectSlug}".${agentInfo}
|
|
824
|
+
|
|
825
|
+
Position: #${result.position} of ${result.totalBacklog} backlog tickets
|
|
826
|
+
Preview: ${result.preview}
|
|
827
|
+
|
|
828
|
+
_Ticket created in backlog. Use \`tickets_work ${result.slug}\` to move it to working status._`
|
|
829
|
+
}
|
|
830
|
+
]
|
|
831
|
+
};
|
|
832
|
+
} catch (error) {
|
|
833
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
834
|
+
console.error(`[MCP] tickets_create error:`, error);
|
|
835
|
+
return {
|
|
836
|
+
content: [{ type: "text", text: `Error creating ticket: ${errorMessage}` }],
|
|
837
|
+
isError: true
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
async function handleTicketsClose(args, config, convexClient, projectSlug) {
|
|
842
|
+
const parsedArgs = parseCloseArgs(args);
|
|
843
|
+
if (!parsedArgs) {
|
|
844
|
+
return {
|
|
845
|
+
content: [
|
|
846
|
+
{
|
|
847
|
+
type: "text",
|
|
848
|
+
text: `Error: Missing ticketSlug parameter. Usage: Provide a ticket number (e.g., "102") or full slug (e.g., "102-fix-auth").`
|
|
849
|
+
}
|
|
850
|
+
],
|
|
851
|
+
isError: true
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
const { ticketSlug, metadata } = parsedArgs;
|
|
855
|
+
const typedClient = convexClient;
|
|
856
|
+
try {
|
|
857
|
+
const result = await typedClient.mutation(
|
|
858
|
+
"mcp_tickets:closeMcpTicket",
|
|
859
|
+
{
|
|
860
|
+
...buildAuthArgs(config),
|
|
861
|
+
projectSlug,
|
|
862
|
+
ticketSlug,
|
|
863
|
+
metadata
|
|
864
|
+
}
|
|
865
|
+
);
|
|
866
|
+
let responseText = `\u2705 Ticket [${result.slug}] closed in project "${projectSlug}".
|
|
867
|
+
|
|
868
|
+
Closed at: ${new Date(result.closedAt).toISOString()}`;
|
|
869
|
+
if (result.metadata && Object.keys(result.metadata).length > 0) {
|
|
870
|
+
responseText += `
|
|
540
871
|
|
|
541
|
-
|
|
872
|
+
**Metadata saved:**`;
|
|
873
|
+
for (const [key, value] of Object.entries(result.metadata)) {
|
|
874
|
+
responseText += `
|
|
875
|
+
- ${key}: ${JSON.stringify(value)}`;
|
|
542
876
|
}
|
|
877
|
+
}
|
|
878
|
+
responseText += `
|
|
879
|
+
|
|
880
|
+
_Reminder: Ensure all embedded \`[RUN_PROMPT ...]\` directives were executed before closing._`;
|
|
881
|
+
return {
|
|
882
|
+
content: [{ type: "text", text: responseText }]
|
|
883
|
+
};
|
|
884
|
+
} catch (error) {
|
|
885
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
886
|
+
console.error(`[MCP] tickets_close error:`, error);
|
|
887
|
+
return {
|
|
888
|
+
content: [{ type: "text", text: `Error closing ticket: ${errorMessage}` }],
|
|
889
|
+
isError: true
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
async function handleTicketsPause(args, config, convexClient, projectSlug) {
|
|
894
|
+
const parsedArgs = parsePauseArgs(args);
|
|
895
|
+
if (!parsedArgs) {
|
|
896
|
+
const rawArgs = args;
|
|
897
|
+
const hasTicketSlug = typeof rawArgs?.ticketSlug === "string" && rawArgs.ticketSlug;
|
|
898
|
+
const hasContent = typeof rawArgs?.content === "string" && rawArgs.content;
|
|
899
|
+
if (!hasTicketSlug) {
|
|
543
900
|
return {
|
|
544
|
-
|
|
545
|
-
messages: [
|
|
901
|
+
content: [
|
|
546
902
|
{
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
type: "text",
|
|
550
|
-
text: promptContent
|
|
551
|
-
}
|
|
903
|
+
type: "text",
|
|
904
|
+
text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., "102") or full slug.`
|
|
552
905
|
}
|
|
553
|
-
]
|
|
906
|
+
],
|
|
907
|
+
isError: true
|
|
554
908
|
};
|
|
555
909
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
name: st.name,
|
|
563
|
-
description: st.description,
|
|
564
|
-
inputSchema: st.inputSchema
|
|
565
|
-
})),
|
|
566
|
-
// Dynamic ticket tools per project (Ticket 135, 149, 151, 153, unified work)
|
|
567
|
-
...dynamicTicketTools.map((tt) => {
|
|
568
|
-
let inputSchema;
|
|
569
|
-
if (tt.type === "close") {
|
|
570
|
-
inputSchema = {
|
|
571
|
-
type: "object",
|
|
572
|
-
properties: {
|
|
573
|
-
ticketSlug: {
|
|
574
|
-
type: "string",
|
|
575
|
-
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
576
|
-
},
|
|
577
|
-
metadata: {
|
|
578
|
-
type: "object",
|
|
579
|
-
description: "Optional: Key-value metadata to store with the closed ticket (e.g., evaluation scores, completion notes).",
|
|
580
|
-
additionalProperties: {
|
|
581
|
-
oneOf: [
|
|
582
|
-
{ type: "string" },
|
|
583
|
-
{ type: "number" },
|
|
584
|
-
{ type: "boolean" }
|
|
585
|
-
]
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
required: ["ticketSlug"]
|
|
590
|
-
};
|
|
591
|
-
} else if (tt.type === "pause") {
|
|
592
|
-
inputSchema = {
|
|
593
|
-
type: "object",
|
|
594
|
-
properties: {
|
|
595
|
-
ticketSlug: {
|
|
596
|
-
type: "string",
|
|
597
|
-
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
598
|
-
},
|
|
599
|
-
content: {
|
|
600
|
-
type: "string",
|
|
601
|
-
description: "Comprehensive checkpoint for handoff. Review the session and include: ## Progress (completed tasks with specifics, decisions made and why) ## Context (user preferences, edge cases discovered, key feedback received) ## Remaining (specific next steps, file:line references if applicable) ## Blockers (unresolved issues, open questions). This enables the next agent to continue seamlessly."
|
|
602
|
-
}
|
|
603
|
-
},
|
|
604
|
-
required: ["ticketSlug", "content"]
|
|
605
|
-
};
|
|
606
|
-
} else if (tt.type === "work") {
|
|
607
|
-
inputSchema = {
|
|
608
|
-
type: "object",
|
|
609
|
-
properties: {
|
|
610
|
-
ticketSlug: {
|
|
611
|
-
type: "string",
|
|
612
|
-
description: "Optional: Ticket number (e.g., '102') or full slug. If not provided, gets next ticket from open queue."
|
|
613
|
-
},
|
|
614
|
-
agent: {
|
|
615
|
-
type: "string",
|
|
616
|
-
description: "Optional: Agent slug to filter queue. Overrides PPM_AGENT_NAME env var. Omit for owner mode (no filtering)."
|
|
617
|
-
}
|
|
618
|
-
},
|
|
619
|
-
required: []
|
|
620
|
-
};
|
|
621
|
-
} else if (tt.type === "create") {
|
|
622
|
-
inputSchema = {
|
|
623
|
-
type: "object",
|
|
624
|
-
properties: {
|
|
625
|
-
content: {
|
|
626
|
-
type: "string",
|
|
627
|
-
description: "The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed."
|
|
628
|
-
},
|
|
629
|
-
agent: {
|
|
630
|
-
type: "string",
|
|
631
|
-
description: "Optional: Agent slug to assign ticket to. Overrides PPM_AGENT_NAME env var. Omit for unassigned."
|
|
632
|
-
}
|
|
633
|
-
},
|
|
634
|
-
required: ["content"]
|
|
635
|
-
};
|
|
636
|
-
} else if (tt.type === "search") {
|
|
637
|
-
inputSchema = {
|
|
638
|
-
type: "object",
|
|
639
|
-
properties: {
|
|
640
|
-
query: {
|
|
641
|
-
type: "string",
|
|
642
|
-
description: "Search query (min 3 characters)"
|
|
643
|
-
},
|
|
644
|
-
agent: {
|
|
645
|
-
type: "string",
|
|
646
|
-
description: "Optional: Agent slug to filter results. Overrides PPM_AGENT_NAME env var. Omit for all tickets."
|
|
647
|
-
}
|
|
648
|
-
},
|
|
649
|
-
required: ["query"]
|
|
650
|
-
};
|
|
651
|
-
} else if (tt.type === "get") {
|
|
652
|
-
inputSchema = {
|
|
653
|
-
type: "object",
|
|
654
|
-
properties: {
|
|
655
|
-
ticketSlug: {
|
|
656
|
-
type: "string",
|
|
657
|
-
description: "Ticket number (e.g., '102') or full slug"
|
|
658
|
-
}
|
|
659
|
-
},
|
|
660
|
-
required: ["ticketSlug"]
|
|
661
|
-
};
|
|
662
|
-
} else if (tt.type === "list") {
|
|
663
|
-
inputSchema = {
|
|
664
|
-
type: "object",
|
|
665
|
-
properties: {
|
|
666
|
-
agent: {
|
|
667
|
-
type: "string",
|
|
668
|
-
description: "Optional: Agent slug to filter tickets. Overrides PPM_AGENT_NAME env var. Omit for all tickets."
|
|
669
|
-
}
|
|
670
|
-
},
|
|
671
|
-
required: []
|
|
672
|
-
};
|
|
673
|
-
} else if (tt.type === "update") {
|
|
674
|
-
inputSchema = {
|
|
675
|
-
type: "object",
|
|
676
|
-
properties: {
|
|
677
|
-
ticketSlug: {
|
|
678
|
-
type: "string",
|
|
679
|
-
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
680
|
-
},
|
|
681
|
-
content: {
|
|
682
|
-
type: "string",
|
|
683
|
-
description: "Update content to append to the ticket (markdown supported)"
|
|
684
|
-
},
|
|
685
|
-
agent: {
|
|
686
|
-
type: "string",
|
|
687
|
-
description: "Optional: Reassign ticket to this agent. Use empty string '' to unassign."
|
|
688
|
-
}
|
|
689
|
-
},
|
|
690
|
-
required: ["ticketSlug", "content"]
|
|
691
|
-
};
|
|
692
|
-
} else {
|
|
693
|
-
inputSchema = {
|
|
694
|
-
type: "object",
|
|
695
|
-
properties: {},
|
|
696
|
-
required: []
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
return {
|
|
700
|
-
name: tt.name,
|
|
701
|
-
description: tt.description,
|
|
702
|
-
inputSchema
|
|
703
|
-
};
|
|
704
|
-
}),
|
|
705
|
-
// Global prompts_run tool
|
|
706
|
-
{
|
|
707
|
-
name: "prompts_run",
|
|
708
|
-
description: "Execute a prompt by slug. Use prompts_list to list available prompts.",
|
|
709
|
-
inputSchema: {
|
|
710
|
-
type: "object",
|
|
711
|
-
properties: {
|
|
712
|
-
slug: {
|
|
713
|
-
type: "string",
|
|
714
|
-
description: "Prompt slug to execute (e.g., 'code-review', 'plan')"
|
|
715
|
-
}
|
|
716
|
-
},
|
|
717
|
-
required: ["slug"]
|
|
718
|
-
}
|
|
719
|
-
},
|
|
720
|
-
// Global prompts_list tool
|
|
721
|
-
{
|
|
722
|
-
name: "prompts_list",
|
|
723
|
-
description: "List all available prompts",
|
|
724
|
-
inputSchema: {
|
|
725
|
-
type: "object",
|
|
726
|
-
properties: {
|
|
727
|
-
search: {
|
|
728
|
-
type: "string",
|
|
729
|
-
description: "Optional search term to filter prompts by name or description (case-insensitive)"
|
|
730
|
-
}
|
|
910
|
+
if (!hasContent) {
|
|
911
|
+
return {
|
|
912
|
+
content: [
|
|
913
|
+
{
|
|
914
|
+
type: "text",
|
|
915
|
+
text: `Error: Missing content parameter. Describe what's done, what remains, and any blockers.`
|
|
731
916
|
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
917
|
+
],
|
|
918
|
+
isError: true
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
return {
|
|
922
|
+
content: [{ type: "text", text: `Error: Missing required parameters.` }],
|
|
923
|
+
isError: true
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
const { ticketSlug, content } = parsedArgs;
|
|
927
|
+
const typedClient = convexClient;
|
|
928
|
+
try {
|
|
929
|
+
const result = await typedClient.mutation(
|
|
930
|
+
"mcp_tickets:pauseMcpTicket",
|
|
735
931
|
{
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
properties: {
|
|
741
|
-
slug: {
|
|
742
|
-
type: "string",
|
|
743
|
-
description: "Resource slug to read or update. Searches prompts first, then snippets, then templates. Supports fuzzy matching."
|
|
744
|
-
},
|
|
745
|
-
content: {
|
|
746
|
-
type: "string",
|
|
747
|
-
description: "New resource content (omit for read-only mode)"
|
|
748
|
-
}
|
|
749
|
-
},
|
|
750
|
-
required: ["slug"]
|
|
751
|
-
}
|
|
752
|
-
},
|
|
753
|
-
// Dynamic memory tools (implement-memory-system)
|
|
754
|
-
...dynamicMemoryTools.map((mt) => {
|
|
755
|
-
let inputSchema;
|
|
756
|
-
if (mt.type === "load") {
|
|
757
|
-
inputSchema = {
|
|
758
|
-
type: "object",
|
|
759
|
-
properties: {
|
|
760
|
-
query: {
|
|
761
|
-
type: "string",
|
|
762
|
-
description: "Optional: search query to filter agent memories. If not provided, returns recent memories."
|
|
763
|
-
}
|
|
764
|
-
},
|
|
765
|
-
required: []
|
|
766
|
-
};
|
|
767
|
-
} else if (mt.type === "load_extreme") {
|
|
768
|
-
inputSchema = {
|
|
769
|
-
type: "object",
|
|
770
|
-
properties: {
|
|
771
|
-
query: {
|
|
772
|
-
type: "string",
|
|
773
|
-
description: "Optional: search query to filter agent memories. If not provided, returns recent memories."
|
|
774
|
-
}
|
|
775
|
-
},
|
|
776
|
-
required: []
|
|
777
|
-
};
|
|
778
|
-
} else if (mt.type === "add") {
|
|
779
|
-
inputSchema = {
|
|
780
|
-
type: "object",
|
|
781
|
-
properties: {
|
|
782
|
-
content: {
|
|
783
|
-
type: "string",
|
|
784
|
-
description: "Memory content to record. Be concise and focused on key decisions, patterns, or context."
|
|
785
|
-
}
|
|
786
|
-
},
|
|
787
|
-
required: ["content"]
|
|
788
|
-
};
|
|
789
|
-
} else if (mt.type === "list_all") {
|
|
790
|
-
inputSchema = {
|
|
791
|
-
type: "object",
|
|
792
|
-
properties: {
|
|
793
|
-
typeFilter: {
|
|
794
|
-
type: "string",
|
|
795
|
-
enum: ["all", "user", "agent"],
|
|
796
|
-
description: "Optional: Filter by memory type. 'user' = rules, 'agent' = history. Default: 'all'"
|
|
797
|
-
}
|
|
798
|
-
},
|
|
799
|
-
required: []
|
|
800
|
-
};
|
|
801
|
-
} else {
|
|
802
|
-
inputSchema = {
|
|
803
|
-
type: "object",
|
|
804
|
-
properties: {},
|
|
805
|
-
required: []
|
|
806
|
-
};
|
|
807
|
-
}
|
|
808
|
-
return {
|
|
809
|
-
name: mt.name,
|
|
810
|
-
description: mt.description,
|
|
811
|
-
inputSchema
|
|
812
|
-
};
|
|
813
|
-
}),
|
|
814
|
-
// Dynamic per-prompt tools (each prompt as its own global tool)
|
|
815
|
-
...dynamicPromptTools.map((pt) => ({
|
|
816
|
-
name: pt.name,
|
|
817
|
-
description: pt.description,
|
|
818
|
-
inputSchema: {
|
|
819
|
-
type: "object",
|
|
820
|
-
properties: {},
|
|
821
|
-
required: []
|
|
822
|
-
}
|
|
823
|
-
}))
|
|
824
|
-
];
|
|
825
|
-
return { tools };
|
|
826
|
-
});
|
|
827
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
828
|
-
const toolName = request.params.name;
|
|
829
|
-
if (toolName === "prompts_run") {
|
|
830
|
-
const parsedArgs = parsePromptsRunArgs(request.params.arguments);
|
|
831
|
-
if (!parsedArgs) {
|
|
832
|
-
return {
|
|
833
|
-
content: [
|
|
834
|
-
{
|
|
835
|
-
type: "text",
|
|
836
|
-
text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').
|
|
837
|
-
|
|
838
|
-
Use \`prompts_list\` to list available prompts.`
|
|
839
|
-
}
|
|
840
|
-
],
|
|
841
|
-
isError: true
|
|
842
|
-
};
|
|
932
|
+
...buildAuthArgs(config),
|
|
933
|
+
projectSlug,
|
|
934
|
+
ticketSlug,
|
|
935
|
+
content
|
|
843
936
|
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
);
|
|
851
|
-
const promptText = result.messages.map((msg) => msg.content.text).join("\n\n");
|
|
852
|
-
return {
|
|
853
|
-
content: [
|
|
854
|
-
{
|
|
855
|
-
type: "text",
|
|
856
|
-
text: promptText
|
|
857
|
-
}
|
|
858
|
-
]
|
|
859
|
-
};
|
|
860
|
-
} catch (error) {
|
|
861
|
-
if (error instanceof AmbiguousPromptError) {
|
|
862
|
-
const suggestionsList = error.suggestions.map((s) => ` \u2022 ${s}`).join("\n");
|
|
863
|
-
console.error(`[MCP] ${toolName} ambiguous match:`, error.suggestions);
|
|
864
|
-
return {
|
|
865
|
-
content: [
|
|
866
|
-
{
|
|
867
|
-
type: "text",
|
|
868
|
-
text: `Multiple prompts match "${promptSlug}". Please specify one of:
|
|
937
|
+
);
|
|
938
|
+
return {
|
|
939
|
+
content: [
|
|
940
|
+
{
|
|
941
|
+
type: "text",
|
|
942
|
+
text: `\u2705 Ticket [${result.slug}] paused and returned to open queue in project "${projectSlug}".
|
|
869
943
|
|
|
870
|
-
${
|
|
944
|
+
Paused at: ${new Date(result.pausedAt).toISOString()}
|
|
871
945
|
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
],
|
|
875
|
-
isError: true
|
|
876
|
-
};
|
|
946
|
+
_Ticket moved from working \u2192 open. Checkpoint content has been appended. Any agent can pick it up with \`tickets_work\`._`
|
|
877
947
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
948
|
+
]
|
|
949
|
+
};
|
|
950
|
+
} catch (error) {
|
|
951
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
952
|
+
console.error(`[MCP] tickets_pause error:`, error);
|
|
953
|
+
return {
|
|
954
|
+
content: [{ type: "text", text: `Error pausing ticket: ${errorMessage}` }],
|
|
955
|
+
isError: true
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
async function handleTicketsSearch(args, config, convexClient, projectSlug) {
|
|
960
|
+
const parsedArgs = parseSearchArgs(args);
|
|
961
|
+
if (!parsedArgs) {
|
|
962
|
+
return {
|
|
963
|
+
content: [{ type: "text", text: `Error: Search query must be at least 3 characters` }],
|
|
964
|
+
isError: true
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
const { query, agent } = parsedArgs;
|
|
968
|
+
const resolvedAgent = resolveAgent(agent);
|
|
969
|
+
const typedClient = convexClient;
|
|
970
|
+
try {
|
|
971
|
+
const result = await typedClient.query("mcp_tickets:searchMcpTickets", {
|
|
972
|
+
...buildAuthArgs(config),
|
|
973
|
+
projectSlug,
|
|
974
|
+
query,
|
|
975
|
+
agentId: resolvedAgent
|
|
976
|
+
});
|
|
977
|
+
const agentContext = resolvedAgent ? ` for agent "${resolvedAgent}"` : "";
|
|
978
|
+
if (result.length === 0) {
|
|
907
979
|
return {
|
|
908
980
|
content: [
|
|
909
981
|
{
|
|
910
982
|
type: "text",
|
|
911
|
-
text:
|
|
912
|
-
|
|
913
|
-
${userPromptList}` : `${summary}
|
|
914
|
-
|
|
915
|
-
No prompts found.`
|
|
983
|
+
text: `No tickets found matching "${query}"${agentContext} in project "${projectSlug}".`
|
|
916
984
|
}
|
|
917
985
|
]
|
|
918
986
|
};
|
|
919
987
|
}
|
|
920
|
-
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
988
|
+
const formattedList = result.map((t) => {
|
|
989
|
+
const statusBadge = t.status === "open" ? "\u{1F7E2}" : t.status === "working" ? "\u{1F7E1}" : t.status === "closed" ? "\u26AB" : "\u26AA";
|
|
990
|
+
const num = t.ticketNumber ? `#${t.ticketNumber}` : "";
|
|
991
|
+
const agentBadge = t.agentId ? ` [${t.agentId}]` : "";
|
|
992
|
+
const metadataHint = t.metadata && Object.keys(t.metadata).length > 0 ? ` {${Object.keys(t.metadata).length} meta}` : "";
|
|
993
|
+
return `${statusBadge} ${num} ${t.slug}${agentBadge}${metadataHint}
|
|
994
|
+
${t.matchSnippet}`;
|
|
995
|
+
}).join("\n\n");
|
|
996
|
+
return {
|
|
997
|
+
content: [
|
|
998
|
+
{
|
|
999
|
+
type: "text",
|
|
1000
|
+
text: `Found ${result.length} ticket(s) matching "${query}"${agentContext}:
|
|
928
1001
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1002
|
+
${formattedList}`
|
|
1003
|
+
}
|
|
1004
|
+
]
|
|
1005
|
+
};
|
|
1006
|
+
} catch (error) {
|
|
1007
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1008
|
+
console.error(`[MCP] tickets_search error:`, error);
|
|
1009
|
+
return {
|
|
1010
|
+
content: [{ type: "text", text: `Error searching tickets: ${errorMessage}` }],
|
|
1011
|
+
isError: true
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
async function handleTicketsGet(args, config, convexClient, projectSlug) {
|
|
1016
|
+
const parsedArgs = parseGetArgs(args);
|
|
1017
|
+
if (!parsedArgs) {
|
|
1018
|
+
return {
|
|
1019
|
+
content: [
|
|
1020
|
+
{
|
|
1021
|
+
type: "text",
|
|
1022
|
+
text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., "102") or full slug.`
|
|
1023
|
+
}
|
|
1024
|
+
],
|
|
1025
|
+
isError: true
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
const { ticketSlug } = parsedArgs;
|
|
1029
|
+
const typedClient = convexClient;
|
|
1030
|
+
try {
|
|
1031
|
+
const result = await typedClient.query(
|
|
1032
|
+
"mcp_tickets:getMcpTicket",
|
|
1033
|
+
{
|
|
1034
|
+
...buildAuthArgs(config),
|
|
1035
|
+
projectSlug,
|
|
1036
|
+
ticketSlug
|
|
936
1037
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1038
|
+
);
|
|
1039
|
+
if (!result) {
|
|
1040
|
+
return {
|
|
1041
|
+
content: [
|
|
940
1042
|
{
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
content: parsedArgs.content
|
|
944
|
-
}
|
|
945
|
-
);
|
|
946
|
-
if (!result.success) {
|
|
947
|
-
const errorResult = result;
|
|
948
|
-
if (errorResult.suggestions) {
|
|
949
|
-
const suggestionsList = errorResult.suggestions.map((s) => ` \u2022 ${s}`).join("\n");
|
|
950
|
-
return {
|
|
951
|
-
content: [
|
|
952
|
-
{
|
|
953
|
-
type: "text",
|
|
954
|
-
text: `${errorResult.error}
|
|
955
|
-
|
|
956
|
-
Did you mean one of these?
|
|
957
|
-
${suggestionsList}`
|
|
958
|
-
}
|
|
959
|
-
],
|
|
960
|
-
isError: true
|
|
961
|
-
};
|
|
1043
|
+
type: "text",
|
|
1044
|
+
text: `Ticket "${ticketSlug}" not found in project "${projectSlug}".`
|
|
962
1045
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
## Description
|
|
993
|
-
${readResult.description || "No description"}
|
|
994
|
-
|
|
995
|
-
## Content
|
|
996
|
-
\`\`\`
|
|
997
|
-
${readResult.content}
|
|
998
|
-
\`\`\`${contextSection}
|
|
1046
|
+
]
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
if (isAccessDenied(result)) {
|
|
1050
|
+
return {
|
|
1051
|
+
content: [{ type: "text", text: `Access denied: ${result.error}` }],
|
|
1052
|
+
isError: true
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1055
|
+
const getResult = result;
|
|
1056
|
+
const statusBadge = getResult.status.toUpperCase();
|
|
1057
|
+
const startedInfo = getResult.startedAt ? `
|
|
1058
|
+
Started: ${new Date(getResult.startedAt).toISOString()}` : "";
|
|
1059
|
+
const closedInfo = getResult.closedAt ? `
|
|
1060
|
+
Closed: ${new Date(getResult.closedAt).toISOString()}` : "";
|
|
1061
|
+
let metadataInfo = "";
|
|
1062
|
+
if (getResult.metadata && Object.keys(getResult.metadata).length > 0) {
|
|
1063
|
+
metadataInfo = "\n\n**Metadata:**";
|
|
1064
|
+
for (const [key, value] of Object.entries(getResult.metadata)) {
|
|
1065
|
+
metadataInfo += `
|
|
1066
|
+
- ${key}: ${JSON.stringify(value)}`;
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
return {
|
|
1070
|
+
content: [
|
|
1071
|
+
{
|
|
1072
|
+
type: "text",
|
|
1073
|
+
text: `# Ticket: ${getResult.slug} [${statusBadge}]${startedInfo}${closedInfo}
|
|
999
1074
|
|
|
1000
|
-
${
|
|
1075
|
+
${getResult.content}${metadataInfo}
|
|
1001
1076
|
|
|
1002
1077
|
---
|
|
1003
|
-
|
|
1004
|
-
}
|
|
1005
|
-
]
|
|
1006
|
-
};
|
|
1007
|
-
}
|
|
1008
|
-
if (result.mode === "write") {
|
|
1009
|
-
const writeResult = result;
|
|
1010
|
-
const resourceLabel = writeResult.resourceType.charAt(0).toUpperCase() + writeResult.resourceType.slice(1);
|
|
1011
|
-
const versionInfo = writeResult.version !== void 0 ? `
|
|
1012
|
-
Version: ${writeResult.version}` : "";
|
|
1013
|
-
const updatedInfo = writeResult.updatedAt ? `
|
|
1014
|
-
Updated: ${writeResult.updatedAt}` : "";
|
|
1015
|
-
return {
|
|
1016
|
-
content: [
|
|
1017
|
-
{
|
|
1018
|
-
type: "text",
|
|
1019
|
-
text: `\u2705 ${writeResult.message}
|
|
1020
|
-
|
|
1021
|
-
${resourceLabel}: ${writeResult.slug}${versionInfo}${updatedInfo}`
|
|
1022
|
-
}
|
|
1023
|
-
]
|
|
1024
|
-
};
|
|
1078
|
+
_Read-only inspection. Use tickets_work to start working on this ticket._`
|
|
1025
1079
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
projectSlug: ticketTool.projectSlug,
|
|
1055
|
-
ticketSlug,
|
|
1056
|
-
// Optional: specific ticket to work on
|
|
1057
|
-
agentId: resolvedAgent
|
|
1058
|
-
// Agent filtering (implement-agent-routing)
|
|
1059
|
-
}
|
|
1060
|
-
);
|
|
1061
|
-
if (!result) {
|
|
1062
|
-
const agentContext = resolvedAgent ? ` for agent "${resolvedAgent}"` : "";
|
|
1063
|
-
const message = ticketSlug ? `Ticket "${ticketSlug}" not found or not in open/working status in project "${ticketTool.projectSlug}"${agentContext}.` : `No open tickets${agentContext} in project "${ticketTool.projectSlug}".`;
|
|
1064
|
-
return {
|
|
1065
|
-
content: [
|
|
1066
|
-
{
|
|
1067
|
-
type: "text",
|
|
1068
|
-
text: message
|
|
1069
|
-
}
|
|
1070
|
-
]
|
|
1071
|
-
};
|
|
1072
|
-
}
|
|
1073
|
-
if (isAccessDenied(result)) {
|
|
1074
|
-
return {
|
|
1075
|
-
content: [
|
|
1076
|
-
{
|
|
1077
|
-
type: "text",
|
|
1078
|
-
text: `Access denied: ${result.error}`
|
|
1079
|
-
}
|
|
1080
|
-
],
|
|
1081
|
-
isError: true
|
|
1082
|
-
};
|
|
1080
|
+
]
|
|
1081
|
+
};
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1084
|
+
console.error(`[MCP] tickets_get error:`, error);
|
|
1085
|
+
return {
|
|
1086
|
+
content: [{ type: "text", text: `Error getting ticket: ${errorMessage}` }],
|
|
1087
|
+
isError: true
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async function handleTicketsList(args, config, convexClient, projectSlug) {
|
|
1092
|
+
const { agent } = parseListArgs(args);
|
|
1093
|
+
const resolvedAgent = resolveAgent(agent);
|
|
1094
|
+
const typedClient = convexClient;
|
|
1095
|
+
try {
|
|
1096
|
+
const result = await typedClient.query("mcp_tickets:listMcpTickets", {
|
|
1097
|
+
...buildAuthArgs(config),
|
|
1098
|
+
projectSlug,
|
|
1099
|
+
agentId: resolvedAgent
|
|
1100
|
+
});
|
|
1101
|
+
const agentContext = resolvedAgent ? ` for agent "${resolvedAgent}"` : "";
|
|
1102
|
+
if (result.length === 0) {
|
|
1103
|
+
return {
|
|
1104
|
+
content: [
|
|
1105
|
+
{
|
|
1106
|
+
type: "text",
|
|
1107
|
+
text: `No active tickets${agentContext} in project "${projectSlug}". All tickets are closed or archived.`
|
|
1083
1108
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1109
|
+
]
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
const openTickets = result.filter((t) => t.status === "open");
|
|
1113
|
+
const workingTickets = result.filter((t) => t.status === "working");
|
|
1114
|
+
const backlogTickets = result.filter((t) => t.status === "backlog");
|
|
1115
|
+
const formatTicketLine = (t) => {
|
|
1116
|
+
const num = t.ticketNumber ? `#${t.ticketNumber}` : "";
|
|
1117
|
+
const agentBadge = !resolvedAgent && t.agentId ? ` [${t.agentId}]` : "";
|
|
1118
|
+
return ` ${t.position}. ${num} ${t.slug}${agentBadge}
|
|
1119
|
+
${t.preview}`;
|
|
1120
|
+
};
|
|
1121
|
+
const sections = [];
|
|
1122
|
+
if (openTickets.length > 0) {
|
|
1123
|
+
sections.push(
|
|
1124
|
+
`**\u{1F7E2} Open (${openTickets.length})**
|
|
1125
|
+
${openTickets.map(formatTicketLine).join("\n")}`
|
|
1126
|
+
);
|
|
1127
|
+
}
|
|
1128
|
+
if (workingTickets.length > 0) {
|
|
1129
|
+
sections.push(
|
|
1130
|
+
`**\u{1F7E1} Working (${workingTickets.length})**
|
|
1131
|
+
${workingTickets.map(formatTicketLine).join("\n")}`
|
|
1132
|
+
);
|
|
1133
|
+
}
|
|
1134
|
+
if (backlogTickets.length > 0) {
|
|
1135
|
+
sections.push(
|
|
1136
|
+
`**\u26AA Backlog (${backlogTickets.length})**
|
|
1137
|
+
${backlogTickets.map(formatTicketLine).join("\n")}`
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
const headerSuffix = resolvedAgent ? ` (Agent: ${resolvedAgent})` : "";
|
|
1141
|
+
return {
|
|
1142
|
+
content: [
|
|
1143
|
+
{
|
|
1144
|
+
type: "text",
|
|
1145
|
+
text: `# Active Queue: ${projectSlug}${headerSuffix}
|
|
1097
1146
|
|
|
1098
|
-
${
|
|
1147
|
+
${result.length} ticket(s) in queue
|
|
1099
1148
|
|
|
1100
|
-
|
|
1101
|
-
${statusNote}`
|
|
1102
|
-
}
|
|
1103
|
-
]
|
|
1104
|
-
};
|
|
1105
|
-
} catch (error) {
|
|
1106
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1107
|
-
console.error(`[MCP] tickets_work error:`, error);
|
|
1108
|
-
return {
|
|
1109
|
-
content: [
|
|
1110
|
-
{
|
|
1111
|
-
type: "text",
|
|
1112
|
-
text: `Error getting work: ${errorMessage}`
|
|
1113
|
-
}
|
|
1114
|
-
],
|
|
1115
|
-
isError: true
|
|
1116
|
-
};
|
|
1149
|
+
${sections.join("\n\n")}`
|
|
1117
1150
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1151
|
+
]
|
|
1152
|
+
};
|
|
1153
|
+
} catch (error) {
|
|
1154
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1155
|
+
console.error(`[MCP] tickets_list error:`, error);
|
|
1156
|
+
return {
|
|
1157
|
+
content: [{ type: "text", text: `Error listing tickets: ${errorMessage}` }],
|
|
1158
|
+
isError: true
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
async function handleTicketsUpdate(args, config, convexClient, projectSlug) {
|
|
1163
|
+
const parsedArgs = parseUpdateArgs(args);
|
|
1164
|
+
if (!parsedArgs) {
|
|
1165
|
+
const rawArgs = args;
|
|
1166
|
+
const hasTicketSlug = typeof rawArgs?.ticketSlug === "string" && rawArgs.ticketSlug;
|
|
1167
|
+
const hasContent = typeof rawArgs?.content === "string" && rawArgs.content;
|
|
1168
|
+
if (!hasTicketSlug) {
|
|
1169
|
+
return {
|
|
1170
|
+
content: [
|
|
1171
|
+
{
|
|
1172
|
+
type: "text",
|
|
1173
|
+
text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., "102") or full slug.`
|
|
1174
|
+
}
|
|
1175
|
+
],
|
|
1176
|
+
isError: true
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
if (!hasContent) {
|
|
1180
|
+
return {
|
|
1181
|
+
content: [
|
|
1182
|
+
{
|
|
1183
|
+
type: "text",
|
|
1184
|
+
text: `Error: Missing content parameter. Provide the update content to append to the ticket.`
|
|
1185
|
+
}
|
|
1186
|
+
],
|
|
1187
|
+
isError: true
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
return {
|
|
1191
|
+
content: [{ type: "text", text: `Error: Missing required parameters.` }],
|
|
1192
|
+
isError: true
|
|
1193
|
+
};
|
|
1194
|
+
}
|
|
1195
|
+
const { ticketSlug, content, agent } = parsedArgs;
|
|
1196
|
+
const typedClient = convexClient;
|
|
1197
|
+
try {
|
|
1198
|
+
const result = await typedClient.mutation("mcp_tickets:updateMcpTicket", {
|
|
1199
|
+
...buildAuthArgs(config),
|
|
1200
|
+
projectSlug,
|
|
1201
|
+
ticketSlug,
|
|
1202
|
+
content,
|
|
1203
|
+
...agent !== void 0 && { agentId: agent }
|
|
1204
|
+
});
|
|
1205
|
+
const agentInfo = result.agentId ? `
|
|
1206
|
+
Assigned to: ${result.agentId}` : agent === "" ? "\nAgent: Unassigned" : "";
|
|
1207
|
+
return {
|
|
1208
|
+
content: [
|
|
1209
|
+
{
|
|
1210
|
+
type: "text",
|
|
1211
|
+
text: `\u2705 Ticket [${result.slug}] updated in project "${projectSlug}".${agentInfo}
|
|
1212
|
+
Updated at: ${new Date(result.updatedAt).toISOString()}
|
|
1213
|
+
_Ticket content has been appended with your update._`
|
|
1130
1214
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
}
|
|
1143
|
-
);
|
|
1144
|
-
const agentInfo = result.agentId ? `
|
|
1145
|
-
Assigned to: ${result.agentId}` : "";
|
|
1146
|
-
return {
|
|
1147
|
-
content: [
|
|
1148
|
-
{
|
|
1149
|
-
type: "text",
|
|
1150
|
-
text: `\u2705 Created ticket [${result.slug}] in backlog for project "${ticketTool.projectSlug}".${agentInfo}
|
|
1215
|
+
]
|
|
1216
|
+
};
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1219
|
+
console.error(`[MCP] tickets_update error:`, error);
|
|
1220
|
+
return {
|
|
1221
|
+
content: [{ type: "text", text: `Error updating ticket: ${errorMessage}` }],
|
|
1222
|
+
isError: true
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1151
1226
|
|
|
1152
|
-
|
|
1153
|
-
|
|
1227
|
+
// src/handlers/memories.ts
|
|
1228
|
+
async function handleMemoriesLoad(args, config, convexClient, projectSlug) {
|
|
1229
|
+
const { query } = parseMemoriesLoadArgs(args);
|
|
1230
|
+
const typedClient = convexClient;
|
|
1231
|
+
try {
|
|
1232
|
+
const result = await typedClient.query("mcp_memories:loadMemories", {
|
|
1233
|
+
...buildAuthArgs(config),
|
|
1234
|
+
projectSlug,
|
|
1235
|
+
query
|
|
1236
|
+
});
|
|
1237
|
+
const sections = [];
|
|
1238
|
+
if (result.userMemories.length > 0) {
|
|
1239
|
+
const rulesText = result.userMemories.map((m, i) => `${i + 1}. ${m.content}`).join("\n");
|
|
1240
|
+
sections.push(`## Rules (${result.userMemories.length})
|
|
1241
|
+
${rulesText}`);
|
|
1242
|
+
} else {
|
|
1243
|
+
sections.push(`## Rules
|
|
1244
|
+
_No rules defined. Create rules in the Memories page._`);
|
|
1245
|
+
}
|
|
1246
|
+
if (result.agentMemories.length > 0) {
|
|
1247
|
+
const historyText = result.agentMemories.map((m) => {
|
|
1248
|
+
const ticketRef = m.ticketNumber ? `[#${m.ticketNumber}]` : "";
|
|
1249
|
+
const date = new Date(m.createdAt).toISOString().split("T")[0];
|
|
1250
|
+
return `\u2022 ${ticketRef} ${m.content} _(${date})_`;
|
|
1251
|
+
}).join("\n");
|
|
1252
|
+
const searchNote = query ? ` matching "${query}"` : " (recent)";
|
|
1253
|
+
sections.push(`## History${searchNote} (${result.agentMemories.length})
|
|
1254
|
+
${historyText}`);
|
|
1255
|
+
} else {
|
|
1256
|
+
const searchNote = query ? ` matching "${query}"` : "";
|
|
1257
|
+
sections.push(`## History${searchNote}
|
|
1258
|
+
_No memories recorded yet._`);
|
|
1259
|
+
}
|
|
1260
|
+
return {
|
|
1261
|
+
content: [
|
|
1262
|
+
{
|
|
1263
|
+
type: "text",
|
|
1264
|
+
text: `# Project Memories: ${projectSlug}
|
|
1154
1265
|
|
|
1155
|
-
|
|
1156
|
-
}
|
|
1157
|
-
]
|
|
1158
|
-
};
|
|
1159
|
-
} catch (error) {
|
|
1160
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1161
|
-
console.error(`[MCP] tickets_create error:`, error);
|
|
1162
|
-
return {
|
|
1163
|
-
content: [
|
|
1164
|
-
{
|
|
1165
|
-
type: "text",
|
|
1166
|
-
text: `Error creating ticket: ${errorMessage}`
|
|
1167
|
-
}
|
|
1168
|
-
],
|
|
1169
|
-
isError: true
|
|
1170
|
-
};
|
|
1266
|
+
${sections.join("\n\n")}`
|
|
1171
1267
|
}
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1268
|
+
]
|
|
1269
|
+
};
|
|
1270
|
+
} catch (error) {
|
|
1271
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1272
|
+
console.error(`[MCP] memories_load error:`, error);
|
|
1273
|
+
return {
|
|
1274
|
+
content: [{ type: "text", text: `Error loading memories: ${errorMessage}` }],
|
|
1275
|
+
isError: true
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async function handleMemoriesLoadExtreme(args, config, convexClient, projectSlug) {
|
|
1280
|
+
const { query } = parseMemoriesLoadArgs(args);
|
|
1281
|
+
const typedClient = convexClient;
|
|
1282
|
+
try {
|
|
1283
|
+
const result = await typedClient.query(
|
|
1284
|
+
"mcp_memories:loadMemoriesExtreme",
|
|
1285
|
+
{
|
|
1286
|
+
...buildAuthArgs(config),
|
|
1287
|
+
projectSlug,
|
|
1288
|
+
query
|
|
1289
|
+
}
|
|
1290
|
+
);
|
|
1291
|
+
const lines = [];
|
|
1292
|
+
lines.push(`# Project Memories: ${projectSlug}
|
|
1293
|
+
`);
|
|
1294
|
+
lines.push(`## Rules (${result.userMemories.length})`);
|
|
1295
|
+
if (result.userMemories.length > 0) {
|
|
1296
|
+
result.userMemories.forEach((m, i) => {
|
|
1297
|
+
lines.push(`${i + 1}. ${m.content}`);
|
|
1298
|
+
});
|
|
1299
|
+
} else {
|
|
1300
|
+
lines.push(`_No rules defined. Create rules in the Memories page._`);
|
|
1301
|
+
}
|
|
1302
|
+
lines.push("");
|
|
1303
|
+
const historyHeader = query ? `## History matching "${query}" (${result.agentMemories.length})` : `## Recent History (${result.agentMemories.length})`;
|
|
1304
|
+
lines.push(historyHeader);
|
|
1305
|
+
lines.push("");
|
|
1306
|
+
for (const memory of result.agentMemories) {
|
|
1307
|
+
lines.push("---");
|
|
1308
|
+
lines.push(`**Memory:** ${memory.content}`);
|
|
1309
|
+
if (memory.ticket) {
|
|
1310
|
+
lines.push(`**Ticket:** #${memory.ticket.ticketNumber}-${memory.ticket.slug}`);
|
|
1311
|
+
}
|
|
1312
|
+
const date = new Date(memory.createdAt).toISOString().split("T")[0];
|
|
1313
|
+
lines.push(`**Date:** ${date}`);
|
|
1314
|
+
lines.push("");
|
|
1315
|
+
if (memory.ticket) {
|
|
1316
|
+
lines.push("<ticket-content>");
|
|
1317
|
+
lines.push(memory.ticket.content);
|
|
1318
|
+
lines.push("</ticket-content>");
|
|
1319
|
+
} else {
|
|
1320
|
+
lines.push("_(No linked ticket)_");
|
|
1321
|
+
}
|
|
1322
|
+
lines.push("");
|
|
1323
|
+
}
|
|
1324
|
+
return {
|
|
1325
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
1326
|
+
};
|
|
1327
|
+
} catch (error) {
|
|
1328
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1329
|
+
console.error(`[MCP] memories_load_extreme error:`, error);
|
|
1330
|
+
return {
|
|
1331
|
+
content: [{ type: "text", text: `Error loading extreme memories: ${errorMessage}` }],
|
|
1332
|
+
isError: true
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
async function handleMemoriesAdd(args, config, convexClient, projectSlug) {
|
|
1337
|
+
const parsedArgs = parseMemoriesAddArgs(args);
|
|
1338
|
+
if (!parsedArgs) {
|
|
1339
|
+
return {
|
|
1340
|
+
content: [
|
|
1341
|
+
{
|
|
1342
|
+
type: "text",
|
|
1343
|
+
text: `Error: Missing content parameter. Provide the memory content to record.`
|
|
1184
1344
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1345
|
+
],
|
|
1346
|
+
isError: true
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
const { content } = parsedArgs;
|
|
1350
|
+
const typedClient = convexClient;
|
|
1351
|
+
try {
|
|
1352
|
+
const result = await typedClient.mutation("mcp_memories:addMemory", {
|
|
1353
|
+
...buildAuthArgs(config),
|
|
1354
|
+
projectSlug,
|
|
1355
|
+
content
|
|
1356
|
+
});
|
|
1357
|
+
let responseText;
|
|
1358
|
+
if (result.linkedTicket) {
|
|
1359
|
+
const ticketRef = `#${result.linkedTicket.number}`;
|
|
1360
|
+
const fallbackNote = result.linkedViaFallback ? " (recently closed)" : "";
|
|
1361
|
+
responseText = `\u2705 Memory recorded for ticket [${ticketRef}]${fallbackNote} in project "${projectSlug}".
|
|
1197
1362
|
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1363
|
+
_Memory will be available in future context via [MEMORY_LOAD]._`;
|
|
1364
|
+
} else {
|
|
1365
|
+
responseText = `\u2705 Memory recorded in project "${projectSlug}".
|
|
1201
1366
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1367
|
+
_Memory created without ticket linkage (no working ticket or recent closed ticket)._
|
|
1368
|
+
_Memory will be available in future context via [MEMORY_LOAD]._`;
|
|
1369
|
+
}
|
|
1370
|
+
return {
|
|
1371
|
+
content: [{ type: "text", text: responseText }]
|
|
1372
|
+
};
|
|
1373
|
+
} catch (error) {
|
|
1374
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1375
|
+
console.error(`[MCP] memories_add error:`, error);
|
|
1376
|
+
return {
|
|
1377
|
+
content: [{ type: "text", text: `Error adding memory: ${errorMessage}` }],
|
|
1378
|
+
isError: true
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
async function handleMemoriesListAll(args, config, convexClient, projectSlug) {
|
|
1383
|
+
const parsedArgs = parseMemoriesListAllArgs(args);
|
|
1384
|
+
const typedClient = convexClient;
|
|
1385
|
+
try {
|
|
1386
|
+
const result = await typedClient.query("mcp_memories:listAllMemories", {
|
|
1387
|
+
...buildAuthArgs(config),
|
|
1388
|
+
projectSlug,
|
|
1389
|
+
typeFilter: parsedArgs.typeFilter
|
|
1390
|
+
});
|
|
1391
|
+
const formatDate = (ts) => new Date(ts).toISOString().split("T")[0];
|
|
1392
|
+
const userMemories = result.memories.filter((m) => m.type === "user");
|
|
1393
|
+
const agentMemories = result.memories.filter((m) => m.type === "agent");
|
|
1394
|
+
let output = `# Project Memories (${result.totalCount} total)
|
|
1209
1395
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1396
|
+
`;
|
|
1397
|
+
if (userMemories.length > 0) {
|
|
1398
|
+
output += `## Rules (${userMemories.length})
|
|
1399
|
+
`;
|
|
1400
|
+
userMemories.forEach((m, i) => {
|
|
1401
|
+
output += `${i + 1}. [${m.id}] ${m.content}
|
|
1402
|
+
`;
|
|
1403
|
+
});
|
|
1404
|
+
output += "\n";
|
|
1405
|
+
}
|
|
1406
|
+
if (agentMemories.length > 0) {
|
|
1407
|
+
output += `## Agent History (${agentMemories.length})
|
|
1408
|
+
`;
|
|
1409
|
+
agentMemories.forEach((m) => {
|
|
1410
|
+
const ticketRef = m.ticketNumber ? ` [#${m.ticketNumber}]` : "";
|
|
1411
|
+
output += `\u2022 [${m.id}]${ticketRef} ${m.content.substring(0, 100)}${m.content.length > 100 ? "..." : ""} _(${formatDate(m.createdAt)})_
|
|
1412
|
+
`;
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
return {
|
|
1416
|
+
content: [{ type: "text", text: output }]
|
|
1417
|
+
};
|
|
1418
|
+
} catch (error) {
|
|
1419
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1420
|
+
console.error(`[MCP] memories_list_all error:`, error);
|
|
1421
|
+
return {
|
|
1422
|
+
content: [{ type: "text", text: `Error listing memories: ${errorMessage}` }],
|
|
1423
|
+
isError: true
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
// src/server.ts
|
|
1429
|
+
async function startServer(config, convexClient) {
|
|
1430
|
+
console.error("[MCP] Validating project token...");
|
|
1431
|
+
const validation = await validateProjectToken(convexClient, config.projectToken);
|
|
1432
|
+
if (!validation.valid) {
|
|
1433
|
+
throw new Error(`Invalid project token: ${validation.error}`);
|
|
1434
|
+
}
|
|
1435
|
+
const projectSlug = validation.projectSlug;
|
|
1436
|
+
console.error(`[MCP] Project token validated for project "${projectSlug}"`);
|
|
1437
|
+
console.error("[MCP] Fetching prompt metadata...");
|
|
1438
|
+
const accountScopedPrompts = await fetchAccountScopedPromptMetadataByToken(
|
|
1439
|
+
convexClient,
|
|
1440
|
+
config.projectToken
|
|
1441
|
+
);
|
|
1442
|
+
console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);
|
|
1443
|
+
const slashCommandPrompts = accountScopedPrompts.filter((p) => p.slashCommand === true);
|
|
1444
|
+
console.error(`[MCP] ${slashCommandPrompts.length} prompts registered as slash commands`);
|
|
1445
|
+
if (accountScopedPrompts.length === 0) {
|
|
1446
|
+
console.error(
|
|
1447
|
+
"[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP."
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
const ticketTools = getTicketToolDefinitions(projectSlug);
|
|
1451
|
+
const memoryTools = getMemoryToolDefinitions();
|
|
1452
|
+
const promptTools = getPromptToolDefinitions();
|
|
1453
|
+
console.error(`[MCP] Registering ${ticketTools.length} ticket tools...`);
|
|
1454
|
+
console.error(`[MCP] Registering ${memoryTools.length} memory tools...`);
|
|
1455
|
+
console.error(`[MCP] Registering ${promptTools.length} global prompt tools...`);
|
|
1456
|
+
const dynamicPromptTools = accountScopedPrompts.map((prompt) => {
|
|
1457
|
+
const folderPrefix = prompt.folderPath ? `[${prompt.folderPath}] ` : "";
|
|
1458
|
+
const baseDescription = prompt.description || `Execute the "${prompt.slug}" prompt`;
|
|
1459
|
+
return {
|
|
1460
|
+
name: prompt.slug,
|
|
1461
|
+
description: folderPrefix + baseDescription,
|
|
1462
|
+
promptSlug: prompt.slug
|
|
1463
|
+
};
|
|
1464
|
+
});
|
|
1465
|
+
console.error(`[MCP] Registering ${dynamicPromptTools.length} per-prompt tools (global)...`);
|
|
1466
|
+
const promptNames = /* @__PURE__ */ new Set();
|
|
1467
|
+
const duplicates = [];
|
|
1468
|
+
accountScopedPrompts.forEach((p) => {
|
|
1469
|
+
if (promptNames.has(p.slug)) {
|
|
1470
|
+
duplicates.push(p.slug);
|
|
1471
|
+
}
|
|
1472
|
+
promptNames.add(p.slug);
|
|
1473
|
+
});
|
|
1474
|
+
if (duplicates.length > 0) {
|
|
1475
|
+
console.error(
|
|
1476
|
+
`[MCP] WARNING: Duplicate prompt slugs detected: ${duplicates.join(", ")}. Only the first occurrence will be registered.`
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
const server = new Server(
|
|
1480
|
+
{ name: "ppm-mcp-server", version: "3.1.0" },
|
|
1481
|
+
{ capabilities: { prompts: {}, tools: {} } }
|
|
1482
|
+
);
|
|
1483
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
1484
|
+
return {
|
|
1485
|
+
prompts: [
|
|
1486
|
+
// Global prompt tools (prompts_run, prompts_list, prompts_update)
|
|
1487
|
+
...promptTools.map((t) => ({ name: t.name, description: t.description })),
|
|
1488
|
+
// Ticket tools
|
|
1489
|
+
...ticketTools.map((t) => ({ name: t.name, description: t.description })),
|
|
1490
|
+
// Memory tools
|
|
1491
|
+
...memoryTools.map((t) => ({ name: t.name, description: t.description })),
|
|
1492
|
+
// User prompts with slashCommand: true
|
|
1493
|
+
...slashCommandPrompts.map((p) => ({
|
|
1494
|
+
name: p.slug,
|
|
1495
|
+
description: p.description || `Execute the "${p.slug}" prompt`
|
|
1496
|
+
}))
|
|
1497
|
+
]
|
|
1498
|
+
};
|
|
1499
|
+
});
|
|
1500
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
1501
|
+
const promptName = request.params.name;
|
|
1502
|
+
const ticketWorkMatch = promptName.match(/^tickets_work\s+(.+)$/);
|
|
1503
|
+
if (ticketWorkMatch) {
|
|
1504
|
+
const ticketArg = ticketWorkMatch[1].trim();
|
|
1505
|
+
return {
|
|
1506
|
+
description: `Work on ticket "${ticketArg}" from "${projectSlug}"`,
|
|
1507
|
+
messages: [
|
|
1508
|
+
{
|
|
1509
|
+
role: "user",
|
|
1510
|
+
content: {
|
|
1511
|
+
type: "text",
|
|
1512
|
+
text: `Get work on ticket "${ticketArg}" from the "${projectSlug}" project.
|
|
1286
1513
|
|
|
1287
|
-
|
|
1514
|
+
Call the \`tickets_work\` tool with ticketSlug: "${ticketArg}".
|
|
1288
1515
|
|
|
1289
|
-
|
|
1290
|
-
}
|
|
1291
|
-
]
|
|
1292
|
-
};
|
|
1293
|
-
} catch (error) {
|
|
1294
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1295
|
-
console.error(`[MCP] tickets_pause error:`, error);
|
|
1296
|
-
return {
|
|
1297
|
-
content: [
|
|
1298
|
-
{
|
|
1299
|
-
type: "text",
|
|
1300
|
-
text: `Error pausing ticket: ${errorMessage}`
|
|
1301
|
-
}
|
|
1302
|
-
],
|
|
1303
|
-
isError: true
|
|
1304
|
-
};
|
|
1305
|
-
}
|
|
1306
|
-
} else if (ticketTool.type === "search") {
|
|
1307
|
-
const parsedArgs = parseSearchArgs(request.params.arguments);
|
|
1308
|
-
if (!parsedArgs) {
|
|
1309
|
-
return {
|
|
1310
|
-
content: [
|
|
1311
|
-
{
|
|
1312
|
-
type: "text",
|
|
1313
|
-
text: `Error: Search query must be at least 3 characters`
|
|
1314
|
-
}
|
|
1315
|
-
],
|
|
1316
|
-
isError: true
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
const { query, agent } = parsedArgs;
|
|
1320
|
-
const resolvedAgent = resolveAgent(agent);
|
|
1321
|
-
try {
|
|
1322
|
-
const result = await convexClient.query(
|
|
1323
|
-
"mcp_tickets:searchMcpTickets",
|
|
1324
|
-
{
|
|
1325
|
-
...buildAuthArgs(config),
|
|
1326
|
-
projectSlug: ticketTool.projectSlug,
|
|
1327
|
-
query,
|
|
1328
|
-
agentId: resolvedAgent
|
|
1329
|
-
// Agent filtering (implement-agent-routing)
|
|
1516
|
+
This will open the ticket if it's in the open queue, or resume it if already working.`
|
|
1330
1517
|
}
|
|
1331
|
-
);
|
|
1332
|
-
const agentContext = resolvedAgent ? ` for agent "${resolvedAgent}"` : "";
|
|
1333
|
-
if (result.length === 0) {
|
|
1334
|
-
return {
|
|
1335
|
-
content: [
|
|
1336
|
-
{
|
|
1337
|
-
type: "text",
|
|
1338
|
-
text: `No tickets found matching "${query}"${agentContext} in project "${ticketTool.projectSlug}".`
|
|
1339
|
-
}
|
|
1340
|
-
]
|
|
1341
|
-
};
|
|
1342
1518
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1519
|
+
]
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
const ticketTool = ticketTools.find((t) => t.name === promptName);
|
|
1523
|
+
if (ticketTool) {
|
|
1524
|
+
return buildTicketPromptResponse(ticketTool.type, ticketTool.name, projectSlug);
|
|
1525
|
+
}
|
|
1526
|
+
if (promptName === "prompts_list") {
|
|
1527
|
+
return {
|
|
1528
|
+
description: "List all available prompts",
|
|
1529
|
+
messages: [
|
|
1530
|
+
{
|
|
1531
|
+
role: "user",
|
|
1532
|
+
content: {
|
|
1533
|
+
type: "text",
|
|
1534
|
+
text: `List all available prompts.
|
|
1356
1535
|
|
|
1357
|
-
|
|
1358
|
-
}
|
|
1359
|
-
]
|
|
1360
|
-
};
|
|
1361
|
-
} catch (error) {
|
|
1362
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1363
|
-
console.error(`[MCP] tickets_search error:`, error);
|
|
1364
|
-
return {
|
|
1365
|
-
content: [
|
|
1366
|
-
{
|
|
1367
|
-
type: "text",
|
|
1368
|
-
text: `Error searching tickets: ${errorMessage}`
|
|
1369
|
-
}
|
|
1370
|
-
],
|
|
1371
|
-
isError: true
|
|
1372
|
-
};
|
|
1373
|
-
}
|
|
1374
|
-
} else if (ticketTool.type === "get") {
|
|
1375
|
-
const parsedArgs = parseGetArgs(request.params.arguments);
|
|
1376
|
-
if (!parsedArgs) {
|
|
1377
|
-
return {
|
|
1378
|
-
content: [
|
|
1379
|
-
{
|
|
1380
|
-
type: "text",
|
|
1381
|
-
text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., "102") or full slug.`
|
|
1382
|
-
}
|
|
1383
|
-
],
|
|
1384
|
-
isError: true
|
|
1385
|
-
};
|
|
1386
|
-
}
|
|
1387
|
-
const { ticketSlug } = parsedArgs;
|
|
1388
|
-
try {
|
|
1389
|
-
const result = await convexClient.query(
|
|
1390
|
-
"mcp_tickets:getMcpTicket",
|
|
1391
|
-
{
|
|
1392
|
-
...buildAuthArgs(config),
|
|
1393
|
-
projectSlug: ticketTool.projectSlug,
|
|
1394
|
-
ticketSlug
|
|
1395
|
-
}
|
|
1396
|
-
);
|
|
1397
|
-
if (!result) {
|
|
1398
|
-
return {
|
|
1399
|
-
content: [
|
|
1400
|
-
{
|
|
1401
|
-
type: "text",
|
|
1402
|
-
text: `Ticket "${ticketSlug}" not found in project "${ticketTool.projectSlug}".`
|
|
1403
|
-
}
|
|
1404
|
-
]
|
|
1405
|
-
};
|
|
1406
|
-
}
|
|
1407
|
-
if (isAccessDenied(result)) {
|
|
1408
|
-
return {
|
|
1409
|
-
content: [
|
|
1410
|
-
{
|
|
1411
|
-
type: "text",
|
|
1412
|
-
text: `Access denied: ${result.error}`
|
|
1413
|
-
}
|
|
1414
|
-
],
|
|
1415
|
-
isError: true
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
const getResult = result;
|
|
1419
|
-
const statusBadge = getResult.status.toUpperCase();
|
|
1420
|
-
const startedInfo = getResult.startedAt ? `
|
|
1421
|
-
Started: ${new Date(getResult.startedAt).toISOString()}` : "";
|
|
1422
|
-
const closedInfo = getResult.closedAt ? `
|
|
1423
|
-
Closed: ${new Date(getResult.closedAt).toISOString()}` : "";
|
|
1424
|
-
let metadataInfo = "";
|
|
1425
|
-
if (getResult.metadata && Object.keys(getResult.metadata).length > 0) {
|
|
1426
|
-
metadataInfo = "\n\n**Metadata:**";
|
|
1427
|
-
for (const [key, value] of Object.entries(getResult.metadata)) {
|
|
1428
|
-
metadataInfo += `
|
|
1429
|
-
- ${key}: ${JSON.stringify(value)}`;
|
|
1536
|
+
Call the \`prompts_list\` tool with optional \`search\` parameter to filter results.`
|
|
1430
1537
|
}
|
|
1431
1538
|
}
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
]
|
|
1444
|
-
};
|
|
1445
|
-
} catch (error) {
|
|
1446
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1447
|
-
console.error(`[MCP] tickets_get error:`, error);
|
|
1448
|
-
return {
|
|
1449
|
-
content: [
|
|
1450
|
-
{
|
|
1451
|
-
type: "text",
|
|
1452
|
-
text: `Error getting ticket: ${errorMessage}`
|
|
1453
|
-
}
|
|
1454
|
-
],
|
|
1455
|
-
isError: true
|
|
1456
|
-
};
|
|
1457
|
-
}
|
|
1458
|
-
} else if (ticketTool.type === "list") {
|
|
1459
|
-
const { agent } = parseListArgs(request.params.arguments);
|
|
1460
|
-
const resolvedAgent = resolveAgent(agent);
|
|
1461
|
-
try {
|
|
1462
|
-
const result = await convexClient.query(
|
|
1463
|
-
"mcp_tickets:listMcpTickets",
|
|
1539
|
+
]
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
const runPromptMatch = promptName.match(/^prompts_run\s+(.+)$/);
|
|
1543
|
+
if (promptName === "prompts_run" || runPromptMatch) {
|
|
1544
|
+
if (runPromptMatch) {
|
|
1545
|
+
const promptSlug = runPromptMatch[1].trim();
|
|
1546
|
+
return {
|
|
1547
|
+
description: `Execute prompt "${promptSlug}"`,
|
|
1548
|
+
messages: [
|
|
1464
1549
|
{
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
agentId: resolvedAgent
|
|
1468
|
-
// Agent filtering (implement-agent-routing)
|
|
1469
|
-
}
|
|
1470
|
-
);
|
|
1471
|
-
const agentContext = resolvedAgent ? ` for agent "${resolvedAgent}"` : "";
|
|
1472
|
-
if (result.length === 0) {
|
|
1473
|
-
return {
|
|
1474
|
-
content: [
|
|
1475
|
-
{
|
|
1476
|
-
type: "text",
|
|
1477
|
-
text: `No active tickets${agentContext} in project "${ticketTool.projectSlug}". All tickets are closed or archived.`
|
|
1478
|
-
}
|
|
1479
|
-
]
|
|
1480
|
-
};
|
|
1481
|
-
}
|
|
1482
|
-
const openTickets = result.filter((t) => t.status === "open");
|
|
1483
|
-
const workingTickets = result.filter((t) => t.status === "working");
|
|
1484
|
-
const backlogTickets = result.filter((t) => t.status === "backlog");
|
|
1485
|
-
const formatTicketLine = (t) => {
|
|
1486
|
-
const num = t.ticketNumber ? `#${t.ticketNumber}` : "";
|
|
1487
|
-
const agentBadge = !resolvedAgent && t.agentId ? ` [${t.agentId}]` : "";
|
|
1488
|
-
return ` ${t.position}. ${num} ${t.slug}${agentBadge}
|
|
1489
|
-
${t.preview}`;
|
|
1490
|
-
};
|
|
1491
|
-
const sections = [];
|
|
1492
|
-
if (openTickets.length > 0) {
|
|
1493
|
-
sections.push(`**\u{1F7E2} Open (${openTickets.length})**
|
|
1494
|
-
${openTickets.map(formatTicketLine).join("\n")}`);
|
|
1495
|
-
}
|
|
1496
|
-
if (workingTickets.length > 0) {
|
|
1497
|
-
sections.push(`**\u{1F7E1} Working (${workingTickets.length})**
|
|
1498
|
-
${workingTickets.map(formatTicketLine).join("\n")}`);
|
|
1499
|
-
}
|
|
1500
|
-
if (backlogTickets.length > 0) {
|
|
1501
|
-
sections.push(`**\u26AA Backlog (${backlogTickets.length})**
|
|
1502
|
-
${backlogTickets.map(formatTicketLine).join("\n")}`);
|
|
1503
|
-
}
|
|
1504
|
-
const headerSuffix = resolvedAgent ? ` (Agent: ${resolvedAgent})` : "";
|
|
1505
|
-
return {
|
|
1506
|
-
content: [
|
|
1507
|
-
{
|
|
1550
|
+
role: "user",
|
|
1551
|
+
content: {
|
|
1508
1552
|
type: "text",
|
|
1509
|
-
text:
|
|
1553
|
+
text: `Execute the "${promptSlug}" prompt.
|
|
1510
1554
|
|
|
1511
|
-
${
|
|
1512
|
-
|
|
1513
|
-
${sections.join("\n\n")}`
|
|
1514
|
-
}
|
|
1515
|
-
]
|
|
1516
|
-
};
|
|
1517
|
-
} catch (error) {
|
|
1518
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1519
|
-
console.error(`[MCP] tickets_list error:`, error);
|
|
1520
|
-
return {
|
|
1521
|
-
content: [
|
|
1522
|
-
{
|
|
1523
|
-
type: "text",
|
|
1524
|
-
text: `Error listing tickets: ${errorMessage}`
|
|
1525
|
-
}
|
|
1526
|
-
],
|
|
1527
|
-
isError: true
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
} else if (ticketTool.type === "update") {
|
|
1531
|
-
const parsedArgs = parseUpdateArgs(request.params.arguments);
|
|
1532
|
-
if (!parsedArgs) {
|
|
1533
|
-
const args = request.params.arguments;
|
|
1534
|
-
const hasTicketSlug = typeof args?.ticketSlug === "string" && args.ticketSlug;
|
|
1535
|
-
const hasContent = typeof args?.content === "string" && args.content;
|
|
1536
|
-
if (!hasTicketSlug) {
|
|
1537
|
-
return {
|
|
1538
|
-
content: [
|
|
1539
|
-
{
|
|
1540
|
-
type: "text",
|
|
1541
|
-
text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., "102") or full slug.`
|
|
1542
|
-
}
|
|
1543
|
-
],
|
|
1544
|
-
isError: true
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
if (!hasContent) {
|
|
1548
|
-
return {
|
|
1549
|
-
content: [
|
|
1550
|
-
{
|
|
1551
|
-
type: "text",
|
|
1552
|
-
text: `Error: Missing content parameter. Provide the update content to append to the ticket.`
|
|
1553
|
-
}
|
|
1554
|
-
],
|
|
1555
|
-
isError: true
|
|
1556
|
-
};
|
|
1557
|
-
}
|
|
1558
|
-
return {
|
|
1559
|
-
content: [
|
|
1560
|
-
{
|
|
1561
|
-
type: "text",
|
|
1562
|
-
text: `Error: Missing required parameters.`
|
|
1555
|
+
Call the \`prompts_run\` tool with slug: "${promptSlug}".`
|
|
1563
1556
|
}
|
|
1564
|
-
],
|
|
1565
|
-
isError: true
|
|
1566
|
-
};
|
|
1567
|
-
}
|
|
1568
|
-
const { ticketSlug, content, agent } = parsedArgs;
|
|
1569
|
-
try {
|
|
1570
|
-
const result = await convexClient.mutation(
|
|
1571
|
-
"mcp_tickets:updateMcpTicket",
|
|
1572
|
-
{
|
|
1573
|
-
...buildAuthArgs(config),
|
|
1574
|
-
projectSlug: ticketTool.projectSlug,
|
|
1575
|
-
ticketSlug,
|
|
1576
|
-
content,
|
|
1577
|
-
// Agent reassignment (implement-agent-routing)
|
|
1578
|
-
// Only include if explicitly provided (empty string = unassign)
|
|
1579
|
-
...agent !== void 0 && { agentId: agent }
|
|
1580
1557
|
}
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
Assigned to: ${result.agentId}` : agent === "" ? "\nAgent: Unassigned" : "";
|
|
1584
|
-
return {
|
|
1585
|
-
content: [
|
|
1586
|
-
{
|
|
1587
|
-
type: "text",
|
|
1588
|
-
text: `\u2705 Ticket [${result.slug}] updated in project "${ticketTool.projectSlug}".${agentInfo}
|
|
1589
|
-
Updated at: ${new Date(result.updatedAt).toISOString()}
|
|
1590
|
-
_Ticket content has been appended with your update._`
|
|
1591
|
-
}
|
|
1592
|
-
]
|
|
1593
|
-
};
|
|
1594
|
-
} catch (error) {
|
|
1595
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1596
|
-
console.error(`[MCP] tickets_update error:`, error);
|
|
1597
|
-
return {
|
|
1598
|
-
content: [
|
|
1599
|
-
{
|
|
1600
|
-
type: "text",
|
|
1601
|
-
text: `Error updating ticket: ${errorMessage}`
|
|
1602
|
-
}
|
|
1603
|
-
],
|
|
1604
|
-
isError: true
|
|
1605
|
-
};
|
|
1606
|
-
}
|
|
1558
|
+
]
|
|
1559
|
+
};
|
|
1607
1560
|
}
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
{
|
|
1617
|
-
...buildAuthArgs(config),
|
|
1618
|
-
projectSlug: memoryTool.projectSlug,
|
|
1619
|
-
query
|
|
1620
|
-
}
|
|
1621
|
-
);
|
|
1622
|
-
const sections = [];
|
|
1623
|
-
if (result.userMemories.length > 0) {
|
|
1624
|
-
const rulesText = result.userMemories.map((m, i) => `${i + 1}. ${m.content}`).join("\n");
|
|
1625
|
-
sections.push(`## Rules (${result.userMemories.length})
|
|
1626
|
-
${rulesText}`);
|
|
1627
|
-
} else {
|
|
1628
|
-
sections.push(`## Rules
|
|
1629
|
-
_No rules defined. Create rules in the Memories page._`);
|
|
1630
|
-
}
|
|
1631
|
-
if (result.agentMemories.length > 0) {
|
|
1632
|
-
const historyText = result.agentMemories.map((m) => {
|
|
1633
|
-
const ticketRef = m.ticketNumber ? `[#${m.ticketNumber}]` : "";
|
|
1634
|
-
const date = new Date(m.createdAt).toISOString().split("T")[0];
|
|
1635
|
-
return `\u2022 ${ticketRef} ${m.content} _(${date})_`;
|
|
1636
|
-
}).join("\n");
|
|
1637
|
-
const searchNote = query ? ` matching "${query}"` : " (recent)";
|
|
1638
|
-
sections.push(`## History${searchNote} (${result.agentMemories.length})
|
|
1639
|
-
${historyText}`);
|
|
1640
|
-
} else {
|
|
1641
|
-
const searchNote = query ? ` matching "${query}"` : "";
|
|
1642
|
-
sections.push(`## History${searchNote}
|
|
1643
|
-
_No memories recorded yet._`);
|
|
1644
|
-
}
|
|
1645
|
-
return {
|
|
1646
|
-
content: [
|
|
1647
|
-
{
|
|
1648
|
-
type: "text",
|
|
1649
|
-
text: `# Project Memories: ${memoryTool.projectSlug}
|
|
1561
|
+
return {
|
|
1562
|
+
description: "Execute a prompt by slug",
|
|
1563
|
+
messages: [
|
|
1564
|
+
{
|
|
1565
|
+
role: "user",
|
|
1566
|
+
content: {
|
|
1567
|
+
type: "text",
|
|
1568
|
+
text: `Execute a prompt by slug.
|
|
1650
1569
|
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
]
|
|
1654
|
-
};
|
|
1655
|
-
} catch (error) {
|
|
1656
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1657
|
-
console.error(`[MCP] memories_load error:`, error);
|
|
1658
|
-
return {
|
|
1659
|
-
content: [
|
|
1660
|
-
{
|
|
1661
|
-
type: "text",
|
|
1662
|
-
text: `Error loading memories: ${errorMessage}`
|
|
1663
|
-
}
|
|
1664
|
-
],
|
|
1665
|
-
isError: true
|
|
1666
|
-
};
|
|
1667
|
-
}
|
|
1668
|
-
} else if (memoryTool.type === "load_extreme") {
|
|
1669
|
-
const { query } = parseMemoriesLoadArgs(request.params.arguments);
|
|
1670
|
-
try {
|
|
1671
|
-
const result = await convexClient.query(
|
|
1672
|
-
"mcp_memories:loadMemoriesExtreme",
|
|
1673
|
-
{
|
|
1674
|
-
...buildAuthArgs(config),
|
|
1675
|
-
projectSlug: memoryTool.projectSlug,
|
|
1676
|
-
query
|
|
1677
|
-
}
|
|
1678
|
-
);
|
|
1679
|
-
const lines = [];
|
|
1680
|
-
lines.push(`# Project Memories: ${memoryTool.projectSlug}
|
|
1681
|
-
`);
|
|
1682
|
-
lines.push(`## Rules (${result.userMemories.length})`);
|
|
1683
|
-
if (result.userMemories.length > 0) {
|
|
1684
|
-
result.userMemories.forEach((m, i) => {
|
|
1685
|
-
lines.push(`${i + 1}. ${m.content}`);
|
|
1686
|
-
});
|
|
1687
|
-
} else {
|
|
1688
|
-
lines.push(`_No rules defined. Create rules in the Memories page._`);
|
|
1689
|
-
}
|
|
1690
|
-
lines.push("");
|
|
1691
|
-
const historyHeader = query ? `## History matching "${query}" (${result.agentMemories.length})` : `## Recent History (${result.agentMemories.length})`;
|
|
1692
|
-
lines.push(historyHeader);
|
|
1693
|
-
lines.push("");
|
|
1694
|
-
for (const memory of result.agentMemories) {
|
|
1695
|
-
lines.push("---");
|
|
1696
|
-
lines.push(`**Memory:** ${memory.content}`);
|
|
1697
|
-
if (memory.ticket) {
|
|
1698
|
-
lines.push(`**Ticket:** #${memory.ticket.ticketNumber}-${memory.ticket.slug}`);
|
|
1699
|
-
}
|
|
1700
|
-
const date = new Date(memory.createdAt).toISOString().split("T")[0];
|
|
1701
|
-
lines.push(`**Date:** ${date}`);
|
|
1702
|
-
lines.push("");
|
|
1703
|
-
if (memory.ticket) {
|
|
1704
|
-
lines.push("<ticket-content>");
|
|
1705
|
-
lines.push(memory.ticket.content);
|
|
1706
|
-
lines.push("</ticket-content>");
|
|
1707
|
-
} else {
|
|
1708
|
-
lines.push("_(No linked ticket)_");
|
|
1709
|
-
}
|
|
1710
|
-
lines.push("");
|
|
1711
|
-
}
|
|
1712
|
-
return {
|
|
1713
|
-
content: [
|
|
1714
|
-
{
|
|
1715
|
-
type: "text",
|
|
1716
|
-
text: lines.join("\n")
|
|
1717
|
-
}
|
|
1718
|
-
]
|
|
1719
|
-
};
|
|
1720
|
-
} catch (error) {
|
|
1721
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1722
|
-
console.error(`[MCP] memories_load_extreme error:`, error);
|
|
1723
|
-
return {
|
|
1724
|
-
content: [
|
|
1725
|
-
{
|
|
1726
|
-
type: "text",
|
|
1727
|
-
text: `Error loading extreme memories: ${errorMessage}`
|
|
1728
|
-
}
|
|
1729
|
-
],
|
|
1730
|
-
isError: true
|
|
1731
|
-
};
|
|
1732
|
-
}
|
|
1733
|
-
} else if (memoryTool.type === "add") {
|
|
1734
|
-
const parsedArgs = parseMemoriesAddArgs(request.params.arguments);
|
|
1735
|
-
if (!parsedArgs) {
|
|
1736
|
-
return {
|
|
1737
|
-
content: [
|
|
1738
|
-
{
|
|
1739
|
-
type: "text",
|
|
1740
|
-
text: `Error: Missing content parameter. Provide the memory content to record.`
|
|
1741
|
-
}
|
|
1742
|
-
],
|
|
1743
|
-
isError: true
|
|
1744
|
-
};
|
|
1745
|
-
}
|
|
1746
|
-
const { content } = parsedArgs;
|
|
1747
|
-
try {
|
|
1748
|
-
const result = await convexClient.mutation(
|
|
1749
|
-
"mcp_memories:addMemory",
|
|
1750
|
-
{
|
|
1751
|
-
...buildAuthArgs(config),
|
|
1752
|
-
projectSlug: memoryTool.projectSlug,
|
|
1753
|
-
content
|
|
1754
|
-
}
|
|
1755
|
-
);
|
|
1756
|
-
let responseText;
|
|
1757
|
-
if (result.linkedTicket) {
|
|
1758
|
-
const ticketRef = `#${result.linkedTicket.number}`;
|
|
1759
|
-
const fallbackNote = result.linkedViaFallback ? " (recently closed)" : "";
|
|
1760
|
-
responseText = `\u2705 Memory recorded for ticket [${ticketRef}]${fallbackNote} in project "${memoryTool.projectSlug}".
|
|
1570
|
+
## Usage
|
|
1571
|
+
Call the \`prompts_run\` tool with the prompt slug.
|
|
1761
1572
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
responseText = `\u2705 Memory recorded in project "${memoryTool.projectSlug}".
|
|
1573
|
+
## Available Prompts
|
|
1574
|
+
Use \`prompts_list\` to list all available prompts.
|
|
1765
1575
|
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
}
|
|
1769
|
-
return {
|
|
1770
|
-
content: [
|
|
1771
|
-
{
|
|
1772
|
-
type: "text",
|
|
1773
|
-
text: responseText
|
|
1774
|
-
}
|
|
1775
|
-
]
|
|
1776
|
-
};
|
|
1777
|
-
} catch (error) {
|
|
1778
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1779
|
-
console.error(`[MCP] memories_add error:`, error);
|
|
1780
|
-
return {
|
|
1781
|
-
content: [
|
|
1782
|
-
{
|
|
1783
|
-
type: "text",
|
|
1784
|
-
text: `Error adding memory: ${errorMessage}`
|
|
1785
|
-
}
|
|
1786
|
-
],
|
|
1787
|
-
isError: true
|
|
1788
|
-
};
|
|
1789
|
-
}
|
|
1790
|
-
} else if (memoryTool.type === "list_all") {
|
|
1791
|
-
const parsedArgs = parseMemoriesListAllArgs(request.params.arguments);
|
|
1792
|
-
try {
|
|
1793
|
-
const result = await convexClient.query(
|
|
1794
|
-
"mcp_memories:listAllMemories",
|
|
1795
|
-
{
|
|
1796
|
-
...buildAuthArgs(config),
|
|
1797
|
-
projectSlug: memoryTool.projectSlug,
|
|
1798
|
-
typeFilter: parsedArgs.typeFilter
|
|
1799
|
-
}
|
|
1800
|
-
);
|
|
1801
|
-
const formatDate = (ts) => new Date(ts).toISOString().split("T")[0];
|
|
1802
|
-
const userMemories = result.memories.filter((m) => m.type === "user");
|
|
1803
|
-
const agentMemories = result.memories.filter((m) => m.type === "agent");
|
|
1804
|
-
let output = `# Project Memories (${result.totalCount} total)
|
|
1576
|
+
## Example
|
|
1577
|
+
/ppm:prompts_run code-review
|
|
1805
1578
|
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
output += `## Rules (${userMemories.length})
|
|
1809
|
-
`;
|
|
1810
|
-
userMemories.forEach((m, i) => {
|
|
1811
|
-
output += `${i + 1}. [${m.id}] ${m.content}
|
|
1812
|
-
`;
|
|
1813
|
-
});
|
|
1814
|
-
output += "\n";
|
|
1815
|
-
}
|
|
1816
|
-
if (agentMemories.length > 0) {
|
|
1817
|
-
output += `## Agent History (${agentMemories.length})
|
|
1818
|
-
`;
|
|
1819
|
-
agentMemories.forEach((m) => {
|
|
1820
|
-
const ticketRef = m.ticketNumber ? ` [#${m.ticketNumber}]` : "";
|
|
1821
|
-
output += `\u2022 [${m.id}]${ticketRef} ${m.content.substring(0, 100)}${m.content.length > 100 ? "..." : ""} _(${formatDate(m.createdAt)})_
|
|
1822
|
-
`;
|
|
1823
|
-
});
|
|
1579
|
+
This will execute the "code-review" prompt.`
|
|
1580
|
+
}
|
|
1824
1581
|
}
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1582
|
+
]
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
const userSlashCommand = slashCommandPrompts.find((p) => p.slug === promptName);
|
|
1586
|
+
if (userSlashCommand) {
|
|
1587
|
+
try {
|
|
1588
|
+
const result = await fetchAndExecuteAccountScopedPrompt(
|
|
1589
|
+
userSlashCommand.slug,
|
|
1590
|
+
config,
|
|
1591
|
+
convexClient
|
|
1592
|
+
);
|
|
1593
|
+
return {
|
|
1594
|
+
description: userSlashCommand.description || `Execute "${userSlashCommand.slug}"`,
|
|
1595
|
+
messages: result.messages.map((msg) => ({
|
|
1596
|
+
role: msg.role,
|
|
1597
|
+
content: { type: "text", text: msg.content.text }
|
|
1598
|
+
}))
|
|
1599
|
+
};
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1602
|
+
throw new Error(`Error executing slash command "${promptName}": ${errorMessage}`);
|
|
1836
1603
|
}
|
|
1837
1604
|
}
|
|
1605
|
+
throw new Error(`Unknown prompt: ${promptName}. Use prompts_run to execute prompts.`);
|
|
1606
|
+
});
|
|
1607
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
1608
|
+
return {
|
|
1609
|
+
tools: [
|
|
1610
|
+
// Ticket tools
|
|
1611
|
+
...ticketTools.map((t) => ({
|
|
1612
|
+
name: t.name,
|
|
1613
|
+
description: t.description,
|
|
1614
|
+
inputSchema: t.inputSchema
|
|
1615
|
+
})),
|
|
1616
|
+
// Prompt tools (global)
|
|
1617
|
+
...promptTools.map((t) => ({
|
|
1618
|
+
name: t.name,
|
|
1619
|
+
description: t.description,
|
|
1620
|
+
inputSchema: t.inputSchema
|
|
1621
|
+
})),
|
|
1622
|
+
// Memory tools
|
|
1623
|
+
...memoryTools.map((t) => ({
|
|
1624
|
+
name: t.name,
|
|
1625
|
+
description: t.description,
|
|
1626
|
+
inputSchema: t.inputSchema
|
|
1627
|
+
})),
|
|
1628
|
+
// Per-prompt tools
|
|
1629
|
+
...dynamicPromptTools.map((pt) => ({
|
|
1630
|
+
name: pt.name,
|
|
1631
|
+
description: pt.description,
|
|
1632
|
+
inputSchema: { type: "object", properties: {}, required: [] }
|
|
1633
|
+
}))
|
|
1634
|
+
]
|
|
1635
|
+
};
|
|
1636
|
+
});
|
|
1637
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
1638
|
+
const toolName = request.params.name;
|
|
1639
|
+
const args = request.params.arguments;
|
|
1640
|
+
if (toolName === "prompts_run") {
|
|
1641
|
+
return handlePromptsRun(args, config, convexClient);
|
|
1642
|
+
}
|
|
1643
|
+
if (toolName === "prompts_list") {
|
|
1644
|
+
return handlePromptsList(args, config, convexClient, accountScopedPrompts);
|
|
1645
|
+
}
|
|
1646
|
+
if (toolName === "prompts_update") {
|
|
1647
|
+
return handlePromptsUpdate(args, config, convexClient);
|
|
1648
|
+
}
|
|
1649
|
+
if (toolName === "tickets_work") {
|
|
1650
|
+
return handleTicketsWork(args, config, convexClient, projectSlug);
|
|
1651
|
+
}
|
|
1652
|
+
if (toolName === "tickets_create") {
|
|
1653
|
+
return handleTicketsCreate(args, config, convexClient, projectSlug);
|
|
1654
|
+
}
|
|
1655
|
+
if (toolName === "tickets_close") {
|
|
1656
|
+
return handleTicketsClose(args, config, convexClient, projectSlug);
|
|
1657
|
+
}
|
|
1658
|
+
if (toolName === "tickets_pause") {
|
|
1659
|
+
return handleTicketsPause(args, config, convexClient, projectSlug);
|
|
1660
|
+
}
|
|
1661
|
+
if (toolName === "tickets_search") {
|
|
1662
|
+
return handleTicketsSearch(args, config, convexClient, projectSlug);
|
|
1663
|
+
}
|
|
1664
|
+
if (toolName === "tickets_get") {
|
|
1665
|
+
return handleTicketsGet(args, config, convexClient, projectSlug);
|
|
1666
|
+
}
|
|
1667
|
+
if (toolName === "tickets_list") {
|
|
1668
|
+
return handleTicketsList(args, config, convexClient, projectSlug);
|
|
1669
|
+
}
|
|
1670
|
+
if (toolName === "tickets_update") {
|
|
1671
|
+
return handleTicketsUpdate(args, config, convexClient, projectSlug);
|
|
1672
|
+
}
|
|
1673
|
+
if (toolName === "memories_load") {
|
|
1674
|
+
return handleMemoriesLoad(args, config, convexClient, projectSlug);
|
|
1675
|
+
}
|
|
1676
|
+
if (toolName === "memories_load_extreme") {
|
|
1677
|
+
return handleMemoriesLoadExtreme(args, config, convexClient, projectSlug);
|
|
1678
|
+
}
|
|
1679
|
+
if (toolName === "memories_add") {
|
|
1680
|
+
return handleMemoriesAdd(args, config, convexClient, projectSlug);
|
|
1681
|
+
}
|
|
1682
|
+
if (toolName === "memories_list_all") {
|
|
1683
|
+
return handleMemoriesListAll(args, config, convexClient, projectSlug);
|
|
1684
|
+
}
|
|
1838
1685
|
const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);
|
|
1839
1686
|
if (promptTool) {
|
|
1840
1687
|
try {
|
|
@@ -1844,9 +1691,7 @@ _Memory will be available in future context via [MEMORY_LOAD]._`;
|
|
|
1844
1691
|
convexClient
|
|
1845
1692
|
);
|
|
1846
1693
|
const promptText = result.messages.map((msg) => msg.content.text).join("\n\n");
|
|
1847
|
-
return {
|
|
1848
|
-
content: [{ type: "text", text: promptText }]
|
|
1849
|
-
};
|
|
1694
|
+
return { content: [{ type: "text", text: promptText }] };
|
|
1850
1695
|
} catch (error) {
|
|
1851
1696
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1852
1697
|
console.error(`[MCP] ${toolName} error:`, error);
|
|
@@ -1856,7 +1701,15 @@ _Memory will be available in future context via [MEMORY_LOAD]._`;
|
|
|
1856
1701
|
};
|
|
1857
1702
|
}
|
|
1858
1703
|
}
|
|
1859
|
-
|
|
1704
|
+
return {
|
|
1705
|
+
content: [
|
|
1706
|
+
{
|
|
1707
|
+
type: "text",
|
|
1708
|
+
text: `Unknown tool: ${toolName}. Use prompts_run to execute prompts by name, or check available tools.`
|
|
1709
|
+
}
|
|
1710
|
+
],
|
|
1711
|
+
isError: true
|
|
1712
|
+
};
|
|
1860
1713
|
});
|
|
1861
1714
|
const transport = new StdioServerTransport();
|
|
1862
1715
|
await server.connect(transport);
|
|
@@ -1872,28 +1725,63 @@ _Memory will be available in future context via [MEMORY_LOAD]._`;
|
|
|
1872
1725
|
return new Promise(() => {
|
|
1873
1726
|
});
|
|
1874
1727
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
}
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1728
|
+
function buildTicketPromptResponse(type, toolName, projectSlug) {
|
|
1729
|
+
let promptContent;
|
|
1730
|
+
let description;
|
|
1731
|
+
if (type === "work") {
|
|
1732
|
+
description = `Get work from the "${projectSlug}" project`;
|
|
1733
|
+
promptContent = `Get work from the "${projectSlug}" project.
|
|
1734
|
+
|
|
1735
|
+
Call the \`tickets_work\` tool to get the next ticket from the open queue.
|
|
1736
|
+
|
|
1737
|
+
You can also specify a ticket: /ppm:tickets_work <number-or-slug>
|
|
1738
|
+
|
|
1739
|
+
This unified command handles both opening new tickets and resuming in-progress work.`;
|
|
1740
|
+
} else if (type === "create") {
|
|
1741
|
+
description = `Create a new ticket in "${projectSlug}"`;
|
|
1742
|
+
promptContent = `Create a new ticket in the "${projectSlug}" project queue.
|
|
1743
|
+
|
|
1744
|
+
## How This Works
|
|
1745
|
+
The user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.
|
|
1746
|
+
|
|
1747
|
+
## Instructions
|
|
1748
|
+
1. **Read the user's request** - they may reference a file, ask you to summarize a session, or describe what they want
|
|
1749
|
+
2. **Process the input** - read files, extract relevant sections, summarize as needed
|
|
1750
|
+
3. **Generate ticket content** with a clear, descriptive title as the first line
|
|
1751
|
+
4. **Call the tool** with the generated content
|
|
1752
|
+
|
|
1753
|
+
## Content Format
|
|
1754
|
+
\`\`\`
|
|
1755
|
+
[Clear descriptive title - this becomes the slug]
|
|
1756
|
+
|
|
1757
|
+
[Body content - tasks, description, context, etc.]
|
|
1758
|
+
\`\`\`
|
|
1759
|
+
|
|
1760
|
+
## Examples
|
|
1761
|
+
- "create a ticket from /path/to/plan.md" \u2192 Read file, use as ticket content
|
|
1762
|
+
- "summarize our brainstorm into a ticket" \u2192 Extract key points from conversation
|
|
1763
|
+
- "create a ticket for the auth bug we discussed" \u2192 Generate from session context
|
|
1764
|
+
- "just the tasks from this file" \u2192 Extract only task items
|
|
1765
|
+
|
|
1766
|
+
Call the \`${toolName}\` tool with the final generated content.`;
|
|
1767
|
+
} else if (type === "close") {
|
|
1768
|
+
description = `Close a working ticket in "${projectSlug}"`;
|
|
1769
|
+
promptContent = `Close a working ticket in the "${projectSlug}" project.
|
|
1770
|
+
|
|
1771
|
+
Call the \`${toolName}\` tool with the ticket number or slug.`;
|
|
1772
|
+
} else {
|
|
1773
|
+
description = `Use the ${toolName} tool`;
|
|
1774
|
+
promptContent = `Use the \`${toolName}\` tool.`;
|
|
1896
1775
|
}
|
|
1776
|
+
return {
|
|
1777
|
+
description,
|
|
1778
|
+
messages: [
|
|
1779
|
+
{
|
|
1780
|
+
role: "user",
|
|
1781
|
+
content: { type: "text", text: promptContent }
|
|
1782
|
+
}
|
|
1783
|
+
]
|
|
1784
|
+
};
|
|
1897
1785
|
}
|
|
1898
1786
|
|
|
1899
1787
|
// src/index.ts
|