@promptprojectmanager/mcp-server 3.3.6 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -76,16 +76,16 @@ function buildAuthArgs(config) {
76
76
  // Pass agent name for identity resolution
77
77
  };
78
78
  }
79
- if (config.workspaceToken) {
80
- return { workspaceToken: config.workspaceToken };
79
+ if (config.projectToken) {
80
+ return { projectToken: config.projectToken };
81
81
  }
82
82
  return {};
83
83
  }
84
84
  var SYSTEM_TOOLS = [
85
- // All tools are now workspace-scoped (see dynamic*Tools arrays below)
85
+ // All tools are now project-scoped (see dynamic*Tools arrays below)
86
86
  ];
87
87
  async function startServer(config, convexClient) {
88
- let tokenWorkspaceSlug;
88
+ let tokenProjectSlug;
89
89
  if (config.apiKey) {
90
90
  console.error("[MCP] Validating API key...");
91
91
  const validation = await validateApiKey(convexClient, config.apiKey);
@@ -93,101 +93,101 @@ async function startServer(config, convexClient) {
93
93
  throw new Error(`Invalid API key: ${validation.error}`);
94
94
  }
95
95
  console.error(`[MCP] API key validated for user: ${validation.userId}`);
96
- } else if (config.workspaceToken) {
97
- console.error("[MCP] Validating workspace token...");
98
- const validation = await validateWorkspaceToken(convexClient, config.workspaceToken);
96
+ } else if (config.projectToken) {
97
+ console.error("[MCP] Validating project token...");
98
+ const validation = await validateProjectToken(convexClient, config.projectToken);
99
99
  if (!validation.valid) {
100
- throw new Error(`Invalid workspace token: ${validation.error}`);
100
+ throw new Error(`Invalid project token: ${validation.error}`);
101
101
  }
102
- tokenWorkspaceSlug = validation.workspaceSlug;
103
- console.error(`[MCP] Workspace token validated: "${validation.tokenName}" for workspace "${tokenWorkspaceSlug}"`);
102
+ tokenProjectSlug = validation.projectSlug;
103
+ console.error(`[MCP] Project token validated: "${validation.tokenName}" for project "${tokenProjectSlug}"`);
104
104
  } else {
105
- throw new Error("No authentication provided. Set PPM_API_KEY or PPM_WORKSPACE_TOKEN.");
105
+ throw new Error("No authentication provided. Set PPM_API_KEY or PPM_PROJECT_TOKEN.");
106
106
  }
107
107
  let accountScopedPrompts = [];
108
- let workspacesForTickets = [];
109
- console.error("[MCP] Fetching prompt and workspace metadata...");
108
+ let projectsForTickets = [];
109
+ console.error("[MCP] Fetching prompt and project metadata...");
110
110
  if (config.apiKey) {
111
111
  accountScopedPrompts = await fetchAccountScopedPromptMetadata(convexClient, config.apiKey);
112
- const allWorkspaces = await fetchMcpWorkspaces(convexClient, config.apiKey);
113
- if (config.selectedWorkspaces.length > 0) {
114
- const selectedSet = new Set(config.selectedWorkspaces);
115
- workspacesForTickets = allWorkspaces.filter((w) => selectedSet.has(w.slug));
112
+ const allProjects = await fetchMcpProjects(convexClient, config.apiKey);
113
+ if (config.selectedProjects.length > 0) {
114
+ const selectedSet = new Set(config.selectedProjects);
115
+ projectsForTickets = allProjects.filter((p) => selectedSet.has(p.slug));
116
116
  } else {
117
- workspacesForTickets = allWorkspaces;
117
+ projectsForTickets = allProjects;
118
118
  }
119
- } else if (config.workspaceToken && tokenWorkspaceSlug) {
120
- console.error("[MCP] Workspace token mode: prompts not available (tickets only)");
121
- workspacesForTickets = [{ slug: tokenWorkspaceSlug, name: tokenWorkspaceSlug }];
119
+ } else if (config.projectToken && tokenProjectSlug) {
120
+ console.error("[MCP] Project token mode: prompts not available (tickets only)");
121
+ projectsForTickets = [{ slug: tokenProjectSlug, name: tokenProjectSlug }];
122
122
  }
123
123
  console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);
124
- console.error(`[MCP] Found ${workspacesForTickets.length} workspace(s) for ticket tools`);
124
+ console.error(`[MCP] Found ${projectsForTickets.length} project(s) for ticket tools`);
125
125
  if (accountScopedPrompts.length === 0 && config.apiKey) {
126
126
  console.error(
127
127
  "[MCP] WARNING: No account-scoped prompts found. Create prompts in the 'prompts' section to expose them via MCP."
128
128
  );
129
129
  }
130
- if (tokenWorkspaceSlug) {
131
- console.error(`[MCP] Ticket workspace scope: ${tokenWorkspaceSlug} (token-scoped)`);
132
- } else if (config.selectedWorkspaces.length > 0) {
133
- console.error(`[MCP] Ticket workspace filter: ${config.selectedWorkspaces.join(", ")}`);
130
+ if (tokenProjectSlug) {
131
+ console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);
132
+ } else if (config.selectedProjects.length > 0) {
133
+ console.error(`[MCP] Ticket project filter: ${config.selectedProjects.join(", ")}`);
134
134
  } else {
135
- console.error(`[MCP] Ticket workspaces: ALL (${workspacesForTickets.map((w) => w.slug).join(", ")})`);
135
+ console.error(`[MCP] Ticket projects: ALL (${projectsForTickets.map((p) => p.slug).join(", ")})`);
136
136
  }
137
- const workspaceSlugs = new Set(workspacesForTickets.map((w) => w.slug));
137
+ const projectSlugs = new Set(projectsForTickets.map((p) => p.slug));
138
138
  const dynamicTicketTools = [];
139
- for (const workspaceSlug of workspaceSlugs) {
139
+ for (const projectSlug of projectSlugs) {
140
140
  dynamicTicketTools.push({
141
- name: `${workspaceSlug}_tickets_work`,
142
- description: `Get work from the "${workspaceSlug}" workspace. With no args: gets next ticket from open queue. With ticket slug/number: opens or resumes that specific ticket.`,
141
+ name: `${projectSlug}_tickets_work`,
142
+ 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.`,
143
143
  slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,
144
- workspaceSlug,
144
+ projectSlug,
145
145
  type: "work"
146
146
  });
147
147
  dynamicTicketTools.push({
148
- name: `${workspaceSlug}_tickets_close`,
149
- description: `Mark a working ticket as completed in the "${workspaceSlug}" workspace`,
148
+ name: `${projectSlug}_tickets_close`,
149
+ description: `Mark a working ticket as completed in the "${projectSlug}" project`,
150
150
  slashDescription: `Mark a working ticket as completed`,
151
- workspaceSlug,
151
+ projectSlug,
152
152
  type: "close"
153
153
  });
154
154
  dynamicTicketTools.push({
155
- name: `${workspaceSlug}_tickets_create`,
156
- description: `Create a new ticket in the "${workspaceSlug}" workspace queue`,
155
+ name: `${projectSlug}_tickets_create`,
156
+ description: `Create a new ticket in the "${projectSlug}" project queue`,
157
157
  slashDescription: `Create a new ticket in the backlog queue`,
158
- workspaceSlug,
158
+ projectSlug,
159
159
  type: "create"
160
160
  });
161
161
  dynamicTicketTools.push({
162
- name: `${workspaceSlug}_tickets_search`,
163
- description: `Search for tickets by content in the "${workspaceSlug}" workspace`,
162
+ name: `${projectSlug}_tickets_search`,
163
+ description: `Search for tickets by content in the "${projectSlug}" project`,
164
164
  slashDescription: `Search for tickets by content`,
165
- workspaceSlug,
165
+ projectSlug,
166
166
  type: "search"
167
167
  });
168
168
  dynamicTicketTools.push({
169
- name: `${workspaceSlug}_tickets_get`,
170
- description: `Get a specific ticket by number or slug from "${workspaceSlug}" (read-only)`,
169
+ name: `${projectSlug}_tickets_get`,
170
+ description: `Get a specific ticket by number or slug from "${projectSlug}" (read-only)`,
171
171
  slashDescription: `Get a specific ticket by number or slug (read-only)`,
172
- workspaceSlug,
172
+ projectSlug,
173
173
  type: "get"
174
174
  });
175
175
  dynamicTicketTools.push({
176
- name: `${workspaceSlug}_tickets_list`,
177
- description: `List active tickets in the "${workspaceSlug}" workspace (backlog + open + working)`,
176
+ name: `${projectSlug}_tickets_list`,
177
+ description: `List active tickets in the "${projectSlug}" project (backlog + open + working)`,
178
178
  slashDescription: `List active tickets (backlog + open + working)`,
179
- workspaceSlug,
179
+ projectSlug,
180
180
  type: "list"
181
181
  });
182
182
  dynamicTicketTools.push({
183
- name: `${workspaceSlug}_tickets_update`,
184
- description: `Update a ticket in the "${workspaceSlug}" workspace by appending content with timestamp`,
183
+ name: `${projectSlug}_tickets_update`,
184
+ description: `Update a ticket in the "${projectSlug}" project by appending content with timestamp`,
185
185
  slashDescription: `Update a ticket by appending content with timestamp`,
186
- workspaceSlug,
186
+ projectSlug,
187
187
  type: "update"
188
188
  });
189
189
  }
190
- console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${workspaceSlugs.size} workspace(s)...`);
190
+ console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${projectSlugs.size} project(s)...`);
191
191
  const globalSystemTools = [
192
192
  {
193
193
  name: "system_run_prompt",
@@ -205,7 +205,7 @@ async function startServer(config, convexClient) {
205
205
  for (const prompt of accountScopedPrompts) {
206
206
  dynamicPromptTools.push({
207
207
  name: prompt.slug,
208
- // Global tool name, no workspace prefix
208
+ // Global tool name, no project prefix
209
209
  description: prompt.description || `Execute the "${prompt.slug}" prompt`,
210
210
  promptSlug: prompt.slug
211
211
  });
@@ -250,7 +250,7 @@ async function startServer(config, convexClient) {
250
250
  ...systemPromptsSchemas,
251
251
  // system_prompts, system_run_prompt (global)
252
252
  ...ticketPromptSchemas
253
- // workspace_tickets_* (workspace-scoped)
253
+ // project_tickets_* (project-scoped)
254
254
  ]
255
255
  };
256
256
  });
@@ -277,26 +277,26 @@ async function startServer(config, convexClient) {
277
277
  let promptContent;
278
278
  let description;
279
279
  if (ticketWorkMatch) {
280
- const workspaceSlug = ticketWorkMatch[1];
280
+ const projectSlug = ticketWorkMatch[1];
281
281
  const ticketArg = ticketWorkMatch[2].trim();
282
- description = `Work on ticket "${ticketArg}" from "${workspaceSlug}"`;
283
- promptContent = `Get work on ticket "${ticketArg}" from the "${workspaceSlug}" workspace.
282
+ description = `Work on ticket "${ticketArg}" from "${projectSlug}"`;
283
+ promptContent = `Get work on ticket "${ticketArg}" from the "${projectSlug}" project.
284
284
 
285
- Call the \`${workspaceSlug}_tickets_work\` tool with ticketSlug: "${ticketArg}".
285
+ Call the \`${projectSlug}_tickets_work\` tool with ticketSlug: "${ticketArg}".
286
286
 
287
287
  This will open the ticket if it's in the open queue, or resume it if already working.`;
288
288
  } else if (ticketTool.type === "work") {
289
289
  description = ticketTool.description;
290
- promptContent = `Get work from the "${ticketTool.workspaceSlug}" workspace.
290
+ promptContent = `Get work from the "${ticketTool.projectSlug}" project.
291
291
 
292
292
  Call the \`${ticketTool.name}\` tool to get the next ticket from the open queue.
293
293
 
294
- You can also specify a ticket: /ppm:${ticketTool.workspaceSlug}_tickets_work <number-or-slug>
294
+ You can also specify a ticket: /ppm:${ticketTool.projectSlug}_tickets_work <number-or-slug>
295
295
 
296
296
  This unified command handles both opening new tickets and resuming in-progress work.`;
297
297
  } else if (ticketTool.type === "create") {
298
298
  description = ticketTool.description;
299
- promptContent = `Create a new ticket in the "${ticketTool.workspaceSlug}" workspace queue.
299
+ promptContent = `Create a new ticket in the "${ticketTool.projectSlug}" project queue.
300
300
 
301
301
  ## How This Works
302
302
  The user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.
@@ -323,7 +323,7 @@ The user provides **instructions** (not raw content). You interpret those instru
323
323
  Call the \`${ticketTool.name}\` tool with the final generated content.`;
324
324
  } else if (ticketTool.type === "close") {
325
325
  description = ticketTool.description;
326
- promptContent = `Close a working ticket in the "${ticketTool.workspaceSlug}" workspace.
326
+ promptContent = `Close a working ticket in the "${ticketTool.projectSlug}" project.
327
327
 
328
328
  Call the \`${ticketTool.name}\` tool with the ticket number or slug.`;
329
329
  } else {
@@ -407,7 +407,7 @@ This will execute the "code-review" prompt.`;
407
407
  description: st.description,
408
408
  inputSchema: st.inputSchema
409
409
  })),
410
- // Dynamic ticket tools per workspace (Ticket 135, 149, 151, 153, unified work)
410
+ // Dynamic ticket tools per project (Ticket 135, 149, 151, 153, unified work)
411
411
  ...dynamicTicketTools.map((tt) => {
412
412
  let inputSchema;
413
413
  if (tt.type === "close") {
@@ -642,13 +642,13 @@ No prompts found.`
642
642
  "mcp_tickets:workMcpTicket",
643
643
  {
644
644
  ...buildAuthArgs(config),
645
- workspaceSlug: ticketTool.workspaceSlug,
645
+ projectSlug: ticketTool.projectSlug,
646
646
  ticketSlug
647
647
  // Optional: specific ticket to work on
648
648
  }
649
649
  );
650
650
  if (!result) {
651
- const message = ticketSlug ? `Ticket "${ticketSlug}" not found or not in open/working status in workspace "${ticketTool.workspaceSlug}".` : `No open tickets in workspace "${ticketTool.workspaceSlug}".`;
651
+ const message = ticketSlug ? `Ticket "${ticketSlug}" not found or not in open/working status in project "${ticketTool.projectSlug}".` : `No open tickets in project "${ticketTool.projectSlug}".`;
652
652
  return {
653
653
  content: [
654
654
  {
@@ -716,7 +716,7 @@ ${statusNote}`
716
716
  "mcp_tickets:createMcpTicket",
717
717
  {
718
718
  ...buildAuthArgs(config),
719
- workspaceSlug: ticketTool.workspaceSlug,
719
+ projectSlug: ticketTool.projectSlug,
720
720
  content
721
721
  }
722
722
  );
@@ -724,7 +724,7 @@ ${statusNote}`
724
724
  content: [
725
725
  {
726
726
  type: "text",
727
- text: `\u2705 Created ticket [${result.slug}] in backlog for workspace "${ticketTool.workspaceSlug}".
727
+ text: `\u2705 Created ticket [${result.slug}] in backlog for project "${ticketTool.projectSlug}".
728
728
 
729
729
  Position: #${result.position} of ${result.totalBacklog} backlog tickets
730
730
  Preview: ${result.preview}
@@ -764,7 +764,7 @@ _Ticket created in backlog. Use \`tickets_work ${result.slug}\` to move it to wo
764
764
  "mcp_tickets:closeMcpTicket",
765
765
  {
766
766
  ...buildAuthArgs(config),
767
- workspaceSlug: ticketTool.workspaceSlug,
767
+ projectSlug: ticketTool.projectSlug,
768
768
  ticketSlug
769
769
  }
770
770
  );
@@ -772,7 +772,7 @@ _Ticket created in backlog. Use \`tickets_work ${result.slug}\` to move it to wo
772
772
  content: [
773
773
  {
774
774
  type: "text",
775
- text: `\u2705 Ticket [${result.slug}] closed in workspace "${ticketTool.workspaceSlug}".
775
+ text: `\u2705 Ticket [${result.slug}] closed in project "${ticketTool.projectSlug}".
776
776
 
777
777
  Closed at: ${new Date(result.closedAt).toISOString()}
778
778
 
@@ -811,7 +811,7 @@ _Reminder: Ensure all embedded \`[RUN_PROMPT ...]\` directives were executed bef
811
811
  "mcp_tickets:searchMcpTickets",
812
812
  {
813
813
  ...buildAuthArgs(config),
814
- workspaceSlug: ticketTool.workspaceSlug,
814
+ projectSlug: ticketTool.projectSlug,
815
815
  query
816
816
  }
817
817
  );
@@ -820,7 +820,7 @@ _Reminder: Ensure all embedded \`[RUN_PROMPT ...]\` directives were executed bef
820
820
  content: [
821
821
  {
822
822
  type: "text",
823
- text: `No tickets found matching "${query}" in workspace "${ticketTool.workspaceSlug}".`
823
+ text: `No tickets found matching "${query}" in project "${ticketTool.projectSlug}".`
824
824
  }
825
825
  ]
826
826
  };
@@ -872,7 +872,7 @@ ${formattedList}`
872
872
  "mcp_tickets:getMcpTicket",
873
873
  {
874
874
  ...buildAuthArgs(config),
875
- workspaceSlug: ticketTool.workspaceSlug,
875
+ projectSlug: ticketTool.projectSlug,
876
876
  ticketSlug
877
877
  }
878
878
  );
@@ -881,7 +881,7 @@ ${formattedList}`
881
881
  content: [
882
882
  {
883
883
  type: "text",
884
- text: `Ticket "${ticketSlug}" not found in workspace "${ticketTool.workspaceSlug}".`
884
+ text: `Ticket "${ticketSlug}" not found in project "${ticketTool.projectSlug}".`
885
885
  }
886
886
  ]
887
887
  };
@@ -936,7 +936,7 @@ _Read-only inspection. Use tickets_work to start working on this ticket._`
936
936
  "mcp_tickets:listMcpTickets",
937
937
  {
938
938
  ...buildAuthArgs(config),
939
- workspaceSlug: ticketTool.workspaceSlug
939
+ projectSlug: ticketTool.projectSlug
940
940
  }
941
941
  );
942
942
  if (result.length === 0) {
@@ -944,7 +944,7 @@ _Read-only inspection. Use tickets_work to start working on this ticket._`
944
944
  content: [
945
945
  {
946
946
  type: "text",
947
- text: `No active tickets in workspace "${ticketTool.workspaceSlug}". All tickets are closed or archived.`
947
+ text: `No active tickets in project "${ticketTool.projectSlug}". All tickets are closed or archived.`
948
948
  }
949
949
  ]
950
950
  };
@@ -974,7 +974,7 @@ ${backlogTickets.map(formatTicketLine).join("\n")}`);
974
974
  content: [
975
975
  {
976
976
  type: "text",
977
- text: `# Active Queue: ${ticketTool.workspaceSlug}
977
+ text: `# Active Queue: ${ticketTool.projectSlug}
978
978
 
979
979
  ${result.length} ticket(s) in queue
980
980
 
@@ -1025,7 +1025,7 @@ ${sections.join("\n\n")}`
1025
1025
  "mcp_tickets:updateMcpTicket",
1026
1026
  {
1027
1027
  ...buildAuthArgs(config),
1028
- workspaceSlug: ticketTool.workspaceSlug,
1028
+ projectSlug: ticketTool.projectSlug,
1029
1029
  ticketSlug,
1030
1030
  content
1031
1031
  }
@@ -1034,7 +1034,7 @@ ${sections.join("\n\n")}`
1034
1034
  content: [
1035
1035
  {
1036
1036
  type: "text",
1037
- text: `\u2705 Ticket [${result.slug}] updated in workspace "${ticketTool.workspaceSlug}".
1037
+ text: `\u2705 Ticket [${result.slug}] updated in project "${ticketTool.projectSlug}".
1038
1038
  Updated at: ${new Date(result.updatedAt).toISOString()}
1039
1039
  _Ticket content has been appended with your update._`
1040
1040
  }
@@ -1106,20 +1106,20 @@ async function validateApiKey(client, apiKey) {
1106
1106
  };
1107
1107
  }
1108
1108
  }
1109
- async function validateWorkspaceToken(client, token) {
1109
+ async function validateProjectToken(client, token) {
1110
1110
  try {
1111
- const result = await client.query("workspace_tokens:validateWorkspaceToken", { token });
1111
+ const result = await client.query("project_tokens:validateProjectToken", { token });
1112
1112
  if (result && result.valid) {
1113
1113
  return {
1114
1114
  valid: true,
1115
- workspaceId: result.workspaceId,
1116
- workspaceSlug: result.workspaceSlug,
1115
+ projectId: result.projectId,
1116
+ projectSlug: result.projectSlug,
1117
1117
  ownerId: result.ownerId,
1118
1118
  tokenId: result.tokenId,
1119
1119
  tokenName: result.tokenName
1120
1120
  };
1121
1121
  }
1122
- return { valid: false, error: "Invalid or revoked workspace token" };
1122
+ return { valid: false, error: "Invalid or revoked project token" };
1123
1123
  } catch (error) {
1124
1124
  return {
1125
1125
  valid: false,
@@ -1139,15 +1139,15 @@ async function fetchAccountScopedPromptMetadata(client, apiKey) {
1139
1139
  );
1140
1140
  }
1141
1141
  }
1142
- async function fetchMcpWorkspaces(client, apiKey) {
1142
+ async function fetchMcpProjects(client, apiKey) {
1143
1143
  try {
1144
- const workspaces = await client.query("mcp_prompts:getMcpWorkspaces", {
1144
+ const projects = await client.query("mcp_prompts:getMcpProjects", {
1145
1145
  apiKey
1146
1146
  });
1147
- return workspaces;
1147
+ return projects;
1148
1148
  } catch (error) {
1149
1149
  throw new Error(
1150
- `Failed to fetch workspaces: ${error instanceof Error ? error.message : "Unknown error"}`
1150
+ `Failed to fetch projects: ${error instanceof Error ? error.message : "Unknown error"}`
1151
1151
  );
1152
1152
  }
1153
1153
  }
@@ -1157,42 +1157,42 @@ async function main() {
1157
1157
  try {
1158
1158
  const argv = minimist(process.argv.slice(2));
1159
1159
  const isDev = argv.dev === true;
1160
- const workspacesArg = argv.workspaces || argv.w;
1161
- let selectedWorkspaces = [];
1162
- if (workspacesArg) {
1163
- if (Array.isArray(workspacesArg)) {
1164
- selectedWorkspaces = workspacesArg.flatMap((w) => String(w).split(","));
1160
+ const projectsArg = argv.projects || argv.p;
1161
+ let selectedProjects = [];
1162
+ if (projectsArg) {
1163
+ if (Array.isArray(projectsArg)) {
1164
+ selectedProjects = projectsArg.flatMap((p) => String(p).split(","));
1165
1165
  } else {
1166
- selectedWorkspaces = String(workspacesArg).split(",");
1166
+ selectedProjects = String(projectsArg).split(",");
1167
1167
  }
1168
- selectedWorkspaces = selectedWorkspaces.map((s) => s.trim()).filter(Boolean);
1168
+ selectedProjects = selectedProjects.map((s) => s.trim()).filter(Boolean);
1169
1169
  }
1170
1170
  const apiKey = process.env.PPM_API_KEY || process.env.THEPROMPTEDITOR_API_KEY;
1171
- const workspaceToken = process.env.PPM_WORKSPACE_TOKEN;
1171
+ const projectToken = process.env.PPM_PROJECT_TOKEN;
1172
1172
  const ppmAgent = process.env.PPM_AGENT;
1173
- if (!apiKey && !workspaceToken) {
1173
+ if (!apiKey && !projectToken) {
1174
1174
  console.error(
1175
- "[MCP] ERROR: Missing authentication. Provide either PPM_API_KEY or PPM_WORKSPACE_TOKEN"
1175
+ "[MCP] ERROR: Missing authentication. Provide either PPM_API_KEY or PPM_PROJECT_TOKEN"
1176
1176
  );
1177
1177
  console.error(
1178
1178
  "[MCP] Options:"
1179
1179
  );
1180
- console.error("[MCP] export PPM_API_KEY=your_api_key_here # Full access to all workspaces");
1181
- console.error("[MCP] export PPM_WORKSPACE_TOKEN=wst_... # Scoped access to one workspace (tickets only)");
1180
+ console.error("[MCP] export PPM_API_KEY=your_api_key_here # Full access to all projects");
1181
+ console.error("[MCP] export PPM_PROJECT_TOKEN=ppt_... # Scoped access to one project (tickets only)");
1182
1182
  process.exit(1);
1183
1183
  }
1184
1184
  if (apiKey) {
1185
1185
  console.error("[MCP] Auth mode: API Key (full access)");
1186
- if (workspaceToken) {
1187
- console.error("[MCP] Note: PPM_WORKSPACE_TOKEN ignored when PPM_API_KEY is set");
1186
+ if (projectToken) {
1187
+ console.error("[MCP] Note: PPM_PROJECT_TOKEN ignored when PPM_API_KEY is set");
1188
1188
  }
1189
1189
  if (ppmAgent) {
1190
1190
  console.error(`[MCP] Agent identity: ${ppmAgent}`);
1191
1191
  }
1192
1192
  } else {
1193
- console.error("[MCP] Auth mode: Workspace Token (scoped access, tickets only)");
1193
+ console.error("[MCP] Auth mode: Project Token (scoped access, tickets only)");
1194
1194
  if (ppmAgent) {
1195
- console.error("[MCP] Note: PPM_AGENT ignored when using PPM_WORKSPACE_TOKEN directly");
1195
+ console.error("[MCP] Note: PPM_AGENT ignored when using PPM_PROJECT_TOKEN directly");
1196
1196
  }
1197
1197
  }
1198
1198
  const convexClient = createConvexClient(isDev);
@@ -1200,12 +1200,12 @@ async function main() {
1200
1200
  const config = {
1201
1201
  apiKey: apiKey || void 0,
1202
1202
  // Use undefined if not set (not empty string)
1203
- workspaceToken: apiKey ? void 0 : workspaceToken,
1203
+ projectToken: apiKey ? void 0 : projectToken,
1204
1204
  // Only use token if no API key
1205
1205
  isDev,
1206
1206
  convexUrl,
1207
- selectedWorkspaces,
1208
- // Workspace slugs to filter (empty = all workspaces)
1207
+ selectedProjects,
1208
+ // Project slugs to filter (empty = all projects)
1209
1209
  agentName: apiKey && ppmAgent ? ppmAgent : void 0
1210
1210
  // Only use agent name with API key auth
1211
1211
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/convex-client.ts","../src/server.ts","../src/prompt-builder.ts"],"sourcesContent":["import minimist from \"minimist\";\nimport { createConvexClient } from \"./convex-client.js\";\nimport { startServer } from \"./server.js\";\nimport type { ServerConfig } from \"./types.js\";\n\n/**\n * Main entry point for the MCP server\n */\nasync function main() {\n try {\n // Parse CLI arguments\n const argv = minimist(process.argv.slice(2));\n const isDev = argv.dev === true;\n\n // Parse --workspaces CLI argument (Ticket 144)\n // Can be: --workspaces ws1,ws2 OR --workspaces ws1 --workspaces ws2 OR -w ws1,ws2\n const workspacesArg = argv.workspaces || argv.w;\n let selectedWorkspaces: string[] = [];\n if (workspacesArg) {\n if (Array.isArray(workspacesArg)) {\n // Multiple --workspaces flags: [\"ws1\", \"ws2\"]\n selectedWorkspaces = workspacesArg.flatMap((w: string) => String(w).split(','));\n } else {\n // Single --workspaces flag with comma-separated: \"ws1,ws2\"\n selectedWorkspaces = String(workspacesArg).split(',');\n }\n // Clean up whitespace\n selectedWorkspaces = selectedWorkspaces.map((s) => s.trim()).filter(Boolean);\n }\n\n // Check for authentication (API key or workspace token)\n // API key takes precedence if both are provided\n const apiKey = process.env.PPM_API_KEY || process.env.THEPROMPTEDITOR_API_KEY;\n const workspaceToken = process.env.PPM_WORKSPACE_TOKEN;\n\n // PPM_AGENT: Optional agent identity selection (only with API key auth)\n // When set, uses the named workspace token as identity while API key provides access\n const ppmAgent = process.env.PPM_AGENT;\n\n if (!apiKey && !workspaceToken) {\n console.error(\n \"[MCP] ERROR: Missing authentication. Provide either PPM_API_KEY or PPM_WORKSPACE_TOKEN\"\n );\n console.error(\n \"[MCP] Options:\"\n );\n console.error(\"[MCP] export PPM_API_KEY=your_api_key_here # Full access to all workspaces\");\n console.error(\"[MCP] export PPM_WORKSPACE_TOKEN=wst_... # Scoped access to one workspace (tickets only)\");\n process.exit(1);\n }\n\n // Log which auth mode is being used\n if (apiKey) {\n console.error(\"[MCP] Auth mode: API Key (full access)\");\n if (workspaceToken) {\n console.error(\"[MCP] Note: PPM_WORKSPACE_TOKEN ignored when PPM_API_KEY is set\");\n }\n if (ppmAgent) {\n console.error(`[MCP] Agent identity: ${ppmAgent}`);\n }\n } else {\n console.error(\"[MCP] Auth mode: Workspace Token (scoped access, tickets only)\");\n if (ppmAgent) {\n console.error(\"[MCP] Note: PPM_AGENT ignored when using PPM_WORKSPACE_TOKEN directly\");\n }\n }\n\n // Create Convex client\n const convexClient = createConvexClient(isDev);\n const convexUrl = isDev\n ? \"https://hallowed-shrimp-344.convex.cloud\"\n : \"https://trustworthy-squirrel-735.convex.cloud\";\n\n // Build server config\n const config: ServerConfig = {\n apiKey: apiKey || undefined, // Use undefined if not set (not empty string)\n workspaceToken: apiKey ? undefined : workspaceToken, // Only use token if no API key\n isDev,\n convexUrl,\n selectedWorkspaces, // Workspace slugs to filter (empty = all workspaces)\n agentName: apiKey && ppmAgent ? ppmAgent : undefined, // Only use agent name with API key auth\n };\n\n // Start server - this will keep the process alive via stdio transport\n await startServer(config, convexClient);\n\n // This line should never be reached if the promise in startServer never resolves\n console.error(\"[MCP] WARNING: startServer promise resolved unexpectedly!\");\n } catch (error) {\n console.error(\n \"[MCP] FATAL ERROR:\",\n error instanceof Error ? error.message : \"Unknown error\"\n );\n if (error instanceof Error && error.stack) {\n console.error(error.stack);\n }\n process.exit(1);\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[MCP] Received SIGINT, shutting down gracefully...\");\n process.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[MCP] Received SIGTERM, shutting down gracefully...\");\n process.exit(0);\n});\n\nmain();\n","import { ConvexHttpClient } from \"convex/browser\";\n\nconst PROD_URL = \"https://trustworthy-squirrel-735.convex.cloud\";\nconst DEV_URL = \"https://hallowed-shrimp-344.convex.cloud\";\n\n/**\n * Create a Convex HTTP client for the appropriate deployment\n */\nexport function createConvexClient(isDev: boolean): ConvexHttpClient {\n const url = isDev ? DEV_URL : PROD_URL;\n return new ConvexHttpClient(url);\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { ServerConfig, WorkspaceTokenValidation } from \"./types.js\";\n\n/**\n * Close ticket result (Ticket 151)\n */\ninterface CloseTicketResult {\n slug: string;\n status: string;\n closedAt: number;\n}\n\n/**\n * Update ticket result (Ticket 293)\n */\ninterface UpdateTicketResult {\n slug: string;\n ticketNumber?: number;\n status: string;\n updatedAt: number;\n}\n\nimport { fetchAndExecuteAccountScopedPrompt, AmbiguousPromptError } from \"./prompt-builder.js\";\n// Note: McpCommandMetadata removed - commands resource eliminated\n// Note: buildPromptNameFromMetadata, fetchAndExecutePrompt removed - prompts now account-scoped\n\n/**\n * Build authentication arguments for Convex mutations/queries.\n * Passes either apiKey OR workspaceToken (never both) based on config.\n *\n * When using API key with agentName, passes agentName for identity resolution.\n * The backend will look up the workspace token by name to use as the actor identity.\n */\nfunction buildAuthArgs(config: ServerConfig): { apiKey?: string; workspaceToken?: string; agentName?: string } {\n if (config.apiKey) {\n return {\n apiKey: config.apiKey,\n agentName: config.agentName, // Pass agent name for identity resolution\n };\n }\n if (config.workspaceToken) {\n return { workspaceToken: config.workspaceToken };\n }\n return {};\n}\n\n/**\n * Built-in system tools (no global tools - all are workspace-scoped now)\n */\nconst SYSTEM_TOOLS: {\n name: string;\n description: string;\n inputSchema: object;\n promptContent: string;\n isSlashCommand: boolean; // true = appears as /ppm:name, false = tool only\n}[] = [\n // All tools are now workspace-scoped (see dynamic*Tools arrays below)\n];\n\n/**\n * Initialize and start the MCP server\n */\nexport async function startServer(\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<void> {\n // Track auth mode and workspace scope\n let tokenWorkspaceSlug: string | undefined;\n\n // Validate authentication - API key or workspace token\n if (config.apiKey) {\n // API Key mode - full access\n console.error(\"[MCP] Validating API key...\");\n const validation = await validateApiKey(convexClient, config.apiKey);\n if (!validation.valid) {\n throw new Error(`Invalid API key: ${validation.error}`);\n }\n console.error(`[MCP] API key validated for user: ${validation.userId}`);\n } else if (config.workspaceToken) {\n // Workspace Token mode - scoped access\n console.error(\"[MCP] Validating workspace token...\");\n const validation = await validateWorkspaceToken(convexClient, config.workspaceToken);\n if (!validation.valid) {\n throw new Error(`Invalid workspace token: ${validation.error}`);\n }\n tokenWorkspaceSlug = validation.workspaceSlug;\n console.error(`[MCP] Workspace token validated: \"${validation.tokenName}\" for workspace \"${tokenWorkspaceSlug}\"`);\n // Note: In token mode, prompts are NOT accessible - only tickets\n } else {\n throw new Error(\"No authentication provided. Set PPM_API_KEY or PPM_WORKSPACE_TOKEN.\");\n }\n\n // Fetch account-scoped prompts (global MCP tools) and workspaces (for ticket tools)\n // Account-scoped prompts: Available as global tools (e.g., \"code-review\")\n // Workspaces: Needed for ticket tool registration (e.g., \"projects_tickets_work\")\n let accountScopedPrompts: Array<{ slug: string; description?: string }> = [];\n let workspacesForTickets: Array<{ slug: string; description?: string }> = [];\n\n console.error(\"[MCP] Fetching prompt and workspace metadata...\");\n\n if (config.apiKey) {\n // API key mode - fetch account-scoped prompts and workspaces\n accountScopedPrompts = await fetchAccountScopedPromptMetadata(convexClient, config.apiKey);\n\n // Fetch workspaces for ticket tools\n // If --workspaces specified, only register ticket tools for those workspaces\n const allWorkspaces = await fetchMcpWorkspaces(convexClient, config.apiKey);\n if (config.selectedWorkspaces.length > 0) {\n const selectedSet = new Set(config.selectedWorkspaces);\n workspacesForTickets = allWorkspaces.filter((w) => selectedSet.has(w.slug));\n } else {\n workspacesForTickets = allWorkspaces;\n }\n } else if (config.workspaceToken && tokenWorkspaceSlug) {\n // Workspace token mode - no prompts (token mode is tickets-only)\n // Note: Workspace tokens only have access to tickets, not account-scoped prompts\n console.error(\"[MCP] Workspace token mode: prompts not available (tickets only)\");\n workspacesForTickets = [{ slug: tokenWorkspaceSlug, name: tokenWorkspaceSlug }];\n }\n\n console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);\n console.error(`[MCP] Found ${workspacesForTickets.length} workspace(s) for ticket tools`);\n\n if (accountScopedPrompts.length === 0 && config.apiKey) {\n console.error(\n \"[MCP] WARNING: No account-scoped prompts found. Create prompts in the 'prompts' section to expose them via MCP.\"\n );\n }\n\n // Log workspace info for ticket tools\n if (tokenWorkspaceSlug) {\n console.error(`[MCP] Ticket workspace scope: ${tokenWorkspaceSlug} (token-scoped)`);\n } else if (config.selectedWorkspaces.length > 0) {\n console.error(`[MCP] Ticket workspace filter: ${config.selectedWorkspaces.join(', ')}`);\n } else {\n console.error(`[MCP] Ticket workspaces: ALL (${workspacesForTickets.map(w => w.slug).join(', ')})`);\n }\n\n // Build dynamic ticket tools per workspace\n // Ticket tools remain workspace-scoped (e.g., \"projects_tickets_work\")\n const workspaceSlugs = new Set(workspacesForTickets.map((w) => w.slug));\n const dynamicTicketTools: { name: string; description: string; slashDescription: string; workspaceSlug: string; type: 'work' | 'close' | 'create' | 'search' | 'get' | 'list' | 'update' }[] = [];\n\n for (const workspaceSlug of workspaceSlugs) {\n // Unified work command (replaces both tickets_open and tickets_work)\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_work`,\n description: `Get work from the \"${workspaceSlug}\" workspace. With no args: gets next ticket from open queue. With ticket slug/number: opens or resumes that specific ticket.`,\n slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,\n workspaceSlug,\n type: 'work',\n });\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_close`,\n description: `Mark a working ticket as completed in the \"${workspaceSlug}\" workspace`,\n slashDescription: `Mark a working ticket as completed`,\n workspaceSlug,\n type: 'close',\n });\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_create`,\n description: `Create a new ticket in the \"${workspaceSlug}\" workspace queue`,\n slashDescription: `Create a new ticket in the backlog queue`,\n workspaceSlug,\n type: 'create',\n });\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_search`,\n description: `Search for tickets by content in the \"${workspaceSlug}\" workspace`,\n slashDescription: `Search for tickets by content`,\n workspaceSlug,\n type: 'search',\n });\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_get`,\n description: `Get a specific ticket by number or slug from \"${workspaceSlug}\" (read-only)`,\n slashDescription: `Get a specific ticket by number or slug (read-only)`,\n workspaceSlug,\n type: 'get',\n });\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_list`,\n description: `List active tickets in the \"${workspaceSlug}\" workspace (backlog + open + working)`,\n slashDescription: `List active tickets (backlog + open + working)`,\n workspaceSlug,\n type: 'list',\n });\n dynamicTicketTools.push({\n name: `${workspaceSlug}_tickets_update`,\n description: `Update a ticket in the \"${workspaceSlug}\" workspace by appending content with timestamp`,\n slashDescription: `Update a ticket by appending content with timestamp`,\n workspaceSlug,\n type: 'update',\n });\n }\n\n console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${workspaceSlugs.size} workspace(s)...`);\n\n // Build global system tools (not workspace-scoped)\n // run_prompt and system_prompts are now global since prompts are account-scoped\n const globalSystemTools: { name: string; description: string; slashDescription: string }[] = [\n {\n name: \"system_run_prompt\",\n description: \"Execute a prompt by slug. Use system_prompts to list available prompts.\",\n slashDescription: \"Execute a prompt by slug. Use system_prompts to list available prompts.\",\n },\n {\n name: \"system_prompts\",\n description: \"List all available prompts\",\n slashDescription: \"List all available prompts\",\n },\n ];\n\n console.error(`[MCP] Registering ${globalSystemTools.length} global system tools...`);\n\n // Build dynamic per-prompt tools (each prompt as its own tool)\n // Now global: Direct invocation like `code-review` instead of `projects:code-review`\n const dynamicPromptTools: {\n name: string; // \"code-review\" (global, no workspace prefix)\n description: string;\n promptSlug: string;\n }[] = [];\n\n for (const prompt of accountScopedPrompts) {\n dynamicPromptTools.push({\n name: prompt.slug, // Global tool name, no workspace prefix\n description: prompt.description || `Execute the \"${prompt.slug}\" prompt`,\n promptSlug: prompt.slug,\n });\n }\n\n console.error(`[MCP] Registering ${dynamicPromptTools.length} per-prompt tools (global)...`);\n\n // Note: Commands resource removed - users create slash commands directly from flattened prompts\n\n // Check for duplicate prompt names (now global, no workspace prefix)\n const promptNames = new Set<string>();\n const duplicates: string[] = [];\n accountScopedPrompts.forEach((p) => {\n if (promptNames.has(p.slug)) {\n duplicates.push(p.slug);\n }\n promptNames.add(p.slug);\n });\n\n if (duplicates.length > 0) {\n console.error(\n `[MCP] WARNING: Duplicate prompt slugs detected: ${duplicates.join(\", \")}. Only the first occurrence will be registered.`\n );\n }\n\n // Create MCP server\n const server = new Server(\n {\n name: \"ppm-mcp-server\",\n version: \"3.1.0\",\n },\n {\n capabilities: {\n prompts: {},\n tools: {},\n },\n }\n );\n\n // Register list_prompts handler\n // Prompts are now global (account-scoped), tickets remain workspace-scoped\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n // Build ticket prompt schemas (workspace-scoped)\n const ticketPromptSchemas = dynamicTicketTools.map((tt) => ({\n name: tt.name,\n description: tt.slashDescription,\n }));\n\n // Build global system prompt schemas\n const systemPromptsSchemas = globalSystemTools.map((st) => ({\n name: st.name,\n description: st.slashDescription,\n }));\n\n return {\n prompts: [\n ...systemPromptsSchemas, // system_prompts, system_run_prompt (global)\n ...ticketPromptSchemas, // workspace_tickets_* (workspace-scoped)\n ],\n };\n });\n\n // Register get_prompt handler\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const promptName = request.params.name;\n\n // Check for system tools first (as prompts for slash command access)\n const systemTool = SYSTEM_TOOLS.find((st) => st.name === promptName);\n if (systemTool) {\n return {\n description: systemTool.description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: systemTool.promptContent,\n },\n },\n ],\n };\n }\n\n // Check for ticket commands (Ticket 135, 149, 153 - handle as slash commands)\n // Handle exact matches and _work with optional argument (e.g., \"workspace_tickets_work 102\")\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);\n const ticketWorkMatch = promptName.match(/^(.+)_tickets_work\\s+(.+)$/);\n\n if (ticketTool || ticketWorkMatch) {\n let promptContent: string;\n let description: string;\n\n if (ticketWorkMatch) {\n // Handle tickets_work with specific ticket argument\n const workspaceSlug = ticketWorkMatch[1];\n const ticketArg = ticketWorkMatch[2].trim();\n description = `Work on ticket \"${ticketArg}\" from \"${workspaceSlug}\"`;\n promptContent = `Get work on ticket \"${ticketArg}\" from the \"${workspaceSlug}\" workspace.\\n\\nCall the \\`${workspaceSlug}_tickets_work\\` tool with ticketSlug: \"${ticketArg}\".\\n\\nThis will open the ticket if it's in the open queue, or resume it if already working.`;\n } else if (ticketTool!.type === 'work') {\n // Unified work command (replaces open + work)\n description = ticketTool!.description;\n promptContent = `Get work from the \"${ticketTool!.workspaceSlug}\" workspace.\\n\\nCall the \\`${ticketTool!.name}\\` tool to get the next ticket from the open queue.\\n\\nYou can also specify a ticket: /ppm:${ticketTool!.workspaceSlug}_tickets_work <number-or-slug>\\n\\nThis unified command handles both opening new tickets and resuming in-progress work.`;\n } else if (ticketTool!.type === 'create') {\n // Ticket 149: tickets:create slash command\n description = ticketTool!.description;\n promptContent = `Create a new ticket in the \"${ticketTool!.workspaceSlug}\" workspace queue.\n\n## How This Works\nThe user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.\n\n## Instructions\n1. **Read the user's request** - they may reference a file, ask you to summarize a session, or describe what they want\n2. **Process the input** - read files, extract relevant sections, summarize as needed\n3. **Generate ticket content** with a clear, descriptive title as the first line\n4. **Call the tool** with the generated content\n\n## Content Format\n\\`\\`\\`\n[Clear descriptive title - this becomes the slug]\n\n[Body content - tasks, description, context, etc.]\n\\`\\`\\`\n\n## Examples\n- \"create a ticket from /path/to/plan.md\" → Read file, use as ticket content\n- \"summarize our brainstorm into a ticket\" → Extract key points from conversation\n- \"create a ticket for the auth bug we discussed\" → Generate from session context\n- \"just the tasks from this file\" → Extract only task items\n\nCall the \\`${ticketTool!.name}\\` tool with the final generated content.`;\n } else if (ticketTool!.type === 'close') {\n description = ticketTool!.description;\n promptContent = `Close a working ticket in the \"${ticketTool!.workspaceSlug}\" workspace.\\n\\nCall the \\`${ticketTool!.name}\\` tool with the ticket number or slug.`;\n } else {\n // Fallback for unknown type\n description = ticketTool!.description;\n promptContent = `Use the \\`${ticketTool!.name}\\` tool.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Check for global system_prompts command\n if (promptName === \"system_prompts\") {\n return {\n description: \"List all available prompts\",\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: `List all available prompts.\n\nCall the \\`system_prompts\\` tool with optional \\`search\\` parameter to filter results.`,\n },\n },\n ],\n };\n }\n\n // Check for global system_run_prompt command (with optional prompt slug argument)\n const runPromptMatch = promptName.match(/^system_run_prompt\\s+(.+)$/);\n\n if (promptName === \"system_run_prompt\" || runPromptMatch) {\n let promptContent: string;\n let description: string;\n\n if (runPromptMatch) {\n // Handle run_prompt with specific prompt slug argument\n const promptSlug = runPromptMatch[1].trim();\n description = `Execute prompt \"${promptSlug}\"`;\n promptContent = `Execute the \"${promptSlug}\" prompt.\\n\\nCall the \\`system_run_prompt\\` tool with slug: \"${promptSlug}\".`;\n } else {\n // Base run_prompt without argument - show help\n description = \"Execute a prompt by slug\";\n promptContent = `Execute a prompt by slug.\n\n## Usage\nCall the \\`system_run_prompt\\` tool with the prompt slug.\n\n## Available Prompts\nUse \\`system_prompts\\` to list all available prompts.\n\n## Example\n/ppm:system_run_prompt code-review\n\nThis will execute the \"code-review\" prompt.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Note: Command handling removed - commands resource eliminated\n\n // Unknown prompt\n throw new Error(`Unknown prompt: ${promptName}. Use system_run_prompt to execute prompts.`);\n });\n\n // Register list_tools handler\n // Note: Per-prompt tool registration removed in favor of workspace-scoped run_prompt tools\n // This reduces token overhead and allows dynamic prompt discovery without server restart\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n // Build tools array: system tools + ticket tools + workspace:run_prompt tools\n // Individual prompts no longer registered as separate tools\n const tools = [\n // System tools with full input schemas\n ...SYSTEM_TOOLS.map((st) => ({\n name: st.name,\n description: st.description,\n inputSchema: st.inputSchema,\n })),\n // Dynamic ticket tools per workspace (Ticket 135, 149, 151, 153, unified work)\n ...dynamicTicketTools.map((tt) => {\n // Build inputSchema based on tool type\n let inputSchema: { type: \"object\"; properties: Record<string, object>; required: string[] };\n\n if (tt.type === 'close') {\n // close requires a ticketSlug\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'work') {\n // Unified work command (replaces open + work)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Optional: Ticket number (e.g., '102') or full slug. If not provided, gets next ticket from open queue.\",\n },\n },\n required: [],\n };\n } else if (tt.type === 'create') {\n inputSchema = {\n type: \"object\" as const,\n properties: {\n content: {\n type: \"string\",\n description: \"The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed.\",\n },\n },\n required: [\"content\"],\n };\n } else if (tt.type === 'search') {\n // Ticket 155: tickets:search\n inputSchema = {\n type: \"object\" as const,\n properties: {\n query: {\n type: \"string\",\n description: \"Search query (min 3 characters)\",\n },\n },\n required: [\"query\"],\n };\n } else if (tt.type === 'get') {\n // Ticket 155: tickets:get (read-only inspection)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'list') {\n // Ticket 155: tickets:list (no parameters - shows active queue)\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else if (tt.type === 'update') {\n // Ticket 293: tickets:update (append content to ticket)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n content: {\n type: \"string\",\n description: \"Update content to append to the ticket (markdown supported)\",\n },\n },\n required: [\"ticketSlug\", \"content\"],\n };\n } else {\n // Fallback: no params\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n }\n\n return {\n name: tt.name,\n description: tt.description,\n inputSchema,\n };\n }),\n // Global system_run_prompt tool\n {\n name: \"system_run_prompt\",\n description: \"Execute a prompt by slug. Use system_prompts to list available prompts.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: {\n type: \"string\",\n description: \"Prompt slug to execute (e.g., 'code-review', 'plan')\",\n },\n },\n required: [\"slug\"],\n },\n },\n // Global system_prompts tool\n {\n name: \"system_prompts\",\n description: \"List all available prompts\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n search: {\n type: \"string\",\n description: \"Optional search term to filter prompts by name or description (case-insensitive)\",\n },\n },\n },\n },\n // Dynamic per-prompt tools (each prompt as its own global tool)\n ...dynamicPromptTools.map((pt) => ({\n name: pt.name,\n description: pt.description,\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n required: [],\n },\n })),\n ];\n\n return { tools };\n });\n\n // Register call_tool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const toolName = request.params.name;\n\n // Handle global system_run_prompt tool\n if (toolName === \"system_run_prompt\") {\n const promptSlug = request.params.arguments?.slug as string | undefined;\n\n if (!promptSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').\n\nUse \\`system_prompts\\` to list available prompts.`,\n },\n ],\n isError: true,\n };\n }\n\n // Execute the prompt using account-scoped lookup\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptSlug,\n config,\n convexClient\n );\n\n // Convert prompt result to tool result format\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: promptText,\n },\n ],\n };\n } catch (error) {\n // Handle ambiguous prompt matches with helpful suggestions\n if (error instanceof AmbiguousPromptError) {\n const suggestionsList = error.suggestions.map((s) => ` • ${s}`).join(\"\\n\");\n console.error(`[MCP] ${toolName} ambiguous match:`, error.suggestions);\n return {\n content: [\n {\n type: \"text\",\n text: `Multiple prompts match \"${promptSlug}\". Please specify one of:\\n\\n${suggestionsList}\\n\\nExample: \\`system_run_prompt ${error.suggestions[0]}\\``,\n },\n ],\n isError: true,\n };\n }\n\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error executing prompt \"${promptSlug}\": ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // Handle global system_prompts tool (account-scoped prompt listing)\n // Uses cached metadata (lightweight) - no need to fetch full content for listing\n if (toolName === \"system_prompts\") {\n const searchTerm = request.params.arguments?.search as string | undefined;\n\n // Use account-scoped prompts (global, no workspace filter)\n let filteredPrompts = [...accountScopedPrompts];\n\n // Filter by search term if provided\n if (searchTerm) {\n const lowerSearch = searchTerm.toLowerCase();\n filteredPrompts = filteredPrompts.filter(\n (p) =>\n p.slug.toLowerCase().includes(lowerSearch) ||\n p.description?.toLowerCase().includes(lowerSearch)\n );\n }\n\n // Sort prompts by slug\n filteredPrompts.sort((a, b) => a.slug.localeCompare(b.slug));\n\n // Build user prompts list\n const userPromptList = filteredPrompts\n .map((p) => {\n const desc = p.description || \"No description\";\n return `• ${p.slug}\\n Description: ${desc}`;\n })\n .join(\"\\n\\n\");\n\n const summary = searchTerm\n ? `Found ${filteredPrompts.length} prompt(s) matching \"${searchTerm}\":`\n : `Available prompts (${filteredPrompts.length} total):`;\n\n return {\n content: [\n {\n type: \"text\",\n text: userPromptList\n ? `${summary}\\n\\n${userPromptList}`\n : `${summary}\\n\\nNo prompts found.`,\n },\n ],\n };\n }\n\n // Handle ticket tool invocations (Ticket 135, 153, unified work)\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === toolName);\n if (ticketTool) {\n if (ticketTool.type === 'work') {\n // Unified work command (replaces both open and work)\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n\n try {\n // Call mutation (not query) - this can move ticket from open to working\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:workMcpTicket\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n ticketSlug: ticketSlug, // Optional: specific ticket to work on\n }\n ) as {\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string;\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n } | { error: string; accessDenied: true } | null;\n\n if (!result) {\n const message = ticketSlug\n ? `Ticket \"${ticketSlug}\" not found or not in open/working status in workspace \"${ticketTool.workspaceSlug}\".`\n : `No open tickets in workspace \"${ticketTool.workspaceSlug}\".`;\n return {\n content: [\n {\n type: \"text\",\n text: message,\n },\n ],\n };\n }\n\n // Handle access denied response\n if ('accessDenied' in result && result.accessDenied) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n const startedInfo = result.startedAt\n ? `\\nStarted: ${new Date(result.startedAt).toISOString()}`\n : '';\n\n // Different footer based on whether this was a fresh open or resumption\n const statusNote = result.wasOpened\n ? `_Ticket moved to working status. ${result.remainingTickets} ticket(s) remaining in queue._`\n : `_Resuming work on this ticket. ${result.remainingTickets} ticket(s) in queue._`;\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${result.slug} [WORKING]${startedInfo}\n\n${result.content}\n\n---\n${statusNote}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_work error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting work: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'create') {\n // Handle tickets:create (Ticket 149)\n const content = request.params.arguments?.content as string | undefined;\n\n if (!content) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the ticket content (first line becomes the slug).`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:createMcpTicket\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n content,\n }\n ) as { slug: string; preview: string; position: number; totalBacklog: number };\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Created ticket [${result.slug}] in backlog for workspace \"${ticketTool.workspaceSlug}\".\n\nPosition: #${result.position} of ${result.totalBacklog} backlog tickets\nPreview: ${result.preview}\n\n_Ticket created in backlog. Use \\`tickets_work ${result.slug}\\` to move it to working status._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_create error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error creating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'close') {\n // Ticket 151: Handle tickets:close\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n\n if (!ticketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Usage: Provide a ticket number (e.g., \"102\") or full slug (e.g., \"102-fix-auth\").`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:closeMcpTicket\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n ticketSlug: ticketSlug,\n }\n ) as CloseTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] closed in workspace \"${ticketTool.workspaceSlug}\".\n\nClosed at: ${new Date(result.closedAt).toISOString()}\n\n_Reminder: Ensure all embedded \\`[RUN_PROMPT ...]\\` directives were executed before closing._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_close error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error closing ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'search') {\n // Ticket 155: Handle tickets:search\n const query = request.params.arguments?.query as string | undefined;\n\n if (!query || query.length < 3) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Search query must be at least 3 characters`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).query(\n \"mcp_tickets:searchMcpTickets\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n query,\n }\n ) as Array<{ slug: string; ticketNumber?: number; status: string; matchSnippet: string; createdAt: number }>;\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No tickets found matching \"${query}\" in workspace \"${ticketTool.workspaceSlug}\".`,\n },\n ],\n };\n }\n\n // Format as list with status badges\n const formattedList = result\n .map((t) => {\n const statusBadge = t.status === 'open' ? '🟢' : t.status === 'working' ? '🟡' : t.status === 'closed' ? '⚫' : '⚪';\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return `${statusBadge} ${num} ${t.slug}\\n ${t.matchSnippet}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${result.length} ticket(s) matching \"${query}\":\\n\\n${formattedList}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_search error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error searching tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'get') {\n // Ticket 155: Handle tickets:get (read-only inspection)\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n\n if (!ticketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // Uses existing getMcpTicket query - read-only inspection\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).query(\n \"mcp_tickets:getMcpTicket\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n ticketSlug,\n }\n ) as { slug: string; ticketNumber?: number; content: string; status: string; createdAt: number; startedAt?: number; closedAt?: number; assignedTo?: string } | { error: string; accessDenied: true } | null;\n\n if (!result) {\n return {\n content: [\n {\n type: \"text\",\n text: `Ticket \"${ticketSlug}\" not found in workspace \"${ticketTool.workspaceSlug}\".`,\n },\n ],\n };\n }\n\n // Handle access denied response\n if ('accessDenied' in result && result.accessDenied) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n // Build status line\n const statusBadge = result.status.toUpperCase();\n const startedInfo = result.startedAt ? `\\nStarted: ${new Date(result.startedAt).toISOString()}` : '';\n const closedInfo = result.closedAt ? `\\nClosed: ${new Date(result.closedAt).toISOString()}` : '';\n const assignedInfo = result.assignedTo ? `\\nAssigned to: ${result.assignedTo}` : '\\nUnassigned';\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${result.slug} [${statusBadge}]${startedInfo}${closedInfo}${assignedInfo}\n\n${result.content}\n\n---\n_Read-only inspection. Use tickets_work to start working on this ticket._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_get error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'list') {\n // Ticket 155: Handle tickets:list (active queue)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).query(\n \"mcp_tickets:listMcpTickets\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n }\n ) as Array<{ position: number; slug: string; ticketNumber?: number; status: string; preview: string; createdAt: number; startedAt?: number }>;\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No active tickets in workspace \"${ticketTool.workspaceSlug}\". All tickets are closed or archived.`,\n },\n ],\n };\n }\n\n // Group by status for clear display\n const openTickets = result.filter(t => t.status === 'open');\n const workingTickets = result.filter(t => t.status === 'working');\n const backlogTickets = result.filter(t => t.status === 'backlog');\n\n const formatTicketLine = (t: typeof result[0]) => {\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return ` ${t.position}. ${num} ${t.slug}\\n ${t.preview}`;\n };\n\n const sections: string[] = [];\n\n if (openTickets.length > 0) {\n sections.push(`**🟢 Open (${openTickets.length})**\\n${openTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (workingTickets.length > 0) {\n sections.push(`**🟡 Working (${workingTickets.length})**\\n${workingTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (backlogTickets.length > 0) {\n sections.push(`**⚪ Backlog (${backlogTickets.length})**\\n${backlogTickets.map(formatTicketLine).join('\\n')}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Active Queue: ${ticketTool.workspaceSlug}\\n\\n${result.length} ticket(s) in queue\\n\\n${sections.join('\\n\\n')}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_list error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error listing tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'update') {\n // Ticket 293: Handle tickets:update (append content to ticket)\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n const content = request.params.arguments?.content as string | undefined;\n\n if (!ticketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!content) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the update content to append to the ticket.`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:updateMcpTicket\",\n {\n ...buildAuthArgs(config),\n workspaceSlug: ticketTool.workspaceSlug,\n ticketSlug,\n content,\n }\n ) as UpdateTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] updated in workspace \"${ticketTool.workspaceSlug}\".\nUpdated at: ${new Date(result.updatedAt).toISOString()}\n_Ticket content has been appended with your update._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_update error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error updating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n }\n\n // Handle per-prompt tool invocations (e.g., \"code-review\" - now global)\n const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);\n if (promptTool) {\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptTool.promptSlug,\n config,\n convexClient\n );\n\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [{ type: \"text\", text: promptText }],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [{ type: \"text\", text: `Error executing prompt: ${errorMessage}` }],\n isError: true,\n };\n }\n }\n\n // Unknown tool\n throw new Error(`Unknown tool: ${toolName}. Use system_run_prompt to execute prompts by name, or check available tools.`);\n });\n\n // Start server with stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n console.error(\"[MCP] Server started successfully\");\n console.error(`[MCP] Deployment: ${config.isDev ? \"DEVELOPMENT\" : \"PRODUCTION\"}`);\n console.error(`[MCP] Convex URL: ${config.convexUrl}`);\n console.error(`[MCP] Data mode: REAL-TIME (fetches fresh data on each invocation)`);\n\n // List all prompts\n const allPromptNames = [...Array.from(promptNames)].sort();\n console.error(`[MCP] Prompts available: ${allPromptNames.join(\", \")}`);\n console.error(`[MCP] - Total prompts: ${promptNames.size}`);\n\n // Keep the event loop alive with a heartbeat\n // This prevents Node from exiting when there are no active handles\n setInterval(() => {\n // Heartbeat every 60 seconds to keep process alive\n }, 60000);\n\n // Return a promise that never resolves to keep the server running\n return new Promise<void>(() => {\n // The transport handles stdin/stdout communication\n // The interval above keeps the event loop active\n });\n}\n\n/**\n * Validate API key with Convex\n */\nasync function validateApiKey(\n client: ConvexHttpClient,\n apiKey: string\n): Promise<{ valid: boolean; userId?: string; error?: string }> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await client.query(\"apiKeys:validateApiKey\" as any, { key: apiKey });\n if (result) {\n return { valid: true, userId: result.userId };\n }\n return { valid: false, error: \"Invalid API key\" };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n/**\n * Validate workspace token with Convex\n *\n * Workspace tokens provide scoped access to a single workspace for ticket operations.\n * Returns workspace details if valid, error if invalid/revoked.\n */\nasync function validateWorkspaceToken(\n client: ConvexHttpClient,\n token: string\n): Promise<WorkspaceTokenValidation> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await client.query(\"workspace_tokens:validateWorkspaceToken\" as any, { token });\n if (result && result.valid) {\n return {\n valid: true,\n workspaceId: result.workspaceId,\n workspaceSlug: result.workspaceSlug,\n ownerId: result.ownerId,\n tokenId: result.tokenId,\n tokenName: result.tokenName,\n };\n }\n return { valid: false, error: \"Invalid or revoked workspace token\" };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n// Note: fetchMcpPromptMetadata and fetchMcpPromptMetadataByToken removed - prompts now account-scoped\n// Note: fetchMcpCommands and buildCommandName removed - commands resource eliminated\n\n/**\n * Fetch account-scoped prompt metadata for global MCP tools\n * Returns prompts WITHOUT workspaceId - globally available\n */\nasync function fetchAccountScopedPromptMetadata(\n client: ConvexHttpClient,\n apiKey: string\n): Promise<Array<{ slug: string; description?: string }>> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const metadata = await client.query(\"mcp_prompts:getAccountScopedPromptMetadata\" as any, {\n apiKey,\n });\n return metadata;\n } catch (error) {\n throw new Error(\n `Failed to fetch account-scoped prompt metadata: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n}\n\n/**\n * Fetch user's workspaces for ticket tool registration\n * Separate from prompts since prompts are now global\n */\nasync function fetchMcpWorkspaces(\n client: ConvexHttpClient,\n apiKey: string\n): Promise<Array<{ slug: string; description?: string }>> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const workspaces = await client.query(\"mcp_prompts:getMcpWorkspaces\" as any, {\n apiKey,\n });\n return workspaces;\n } catch (error) {\n throw new Error(\n `Failed to fetch workspaces: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n}\n","import type { ConvexHttpClient } from \"convex/browser\";\nimport type { McpPrompt, McpPromptMetadata, PromptMatchResult, ServerConfig } from \"./types.js\";\n\n/**\n * Sanitizes a string for use in MCP tool names.\n * Ensures output matches MCP protocol pattern: ^[a-zA-Z0-9_-]{1,64}$\n *\n * @param str - Raw string to sanitize (workspace slug, folder path, or prompt slug)\n * @returns Sanitized string safe for MCP tool names\n */\nexport function sanitizeForMcp(str: string): string {\n return str\n .trim() // Remove leading/trailing whitespace first\n .toLowerCase() // Convert to lowercase for consistency\n .replace(/[^a-z0-9_-]/g, '-') // Replace ALL invalid chars (including spaces!) with hyphens\n .replace(/-+/g, '-') // Collapse multiple consecutive hyphens into one\n .replace(/^-+|-+$/g, '') // Remove leading and trailing hyphens\n .trim(); // Final trim to ensure no whitespace\n}\n\n/**\n * Build the MCP prompt name for a prompt with workspace scoping\n *\n * MCP tool names must match pattern: ^[a-zA-Z0-9_:-]{1,64}$\n * (letters, numbers, underscores, hyphens, and colons allowed)\n *\n * Format: workspace:prompt\n * - Folders are NOT included in slash command names\n * - Folders are only used for ZIP download organization\n *\n * Character mapping:\n * - Separator: : (colon) between workspace and prompt\n * - Hyphens: - used within multi-word slugs\n * - Example: personal:code-review, work:api-design\n */\nexport function buildPromptName(prompt: McpPrompt): string {\n const workspace = sanitizeForMcp(prompt.workspaceSlug || 'default') || 'default';\n const promptSlug = sanitizeForMcp(prompt.slug || 'unknown') || 'unknown';\n\n // Hierarchical workspace:prompt format\n return `${workspace}:${promptSlug}`.trim();\n}\n\n/**\n * Build the MCP prompt name from lightweight metadata (no flattenedPrompt)\n * Used for startup/tool registration when only metadata is available\n */\nexport function buildPromptNameFromMetadata(metadata: McpPromptMetadata): string {\n const workspace = sanitizeForMcp(metadata.workspaceSlug || 'default') || 'default';\n const promptSlug = sanitizeForMcp(metadata.slug || 'unknown') || 'unknown';\n\n // Hierarchical workspace:prompt format\n return `${workspace}:${promptSlug}`.trim();\n}\n\n/**\n * Build the MCP prompt schema for a prompt\n */\nexport function buildPromptSchema(prompt: McpPrompt) {\n return {\n name: buildPromptName(prompt),\n description: prompt.description || `Prompt: ${prompt.name}`,\n arguments: [],\n };\n}\n\n/**\n * Build the prompt execution handler for a prompt\n * @deprecated Use buildPromptHandlerOptimized for better performance\n */\nexport function buildPromptHandler(\n prompt: McpPrompt,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n) {\n return async () => {\n let flattenedPrompt = prompt.flattenedPrompt;\n let promptName = prompt.name;\n\n // Always fetch fresh data from Convex (real-time mode)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let freshPrompts = await convexClient.query(\"mcp_prompts:getMcpPrompts\" as any, {\n apiKey: config.apiKey,\n });\n\n // Apply workspace filter to fresh data if configured\n if (config.selectedWorkspaces.length > 0) {\n freshPrompts = freshPrompts.filter((p: McpPrompt) =>\n p.workspaceSlug && config.selectedWorkspaces.includes(p.workspaceSlug)\n );\n }\n\n const freshPrompt = freshPrompts.find((p: McpPrompt) => p.slug === prompt.slug && p.workspaceSlug === prompt.workspaceSlug);\n\n if (freshPrompt) {\n flattenedPrompt = freshPrompt.flattenedPrompt;\n promptName = freshPrompt.name;\n console.error(`[MCP] Fetched fresh data for: ${buildPromptName(prompt)}`);\n } else {\n console.error(\n `[MCP] WARNING: Prompt \"${prompt.slug}\" not found in fresh fetch, using cached version`\n );\n }\n } catch (error) {\n console.error(\n `[MCP] WARNING: Failed to fetch fresh data, using cached version: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n\n if (!flattenedPrompt) {\n throw new Error(\n `Prompt \"${promptName}\" has no flattened content. Please re-save the prompt in ContextFS to regenerate it.`\n );\n }\n\n console.error(`[MCP] Executed prompt: ${buildPromptName(prompt)}`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: flattenedPrompt,\n },\n },\n ],\n };\n };\n}\n\n/**\n * Custom error class for ambiguous prompt matches\n * Contains suggestions for the user to choose from\n */\nexport class AmbiguousPromptError extends Error {\n suggestions: string[];\n\n constructor(promptSlug: string, suggestions: string[]) {\n super(`Multiple prompts match \"${promptSlug}\". Please be more specific.`);\n this.name = \"AmbiguousPromptError\";\n this.suggestions = suggestions;\n }\n}\n\n/**\n * Fetch a single prompt and execute it - O(1) index lookup with fuzzy fallback\n *\n * Supports both API key and workspace token authentication:\n * - API key: Full access to all user workspaces\n * - Workspace token: Scoped access to token's workspace only\n *\n * Matching algorithm:\n * 1. Exact match via index (O(1)) - fast path\n * 2. Prefix match fallback if exact fails\n * 3. Contains match fallback if prefix fails\n * 4. Ambiguous error if multiple matches found\n *\n * Performance optimization: Uses getMcpPromptBySlug for direct index lookup\n * with fallback to fuzzy matching only when exact match fails.\n */\nexport async function fetchAndExecutePrompt(\n workspaceSlug: string,\n promptSlug: string,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<{ messages: Array<{ role: string; content: { type: \"text\"; text: string } }> }> {\n let result: PromptMatchResult;\n\n // Use appropriate query based on auth type\n if (config.apiKey) {\n // API key auth - use standard query\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result = await convexClient.query(\"mcp_prompts:getMcpPromptBySlug\" as any, {\n apiKey: config.apiKey,\n workspaceSlug,\n promptSlug,\n }) as PromptMatchResult;\n } else if (config.workspaceToken) {\n // Workspace token auth - use token-specific query\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result = await convexClient.query(\"mcp_prompts:getMcpPromptBySlugWithToken\" as any, {\n workspaceToken: config.workspaceToken,\n workspaceSlug,\n promptSlug,\n }) as PromptMatchResult;\n } else {\n throw new Error(\"No authentication configured\");\n }\n\n // Handle null - no matches found\n if (!result) {\n throw new Error(\n `Prompt \"${promptSlug}\" not found in workspace \"${workspaceSlug}\".`\n );\n }\n\n // Handle ambiguous matches - throw custom error with suggestions\n if (result.matchType === \"ambiguous\") {\n throw new AmbiguousPromptError(promptSlug, result.suggestions);\n }\n\n // Handle successful match (exact, prefix, or contains)\n if (!result.flattenedPrompt) {\n throw new Error(\n `Prompt \"${result.slug}\" has no flattened content. Please re-save the prompt to regenerate it.`\n );\n }\n\n // Log match type for debugging\n const matchNote = result.matchType === \"exact\"\n ? \"exact match\"\n : result.matchType === \"prefix\"\n ? `prefix match → ${result.slug}`\n : `contains match → ${result.slug}`;\n console.error(`[MCP] Fetched prompt: ${workspaceSlug}:${promptSlug} (${matchNote})`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: result.flattenedPrompt,\n },\n },\n ],\n };\n}\n\n/**\n * Type for account-scoped prompt match result\n */\ntype AccountScopedPromptResult =\n | { matchType: \"exact\" | \"prefix\" | \"contains\"; slug: string; description?: string; flattenedPrompt?: string }\n | { matchType: \"ambiguous\"; suggestions: string[] }\n | null;\n\n/**\n * Fetch a single account-scoped prompt and execute it\n *\n * Account-scoped prompts are global (no workspace prefix required).\n * This is for prompts that don't have a workspaceId.\n *\n * Matching algorithm same as fetchAndExecutePrompt but without workspace context.\n */\nexport async function fetchAndExecuteAccountScopedPrompt(\n promptSlug: string,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<{ messages: Array<{ role: string; content: { type: \"text\"; text: string } }> }> {\n if (!config.apiKey) {\n throw new Error(\"Account-scoped prompts require API key authentication\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await convexClient.query(\"mcp_prompts:getAccountScopedPromptBySlug\" as any, {\n apiKey: config.apiKey,\n promptSlug,\n }) as AccountScopedPromptResult;\n\n // Handle null - no matches found\n if (!result) {\n throw new Error(\n `Prompt \"${promptSlug}\" not found. Use system:prompts to list available prompts.`\n );\n }\n\n // Handle ambiguous matches - throw custom error with suggestions\n if (result.matchType === \"ambiguous\") {\n throw new AmbiguousPromptError(promptSlug, result.suggestions);\n }\n\n // Handle successful match (exact, prefix, or contains)\n if (!result.flattenedPrompt) {\n throw new Error(\n `Prompt \"${result.slug}\" has no flattened content. Please re-save the prompt to regenerate it.`\n );\n }\n\n // Log match type for debugging\n const matchNote = result.matchType === \"exact\"\n ? \"exact match\"\n : result.matchType === \"prefix\"\n ? `prefix match → ${result.slug}`\n : `contains match → ${result.slug}`;\n console.error(`[MCP] Fetched account-scoped prompt: ${promptSlug} (${matchNote})`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: result.flattenedPrompt,\n },\n },\n ],\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/convex-client.ts","../src/server.ts","../src/prompt-builder.ts"],"sourcesContent":["import minimist from \"minimist\";\nimport { createConvexClient } from \"./convex-client.js\";\nimport { startServer } from \"./server.js\";\nimport type { ServerConfig } from \"./types.js\";\n\n/**\n * Main entry point for the MCP server\n */\nasync function main() {\n try {\n // Parse CLI arguments\n const argv = minimist(process.argv.slice(2));\n const isDev = argv.dev === true;\n\n // Parse --projects CLI argument (Ticket 144)\n // Can be: --projects p1,p2 OR --projects p1 --projects p2 OR -p p1,p2\n const projectsArg = argv.projects || argv.p;\n let selectedProjects: string[] = [];\n if (projectsArg) {\n if (Array.isArray(projectsArg)) {\n // Multiple --projects flags: [\"p1\", \"p2\"]\n selectedProjects = projectsArg.flatMap((p: string) => String(p).split(','));\n } else {\n // Single --projects flag with comma-separated: \"p1,p2\"\n selectedProjects = String(projectsArg).split(',');\n }\n // Clean up whitespace\n selectedProjects = selectedProjects.map((s) => s.trim()).filter(Boolean);\n }\n\n // Check for authentication (API key or project token)\n // API key takes precedence if both are provided\n const apiKey = process.env.PPM_API_KEY || process.env.THEPROMPTEDITOR_API_KEY;\n const projectToken = process.env.PPM_PROJECT_TOKEN;\n\n // PPM_AGENT: Optional agent identity selection (only with API key auth)\n // When set, uses the named project token as identity while API key provides access\n const ppmAgent = process.env.PPM_AGENT;\n\n if (!apiKey && !projectToken) {\n console.error(\n \"[MCP] ERROR: Missing authentication. Provide either PPM_API_KEY or PPM_PROJECT_TOKEN\"\n );\n console.error(\n \"[MCP] Options:\"\n );\n console.error(\"[MCP] export PPM_API_KEY=your_api_key_here # Full access to all projects\");\n console.error(\"[MCP] export PPM_PROJECT_TOKEN=ppt_... # Scoped access to one project (tickets only)\");\n process.exit(1);\n }\n\n // Log which auth mode is being used\n if (apiKey) {\n console.error(\"[MCP] Auth mode: API Key (full access)\");\n if (projectToken) {\n console.error(\"[MCP] Note: PPM_PROJECT_TOKEN ignored when PPM_API_KEY is set\");\n }\n if (ppmAgent) {\n console.error(`[MCP] Agent identity: ${ppmAgent}`);\n }\n } else {\n console.error(\"[MCP] Auth mode: Project Token (scoped access, tickets only)\");\n if (ppmAgent) {\n console.error(\"[MCP] Note: PPM_AGENT ignored when using PPM_PROJECT_TOKEN directly\");\n }\n }\n\n // Create Convex client\n const convexClient = createConvexClient(isDev);\n const convexUrl = isDev\n ? \"https://hallowed-shrimp-344.convex.cloud\"\n : \"https://trustworthy-squirrel-735.convex.cloud\";\n\n // Build server config\n const config: ServerConfig = {\n apiKey: apiKey || undefined, // Use undefined if not set (not empty string)\n projectToken: apiKey ? undefined : projectToken, // Only use token if no API key\n isDev,\n convexUrl,\n selectedProjects, // Project slugs to filter (empty = all projects)\n agentName: apiKey && ppmAgent ? ppmAgent : undefined, // Only use agent name with API key auth\n };\n\n // Start server - this will keep the process alive via stdio transport\n await startServer(config, convexClient);\n\n // This line should never be reached if the promise in startServer never resolves\n console.error(\"[MCP] WARNING: startServer promise resolved unexpectedly!\");\n } catch (error) {\n console.error(\n \"[MCP] FATAL ERROR:\",\n error instanceof Error ? error.message : \"Unknown error\"\n );\n if (error instanceof Error && error.stack) {\n console.error(error.stack);\n }\n process.exit(1);\n }\n}\n\n// Handle graceful shutdown\nprocess.on(\"SIGINT\", () => {\n console.error(\"[MCP] Received SIGINT, shutting down gracefully...\");\n process.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n console.error(\"[MCP] Received SIGTERM, shutting down gracefully...\");\n process.exit(0);\n});\n\nmain();\n","import { ConvexHttpClient } from \"convex/browser\";\n\nconst PROD_URL = \"https://trustworthy-squirrel-735.convex.cloud\";\nconst DEV_URL = \"https://hallowed-shrimp-344.convex.cloud\";\n\n/**\n * Create a Convex HTTP client for the appropriate deployment\n */\nexport function createConvexClient(isDev: boolean): ConvexHttpClient {\n const url = isDev ? DEV_URL : PROD_URL;\n return new ConvexHttpClient(url);\n}\n","import { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListToolsRequestSchema,\n} from \"@modelcontextprotocol/sdk/types.js\";\nimport type { ConvexHttpClient } from \"convex/browser\";\nimport type { ServerConfig, ProjectTokenValidation } from \"./types.js\";\n\n/**\n * Close ticket result (Ticket 151)\n */\ninterface CloseTicketResult {\n slug: string;\n status: string;\n closedAt: number;\n}\n\n/**\n * Update ticket result (Ticket 293)\n */\ninterface UpdateTicketResult {\n slug: string;\n ticketNumber?: number;\n status: string;\n updatedAt: number;\n}\n\nimport { fetchAndExecuteAccountScopedPrompt, AmbiguousPromptError } from \"./prompt-builder.js\";\n// Note: McpCommandMetadata removed - commands resource eliminated\n// Note: buildPromptNameFromMetadata, fetchAndExecutePrompt removed - prompts now account-scoped\n\n/**\n * Build authentication arguments for Convex mutations/queries.\n * Passes either apiKey OR projectToken (never both) based on config.\n *\n * When using API key with agentName, passes agentName for identity resolution.\n * The backend will look up the project token by name to use as the actor identity.\n */\nfunction buildAuthArgs(config: ServerConfig): { apiKey?: string; projectToken?: string; agentName?: string } {\n if (config.apiKey) {\n return {\n apiKey: config.apiKey,\n agentName: config.agentName, // Pass agent name for identity resolution\n };\n }\n if (config.projectToken) {\n return { projectToken: config.projectToken };\n }\n return {};\n}\n\n/**\n * Built-in system tools (no global tools - all are project-scoped now)\n */\nconst SYSTEM_TOOLS: {\n name: string;\n description: string;\n inputSchema: object;\n promptContent: string;\n isSlashCommand: boolean; // true = appears as /ppm:name, false = tool only\n}[] = [\n // All tools are now project-scoped (see dynamic*Tools arrays below)\n];\n\n/**\n * Initialize and start the MCP server\n */\nexport async function startServer(\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<void> {\n // Track auth mode and project scope\n let tokenProjectSlug: string | undefined;\n\n // Validate authentication - API key or project token\n if (config.apiKey) {\n // API Key mode - full access\n console.error(\"[MCP] Validating API key...\");\n const validation = await validateApiKey(convexClient, config.apiKey);\n if (!validation.valid) {\n throw new Error(`Invalid API key: ${validation.error}`);\n }\n console.error(`[MCP] API key validated for user: ${validation.userId}`);\n } else if (config.projectToken) {\n // Project Token mode - scoped access\n console.error(\"[MCP] Validating project token...\");\n const validation = await validateProjectToken(convexClient, config.projectToken);\n if (!validation.valid) {\n throw new Error(`Invalid project token: ${validation.error}`);\n }\n tokenProjectSlug = validation.projectSlug;\n console.error(`[MCP] Project token validated: \"${validation.tokenName}\" for project \"${tokenProjectSlug}\"`);\n // Note: In token mode, prompts are NOT accessible - only tickets\n } else {\n throw new Error(\"No authentication provided. Set PPM_API_KEY or PPM_PROJECT_TOKEN.\");\n }\n\n // Fetch account-scoped prompts (global MCP tools) and projects (for ticket tools)\n // Account-scoped prompts: Available as global tools (e.g., \"code-review\")\n // Projects: Needed for ticket tool registration (e.g., \"projects_tickets_work\")\n let accountScopedPrompts: Array<{ slug: string; description?: string }> = [];\n let projectsForTickets: Array<{ slug: string; description?: string }> = [];\n\n console.error(\"[MCP] Fetching prompt and project metadata...\");\n\n if (config.apiKey) {\n // API key mode - fetch account-scoped prompts and projects\n accountScopedPrompts = await fetchAccountScopedPromptMetadata(convexClient, config.apiKey);\n\n // Fetch projects for ticket tools\n // If --projects specified, only register ticket tools for those projects\n const allProjects = await fetchMcpProjects(convexClient, config.apiKey);\n if (config.selectedProjects.length > 0) {\n const selectedSet = new Set(config.selectedProjects);\n projectsForTickets = allProjects.filter((p) => selectedSet.has(p.slug));\n } else {\n projectsForTickets = allProjects;\n }\n } else if (config.projectToken && tokenProjectSlug) {\n // Project token mode - no prompts (token mode is tickets-only)\n // Note: Project tokens only have access to tickets, not account-scoped prompts\n console.error(\"[MCP] Project token mode: prompts not available (tickets only)\");\n projectsForTickets = [{ slug: tokenProjectSlug, name: tokenProjectSlug }];\n }\n\n console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);\n console.error(`[MCP] Found ${projectsForTickets.length} project(s) for ticket tools`);\n\n if (accountScopedPrompts.length === 0 && config.apiKey) {\n console.error(\n \"[MCP] WARNING: No account-scoped prompts found. Create prompts in the 'prompts' section to expose them via MCP.\"\n );\n }\n\n // Log project info for ticket tools\n if (tokenProjectSlug) {\n console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);\n } else if (config.selectedProjects.length > 0) {\n console.error(`[MCP] Ticket project filter: ${config.selectedProjects.join(', ')}`);\n } else {\n console.error(`[MCP] Ticket projects: ALL (${projectsForTickets.map(p => p.slug).join(', ')})`);\n }\n\n // Build dynamic ticket tools per project\n // Ticket tools remain project-scoped (e.g., \"projects_tickets_work\")\n const projectSlugs = new Set(projectsForTickets.map((p) => p.slug));\n const dynamicTicketTools: { name: string; description: string; slashDescription: string; projectSlug: string; type: 'work' | 'close' | 'create' | 'search' | 'get' | 'list' | 'update' }[] = [];\n\n for (const projectSlug of projectSlugs) {\n // Unified work command (replaces both tickets_open and tickets_work)\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_work`,\n 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.`,\n slashDescription: `Get next ticket from queue, or work on specific ticket by slug/number`,\n projectSlug,\n type: 'work',\n });\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_close`,\n description: `Mark a working ticket as completed in the \"${projectSlug}\" project`,\n slashDescription: `Mark a working ticket as completed`,\n projectSlug,\n type: 'close',\n });\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_create`,\n description: `Create a new ticket in the \"${projectSlug}\" project queue`,\n slashDescription: `Create a new ticket in the backlog queue`,\n projectSlug,\n type: 'create',\n });\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_search`,\n description: `Search for tickets by content in the \"${projectSlug}\" project`,\n slashDescription: `Search for tickets by content`,\n projectSlug,\n type: 'search',\n });\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_get`,\n description: `Get a specific ticket by number or slug from \"${projectSlug}\" (read-only)`,\n slashDescription: `Get a specific ticket by number or slug (read-only)`,\n projectSlug,\n type: 'get',\n });\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_list`,\n description: `List active tickets in the \"${projectSlug}\" project (backlog + open + working)`,\n slashDescription: `List active tickets (backlog + open + working)`,\n projectSlug,\n type: 'list',\n });\n dynamicTicketTools.push({\n name: `${projectSlug}_tickets_update`,\n description: `Update a ticket in the \"${projectSlug}\" project by appending content with timestamp`,\n slashDescription: `Update a ticket by appending content with timestamp`,\n projectSlug,\n type: 'update',\n });\n }\n\n console.error(`[MCP] Registering ${dynamicTicketTools.length} ticket tools for ${projectSlugs.size} project(s)...`);\n\n // Build global system tools (not project-scoped)\n // run_prompt and system_prompts are now global since prompts are account-scoped\n const globalSystemTools: { name: string; description: string; slashDescription: string }[] = [\n {\n name: \"system_run_prompt\",\n description: \"Execute a prompt by slug. Use system_prompts to list available prompts.\",\n slashDescription: \"Execute a prompt by slug. Use system_prompts to list available prompts.\",\n },\n {\n name: \"system_prompts\",\n description: \"List all available prompts\",\n slashDescription: \"List all available prompts\",\n },\n ];\n\n console.error(`[MCP] Registering ${globalSystemTools.length} global system tools...`);\n\n // Build dynamic per-prompt tools (each prompt as its own tool)\n // Now global: Direct invocation like `code-review` instead of `projects:code-review`\n const dynamicPromptTools: {\n name: string; // \"code-review\" (global, no project prefix)\n description: string;\n promptSlug: string;\n }[] = [];\n\n for (const prompt of accountScopedPrompts) {\n dynamicPromptTools.push({\n name: prompt.slug, // Global tool name, no project prefix\n description: prompt.description || `Execute the \"${prompt.slug}\" prompt`,\n promptSlug: prompt.slug,\n });\n }\n\n console.error(`[MCP] Registering ${dynamicPromptTools.length} per-prompt tools (global)...`);\n\n // Note: Commands resource removed - users create slash commands directly from flattened prompts\n\n // Check for duplicate prompt names (now global, no project prefix)\n const promptNames = new Set<string>();\n const duplicates: string[] = [];\n accountScopedPrompts.forEach((p) => {\n if (promptNames.has(p.slug)) {\n duplicates.push(p.slug);\n }\n promptNames.add(p.slug);\n });\n\n if (duplicates.length > 0) {\n console.error(\n `[MCP] WARNING: Duplicate prompt slugs detected: ${duplicates.join(\", \")}. Only the first occurrence will be registered.`\n );\n }\n\n // Create MCP server\n const server = new Server(\n {\n name: \"ppm-mcp-server\",\n version: \"3.1.0\",\n },\n {\n capabilities: {\n prompts: {},\n tools: {},\n },\n }\n );\n\n // Register list_prompts handler\n // Prompts are now global (account-scoped), tickets remain project-scoped\n server.setRequestHandler(ListPromptsRequestSchema, async () => {\n // Build ticket prompt schemas (project-scoped)\n const ticketPromptSchemas = dynamicTicketTools.map((tt) => ({\n name: tt.name,\n description: tt.slashDescription,\n }));\n\n // Build global system prompt schemas\n const systemPromptsSchemas = globalSystemTools.map((st) => ({\n name: st.name,\n description: st.slashDescription,\n }));\n\n return {\n prompts: [\n ...systemPromptsSchemas, // system_prompts, system_run_prompt (global)\n ...ticketPromptSchemas, // project_tickets_* (project-scoped)\n ],\n };\n });\n\n // Register get_prompt handler\n server.setRequestHandler(GetPromptRequestSchema, async (request) => {\n const promptName = request.params.name;\n\n // Check for system tools first (as prompts for slash command access)\n const systemTool = SYSTEM_TOOLS.find((st) => st.name === promptName);\n if (systemTool) {\n return {\n description: systemTool.description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: systemTool.promptContent,\n },\n },\n ],\n };\n }\n\n // Check for ticket commands (Ticket 135, 149, 153 - handle as slash commands)\n // Handle exact matches and _work with optional argument (e.g., \"project_tickets_work 102\")\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === promptName);\n const ticketWorkMatch = promptName.match(/^(.+)_tickets_work\\s+(.+)$/);\n\n if (ticketTool || ticketWorkMatch) {\n let promptContent: string;\n let description: string;\n\n if (ticketWorkMatch) {\n // Handle tickets_work with specific ticket argument\n const projectSlug = ticketWorkMatch[1];\n const ticketArg = ticketWorkMatch[2].trim();\n description = `Work on ticket \"${ticketArg}\" from \"${projectSlug}\"`;\n promptContent = `Get work on ticket \"${ticketArg}\" from the \"${projectSlug}\" project.\\n\\nCall the \\`${projectSlug}_tickets_work\\` tool with ticketSlug: \"${ticketArg}\".\\n\\nThis will open the ticket if it's in the open queue, or resume it if already working.`;\n } else if (ticketTool!.type === 'work') {\n // Unified work command (replaces open + work)\n description = ticketTool!.description;\n promptContent = `Get work from the \"${ticketTool!.projectSlug}\" project.\\n\\nCall the \\`${ticketTool!.name}\\` tool to get the next ticket from the open queue.\\n\\nYou can also specify a ticket: /ppm:${ticketTool!.projectSlug}_tickets_work <number-or-slug>\\n\\nThis unified command handles both opening new tickets and resuming in-progress work.`;\n } else if (ticketTool!.type === 'create') {\n // Ticket 149: tickets:create slash command\n description = ticketTool!.description;\n promptContent = `Create a new ticket in the \"${ticketTool!.projectSlug}\" project queue.\n\n## How This Works\nThe user provides **instructions** (not raw content). You interpret those instructions to generate the ticket.\n\n## Instructions\n1. **Read the user's request** - they may reference a file, ask you to summarize a session, or describe what they want\n2. **Process the input** - read files, extract relevant sections, summarize as needed\n3. **Generate ticket content** with a clear, descriptive title as the first line\n4. **Call the tool** with the generated content\n\n## Content Format\n\\`\\`\\`\n[Clear descriptive title - this becomes the slug]\n\n[Body content - tasks, description, context, etc.]\n\\`\\`\\`\n\n## Examples\n- \"create a ticket from /path/to/plan.md\" → Read file, use as ticket content\n- \"summarize our brainstorm into a ticket\" → Extract key points from conversation\n- \"create a ticket for the auth bug we discussed\" → Generate from session context\n- \"just the tasks from this file\" → Extract only task items\n\nCall the \\`${ticketTool!.name}\\` tool with the final generated content.`;\n } else if (ticketTool!.type === 'close') {\n description = ticketTool!.description;\n promptContent = `Close a working ticket in the \"${ticketTool!.projectSlug}\" project.\\n\\nCall the \\`${ticketTool!.name}\\` tool with the ticket number or slug.`;\n } else {\n // Fallback for unknown type\n description = ticketTool!.description;\n promptContent = `Use the \\`${ticketTool!.name}\\` tool.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Check for global system_prompts command\n if (promptName === \"system_prompts\") {\n return {\n description: \"List all available prompts\",\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: `List all available prompts.\n\nCall the \\`system_prompts\\` tool with optional \\`search\\` parameter to filter results.`,\n },\n },\n ],\n };\n }\n\n // Check for global system_run_prompt command (with optional prompt slug argument)\n const runPromptMatch = promptName.match(/^system_run_prompt\\s+(.+)$/);\n\n if (promptName === \"system_run_prompt\" || runPromptMatch) {\n let promptContent: string;\n let description: string;\n\n if (runPromptMatch) {\n // Handle run_prompt with specific prompt slug argument\n const promptSlug = runPromptMatch[1].trim();\n description = `Execute prompt \"${promptSlug}\"`;\n promptContent = `Execute the \"${promptSlug}\" prompt.\\n\\nCall the \\`system_run_prompt\\` tool with slug: \"${promptSlug}\".`;\n } else {\n // Base run_prompt without argument - show help\n description = \"Execute a prompt by slug\";\n promptContent = `Execute a prompt by slug.\n\n## Usage\nCall the \\`system_run_prompt\\` tool with the prompt slug.\n\n## Available Prompts\nUse \\`system_prompts\\` to list all available prompts.\n\n## Example\n/ppm:system_run_prompt code-review\n\nThis will execute the \"code-review\" prompt.`;\n }\n\n return {\n description,\n messages: [\n {\n role: \"user\" as const,\n content: {\n type: \"text\" as const,\n text: promptContent,\n },\n },\n ],\n };\n }\n\n // Note: Command handling removed - commands resource eliminated\n\n // Unknown prompt\n throw new Error(`Unknown prompt: ${promptName}. Use system_run_prompt to execute prompts.`);\n });\n\n // Register list_tools handler\n // Note: Per-prompt tool registration removed in favor of project-scoped run_prompt tools\n // This reduces token overhead and allows dynamic prompt discovery without server restart\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n // Build tools array: system tools + ticket tools + project:run_prompt tools\n // Individual prompts no longer registered as separate tools\n const tools = [\n // System tools with full input schemas\n ...SYSTEM_TOOLS.map((st) => ({\n name: st.name,\n description: st.description,\n inputSchema: st.inputSchema,\n })),\n // Dynamic ticket tools per project (Ticket 135, 149, 151, 153, unified work)\n ...dynamicTicketTools.map((tt) => {\n // Build inputSchema based on tool type\n let inputSchema: { type: \"object\"; properties: Record<string, object>; required: string[] };\n\n if (tt.type === 'close') {\n // close requires a ticketSlug\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'work') {\n // Unified work command (replaces open + work)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Optional: Ticket number (e.g., '102') or full slug. If not provided, gets next ticket from open queue.\",\n },\n },\n required: [],\n };\n } else if (tt.type === 'create') {\n inputSchema = {\n type: \"object\" as const,\n properties: {\n content: {\n type: \"string\",\n description: \"The generated ticket content. First line should be a clear descriptive title (becomes the slug). Body contains tasks, description, context as needed.\",\n },\n },\n required: [\"content\"],\n };\n } else if (tt.type === 'search') {\n // Ticket 155: tickets:search\n inputSchema = {\n type: \"object\" as const,\n properties: {\n query: {\n type: \"string\",\n description: \"Search query (min 3 characters)\",\n },\n },\n required: [\"query\"],\n };\n } else if (tt.type === 'get') {\n // Ticket 155: tickets:get (read-only inspection)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug\",\n },\n },\n required: [\"ticketSlug\"],\n };\n } else if (tt.type === 'list') {\n // Ticket 155: tickets:list (no parameters - shows active queue)\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n } else if (tt.type === 'update') {\n // Ticket 293: tickets:update (append content to ticket)\n inputSchema = {\n type: \"object\" as const,\n properties: {\n ticketSlug: {\n type: \"string\",\n description: \"Ticket number (e.g., '102') or full slug (e.g., '102-fix-auth')\",\n },\n content: {\n type: \"string\",\n description: \"Update content to append to the ticket (markdown supported)\",\n },\n },\n required: [\"ticketSlug\", \"content\"],\n };\n } else {\n // Fallback: no params\n inputSchema = {\n type: \"object\" as const,\n properties: {},\n required: [],\n };\n }\n\n return {\n name: tt.name,\n description: tt.description,\n inputSchema,\n };\n }),\n // Global system_run_prompt tool\n {\n name: \"system_run_prompt\",\n description: \"Execute a prompt by slug. Use system_prompts to list available prompts.\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n slug: {\n type: \"string\",\n description: \"Prompt slug to execute (e.g., 'code-review', 'plan')\",\n },\n },\n required: [\"slug\"],\n },\n },\n // Global system_prompts tool\n {\n name: \"system_prompts\",\n description: \"List all available prompts\",\n inputSchema: {\n type: \"object\" as const,\n properties: {\n search: {\n type: \"string\",\n description: \"Optional search term to filter prompts by name or description (case-insensitive)\",\n },\n },\n },\n },\n // Dynamic per-prompt tools (each prompt as its own global tool)\n ...dynamicPromptTools.map((pt) => ({\n name: pt.name,\n description: pt.description,\n inputSchema: {\n type: \"object\" as const,\n properties: {},\n required: [],\n },\n })),\n ];\n\n return { tools };\n });\n\n // Register call_tool handler\n server.setRequestHandler(CallToolRequestSchema, async (request) => {\n const toolName = request.params.name;\n\n // Handle global system_run_prompt tool\n if (toolName === \"system_run_prompt\") {\n const promptSlug = request.params.arguments?.slug as string | undefined;\n\n if (!promptSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing 'slug' parameter. Provide prompt slug (e.g., 'code-review', 'plan').\n\nUse \\`system_prompts\\` to list available prompts.`,\n },\n ],\n isError: true,\n };\n }\n\n // Execute the prompt using account-scoped lookup\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptSlug,\n config,\n convexClient\n );\n\n // Convert prompt result to tool result format\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: promptText,\n },\n ],\n };\n } catch (error) {\n // Handle ambiguous prompt matches with helpful suggestions\n if (error instanceof AmbiguousPromptError) {\n const suggestionsList = error.suggestions.map((s) => ` • ${s}`).join(\"\\n\");\n console.error(`[MCP] ${toolName} ambiguous match:`, error.suggestions);\n return {\n content: [\n {\n type: \"text\",\n text: `Multiple prompts match \"${promptSlug}\". Please specify one of:\\n\\n${suggestionsList}\\n\\nExample: \\`system_run_prompt ${error.suggestions[0]}\\``,\n },\n ],\n isError: true,\n };\n }\n\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error executing prompt \"${promptSlug}\": ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n\n // Handle global system_prompts tool (account-scoped prompt listing)\n // Uses cached metadata (lightweight) - no need to fetch full content for listing\n if (toolName === \"system_prompts\") {\n const searchTerm = request.params.arguments?.search as string | undefined;\n\n // Use account-scoped prompts (global, no project filter)\n let filteredPrompts = [...accountScopedPrompts];\n\n // Filter by search term if provided\n if (searchTerm) {\n const lowerSearch = searchTerm.toLowerCase();\n filteredPrompts = filteredPrompts.filter(\n (p) =>\n p.slug.toLowerCase().includes(lowerSearch) ||\n p.description?.toLowerCase().includes(lowerSearch)\n );\n }\n\n // Sort prompts by slug\n filteredPrompts.sort((a, b) => a.slug.localeCompare(b.slug));\n\n // Build user prompts list\n const userPromptList = filteredPrompts\n .map((p) => {\n const desc = p.description || \"No description\";\n return `• ${p.slug}\\n Description: ${desc}`;\n })\n .join(\"\\n\\n\");\n\n const summary = searchTerm\n ? `Found ${filteredPrompts.length} prompt(s) matching \"${searchTerm}\":`\n : `Available prompts (${filteredPrompts.length} total):`;\n\n return {\n content: [\n {\n type: \"text\",\n text: userPromptList\n ? `${summary}\\n\\n${userPromptList}`\n : `${summary}\\n\\nNo prompts found.`,\n },\n ],\n };\n }\n\n // Handle ticket tool invocations (Ticket 135, 153, unified work)\n const ticketTool = dynamicTicketTools.find((tt) => tt.name === toolName);\n if (ticketTool) {\n if (ticketTool.type === 'work') {\n // Unified work command (replaces both open and work)\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n\n try {\n // Call mutation (not query) - this can move ticket from open to working\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:workMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug: ticketSlug, // Optional: specific ticket to work on\n }\n ) as {\n slug: string;\n ticketNumber?: number;\n status: string;\n content: string;\n startedAt?: number;\n remainingTickets: number;\n wasOpened: boolean;\n } | { error: string; accessDenied: true } | null;\n\n if (!result) {\n const message = ticketSlug\n ? `Ticket \"${ticketSlug}\" not found or not in open/working status in project \"${ticketTool.projectSlug}\".`\n : `No open tickets in project \"${ticketTool.projectSlug}\".`;\n return {\n content: [\n {\n type: \"text\",\n text: message,\n },\n ],\n };\n }\n\n // Handle access denied response\n if ('accessDenied' in result && result.accessDenied) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n const startedInfo = result.startedAt\n ? `\\nStarted: ${new Date(result.startedAt).toISOString()}`\n : '';\n\n // Different footer based on whether this was a fresh open or resumption\n const statusNote = result.wasOpened\n ? `_Ticket moved to working status. ${result.remainingTickets} ticket(s) remaining in queue._`\n : `_Resuming work on this ticket. ${result.remainingTickets} ticket(s) in queue._`;\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${result.slug} [WORKING]${startedInfo}\n\n${result.content}\n\n---\n${statusNote}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_work error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting work: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'create') {\n // Handle tickets:create (Ticket 149)\n const content = request.params.arguments?.content as string | undefined;\n\n if (!content) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the ticket content (first line becomes the slug).`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:createMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n content,\n }\n ) as { slug: string; preview: string; position: number; totalBacklog: number };\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Created ticket [${result.slug}] in backlog for project \"${ticketTool.projectSlug}\".\n\nPosition: #${result.position} of ${result.totalBacklog} backlog tickets\nPreview: ${result.preview}\n\n_Ticket created in backlog. Use \\`tickets_work ${result.slug}\\` to move it to working status._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_create error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error creating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'close') {\n // Ticket 151: Handle tickets:close\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n\n if (!ticketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Usage: Provide a ticket number (e.g., \"102\") or full slug (e.g., \"102-fix-auth\").`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:closeMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug: ticketSlug,\n }\n ) as CloseTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] closed in project \"${ticketTool.projectSlug}\".\n\nClosed at: ${new Date(result.closedAt).toISOString()}\n\n_Reminder: Ensure all embedded \\`[RUN_PROMPT ...]\\` directives were executed before closing._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_close error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error closing ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'search') {\n // Ticket 155: Handle tickets:search\n const query = request.params.arguments?.query as string | undefined;\n\n if (!query || query.length < 3) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Search query must be at least 3 characters`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).query(\n \"mcp_tickets:searchMcpTickets\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n query,\n }\n ) as Array<{ slug: string; ticketNumber?: number; status: string; matchSnippet: string; createdAt: number }>;\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No tickets found matching \"${query}\" in project \"${ticketTool.projectSlug}\".`,\n },\n ],\n };\n }\n\n // Format as list with status badges\n const formattedList = result\n .map((t) => {\n const statusBadge = t.status === 'open' ? '🟢' : t.status === 'working' ? '🟡' : t.status === 'closed' ? '⚫' : '⚪';\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return `${statusBadge} ${num} ${t.slug}\\n ${t.matchSnippet}`;\n })\n .join('\\n\\n');\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${result.length} ticket(s) matching \"${query}\":\\n\\n${formattedList}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_search error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error searching tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'get') {\n // Ticket 155: Handle tickets:get (read-only inspection)\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n\n if (!ticketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // Uses existing getMcpTicket query - read-only inspection\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).query(\n \"mcp_tickets:getMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n }\n ) as { slug: string; ticketNumber?: number; content: string; status: string; createdAt: number; startedAt?: number; closedAt?: number; assignedTo?: string } | { error: string; accessDenied: true } | null;\n\n if (!result) {\n return {\n content: [\n {\n type: \"text\",\n text: `Ticket \"${ticketSlug}\" not found in project \"${ticketTool.projectSlug}\".`,\n },\n ],\n };\n }\n\n // Handle access denied response\n if ('accessDenied' in result && result.accessDenied) {\n return {\n content: [\n {\n type: \"text\",\n text: `Access denied: ${result.error}`,\n },\n ],\n isError: true,\n };\n }\n\n // Build status line\n const statusBadge = result.status.toUpperCase();\n const startedInfo = result.startedAt ? `\\nStarted: ${new Date(result.startedAt).toISOString()}` : '';\n const closedInfo = result.closedAt ? `\\nClosed: ${new Date(result.closedAt).toISOString()}` : '';\n const assignedInfo = result.assignedTo ? `\\nAssigned to: ${result.assignedTo}` : '\\nUnassigned';\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Ticket: ${result.slug} [${statusBadge}]${startedInfo}${closedInfo}${assignedInfo}\n\n${result.content}\n\n---\n_Read-only inspection. Use tickets_work to start working on this ticket._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_get error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error getting ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'list') {\n // Ticket 155: Handle tickets:list (active queue)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).query(\n \"mcp_tickets:listMcpTickets\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n }\n ) as Array<{ position: number; slug: string; ticketNumber?: number; status: string; preview: string; createdAt: number; startedAt?: number }>;\n\n if (result.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No active tickets in project \"${ticketTool.projectSlug}\". All tickets are closed or archived.`,\n },\n ],\n };\n }\n\n // Group by status for clear display\n const openTickets = result.filter(t => t.status === 'open');\n const workingTickets = result.filter(t => t.status === 'working');\n const backlogTickets = result.filter(t => t.status === 'backlog');\n\n const formatTicketLine = (t: typeof result[0]) => {\n const num = t.ticketNumber ? `#${t.ticketNumber}` : '';\n return ` ${t.position}. ${num} ${t.slug}\\n ${t.preview}`;\n };\n\n const sections: string[] = [];\n\n if (openTickets.length > 0) {\n sections.push(`**🟢 Open (${openTickets.length})**\\n${openTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (workingTickets.length > 0) {\n sections.push(`**🟡 Working (${workingTickets.length})**\\n${workingTickets.map(formatTicketLine).join('\\n')}`);\n }\n if (backlogTickets.length > 0) {\n sections.push(`**⚪ Backlog (${backlogTickets.length})**\\n${backlogTickets.map(formatTicketLine).join('\\n')}`);\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `# Active Queue: ${ticketTool.projectSlug}\\n\\n${result.length} ticket(s) in queue\\n\\n${sections.join('\\n\\n')}`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_list error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error listing tickets: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n } else if (ticketTool.type === 'update') {\n // Ticket 293: Handle tickets:update (append content to ticket)\n const ticketSlug = request.params.arguments?.ticketSlug as string | undefined;\n const content = request.params.arguments?.content as string | undefined;\n\n if (!ticketSlug) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., \"102\") or full slug.`,\n },\n ],\n isError: true,\n };\n }\n\n if (!content) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: Missing content parameter. Provide the update content to append to the ticket.`,\n },\n ],\n isError: true,\n };\n }\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await (convexClient as any).mutation(\n \"mcp_tickets:updateMcpTicket\",\n {\n ...buildAuthArgs(config),\n projectSlug: ticketTool.projectSlug,\n ticketSlug,\n content,\n }\n ) as UpdateTicketResult;\n\n return {\n content: [\n {\n type: \"text\",\n text: `✅ Ticket [${result.slug}] updated in project \"${ticketTool.projectSlug}\".\nUpdated at: ${new Date(result.updatedAt).toISOString()}\n_Ticket content has been appended with your update._`,\n },\n ],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] tickets_update error:`, error);\n return {\n content: [\n {\n type: \"text\",\n text: `Error updating ticket: ${errorMessage}`,\n },\n ],\n isError: true,\n };\n }\n }\n }\n\n // Handle per-prompt tool invocations (e.g., \"code-review\" - now global)\n const promptTool = dynamicPromptTools.find((pt) => pt.name === toolName);\n if (promptTool) {\n try {\n const result = await fetchAndExecuteAccountScopedPrompt(\n promptTool.promptSlug,\n config,\n convexClient\n );\n\n const promptText = result.messages\n .map((msg) => msg.content.text)\n .join('\\n\\n');\n\n return {\n content: [{ type: \"text\", text: promptText }],\n };\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : \"Unknown error\";\n console.error(`[MCP] ${toolName} error:`, error);\n return {\n content: [{ type: \"text\", text: `Error executing prompt: ${errorMessage}` }],\n isError: true,\n };\n }\n }\n\n // Unknown tool\n throw new Error(`Unknown tool: ${toolName}. Use system_run_prompt to execute prompts by name, or check available tools.`);\n });\n\n // Start server with stdio transport\n const transport = new StdioServerTransport();\n await server.connect(transport);\n\n console.error(\"[MCP] Server started successfully\");\n console.error(`[MCP] Deployment: ${config.isDev ? \"DEVELOPMENT\" : \"PRODUCTION\"}`);\n console.error(`[MCP] Convex URL: ${config.convexUrl}`);\n console.error(`[MCP] Data mode: REAL-TIME (fetches fresh data on each invocation)`);\n\n // List all prompts\n const allPromptNames = [...Array.from(promptNames)].sort();\n console.error(`[MCP] Prompts available: ${allPromptNames.join(\", \")}`);\n console.error(`[MCP] - Total prompts: ${promptNames.size}`);\n\n // Keep the event loop alive with a heartbeat\n // This prevents Node from exiting when there are no active handles\n setInterval(() => {\n // Heartbeat every 60 seconds to keep process alive\n }, 60000);\n\n // Return a promise that never resolves to keep the server running\n return new Promise<void>(() => {\n // The transport handles stdin/stdout communication\n // The interval above keeps the event loop active\n });\n}\n\n/**\n * Validate API key with Convex\n */\nasync function validateApiKey(\n client: ConvexHttpClient,\n apiKey: string\n): Promise<{ valid: boolean; userId?: string; error?: string }> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await client.query(\"apiKeys:validateApiKey\" as any, { key: apiKey });\n if (result) {\n return { valid: true, userId: result.userId };\n }\n return { valid: false, error: \"Invalid API key\" };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n/**\n * Validate project token with Convex\n *\n * Project tokens provide scoped access to a single project for ticket operations.\n * Returns project details if valid, error if invalid/revoked.\n */\nasync function validateProjectToken(\n client: ConvexHttpClient,\n token: string\n): Promise<ProjectTokenValidation> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await client.query(\"project_tokens:validateProjectToken\" as any, { token });\n if (result && result.valid) {\n return {\n valid: true,\n projectId: result.projectId,\n projectSlug: result.projectSlug,\n ownerId: result.ownerId,\n tokenId: result.tokenId,\n tokenName: result.tokenName,\n };\n }\n return { valid: false, error: \"Invalid or revoked project token\" };\n } catch (error) {\n return {\n valid: false,\n error: error instanceof Error ? error.message : \"Unknown error\",\n };\n }\n}\n\n// Note: fetchMcpPromptMetadata and fetchMcpPromptMetadataByToken removed - prompts now account-scoped\n// Note: fetchMcpCommands and buildCommandName removed - commands resource eliminated\n\n/**\n * Fetch account-scoped prompt metadata for global MCP tools\n * Returns prompts WITHOUT projectId - globally available\n */\nasync function fetchAccountScopedPromptMetadata(\n client: ConvexHttpClient,\n apiKey: string\n): Promise<Array<{ slug: string; description?: string }>> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const metadata = await client.query(\"mcp_prompts:getAccountScopedPromptMetadata\" as any, {\n apiKey,\n });\n return metadata;\n } catch (error) {\n throw new Error(\n `Failed to fetch account-scoped prompt metadata: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n}\n\n/**\n * Fetch user's projects for ticket tool registration\n * Separate from prompts since prompts are now global\n */\nasync function fetchMcpProjects(\n client: ConvexHttpClient,\n apiKey: string\n): Promise<Array<{ slug: string; description?: string }>> {\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const projects = await client.query(\"mcp_prompts:getMcpProjects\" as any, {\n apiKey,\n });\n return projects;\n } catch (error) {\n throw new Error(\n `Failed to fetch projects: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n}\n","import type { ConvexHttpClient } from \"convex/browser\";\nimport type { McpPrompt, McpPromptMetadata, PromptMatchResult, ServerConfig } from \"./types.js\";\n\n/**\n * Sanitizes a string for use in MCP tool names.\n * Ensures output matches MCP protocol pattern: ^[a-zA-Z0-9_-]{1,64}$\n *\n * @param str - Raw string to sanitize (project slug, folder path, or prompt slug)\n * @returns Sanitized string safe for MCP tool names\n */\nexport function sanitizeForMcp(str: string): string {\n return str\n .trim() // Remove leading/trailing whitespace first\n .toLowerCase() // Convert to lowercase for consistency\n .replace(/[^a-z0-9_-]/g, '-') // Replace ALL invalid chars (including spaces!) with hyphens\n .replace(/-+/g, '-') // Collapse multiple consecutive hyphens into one\n .replace(/^-+|-+$/g, '') // Remove leading and trailing hyphens\n .trim(); // Final trim to ensure no whitespace\n}\n\n/**\n * Build the MCP prompt name for a prompt with project scoping\n *\n * MCP tool names must match pattern: ^[a-zA-Z0-9_:-]{1,64}$\n * (letters, numbers, underscores, hyphens, and colons allowed)\n *\n * Format: project:prompt\n * - Folders are NOT included in slash command names\n * - Folders are only used for ZIP download organization\n *\n * Character mapping:\n * - Separator: : (colon) between project and prompt\n * - Hyphens: - used within multi-word slugs\n * - Example: personal:code-review, work:api-design\n */\nexport function buildPromptName(prompt: McpPrompt): string {\n const project = sanitizeForMcp(prompt.projectSlug || 'default') || 'default';\n const promptSlug = sanitizeForMcp(prompt.slug || 'unknown') || 'unknown';\n\n // Hierarchical project:prompt format\n return `${project}:${promptSlug}`.trim();\n}\n\n/**\n * Build the MCP prompt name from lightweight metadata (no flattenedPrompt)\n * Used for startup/tool registration when only metadata is available\n */\nexport function buildPromptNameFromMetadata(metadata: McpPromptMetadata): string {\n const project = sanitizeForMcp(metadata.projectSlug || 'default') || 'default';\n const promptSlug = sanitizeForMcp(metadata.slug || 'unknown') || 'unknown';\n\n // Hierarchical project:prompt format\n return `${project}:${promptSlug}`.trim();\n}\n\n/**\n * Build the MCP prompt schema for a prompt\n */\nexport function buildPromptSchema(prompt: McpPrompt) {\n return {\n name: buildPromptName(prompt),\n description: prompt.description || `Prompt: ${prompt.name}`,\n arguments: [],\n };\n}\n\n/**\n * Build the prompt execution handler for a prompt\n * @deprecated Use buildPromptHandlerOptimized for better performance\n */\nexport function buildPromptHandler(\n prompt: McpPrompt,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n) {\n return async () => {\n let flattenedPrompt = prompt.flattenedPrompt;\n let promptName = prompt.name;\n\n // Always fetch fresh data from Convex (real-time mode)\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let freshPrompts = await convexClient.query(\"mcp_prompts:getMcpPrompts\" as any, {\n apiKey: config.apiKey,\n });\n\n // Apply project filter to fresh data if configured\n if (config.selectedProjects.length > 0) {\n freshPrompts = freshPrompts.filter((p: McpPrompt) =>\n p.projectSlug && config.selectedProjects.includes(p.projectSlug)\n );\n }\n\n const freshPrompt = freshPrompts.find((p: McpPrompt) => p.slug === prompt.slug && p.projectSlug === prompt.projectSlug);\n\n if (freshPrompt) {\n flattenedPrompt = freshPrompt.flattenedPrompt;\n promptName = freshPrompt.name;\n console.error(`[MCP] Fetched fresh data for: ${buildPromptName(prompt)}`);\n } else {\n console.error(\n `[MCP] WARNING: Prompt \"${prompt.slug}\" not found in fresh fetch, using cached version`\n );\n }\n } catch (error) {\n console.error(\n `[MCP] WARNING: Failed to fetch fresh data, using cached version: ${error instanceof Error ? error.message : \"Unknown error\"}`\n );\n }\n\n if (!flattenedPrompt) {\n throw new Error(\n `Prompt \"${promptName}\" has no flattened content. Please re-save the prompt in ContextFS to regenerate it.`\n );\n }\n\n console.error(`[MCP] Executed prompt: ${buildPromptName(prompt)}`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: flattenedPrompt,\n },\n },\n ],\n };\n };\n}\n\n/**\n * Custom error class for ambiguous prompt matches\n * Contains suggestions for the user to choose from\n */\nexport class AmbiguousPromptError extends Error {\n suggestions: string[];\n\n constructor(promptSlug: string, suggestions: string[]) {\n super(`Multiple prompts match \"${promptSlug}\". Please be more specific.`);\n this.name = \"AmbiguousPromptError\";\n this.suggestions = suggestions;\n }\n}\n\n/**\n * Fetch a single prompt and execute it - O(1) index lookup with fuzzy fallback\n *\n * Supports both API key and project token authentication:\n * - API key: Full access to all user projects\n * - Project token: Scoped access to token's project only\n *\n * Matching algorithm:\n * 1. Exact match via index (O(1)) - fast path\n * 2. Prefix match fallback if exact fails\n * 3. Contains match fallback if prefix fails\n * 4. Ambiguous error if multiple matches found\n *\n * Performance optimization: Uses getMcpPromptBySlug for direct index lookup\n * with fallback to fuzzy matching only when exact match fails.\n */\nexport async function fetchAndExecutePrompt(\n projectSlug: string,\n promptSlug: string,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<{ messages: Array<{ role: string; content: { type: \"text\"; text: string } }> }> {\n let result: PromptMatchResult;\n\n // Use appropriate query based on auth type\n if (config.apiKey) {\n // API key auth - use standard query\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result = await convexClient.query(\"mcp_prompts:getMcpPromptBySlug\" as any, {\n apiKey: config.apiKey,\n projectSlug,\n promptSlug,\n }) as PromptMatchResult;\n } else if (config.projectToken) {\n // Project token auth - use token-specific query\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n result = await convexClient.query(\"mcp_prompts:getMcpPromptBySlugWithToken\" as any, {\n projectToken: config.projectToken,\n projectSlug,\n promptSlug,\n }) as PromptMatchResult;\n } else {\n throw new Error(\"No authentication configured\");\n }\n\n // Handle null - no matches found\n if (!result) {\n throw new Error(\n `Prompt \"${promptSlug}\" not found in project \"${projectSlug}\".`\n );\n }\n\n // Handle ambiguous matches - throw custom error with suggestions\n if (result.matchType === \"ambiguous\") {\n throw new AmbiguousPromptError(promptSlug, result.suggestions);\n }\n\n // Handle successful match (exact, prefix, or contains)\n if (!result.flattenedPrompt) {\n throw new Error(\n `Prompt \"${result.slug}\" has no flattened content. Please re-save the prompt to regenerate it.`\n );\n }\n\n // Log match type for debugging\n const matchNote = result.matchType === \"exact\"\n ? \"exact match\"\n : result.matchType === \"prefix\"\n ? `prefix match → ${result.slug}`\n : `contains match → ${result.slug}`;\n console.error(`[MCP] Fetched prompt: ${projectSlug}:${promptSlug} (${matchNote})`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: result.flattenedPrompt,\n },\n },\n ],\n };\n}\n\n/**\n * Type for account-scoped prompt match result\n */\ntype AccountScopedPromptResult =\n | { matchType: \"exact\" | \"prefix\" | \"contains\"; slug: string; description?: string; flattenedPrompt?: string }\n | { matchType: \"ambiguous\"; suggestions: string[] }\n | null;\n\n/**\n * Fetch a single account-scoped prompt and execute it\n *\n * Account-scoped prompts are global (no project prefix required).\n * This is for prompts that don't have a projectId.\n *\n * Matching algorithm same as fetchAndExecutePrompt but without project context.\n */\nexport async function fetchAndExecuteAccountScopedPrompt(\n promptSlug: string,\n config: ServerConfig,\n convexClient: ConvexHttpClient\n): Promise<{ messages: Array<{ role: string; content: { type: \"text\"; text: string } }> }> {\n if (!config.apiKey) {\n throw new Error(\"Account-scoped prompts require API key authentication\");\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = await convexClient.query(\"mcp_prompts:getAccountScopedPromptBySlug\" as any, {\n apiKey: config.apiKey,\n promptSlug,\n }) as AccountScopedPromptResult;\n\n // Handle null - no matches found\n if (!result) {\n throw new Error(\n `Prompt \"${promptSlug}\" not found. Use system:prompts to list available prompts.`\n );\n }\n\n // Handle ambiguous matches - throw custom error with suggestions\n if (result.matchType === \"ambiguous\") {\n throw new AmbiguousPromptError(promptSlug, result.suggestions);\n }\n\n // Handle successful match (exact, prefix, or contains)\n if (!result.flattenedPrompt) {\n throw new Error(\n `Prompt \"${result.slug}\" has no flattened content. Please re-save the prompt to regenerate it.`\n );\n }\n\n // Log match type for debugging\n const matchNote = result.matchType === \"exact\"\n ? \"exact match\"\n : result.matchType === \"prefix\"\n ? `prefix match → ${result.slug}`\n : `contains match → ${result.slug}`;\n console.error(`[MCP] Fetched account-scoped prompt: ${promptSlug} (${matchNote})`);\n\n // Return raw flattened prompt (client will parse YAML front matter)\n return {\n messages: [\n {\n role: \"user\",\n content: {\n type: \"text\" as const,\n text: result.flattenedPrompt,\n },\n },\n ],\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@promptprojectmanager/mcp-server",
3
- "version": "3.3.6",
4
- "description": "MCP server that exposes Prompt Project Manager workspace prompts as slash commands",
3
+ "version": "4.0.0",
4
+ "description": "MCP server that exposes Prompt Project Manager project prompts as slash commands",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "bin": {