@promptprojectmanager/mcp-server 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +516 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1010 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1010 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import minimist from "minimist";
|
|
5
|
+
|
|
6
|
+
// src/convex-client.ts
|
|
7
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
8
|
+
var PROD_URL = "https://trustworthy-squirrel-735.convex.cloud";
|
|
9
|
+
var DEV_URL = "https://hallowed-shrimp-344.convex.cloud";
|
|
10
|
+
function createConvexClient(isDev) {
|
|
11
|
+
const url = isDev ? DEV_URL : PROD_URL;
|
|
12
|
+
return new ConvexHttpClient(url);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/server.ts
|
|
16
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import {
|
|
19
|
+
CallToolRequestSchema,
|
|
20
|
+
GetPromptRequestSchema,
|
|
21
|
+
ListPromptsRequestSchema,
|
|
22
|
+
ListToolsRequestSchema
|
|
23
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
24
|
+
|
|
25
|
+
// src/prompt-builder.ts
|
|
26
|
+
function sanitizeForMcp(str) {
|
|
27
|
+
return str.trim().toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "").trim();
|
|
28
|
+
}
|
|
29
|
+
function buildPromptName(prompt) {
|
|
30
|
+
const workspace = sanitizeForMcp(prompt.workspaceSlug || "default") || "default";
|
|
31
|
+
const promptSlug = sanitizeForMcp(prompt.slug || "unknown") || "unknown";
|
|
32
|
+
return `${workspace}:${promptSlug}`.trim();
|
|
33
|
+
}
|
|
34
|
+
function buildPromptSchema(prompt) {
|
|
35
|
+
return {
|
|
36
|
+
name: buildPromptName(prompt),
|
|
37
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
38
|
+
arguments: []
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function buildPromptHandler(prompt, config, convexClient) {
|
|
42
|
+
return async () => {
|
|
43
|
+
let flattenedPrompt = prompt.flattenedPrompt;
|
|
44
|
+
let promptName = prompt.name;
|
|
45
|
+
try {
|
|
46
|
+
let freshPrompts = await convexClient.query("mcp_prompts:getMcpPrompts", {
|
|
47
|
+
apiKey: config.apiKey
|
|
48
|
+
});
|
|
49
|
+
if (config.selectedWorkspaces.length > 0) {
|
|
50
|
+
freshPrompts = freshPrompts.filter(
|
|
51
|
+
(p) => p.workspaceSlug && config.selectedWorkspaces.includes(p.workspaceSlug)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const freshPrompt = freshPrompts.find((p) => p.slug === prompt.slug && p.workspaceSlug === prompt.workspaceSlug);
|
|
55
|
+
if (freshPrompt) {
|
|
56
|
+
flattenedPrompt = freshPrompt.flattenedPrompt;
|
|
57
|
+
promptName = freshPrompt.name;
|
|
58
|
+
console.error(`[MCP] Fetched fresh data for: ${buildPromptName(prompt)}`);
|
|
59
|
+
} else {
|
|
60
|
+
console.error(
|
|
61
|
+
`[MCP] WARNING: Prompt "${prompt.slug}" not found in fresh fetch, using cached version`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.error(
|
|
66
|
+
`[MCP] WARNING: Failed to fetch fresh data, using cached version: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
if (!flattenedPrompt) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Prompt "${promptName}" has no flattened content. Please re-save the prompt in ContextFS to regenerate it.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
console.error(`[MCP] Executed prompt: ${buildPromptName(prompt)}`);
|
|
75
|
+
return {
|
|
76
|
+
messages: [
|
|
77
|
+
{
|
|
78
|
+
role: "user",
|
|
79
|
+
content: {
|
|
80
|
+
type: "text",
|
|
81
|
+
text: flattenedPrompt
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/server.ts
|
|
90
|
+
var SYSTEM_TOOLS = [
|
|
91
|
+
// User-facing slash command: /ppm:system:optimize
|
|
92
|
+
{
|
|
93
|
+
name: "system:optimize",
|
|
94
|
+
description: "Evaluate a prompt and submit optimization suggestions to Prompt Project Manager",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {}
|
|
98
|
+
},
|
|
99
|
+
isSlashCommand: true,
|
|
100
|
+
promptContent: `# Optimize
|
|
101
|
+
|
|
102
|
+
Evaluate the prompt you just used and submit optimization suggestions.
|
|
103
|
+
|
|
104
|
+
## Instructions
|
|
105
|
+
|
|
106
|
+
1. **Identify the prompt** from the PREVIOUS tool call in this conversation:
|
|
107
|
+
- Look at the last \`/ppm:*\` slash command or MCP tool you called
|
|
108
|
+
- The format is \`/ppm:workspace:prompt\` (e.g., \`/ppm:test:joke\` means workspace=\`test\`, prompt=\`joke\`)
|
|
109
|
+
- Extract the workspace slug (before the colon) and prompt slug (after the colon)
|
|
110
|
+
- **CRITICAL**: Use the EXACT slugs from the tool name, do NOT guess or invent slugs
|
|
111
|
+
|
|
112
|
+
2. **Evaluate** for issues in these categories:
|
|
113
|
+
- **clarity**: Is the language clear and unambiguous?
|
|
114
|
+
- **specificity**: Are instructions specific enough?
|
|
115
|
+
- **structure**: Is the organization logical?
|
|
116
|
+
- **completeness**: Are there missing instructions?
|
|
117
|
+
- **efficiency**: Is there redundancy to remove?
|
|
118
|
+
|
|
119
|
+
3. **Call the submission tool** (\`system:optimize-prompt\`) with:
|
|
120
|
+
- \`promptSlug\`: The prompt's slug
|
|
121
|
+
- \`workspaceSlug\`: The workspace slug
|
|
122
|
+
- \`suggestions\`: Array of issues found, each with category, priority, issue, originalText (optional), suggestedText
|
|
123
|
+
|
|
124
|
+
4. The tool will submit and confirm the count
|
|
125
|
+
|
|
126
|
+
## Scope of Analysis
|
|
127
|
+
|
|
128
|
+
**CRITICAL - READ CAREFULLY**:
|
|
129
|
+
|
|
130
|
+
The flattened prompt contains TWO parts:
|
|
131
|
+
1. **MAIN PROMPT CONTENT** (at the top) - This is what you should analyze
|
|
132
|
+
2. **Technical Reference Footer** (at the bottom) - **COMPLETELY IGNORE THIS ENTIRE SECTION**
|
|
133
|
+
|
|
134
|
+
**The Technical Reference Footer starts with \`# Technical Reference\` and includes:**
|
|
135
|
+
- \`## Variable Definitions:\` - Contains variable names, types, and descriptions
|
|
136
|
+
- \`## Snippet Definitions:\` - Contains snippet content
|
|
137
|
+
- \`## CRITICAL RULES:\` - System rules
|
|
138
|
+
- \`## Runtime Variables:\` - Runtime placeholders
|
|
139
|
+
|
|
140
|
+
**IGNORE ALL CONTENT IN THE TECHNICAL REFERENCE FOOTER** - even if it contains typos, grammar issues, or formatting problems. These sections are auto-generated from user-defined resources and should NOT be optimized through this tool.
|
|
141
|
+
|
|
142
|
+
**Only analyze and suggest changes for content BEFORE the \`# Technical Reference\` heading.**
|
|
143
|
+
|
|
144
|
+
## User Feedback
|
|
145
|
+
|
|
146
|
+
If the user provided feedback about issues they experienced, it appears below:
|
|
147
|
+
|
|
148
|
+
**Feedback**: $ARGUMENTS
|
|
149
|
+
|
|
150
|
+
When feedback is provided:
|
|
151
|
+
- Treat it as context about real problems encountered during use
|
|
152
|
+
- Prioritize suggestions that address the described issues
|
|
153
|
+
- Look for patterns in the prompt that could cause the mentioned problem
|
|
154
|
+
|
|
155
|
+
## Important
|
|
156
|
+
|
|
157
|
+
- Don't suggest changes to variable references like \`[VARIABLE_NAME]\` - those are intentional
|
|
158
|
+
- The user will review suggestions in Prompt Project Manager web UI`
|
|
159
|
+
},
|
|
160
|
+
// User-facing slash command: /ppm:system:feedback
|
|
161
|
+
{
|
|
162
|
+
name: "system:feedback",
|
|
163
|
+
description: "Submit user feedback about a prompt to create an optimization suggestion",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {}
|
|
167
|
+
},
|
|
168
|
+
isSlashCommand: true,
|
|
169
|
+
promptContent: `# Feedback
|
|
170
|
+
|
|
171
|
+
Submit your feedback about a prompt to create an optimization suggestion.
|
|
172
|
+
|
|
173
|
+
## Instructions
|
|
174
|
+
|
|
175
|
+
1. **Identify the prompt** from the PREVIOUS tool call in this conversation:
|
|
176
|
+
- Look at the last \`/ppm:*\` slash command or MCP tool you called
|
|
177
|
+
- The format is \`/ppm:workspace:prompt\` (e.g., \`/ppm:test:joke\` means workspace=\`test\`, prompt=\`joke\`)
|
|
178
|
+
- Extract the workspace slug (before the colon) and prompt slug (after the colon)
|
|
179
|
+
- **CRITICAL**: Use the EXACT slugs from the tool name, do NOT guess or invent slugs
|
|
180
|
+
|
|
181
|
+
2. **Read the user's feedback** from $ARGUMENTS below
|
|
182
|
+
|
|
183
|
+
3. **Structure the feedback** into a proper optimization suggestion:
|
|
184
|
+
- Determine the most appropriate category: clarity, specificity, structure, completeness, or efficiency
|
|
185
|
+
- Assess priority: "high" (critical issue), "medium" (noticeable problem), "low" (minor improvement)
|
|
186
|
+
- Identify the specific text to change (originalText) if applicable
|
|
187
|
+
- Write a clear, actionable fix (suggestedText)
|
|
188
|
+
|
|
189
|
+
4. **Call the submission tool** (\`system:optimize-prompt\`) with:
|
|
190
|
+
- \`promptSlug\`: The prompt's slug
|
|
191
|
+
- \`workspaceSlug\`: The workspace slug
|
|
192
|
+
- \`suggestions\`: Array containing a SINGLE suggestion based on the user's feedback
|
|
193
|
+
|
|
194
|
+
## User Feedback
|
|
195
|
+
|
|
196
|
+
**$ARGUMENTS**
|
|
197
|
+
|
|
198
|
+
## Scope of Analysis
|
|
199
|
+
|
|
200
|
+
When analyzing the prompt to address the feedback:
|
|
201
|
+
- **ONLY** focus on content BEFORE the \`# Technical Reference\` heading
|
|
202
|
+
- **IGNORE** the Technical Reference Footer (Variable Definitions, Snippet Definitions, CRITICAL RULES, Runtime Variables)
|
|
203
|
+
- Don't suggest changes to variable references like \`[VARIABLE_NAME]\` - those are intentional
|
|
204
|
+
|
|
205
|
+
## Example
|
|
206
|
+
|
|
207
|
+
If the user runs:
|
|
208
|
+
\`/feedback the output format should specify JSON not markdown\`
|
|
209
|
+
|
|
210
|
+
You should:
|
|
211
|
+
1. Find the prompt that was used earlier in the session
|
|
212
|
+
2. Create a suggestion like:
|
|
213
|
+
\`\`\`json
|
|
214
|
+
{
|
|
215
|
+
"category": "specificity",
|
|
216
|
+
"priority": "medium",
|
|
217
|
+
"issue": "Output format should specify JSON instead of markdown",
|
|
218
|
+
"originalText": "Output format: markdown",
|
|
219
|
+
"suggestedText": "Output format: JSON with fields: { result: string, status: string }"
|
|
220
|
+
}
|
|
221
|
+
\`\`\`
|
|
222
|
+
3. Submit via \`system:optimize-prompt\` tool
|
|
223
|
+
|
|
224
|
+
## Important
|
|
225
|
+
|
|
226
|
+
- Create exactly ONE suggestion from the user's feedback (not multiple)
|
|
227
|
+
- The suggestion will be reviewed in Prompt Project Manager web UI
|
|
228
|
+
- Use your understanding of the session to interpret vague feedback into actionable fixes`
|
|
229
|
+
},
|
|
230
|
+
// Utility tool: /ppm:system:prompts
|
|
231
|
+
{
|
|
232
|
+
name: "system:prompts",
|
|
233
|
+
description: "List all available prompts from Prompt Project Manager",
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: "object",
|
|
236
|
+
properties: {
|
|
237
|
+
search: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Optional search term to filter prompts by name or description (case-insensitive)"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
isSlashCommand: true,
|
|
244
|
+
promptContent: `# List Prompts
|
|
245
|
+
|
|
246
|
+
This is a utility tool. Call it directly as a tool to list all available prompts.
|
|
247
|
+
|
|
248
|
+
Example: Use the \`system:prompts\` tool with optional \`search\` parameter to filter results.`
|
|
249
|
+
},
|
|
250
|
+
// Backend submission tool (called by system:optimize and system:feedback)
|
|
251
|
+
// NOT a slash command - only available as a tool for AI to call
|
|
252
|
+
{
|
|
253
|
+
name: "system:optimize-prompt",
|
|
254
|
+
description: "Submit optimization suggestions to Prompt Project Manager (internal tool)",
|
|
255
|
+
inputSchema: {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
promptSlug: {
|
|
259
|
+
type: "string",
|
|
260
|
+
description: "The slug of the prompt to optimize (e.g., 'code-review')"
|
|
261
|
+
},
|
|
262
|
+
workspaceSlug: {
|
|
263
|
+
type: "string",
|
|
264
|
+
description: "The workspace containing the prompt (e.g., 'personal')"
|
|
265
|
+
},
|
|
266
|
+
suggestions: {
|
|
267
|
+
type: "array",
|
|
268
|
+
description: "Array of optimization suggestions",
|
|
269
|
+
items: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
category: {
|
|
273
|
+
type: "string",
|
|
274
|
+
description: "Category: 'clarity', 'specificity', 'structure', 'completeness', 'efficiency'"
|
|
275
|
+
},
|
|
276
|
+
priority: {
|
|
277
|
+
type: "string",
|
|
278
|
+
enum: ["high", "medium", "low"],
|
|
279
|
+
description: "Priority level"
|
|
280
|
+
},
|
|
281
|
+
issue: {
|
|
282
|
+
type: "string",
|
|
283
|
+
description: "Brief description of the problem"
|
|
284
|
+
},
|
|
285
|
+
originalText: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description: "Optional: The specific text that should be changed"
|
|
288
|
+
},
|
|
289
|
+
suggestedText: {
|
|
290
|
+
type: "string",
|
|
291
|
+
description: "The improved text"
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
required: ["category", "priority", "issue", "suggestedText"]
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
required: ["promptSlug", "workspaceSlug", "suggestions"]
|
|
299
|
+
},
|
|
300
|
+
isSlashCommand: false,
|
|
301
|
+
// Tool only - not a slash command
|
|
302
|
+
promptContent: `# Submit Optimization Suggestions
|
|
303
|
+
|
|
304
|
+
This is an internal submission tool. Do not call it directly.
|
|
305
|
+
|
|
306
|
+
Use \`/ppm:system:optimize\` for full prompt evaluation or \`/ppm:system:feedback\` to submit specific feedback.`
|
|
307
|
+
}
|
|
308
|
+
];
|
|
309
|
+
async function startServer(config, convexClient) {
|
|
310
|
+
console.error("[MCP] Validating API key...");
|
|
311
|
+
const validation = await validateApiKey(convexClient, config.apiKey);
|
|
312
|
+
if (!validation.valid) {
|
|
313
|
+
throw new Error(`Invalid API key: ${validation.error}`);
|
|
314
|
+
}
|
|
315
|
+
console.error(`[MCP] API key validated for user: ${validation.userId}`);
|
|
316
|
+
console.error("[MCP] Fetching exposed prompts...");
|
|
317
|
+
const prompts = await fetchMcpPrompts(convexClient, config.apiKey, config.selectedWorkspaces);
|
|
318
|
+
console.error(`[MCP] Found ${prompts.length} exposed prompts`);
|
|
319
|
+
if (prompts.length === 0) {
|
|
320
|
+
if (config.selectedWorkspaces.length > 0) {
|
|
321
|
+
console.error(
|
|
322
|
+
`[MCP] WARNING: No prompts found in workspaces: ${config.selectedWorkspaces.join(", ")}. Check that these workspace slugs exist and contain prompts.`
|
|
323
|
+
);
|
|
324
|
+
} else {
|
|
325
|
+
console.error(
|
|
326
|
+
"[MCP] WARNING: No prompts found. Create some prompts in Prompt Project Manager to expose them via MCP."
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
const validPrompts = prompts.filter((p) => {
|
|
331
|
+
if (!p.flattenedPrompt) {
|
|
332
|
+
console.error(
|
|
333
|
+
`[MCP] WARNING: Skipping prompt "${p.name}" (${p.slug}) - missing flattened content. Re-save in ContextFS to regenerate.`
|
|
334
|
+
);
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
});
|
|
339
|
+
if (config.selectedWorkspaces.length > 0) {
|
|
340
|
+
console.error(`[MCP] Workspace filter: ${config.selectedWorkspaces.join(", ")}`);
|
|
341
|
+
} else {
|
|
342
|
+
console.error(`[MCP] Workspace filter: ALL (no --workspaces specified)`);
|
|
343
|
+
}
|
|
344
|
+
console.error(`[MCP] Registering ${validPrompts.length} prompts...`);
|
|
345
|
+
const workspaceSlugs = new Set(validPrompts.map((p) => p.workspaceSlug).filter((s) => !!s));
|
|
346
|
+
const dynamicTicketTools = [];
|
|
347
|
+
for (const workspaceSlug of workspaceSlugs) {
|
|
348
|
+
dynamicTicketTools.push({
|
|
349
|
+
name: `${workspaceSlug}:tickets:list`,
|
|
350
|
+
description: `List pending tickets in the "${workspaceSlug}" workspace queue`,
|
|
351
|
+
workspaceSlug,
|
|
352
|
+
type: "list"
|
|
353
|
+
});
|
|
354
|
+
dynamicTicketTools.push({
|
|
355
|
+
name: `${workspaceSlug}:tickets:next`,
|
|
356
|
+
description: `Execute and remove the next ticket from the "${workspaceSlug}" workspace queue. Optionally specify a ticket number or slug to execute a specific ticket.`,
|
|
357
|
+
workspaceSlug,
|
|
358
|
+
type: "next"
|
|
359
|
+
});
|
|
360
|
+
dynamicTicketTools.push({
|
|
361
|
+
name: `${workspaceSlug}:tickets:get`,
|
|
362
|
+
description: `Get a specific ticket from the "${workspaceSlug}" workspace by number or slug`,
|
|
363
|
+
workspaceSlug,
|
|
364
|
+
type: "get"
|
|
365
|
+
});
|
|
366
|
+
dynamicTicketTools.push({
|
|
367
|
+
name: `${workspaceSlug}:tickets:create`,
|
|
368
|
+
description: `Create a new ticket in the "${workspaceSlug}" workspace queue`,
|
|
369
|
+
workspaceSlug,
|
|
370
|
+
type: "create"
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${workspaceSlugs.size} workspace(s)...`);
|
|
374
|
+
const promptNames = /* @__PURE__ */ new Set();
|
|
375
|
+
const duplicates = [];
|
|
376
|
+
validPrompts.forEach((p) => {
|
|
377
|
+
const name = buildPromptName(p);
|
|
378
|
+
if (promptNames.has(name)) {
|
|
379
|
+
duplicates.push(name);
|
|
380
|
+
}
|
|
381
|
+
promptNames.add(name);
|
|
382
|
+
});
|
|
383
|
+
if (duplicates.length > 0) {
|
|
384
|
+
console.error(
|
|
385
|
+
`[MCP] WARNING: Duplicate prompt names detected: ${duplicates.join(", ")}. Only the first occurrence will be registered.`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
const server = new Server(
|
|
389
|
+
{
|
|
390
|
+
name: "ppm-mcp-server",
|
|
391
|
+
version: "2.8.1"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
capabilities: {
|
|
395
|
+
prompts: {},
|
|
396
|
+
tools: {}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
);
|
|
400
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
401
|
+
const uniquePrompts = Array.from(
|
|
402
|
+
new Map(validPrompts.map((p) => [buildPromptName(p), p])).values()
|
|
403
|
+
);
|
|
404
|
+
const systemPromptSchemas = SYSTEM_TOOLS.filter((st) => st.isSlashCommand).map((st) => ({
|
|
405
|
+
name: st.name,
|
|
406
|
+
description: st.description
|
|
407
|
+
}));
|
|
408
|
+
const ticketPromptSchemas = dynamicTicketTools.filter((tt) => tt.type !== "get").map((tt) => ({
|
|
409
|
+
name: tt.name,
|
|
410
|
+
description: tt.description
|
|
411
|
+
}));
|
|
412
|
+
return {
|
|
413
|
+
prompts: [
|
|
414
|
+
...systemPromptSchemas,
|
|
415
|
+
...ticketPromptSchemas,
|
|
416
|
+
...uniquePrompts.map(buildPromptSchema)
|
|
417
|
+
]
|
|
418
|
+
};
|
|
419
|
+
});
|
|
420
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
421
|
+
const promptName = request.params.name;
|
|
422
|
+
const systemTool = SYSTEM_TOOLS.find((st) => st.name === promptName);
|
|
423
|
+
if (systemTool) {
|
|
424
|
+
return {
|
|
425
|
+
description: systemTool.description,
|
|
426
|
+
messages: [
|
|
427
|
+
{
|
|
428
|
+
role: "user",
|
|
429
|
+
content: {
|
|
430
|
+
type: "text",
|
|
431
|
+
text: systemTool.promptContent
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);
|
|
438
|
+
const ticketNextMatch = promptName.match(/^(.+):tickets:next\s+(.+)$/);
|
|
439
|
+
if (ticketTool || ticketNextMatch) {
|
|
440
|
+
let promptContent;
|
|
441
|
+
let description;
|
|
442
|
+
if (ticketNextMatch) {
|
|
443
|
+
const workspaceSlug = ticketNextMatch[1];
|
|
444
|
+
const ticketArg = ticketNextMatch[2].trim();
|
|
445
|
+
description = `Execute ticket "${ticketArg}" from "${workspaceSlug}"`;
|
|
446
|
+
promptContent = `Execute ticket "${ticketArg}" from the "${workspaceSlug}" workspace queue.
|
|
447
|
+
|
|
448
|
+
Call the \`${workspaceSlug}:tickets:next\` tool with ticketSlug: "${ticketArg}" to get and close the specified ticket.`;
|
|
449
|
+
} else if (ticketTool.type === "list") {
|
|
450
|
+
description = ticketTool.description;
|
|
451
|
+
promptContent = `List all pending tickets in the "${ticketTool.workspaceSlug}" workspace queue.
|
|
452
|
+
|
|
453
|
+
Call the \`${ticketTool.name}\` tool to get the list.`;
|
|
454
|
+
} else if (ticketTool.type === "next") {
|
|
455
|
+
description = ticketTool.description;
|
|
456
|
+
promptContent = `Get and execute the next ticket from the "${ticketTool.workspaceSlug}" workspace queue.
|
|
457
|
+
|
|
458
|
+
Call the \`${ticketTool.name}\` tool to get the next ticket. This will atomically close the ticket and return its content.
|
|
459
|
+
|
|
460
|
+
You can also specify a ticket: /ppm:${ticketTool.workspaceSlug}:tickets:next <number-or-slug>`;
|
|
461
|
+
} else if (ticketTool.type === "create") {
|
|
462
|
+
description = ticketTool.description;
|
|
463
|
+
promptContent = `Create a new ticket in the "${ticketTool.workspaceSlug}" workspace queue.
|
|
464
|
+
|
|
465
|
+
## How This Works
|
|
466
|
+
The user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.
|
|
467
|
+
|
|
468
|
+
## Instructions
|
|
469
|
+
1. **Read the user's request** - they may reference a file, ask you to summarize a session, or describe what they want
|
|
470
|
+
2. **Process the input** - read files, extract relevant sections, summarize as needed
|
|
471
|
+
3. **Generate ticket content** with a clear, descriptive title as the first line
|
|
472
|
+
4. **Call the tool** with the generated content
|
|
473
|
+
|
|
474
|
+
## Content Format
|
|
475
|
+
\`\`\`
|
|
476
|
+
[Clear descriptive title - this becomes the slug]
|
|
477
|
+
|
|
478
|
+
[Body content - tasks, description, context, etc.]
|
|
479
|
+
\`\`\`
|
|
480
|
+
|
|
481
|
+
## Examples
|
|
482
|
+
- "create a ticket from /path/to/plan.md" \u2192 Read file, use as ticket content
|
|
483
|
+
- "summarize our brainstorm into a ticket" \u2192 Extract key points from conversation
|
|
484
|
+
- "create a ticket for the auth bug we discussed" \u2192 Generate from session context
|
|
485
|
+
- "just the tasks from this file" \u2192 Extract only task items
|
|
486
|
+
|
|
487
|
+
Call the \`${ticketTool.name}\` tool with the final generated content.`;
|
|
488
|
+
} else {
|
|
489
|
+
description = ticketTool.description;
|
|
490
|
+
promptContent = `This command is not available as a slash command. Use the \`${ticketTool.name}\` tool directly to inspect a ticket.`;
|
|
491
|
+
}
|
|
492
|
+
return {
|
|
493
|
+
description,
|
|
494
|
+
messages: [
|
|
495
|
+
{
|
|
496
|
+
role: "user",
|
|
497
|
+
content: {
|
|
498
|
+
type: "text",
|
|
499
|
+
text: promptContent
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
]
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const prompt = validPrompts.find((p) => buildPromptName(p) === promptName);
|
|
506
|
+
if (!prompt) {
|
|
507
|
+
throw new Error(`Unknown prompt: ${promptName}`);
|
|
508
|
+
}
|
|
509
|
+
const handler = buildPromptHandler(prompt, config, convexClient);
|
|
510
|
+
return await handler();
|
|
511
|
+
});
|
|
512
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
513
|
+
const uniquePrompts = Array.from(
|
|
514
|
+
new Map(validPrompts.map((p) => [buildPromptName(p), p])).values()
|
|
515
|
+
);
|
|
516
|
+
const tools = [
|
|
517
|
+
// System tools with full input schemas
|
|
518
|
+
...SYSTEM_TOOLS.map((st) => ({
|
|
519
|
+
name: st.name,
|
|
520
|
+
description: st.description,
|
|
521
|
+
inputSchema: st.inputSchema
|
|
522
|
+
})),
|
|
523
|
+
// Dynamic ticket tools per workspace (Ticket 135, 149)
|
|
524
|
+
...dynamicTicketTools.map((tt) => ({
|
|
525
|
+
name: tt.name,
|
|
526
|
+
description: tt.description,
|
|
527
|
+
inputSchema: tt.type === "get" ? {
|
|
528
|
+
type: "object",
|
|
529
|
+
properties: {
|
|
530
|
+
ticketSlug: {
|
|
531
|
+
type: "string",
|
|
532
|
+
description: "Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')"
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
required: ["ticketSlug"]
|
|
536
|
+
} : tt.type === "next" ? {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
ticketSlug: {
|
|
540
|
+
type: "string",
|
|
541
|
+
description: "Optional: Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth'). If not provided, executes the next ticket in queue."
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
required: []
|
|
545
|
+
} : tt.type === "create" ? {
|
|
546
|
+
type: "object",
|
|
547
|
+
properties: {
|
|
548
|
+
content: {
|
|
549
|
+
type: "string",
|
|
550
|
+
description: "The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed."
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
required: ["content"]
|
|
554
|
+
} : {
|
|
555
|
+
type: "object",
|
|
556
|
+
properties: {},
|
|
557
|
+
required: []
|
|
558
|
+
}
|
|
559
|
+
})),
|
|
560
|
+
// Each user prompt becomes a discoverable tool
|
|
561
|
+
...uniquePrompts.map((prompt) => ({
|
|
562
|
+
name: buildPromptName(prompt),
|
|
563
|
+
description: prompt.description || `Prompt: ${prompt.name}`,
|
|
564
|
+
inputSchema: {
|
|
565
|
+
type: "object",
|
|
566
|
+
properties: {},
|
|
567
|
+
required: []
|
|
568
|
+
}
|
|
569
|
+
}))
|
|
570
|
+
];
|
|
571
|
+
return { tools };
|
|
572
|
+
});
|
|
573
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
574
|
+
const toolName = request.params.name;
|
|
575
|
+
if (toolName === "system:prompts") {
|
|
576
|
+
const searchTerm = request.params.arguments?.search;
|
|
577
|
+
const uniquePrompts = Array.from(
|
|
578
|
+
new Map(validPrompts.map((p) => [buildPromptName(p), p])).values()
|
|
579
|
+
);
|
|
580
|
+
let filteredSystemTools = SYSTEM_TOOLS;
|
|
581
|
+
if (searchTerm) {
|
|
582
|
+
const lowerSearch = searchTerm.toLowerCase();
|
|
583
|
+
filteredSystemTools = SYSTEM_TOOLS.filter(
|
|
584
|
+
(st) => st.name.toLowerCase().includes(lowerSearch) || st.description.toLowerCase().includes(lowerSearch)
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
let filteredPrompts = uniquePrompts;
|
|
588
|
+
if (searchTerm) {
|
|
589
|
+
const lowerSearch = searchTerm.toLowerCase();
|
|
590
|
+
filteredPrompts = uniquePrompts.filter(
|
|
591
|
+
(p) => buildPromptName(p).toLowerCase().includes(lowerSearch) || p.name.toLowerCase().includes(lowerSearch) || p.description?.toLowerCase().includes(lowerSearch)
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
filteredPrompts.sort((a, b) => {
|
|
595
|
+
const aName = buildPromptName(a);
|
|
596
|
+
const bName = buildPromptName(b);
|
|
597
|
+
return aName.localeCompare(bName);
|
|
598
|
+
});
|
|
599
|
+
const systemToolList = filteredSystemTools.map((st) => `\u2022 ${st.name}
|
|
600
|
+
Type: System
|
|
601
|
+
Description: ${st.description}`).join("\n\n");
|
|
602
|
+
const userPromptList = filteredPrompts.map((p) => {
|
|
603
|
+
const name = buildPromptName(p);
|
|
604
|
+
const workspace = p.workspaceSlug || "unknown";
|
|
605
|
+
const desc = p.description || "No description";
|
|
606
|
+
return `\u2022 ${name}
|
|
607
|
+
Workspace: ${workspace}
|
|
608
|
+
Description: ${desc}`;
|
|
609
|
+
}).join("\n\n");
|
|
610
|
+
const totalCount = filteredSystemTools.length + filteredPrompts.length;
|
|
611
|
+
const summary = searchTerm ? `Found ${totalCount} prompts matching "${searchTerm}":` : `Available prompts (${totalCount} total):`;
|
|
612
|
+
const sections = [];
|
|
613
|
+
if (systemToolList) {
|
|
614
|
+
sections.push(`**System Tools:**
|
|
615
|
+
|
|
616
|
+
${systemToolList}`);
|
|
617
|
+
}
|
|
618
|
+
if (userPromptList) {
|
|
619
|
+
sections.push(`**User Prompts:**
|
|
620
|
+
|
|
621
|
+
${userPromptList}`);
|
|
622
|
+
}
|
|
623
|
+
return {
|
|
624
|
+
content: [
|
|
625
|
+
{
|
|
626
|
+
type: "text",
|
|
627
|
+
text: `${summary}
|
|
628
|
+
|
|
629
|
+
${sections.join("\n\n") || "No prompts found."}`
|
|
630
|
+
}
|
|
631
|
+
]
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (toolName === "system:optimize-prompt") {
|
|
635
|
+
const input = request.params.arguments;
|
|
636
|
+
if (!input.promptSlug || !input.workspaceSlug || !input.suggestions) {
|
|
637
|
+
return {
|
|
638
|
+
content: [
|
|
639
|
+
{
|
|
640
|
+
type: "text",
|
|
641
|
+
text: "Error: Missing required fields. Please provide promptSlug, workspaceSlug, and suggestions."
|
|
642
|
+
}
|
|
643
|
+
],
|
|
644
|
+
isError: true
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (!Array.isArray(input.suggestions) || input.suggestions.length === 0) {
|
|
648
|
+
return {
|
|
649
|
+
content: [
|
|
650
|
+
{
|
|
651
|
+
type: "text",
|
|
652
|
+
text: "Error: suggestions must be a non-empty array."
|
|
653
|
+
}
|
|
654
|
+
],
|
|
655
|
+
isError: true
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
try {
|
|
659
|
+
const result2 = await convexClient.mutation(
|
|
660
|
+
"prompts:addOptimizationSuggestions",
|
|
661
|
+
{
|
|
662
|
+
apiKey: config.apiKey,
|
|
663
|
+
workspaceSlug: input.workspaceSlug,
|
|
664
|
+
promptSlug: input.promptSlug,
|
|
665
|
+
suggestions: input.suggestions
|
|
666
|
+
}
|
|
667
|
+
);
|
|
668
|
+
return {
|
|
669
|
+
content: [
|
|
670
|
+
{
|
|
671
|
+
type: "text",
|
|
672
|
+
text: `Submitted ${result2.addedCount} optimization suggestion(s) for prompt "${input.promptSlug}" in workspace "${input.workspaceSlug}". Review and apply them in Prompt Project Manager.`
|
|
673
|
+
}
|
|
674
|
+
]
|
|
675
|
+
};
|
|
676
|
+
} catch (error) {
|
|
677
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
678
|
+
console.error(`[MCP] system:optimize-prompt error:`, error);
|
|
679
|
+
return {
|
|
680
|
+
content: [
|
|
681
|
+
{
|
|
682
|
+
type: "text",
|
|
683
|
+
text: `Error submitting optimization suggestions: ${errorMessage}`
|
|
684
|
+
}
|
|
685
|
+
],
|
|
686
|
+
isError: true
|
|
687
|
+
};
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
const ticketTool = dynamicTicketTools.find((tt) => tt.name === toolName);
|
|
691
|
+
if (ticketTool) {
|
|
692
|
+
if (ticketTool.type === "list") {
|
|
693
|
+
try {
|
|
694
|
+
const tickets = await convexClient.query(
|
|
695
|
+
"mcp_tickets:getMcpTickets",
|
|
696
|
+
{
|
|
697
|
+
apiKey: config.apiKey,
|
|
698
|
+
workspaceSlug: ticketTool.workspaceSlug
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
if (tickets.length === 0) {
|
|
702
|
+
return {
|
|
703
|
+
content: [
|
|
704
|
+
{
|
|
705
|
+
type: "text",
|
|
706
|
+
text: `No pending tickets in workspace "${ticketTool.workspaceSlug}".`
|
|
707
|
+
}
|
|
708
|
+
]
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
const ticketList = tickets.map((t) => `${t.position}. [${t.slug}] ${t.preview}`).join("\n");
|
|
712
|
+
return {
|
|
713
|
+
content: [
|
|
714
|
+
{
|
|
715
|
+
type: "text",
|
|
716
|
+
text: `Pending tickets in "${ticketTool.workspaceSlug}" (${tickets.length} total):
|
|
717
|
+
|
|
718
|
+
${ticketList}`
|
|
719
|
+
}
|
|
720
|
+
]
|
|
721
|
+
};
|
|
722
|
+
} catch (error) {
|
|
723
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
724
|
+
console.error(`[MCP] tickets:list error:`, error);
|
|
725
|
+
return {
|
|
726
|
+
content: [
|
|
727
|
+
{
|
|
728
|
+
type: "text",
|
|
729
|
+
text: `Error listing tickets: ${errorMessage}`
|
|
730
|
+
}
|
|
731
|
+
],
|
|
732
|
+
isError: true
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
} else if (ticketTool.type === "next") {
|
|
736
|
+
const ticketSlug = request.params.arguments?.ticketSlug;
|
|
737
|
+
try {
|
|
738
|
+
const result2 = await convexClient.mutation(
|
|
739
|
+
"mcp_tickets:executeMcpNextTicket",
|
|
740
|
+
{
|
|
741
|
+
apiKey: config.apiKey,
|
|
742
|
+
workspaceSlug: ticketTool.workspaceSlug,
|
|
743
|
+
ticketSlug
|
|
744
|
+
// Optional: specific ticket to execute
|
|
745
|
+
}
|
|
746
|
+
);
|
|
747
|
+
if (!result2) {
|
|
748
|
+
const message = ticketSlug ? `Ticket "${ticketSlug}" not found or already closed in workspace "${ticketTool.workspaceSlug}".` : `No pending tickets in workspace "${ticketTool.workspaceSlug}".`;
|
|
749
|
+
return {
|
|
750
|
+
content: [
|
|
751
|
+
{
|
|
752
|
+
type: "text",
|
|
753
|
+
text: message
|
|
754
|
+
}
|
|
755
|
+
]
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return {
|
|
759
|
+
content: [
|
|
760
|
+
{
|
|
761
|
+
type: "text",
|
|
762
|
+
text: `# Ticket: ${result2.slug}
|
|
763
|
+
|
|
764
|
+
${result2.content}
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
_Ticket closed. ${result2.remainingTickets} ticket(s) remaining in queue._`
|
|
768
|
+
}
|
|
769
|
+
]
|
|
770
|
+
};
|
|
771
|
+
} catch (error) {
|
|
772
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
773
|
+
console.error(`[MCP] tickets:next error:`, error);
|
|
774
|
+
return {
|
|
775
|
+
content: [
|
|
776
|
+
{
|
|
777
|
+
type: "text",
|
|
778
|
+
text: `Error executing ticket: ${errorMessage}`
|
|
779
|
+
}
|
|
780
|
+
],
|
|
781
|
+
isError: true
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
} else if (ticketTool.type === "create") {
|
|
785
|
+
const content = request.params.arguments?.content;
|
|
786
|
+
if (!content) {
|
|
787
|
+
return {
|
|
788
|
+
content: [
|
|
789
|
+
{
|
|
790
|
+
type: "text",
|
|
791
|
+
text: `Error: Missing content parameter. Provide the ticket content (first line becomes the slug).`
|
|
792
|
+
}
|
|
793
|
+
],
|
|
794
|
+
isError: true
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const result2 = await convexClient.mutation(
|
|
799
|
+
"mcp_tickets:createMcpTicket",
|
|
800
|
+
{
|
|
801
|
+
apiKey: config.apiKey,
|
|
802
|
+
workspaceSlug: ticketTool.workspaceSlug,
|
|
803
|
+
content
|
|
804
|
+
}
|
|
805
|
+
);
|
|
806
|
+
return {
|
|
807
|
+
content: [
|
|
808
|
+
{
|
|
809
|
+
type: "text",
|
|
810
|
+
text: `\u2705 Created ticket [${result2.slug}] in workspace "${ticketTool.workspaceSlug}".
|
|
811
|
+
|
|
812
|
+
Position: #${result2.position} of ${result2.totalOpen} open tickets
|
|
813
|
+
Preview: ${result2.preview}`
|
|
814
|
+
}
|
|
815
|
+
]
|
|
816
|
+
};
|
|
817
|
+
} catch (error) {
|
|
818
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
819
|
+
console.error(`[MCP] tickets:create error:`, error);
|
|
820
|
+
return {
|
|
821
|
+
content: [
|
|
822
|
+
{
|
|
823
|
+
type: "text",
|
|
824
|
+
text: `Error creating ticket: ${errorMessage}`
|
|
825
|
+
}
|
|
826
|
+
],
|
|
827
|
+
isError: true
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
} else {
|
|
831
|
+
const ticketSlug = request.params.arguments?.ticketSlug;
|
|
832
|
+
if (!ticketSlug) {
|
|
833
|
+
return {
|
|
834
|
+
content: [
|
|
835
|
+
{
|
|
836
|
+
type: "text",
|
|
837
|
+
text: `Error: Missing ticketSlug parameter. Usage: Provide a ticket number (e.g., "102") or full slug (e.g., "102-fix-auth").`
|
|
838
|
+
}
|
|
839
|
+
],
|
|
840
|
+
isError: true
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
try {
|
|
844
|
+
const result2 = await convexClient.query(
|
|
845
|
+
"mcp_tickets:getMcpTicket",
|
|
846
|
+
{
|
|
847
|
+
apiKey: config.apiKey,
|
|
848
|
+
workspaceSlug: ticketTool.workspaceSlug,
|
|
849
|
+
ticketSlug
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
if (!result2) {
|
|
853
|
+
return {
|
|
854
|
+
content: [
|
|
855
|
+
{
|
|
856
|
+
type: "text",
|
|
857
|
+
text: `Ticket "${ticketSlug}" not found in workspace "${ticketTool.workspaceSlug}".`
|
|
858
|
+
}
|
|
859
|
+
]
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
const statusLabel = result2.status === "open" ? "\u{1F7E2} OPEN" : "\u26AB CLOSED";
|
|
863
|
+
const closedInfo = result2.closedAt ? `
|
|
864
|
+
Closed: ${new Date(result2.closedAt).toISOString()}` : "";
|
|
865
|
+
return {
|
|
866
|
+
content: [
|
|
867
|
+
{
|
|
868
|
+
type: "text",
|
|
869
|
+
text: `# Ticket: ${result2.slug}
|
|
870
|
+
|
|
871
|
+
Status: ${statusLabel}${closedInfo}
|
|
872
|
+
|
|
873
|
+
---
|
|
874
|
+
|
|
875
|
+
${result2.content}`
|
|
876
|
+
}
|
|
877
|
+
]
|
|
878
|
+
};
|
|
879
|
+
} catch (error) {
|
|
880
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
881
|
+
console.error(`[MCP] tickets:get error:`, error);
|
|
882
|
+
return {
|
|
883
|
+
content: [
|
|
884
|
+
{
|
|
885
|
+
type: "text",
|
|
886
|
+
text: `Error getting ticket: ${errorMessage}`
|
|
887
|
+
}
|
|
888
|
+
],
|
|
889
|
+
isError: true
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const prompt = validPrompts.find((p) => buildPromptName(p) === toolName);
|
|
895
|
+
if (!prompt) {
|
|
896
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
897
|
+
}
|
|
898
|
+
const handler = buildPromptHandler(prompt, config, convexClient);
|
|
899
|
+
const result = await handler();
|
|
900
|
+
const promptText = result.messages.map((msg) => typeof msg.content === "string" ? msg.content : msg.content.text).join("\n\n");
|
|
901
|
+
return {
|
|
902
|
+
content: [
|
|
903
|
+
{
|
|
904
|
+
type: "text",
|
|
905
|
+
text: promptText
|
|
906
|
+
}
|
|
907
|
+
]
|
|
908
|
+
};
|
|
909
|
+
});
|
|
910
|
+
const transport = new StdioServerTransport();
|
|
911
|
+
await server.connect(transport);
|
|
912
|
+
console.error("[MCP] Server started successfully");
|
|
913
|
+
console.error(`[MCP] Deployment: ${config.isDev ? "DEVELOPMENT" : "PRODUCTION"}`);
|
|
914
|
+
console.error(`[MCP] Convex URL: ${config.convexUrl}`);
|
|
915
|
+
console.error(`[MCP] Data mode: REAL-TIME (fetches fresh data on each invocation)`);
|
|
916
|
+
const allPromptNames = [...Array.from(promptNames)].sort();
|
|
917
|
+
console.error(`[MCP] Prompts available: ${allPromptNames.join(", ")}`);
|
|
918
|
+
console.error(`[MCP] - Total prompts: ${promptNames.size}`);
|
|
919
|
+
setInterval(() => {
|
|
920
|
+
}, 6e4);
|
|
921
|
+
return new Promise(() => {
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
async function validateApiKey(client, apiKey) {
|
|
925
|
+
try {
|
|
926
|
+
const result = await client.query("apiKeys:validateApiKey", { key: apiKey });
|
|
927
|
+
if (result) {
|
|
928
|
+
return { valid: true, userId: result.userId };
|
|
929
|
+
}
|
|
930
|
+
return { valid: false, error: "Invalid API key" };
|
|
931
|
+
} catch (error) {
|
|
932
|
+
return {
|
|
933
|
+
valid: false,
|
|
934
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
async function fetchMcpPrompts(client, apiKey, workspaces) {
|
|
939
|
+
try {
|
|
940
|
+
const prompts = await client.query("mcp_prompts:getMcpPrompts", {
|
|
941
|
+
apiKey,
|
|
942
|
+
workspaces: workspaces.length > 0 ? workspaces : void 0
|
|
943
|
+
// Only pass if specified
|
|
944
|
+
});
|
|
945
|
+
return prompts;
|
|
946
|
+
} catch (error) {
|
|
947
|
+
throw new Error(
|
|
948
|
+
`Failed to fetch prompts: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/index.ts
|
|
954
|
+
async function main() {
|
|
955
|
+
try {
|
|
956
|
+
const argv = minimist(process.argv.slice(2));
|
|
957
|
+
const isDev = argv.dev === true;
|
|
958
|
+
const workspacesArg = argv.workspaces || argv.w;
|
|
959
|
+
let selectedWorkspaces = [];
|
|
960
|
+
if (workspacesArg) {
|
|
961
|
+
if (Array.isArray(workspacesArg)) {
|
|
962
|
+
selectedWorkspaces = workspacesArg.flatMap((w) => String(w).split(","));
|
|
963
|
+
} else {
|
|
964
|
+
selectedWorkspaces = String(workspacesArg).split(",");
|
|
965
|
+
}
|
|
966
|
+
selectedWorkspaces = selectedWorkspaces.map((s) => s.trim()).filter(Boolean);
|
|
967
|
+
}
|
|
968
|
+
const apiKey = process.env.PPM_API_KEY || process.env.THEPROMPTEDITOR_API_KEY;
|
|
969
|
+
if (!apiKey) {
|
|
970
|
+
console.error(
|
|
971
|
+
"[MCP] ERROR: Missing PPM_API_KEY environment variable"
|
|
972
|
+
);
|
|
973
|
+
console.error(
|
|
974
|
+
"[MCP] Please set your API key from Prompt Project Manager Settings page:"
|
|
975
|
+
);
|
|
976
|
+
console.error("[MCP] export PPM_API_KEY=your_api_key_here");
|
|
977
|
+
process.exit(1);
|
|
978
|
+
}
|
|
979
|
+
const convexClient = createConvexClient(isDev);
|
|
980
|
+
const convexUrl = isDev ? "https://hallowed-shrimp-344.convex.cloud" : "https://trustworthy-squirrel-735.convex.cloud";
|
|
981
|
+
const config = {
|
|
982
|
+
apiKey,
|
|
983
|
+
isDev,
|
|
984
|
+
convexUrl,
|
|
985
|
+
selectedWorkspaces
|
|
986
|
+
// Workspace slugs to filter (empty = all workspaces)
|
|
987
|
+
};
|
|
988
|
+
await startServer(config, convexClient);
|
|
989
|
+
console.error("[MCP] WARNING: startServer promise resolved unexpectedly!");
|
|
990
|
+
} catch (error) {
|
|
991
|
+
console.error(
|
|
992
|
+
"[MCP] FATAL ERROR:",
|
|
993
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
994
|
+
);
|
|
995
|
+
if (error instanceof Error && error.stack) {
|
|
996
|
+
console.error(error.stack);
|
|
997
|
+
}
|
|
998
|
+
process.exit(1);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
process.on("SIGINT", () => {
|
|
1002
|
+
console.error("[MCP] Received SIGINT, shutting down gracefully...");
|
|
1003
|
+
process.exit(0);
|
|
1004
|
+
});
|
|
1005
|
+
process.on("SIGTERM", () => {
|
|
1006
|
+
console.error("[MCP] Received SIGTERM, shutting down gracefully...");
|
|
1007
|
+
process.exit(0);
|
|
1008
|
+
});
|
|
1009
|
+
main();
|
|
1010
|
+
//# sourceMappingURL=index.js.map
|