@promptprojectmanager/mcp-server 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -203
- package/dist/index.js +160 -223
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,6 +22,60 @@ import {
|
|
|
22
22
|
ListToolsRequestSchema
|
|
23
23
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
24
24
|
|
|
25
|
+
// src/types.ts
|
|
26
|
+
function isAccessDenied(result) {
|
|
27
|
+
return typeof result === "object" && result !== null && "accessDenied" in result && result.accessDenied === true;
|
|
28
|
+
}
|
|
29
|
+
function parseWorkArgs(args) {
|
|
30
|
+
const parsed = args;
|
|
31
|
+
return {
|
|
32
|
+
ticketSlug: typeof parsed?.ticketSlug === "string" ? parsed.ticketSlug : void 0
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function parseCloseArgs(args) {
|
|
36
|
+
const parsed = args;
|
|
37
|
+
const ticketSlug = typeof parsed?.ticketSlug === "string" ? parsed.ticketSlug : void 0;
|
|
38
|
+
if (!ticketSlug) return null;
|
|
39
|
+
return { ticketSlug };
|
|
40
|
+
}
|
|
41
|
+
function parseCreateArgs(args) {
|
|
42
|
+
const parsed = args;
|
|
43
|
+
const content = typeof parsed?.content === "string" ? parsed.content : void 0;
|
|
44
|
+
if (!content) return null;
|
|
45
|
+
return { content };
|
|
46
|
+
}
|
|
47
|
+
function parseSearchArgs(args) {
|
|
48
|
+
const parsed = args;
|
|
49
|
+
const query = typeof parsed?.query === "string" ? parsed.query : void 0;
|
|
50
|
+
if (!query || query.length < 3) return null;
|
|
51
|
+
return { query };
|
|
52
|
+
}
|
|
53
|
+
function parseGetArgs(args) {
|
|
54
|
+
const parsed = args;
|
|
55
|
+
const ticketSlug = typeof parsed?.ticketSlug === "string" ? parsed.ticketSlug : void 0;
|
|
56
|
+
if (!ticketSlug) return null;
|
|
57
|
+
return { ticketSlug };
|
|
58
|
+
}
|
|
59
|
+
function parseUpdateArgs(args) {
|
|
60
|
+
const parsed = args;
|
|
61
|
+
const ticketSlug = typeof parsed?.ticketSlug === "string" ? parsed.ticketSlug : void 0;
|
|
62
|
+
const content = typeof parsed?.content === "string" ? parsed.content : void 0;
|
|
63
|
+
if (!ticketSlug || !content) return null;
|
|
64
|
+
return { ticketSlug, content };
|
|
65
|
+
}
|
|
66
|
+
function parseRunPromptArgs(args) {
|
|
67
|
+
const parsed = args;
|
|
68
|
+
const slug = typeof parsed?.slug === "string" ? parsed.slug : void 0;
|
|
69
|
+
if (!slug) return null;
|
|
70
|
+
return { slug };
|
|
71
|
+
}
|
|
72
|
+
function parseSystemPromptsArgs(args) {
|
|
73
|
+
const parsed = args;
|
|
74
|
+
return {
|
|
75
|
+
search: typeof parsed?.search === "string" ? parsed.search : void 0
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
25
79
|
// src/prompt-builder.ts
|
|
26
80
|
var AmbiguousPromptError = class extends Error {
|
|
27
81
|
suggestions;
|
|
@@ -31,24 +85,13 @@ var AmbiguousPromptError = class extends Error {
|
|
|
31
85
|
this.suggestions = suggestions;
|
|
32
86
|
}
|
|
33
87
|
};
|
|
34
|
-
async function fetchAccountScopedPromptMetadataForProject(client, apiKey, projectSlug) {
|
|
35
|
-
try {
|
|
36
|
-
const metadata = await client.query("mcp_prompts:getAccountScopedPromptMetadataForProject", {
|
|
37
|
-
apiKey,
|
|
38
|
-
projectSlug
|
|
39
|
-
});
|
|
40
|
-
return metadata;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`Failed to fetch account-scoped prompt metadata for project "${projectSlug}": ${error instanceof Error ? error.message : "Unknown error"}`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
88
|
async function fetchAccountScopedPromptMetadataByToken(client, projectToken) {
|
|
48
89
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
90
|
+
const typedClient = client;
|
|
91
|
+
const metadata = await typedClient.query(
|
|
92
|
+
"mcp_prompts:getAccountScopedPromptMetadataByToken",
|
|
93
|
+
{ projectToken }
|
|
94
|
+
);
|
|
52
95
|
return metadata;
|
|
53
96
|
} catch (error) {
|
|
54
97
|
throw new Error(
|
|
@@ -57,21 +100,14 @@ async function fetchAccountScopedPromptMetadataByToken(client, projectToken) {
|
|
|
57
100
|
}
|
|
58
101
|
}
|
|
59
102
|
async function fetchAndExecuteAccountScopedPrompt(promptSlug, config, convexClient) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (config.apiKey) {
|
|
65
|
-
result = await convexClient.query("mcp_prompts:getAccountScopedPromptBySlug", {
|
|
66
|
-
apiKey: config.apiKey,
|
|
67
|
-
promptSlug
|
|
68
|
-
});
|
|
69
|
-
} else {
|
|
70
|
-
result = await convexClient.query("mcp_prompts:getAccountScopedPromptBySlugByToken", {
|
|
103
|
+
const typedClient = convexClient;
|
|
104
|
+
const result = await typedClient.query(
|
|
105
|
+
"mcp_prompts:getAccountScopedPromptBySlugByToken",
|
|
106
|
+
{
|
|
71
107
|
projectToken: config.projectToken,
|
|
72
108
|
promptSlug
|
|
73
|
-
}
|
|
74
|
-
|
|
109
|
+
}
|
|
110
|
+
);
|
|
75
111
|
if (!result) {
|
|
76
112
|
throw new Error(
|
|
77
113
|
`Prompt "${promptSlug}" not found. Use system:prompts to list available prompts.`
|
|
@@ -102,87 +138,34 @@ async function fetchAndExecuteAccountScopedPrompt(promptSlug, config, convexClie
|
|
|
102
138
|
|
|
103
139
|
// src/server.ts
|
|
104
140
|
function buildAuthArgs(config) {
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
apiKey: config.apiKey,
|
|
108
|
-
agentName: config.agentName
|
|
109
|
-
// Pass agent name for identity resolution
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (config.projectToken) {
|
|
113
|
-
return { projectToken: config.projectToken };
|
|
114
|
-
}
|
|
115
|
-
return {};
|
|
141
|
+
return { projectToken: config.projectToken };
|
|
116
142
|
}
|
|
117
143
|
var SYSTEM_TOOLS = [
|
|
118
144
|
// All tools are now project-scoped (see dynamic*Tools arrays below)
|
|
119
145
|
];
|
|
120
|
-
async function startServer(config,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
throw new Error(`Invalid API key: ${validation.error}`);
|
|
127
|
-
}
|
|
128
|
-
console.error(`[MCP] API key validated for user: ${validation.userId}`);
|
|
129
|
-
} else if (config.projectToken) {
|
|
130
|
-
console.error("[MCP] Validating project token...");
|
|
131
|
-
const validation = await validateProjectToken(convexClient, config.projectToken);
|
|
132
|
-
if (!validation.valid) {
|
|
133
|
-
throw new Error(`Invalid project token: ${validation.error}`);
|
|
134
|
-
}
|
|
135
|
-
tokenProjectSlug = validation.projectSlug;
|
|
136
|
-
console.error(`[MCP] Project token validated: "${validation.tokenName}" for project "${tokenProjectSlug}"`);
|
|
137
|
-
} else {
|
|
138
|
-
throw new Error("No authentication provided. Set PPM_API_KEY or PPM_PROJECT_TOKEN.");
|
|
139
|
-
}
|
|
140
|
-
let accountScopedPrompts = [];
|
|
141
|
-
let projectsForTickets = [];
|
|
142
|
-
console.error("[MCP] Fetching prompt and project metadata...");
|
|
143
|
-
if (config.apiKey) {
|
|
144
|
-
if (config.selectedProjects.length === 1) {
|
|
145
|
-
accountScopedPrompts = await fetchAccountScopedPromptMetadataForProject(
|
|
146
|
-
convexClient,
|
|
147
|
-
config.apiKey,
|
|
148
|
-
config.selectedProjects[0]
|
|
149
|
-
);
|
|
150
|
-
console.error(`[MCP] Using project-filtered prompt visibility for: ${config.selectedProjects[0]}`);
|
|
151
|
-
} else {
|
|
152
|
-
accountScopedPrompts = await fetchAccountScopedPromptMetadata(convexClient, config.apiKey);
|
|
153
|
-
if (config.selectedProjects.length > 1) {
|
|
154
|
-
console.error(`[MCP] Multiple projects specified - showing all prompts (no exclusion filtering)`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const allProjects = await fetchMcpProjects(convexClient, config.apiKey);
|
|
158
|
-
if (config.selectedProjects.length > 0) {
|
|
159
|
-
const selectedSet = new Set(config.selectedProjects);
|
|
160
|
-
projectsForTickets = allProjects.filter((p) => selectedSet.has(p.slug));
|
|
161
|
-
} else {
|
|
162
|
-
projectsForTickets = allProjects;
|
|
163
|
-
}
|
|
164
|
-
} else if (config.projectToken && tokenProjectSlug) {
|
|
165
|
-
accountScopedPrompts = await fetchAccountScopedPromptMetadataByToken(
|
|
166
|
-
convexClient,
|
|
167
|
-
config.projectToken
|
|
168
|
-
);
|
|
169
|
-
console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);
|
|
170
|
-
projectsForTickets = [{ slug: tokenProjectSlug, name: tokenProjectSlug }];
|
|
146
|
+
async function startServer(config, convexClientRaw) {
|
|
147
|
+
const convexClient = convexClientRaw;
|
|
148
|
+
console.error("[MCP] Validating project token...");
|
|
149
|
+
const validation = await validateProjectToken(convexClient, config.projectToken);
|
|
150
|
+
if (!validation.valid) {
|
|
151
|
+
throw new Error(`Invalid project token: ${validation.error}`);
|
|
171
152
|
}
|
|
153
|
+
const tokenProjectSlug = validation.projectSlug;
|
|
154
|
+
console.error(`[MCP] Project token validated: "${validation.tokenName}" for project "${tokenProjectSlug}"`);
|
|
155
|
+
console.error("[MCP] Fetching prompt metadata...");
|
|
156
|
+
const accountScopedPrompts = await fetchAccountScopedPromptMetadataByToken(
|
|
157
|
+
convexClient,
|
|
158
|
+
config.projectToken
|
|
159
|
+
);
|
|
160
|
+
console.error(`[MCP] Project token mode: loaded ${accountScopedPrompts.length} prompts`);
|
|
161
|
+
const projectsForTickets = [{ slug: tokenProjectSlug, name: tokenProjectSlug }];
|
|
172
162
|
console.error(`[MCP] Found ${accountScopedPrompts.length} account-scoped prompts (global tools)`);
|
|
173
|
-
console.error(`[MCP]
|
|
174
|
-
if (accountScopedPrompts.length === 0
|
|
163
|
+
console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);
|
|
164
|
+
if (accountScopedPrompts.length === 0) {
|
|
175
165
|
console.error(
|
|
176
|
-
"[MCP] WARNING: No
|
|
166
|
+
"[MCP] WARNING: No prompts found. Create prompts in the 'prompts' section to expose them via MCP."
|
|
177
167
|
);
|
|
178
168
|
}
|
|
179
|
-
if (tokenProjectSlug) {
|
|
180
|
-
console.error(`[MCP] Ticket project scope: ${tokenProjectSlug} (token-scoped)`);
|
|
181
|
-
} else if (config.selectedProjects.length > 0) {
|
|
182
|
-
console.error(`[MCP] Ticket project filter: ${config.selectedProjects.join(", ")}`);
|
|
183
|
-
} else {
|
|
184
|
-
console.error(`[MCP] Ticket projects: ALL (${projectsForTickets.map((p) => p.slug).join(", ")})`);
|
|
185
|
-
}
|
|
186
169
|
const projectSlugs = new Set(projectsForTickets.map((p) => p.slug));
|
|
187
170
|
const dynamicTicketTools = [];
|
|
188
171
|
for (const projectSlug of projectSlugs) {
|
|
@@ -595,8 +578,8 @@ This will execute the "code-review" prompt.`;
|
|
|
595
578
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
596
579
|
const toolName = request.params.name;
|
|
597
580
|
if (toolName === "system_run_prompt") {
|
|
598
|
-
const
|
|
599
|
-
if (!
|
|
581
|
+
const parsedArgs = parseRunPromptArgs(request.params.arguments);
|
|
582
|
+
if (!parsedArgs) {
|
|
600
583
|
return {
|
|
601
584
|
content: [
|
|
602
585
|
{
|
|
@@ -609,6 +592,7 @@ Use \`system_prompts\` to list available prompts.`
|
|
|
609
592
|
isError: true
|
|
610
593
|
};
|
|
611
594
|
}
|
|
595
|
+
const { slug: promptSlug } = parsedArgs;
|
|
612
596
|
try {
|
|
613
597
|
const result = await fetchAndExecuteAccountScopedPrompt(
|
|
614
598
|
promptSlug,
|
|
@@ -656,7 +640,7 @@ Example: \`system_run_prompt ${error.suggestions[0]}\``
|
|
|
656
640
|
}
|
|
657
641
|
}
|
|
658
642
|
if (toolName === "system_prompts") {
|
|
659
|
-
const searchTerm = request.params.arguments
|
|
643
|
+
const { search: searchTerm } = parseSystemPromptsArgs(request.params.arguments);
|
|
660
644
|
let filteredPrompts = [...accountScopedPrompts];
|
|
661
645
|
if (searchTerm) {
|
|
662
646
|
const lowerSearch = searchTerm.toLowerCase();
|
|
@@ -687,7 +671,7 @@ No prompts found.`
|
|
|
687
671
|
const ticketTool = dynamicTicketTools.find((tt) => tt.name === toolName);
|
|
688
672
|
if (ticketTool) {
|
|
689
673
|
if (ticketTool.type === "work") {
|
|
690
|
-
const ticketSlug = request.params.arguments
|
|
674
|
+
const { ticketSlug } = parseWorkArgs(request.params.arguments);
|
|
691
675
|
try {
|
|
692
676
|
const result = await convexClient.mutation(
|
|
693
677
|
"mcp_tickets:workMcpTicket",
|
|
@@ -709,7 +693,7 @@ No prompts found.`
|
|
|
709
693
|
]
|
|
710
694
|
};
|
|
711
695
|
}
|
|
712
|
-
if (
|
|
696
|
+
if (isAccessDenied(result)) {
|
|
713
697
|
return {
|
|
714
698
|
content: [
|
|
715
699
|
{
|
|
@@ -720,16 +704,17 @@ No prompts found.`
|
|
|
720
704
|
isError: true
|
|
721
705
|
};
|
|
722
706
|
}
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
707
|
+
const workResult = result;
|
|
708
|
+
const startedInfo = workResult.startedAt ? `
|
|
709
|
+
Started: ${new Date(workResult.startedAt).toISOString()}` : "";
|
|
710
|
+
const statusNote = workResult.wasOpened ? `_Ticket moved to working status. ${workResult.remainingTickets} ticket(s) remaining in queue._` : `_Resuming work on this ticket. ${workResult.remainingTickets} ticket(s) in queue._`;
|
|
726
711
|
return {
|
|
727
712
|
content: [
|
|
728
713
|
{
|
|
729
714
|
type: "text",
|
|
730
|
-
text: `# Ticket: ${
|
|
715
|
+
text: `# Ticket: ${workResult.slug} [WORKING]${startedInfo}
|
|
731
716
|
|
|
732
|
-
${
|
|
717
|
+
${workResult.content}
|
|
733
718
|
|
|
734
719
|
---
|
|
735
720
|
${statusNote}`
|
|
@@ -750,8 +735,8 @@ ${statusNote}`
|
|
|
750
735
|
};
|
|
751
736
|
}
|
|
752
737
|
} else if (ticketTool.type === "create") {
|
|
753
|
-
const
|
|
754
|
-
if (!
|
|
738
|
+
const parsedArgs = parseCreateArgs(request.params.arguments);
|
|
739
|
+
if (!parsedArgs) {
|
|
755
740
|
return {
|
|
756
741
|
content: [
|
|
757
742
|
{
|
|
@@ -762,6 +747,7 @@ ${statusNote}`
|
|
|
762
747
|
isError: true
|
|
763
748
|
};
|
|
764
749
|
}
|
|
750
|
+
const { content } = parsedArgs;
|
|
765
751
|
try {
|
|
766
752
|
const result = await convexClient.mutation(
|
|
767
753
|
"mcp_tickets:createMcpTicket",
|
|
@@ -798,8 +784,8 @@ _Ticket created in backlog. Use \`tickets_work ${result.slug}\` to move it to wo
|
|
|
798
784
|
};
|
|
799
785
|
}
|
|
800
786
|
} else if (ticketTool.type === "close") {
|
|
801
|
-
const
|
|
802
|
-
if (!
|
|
787
|
+
const parsedArgs = parseCloseArgs(request.params.arguments);
|
|
788
|
+
if (!parsedArgs) {
|
|
803
789
|
return {
|
|
804
790
|
content: [
|
|
805
791
|
{
|
|
@@ -810,6 +796,7 @@ _Ticket created in backlog. Use \`tickets_work ${result.slug}\` to move it to wo
|
|
|
810
796
|
isError: true
|
|
811
797
|
};
|
|
812
798
|
}
|
|
799
|
+
const { ticketSlug } = parsedArgs;
|
|
813
800
|
try {
|
|
814
801
|
const result = await convexClient.mutation(
|
|
815
802
|
"mcp_tickets:closeMcpTicket",
|
|
@@ -845,8 +832,8 @@ _Reminder: Ensure all embedded \`[RUN_PROMPT ...]\` directives were executed bef
|
|
|
845
832
|
};
|
|
846
833
|
}
|
|
847
834
|
} else if (ticketTool.type === "search") {
|
|
848
|
-
const
|
|
849
|
-
if (!
|
|
835
|
+
const parsedArgs = parseSearchArgs(request.params.arguments);
|
|
836
|
+
if (!parsedArgs) {
|
|
850
837
|
return {
|
|
851
838
|
content: [
|
|
852
839
|
{
|
|
@@ -857,6 +844,7 @@ _Reminder: Ensure all embedded \`[RUN_PROMPT ...]\` directives were executed bef
|
|
|
857
844
|
isError: true
|
|
858
845
|
};
|
|
859
846
|
}
|
|
847
|
+
const { query } = parsedArgs;
|
|
860
848
|
try {
|
|
861
849
|
const result = await convexClient.query(
|
|
862
850
|
"mcp_tickets:searchMcpTickets",
|
|
@@ -906,8 +894,8 @@ ${formattedList}`
|
|
|
906
894
|
};
|
|
907
895
|
}
|
|
908
896
|
} else if (ticketTool.type === "get") {
|
|
909
|
-
const
|
|
910
|
-
if (!
|
|
897
|
+
const parsedArgs = parseGetArgs(request.params.arguments);
|
|
898
|
+
if (!parsedArgs) {
|
|
911
899
|
return {
|
|
912
900
|
content: [
|
|
913
901
|
{
|
|
@@ -918,6 +906,7 @@ ${formattedList}`
|
|
|
918
906
|
isError: true
|
|
919
907
|
};
|
|
920
908
|
}
|
|
909
|
+
const { ticketSlug } = parsedArgs;
|
|
921
910
|
try {
|
|
922
911
|
const result = await convexClient.query(
|
|
923
912
|
"mcp_tickets:getMcpTicket",
|
|
@@ -937,7 +926,7 @@ ${formattedList}`
|
|
|
937
926
|
]
|
|
938
927
|
};
|
|
939
928
|
}
|
|
940
|
-
if (
|
|
929
|
+
if (isAccessDenied(result)) {
|
|
941
930
|
return {
|
|
942
931
|
content: [
|
|
943
932
|
{
|
|
@@ -948,20 +937,21 @@ ${formattedList}`
|
|
|
948
937
|
isError: true
|
|
949
938
|
};
|
|
950
939
|
}
|
|
951
|
-
const
|
|
952
|
-
const
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
940
|
+
const getResult = result;
|
|
941
|
+
const statusBadge = getResult.status.toUpperCase();
|
|
942
|
+
const startedInfo = getResult.startedAt ? `
|
|
943
|
+
Started: ${new Date(getResult.startedAt).toISOString()}` : "";
|
|
944
|
+
const closedInfo = getResult.closedAt ? `
|
|
945
|
+
Closed: ${new Date(getResult.closedAt).toISOString()}` : "";
|
|
946
|
+
const assignedInfo = getResult.assignedTo ? `
|
|
947
|
+
Assigned to: ${getResult.assignedTo}` : "\nUnassigned";
|
|
958
948
|
return {
|
|
959
949
|
content: [
|
|
960
950
|
{
|
|
961
951
|
type: "text",
|
|
962
|
-
text: `# Ticket: ${
|
|
952
|
+
text: `# Ticket: ${getResult.slug} [${statusBadge}]${startedInfo}${closedInfo}${assignedInfo}
|
|
963
953
|
|
|
964
|
-
${
|
|
954
|
+
${getResult.content}
|
|
965
955
|
|
|
966
956
|
---
|
|
967
957
|
_Read-only inspection. Use tickets_work to start working on this ticket._`
|
|
@@ -1047,30 +1037,44 @@ ${sections.join("\n\n")}`
|
|
|
1047
1037
|
};
|
|
1048
1038
|
}
|
|
1049
1039
|
} else if (ticketTool.type === "update") {
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1040
|
+
const parsedArgs = parseUpdateArgs(request.params.arguments);
|
|
1041
|
+
if (!parsedArgs) {
|
|
1042
|
+
const args = request.params.arguments;
|
|
1043
|
+
const hasTicketSlug = typeof args?.ticketSlug === "string" && args.ticketSlug;
|
|
1044
|
+
const hasContent = typeof args?.content === "string" && args.content;
|
|
1045
|
+
if (!hasTicketSlug) {
|
|
1046
|
+
return {
|
|
1047
|
+
content: [
|
|
1048
|
+
{
|
|
1049
|
+
type: "text",
|
|
1050
|
+
text: `Error: Missing ticketSlug parameter. Provide a ticket number (e.g., "102") or full slug.`
|
|
1051
|
+
}
|
|
1052
|
+
],
|
|
1053
|
+
isError: true
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
if (!hasContent) {
|
|
1057
|
+
return {
|
|
1058
|
+
content: [
|
|
1059
|
+
{
|
|
1060
|
+
type: "text",
|
|
1061
|
+
text: `Error: Missing content parameter. Provide the update content to append to the ticket.`
|
|
1062
|
+
}
|
|
1063
|
+
],
|
|
1064
|
+
isError: true
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1064
1067
|
return {
|
|
1065
1068
|
content: [
|
|
1066
1069
|
{
|
|
1067
1070
|
type: "text",
|
|
1068
|
-
text: `Error: Missing
|
|
1071
|
+
text: `Error: Missing required parameters.`
|
|
1069
1072
|
}
|
|
1070
1073
|
],
|
|
1071
1074
|
isError: true
|
|
1072
1075
|
};
|
|
1073
1076
|
}
|
|
1077
|
+
const { ticketSlug, content } = parsedArgs;
|
|
1074
1078
|
try {
|
|
1075
1079
|
const result = await convexClient.mutation(
|
|
1076
1080
|
"mcp_tickets:updateMcpTicket",
|
|
@@ -1143,23 +1147,13 @@ _Ticket content has been appended with your update._`
|
|
|
1143
1147
|
return new Promise(() => {
|
|
1144
1148
|
});
|
|
1145
1149
|
}
|
|
1146
|
-
async function validateApiKey(client, apiKey) {
|
|
1147
|
-
try {
|
|
1148
|
-
const result = await client.query("apiKeys:validateApiKey", { key: apiKey });
|
|
1149
|
-
if (result) {
|
|
1150
|
-
return { valid: true, userId: result.userId };
|
|
1151
|
-
}
|
|
1152
|
-
return { valid: false, error: "Invalid API key" };
|
|
1153
|
-
} catch (error) {
|
|
1154
|
-
return {
|
|
1155
|
-
valid: false,
|
|
1156
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
1150
|
async function validateProjectToken(client, token) {
|
|
1161
1151
|
try {
|
|
1162
|
-
const
|
|
1152
|
+
const typedClient = client;
|
|
1153
|
+
const result = await typedClient.query(
|
|
1154
|
+
"project_tokens:validateProjectToken",
|
|
1155
|
+
{ token }
|
|
1156
|
+
);
|
|
1163
1157
|
if (result && result.valid) {
|
|
1164
1158
|
return {
|
|
1165
1159
|
valid: true,
|
|
@@ -1178,87 +1172,30 @@ async function validateProjectToken(client, token) {
|
|
|
1178
1172
|
};
|
|
1179
1173
|
}
|
|
1180
1174
|
}
|
|
1181
|
-
async function fetchAccountScopedPromptMetadata(client, apiKey) {
|
|
1182
|
-
try {
|
|
1183
|
-
const metadata = await client.query("mcp_prompts:getAccountScopedPromptMetadata", {
|
|
1184
|
-
apiKey
|
|
1185
|
-
});
|
|
1186
|
-
return metadata;
|
|
1187
|
-
} catch (error) {
|
|
1188
|
-
throw new Error(
|
|
1189
|
-
`Failed to fetch account-scoped prompt metadata: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
async function fetchMcpProjects(client, apiKey) {
|
|
1194
|
-
try {
|
|
1195
|
-
const projects = await client.query("mcp_prompts:getMcpProjects", {
|
|
1196
|
-
apiKey
|
|
1197
|
-
});
|
|
1198
|
-
return projects;
|
|
1199
|
-
} catch (error) {
|
|
1200
|
-
throw new Error(
|
|
1201
|
-
`Failed to fetch projects: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1202
|
-
);
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
1175
|
|
|
1206
1176
|
// src/index.ts
|
|
1207
1177
|
async function main() {
|
|
1208
1178
|
try {
|
|
1209
1179
|
const argv = minimist(process.argv.slice(2));
|
|
1210
1180
|
const isDev = argv.dev === true;
|
|
1211
|
-
const projectsArg = argv.projects || argv.p;
|
|
1212
|
-
let selectedProjects = [];
|
|
1213
|
-
if (projectsArg) {
|
|
1214
|
-
if (Array.isArray(projectsArg)) {
|
|
1215
|
-
selectedProjects = projectsArg.flatMap((p) => String(p).split(","));
|
|
1216
|
-
} else {
|
|
1217
|
-
selectedProjects = String(projectsArg).split(",");
|
|
1218
|
-
}
|
|
1219
|
-
selectedProjects = selectedProjects.map((s) => s.trim()).filter(Boolean);
|
|
1220
|
-
}
|
|
1221
|
-
const apiKey = process.env.PPM_API_KEY || process.env.THEPROMPTEDITOR_API_KEY;
|
|
1222
1181
|
const projectToken = process.env.PPM_PROJECT_TOKEN;
|
|
1223
|
-
|
|
1224
|
-
if (!apiKey && !projectToken) {
|
|
1182
|
+
if (!projectToken) {
|
|
1225
1183
|
console.error(
|
|
1226
|
-
"[MCP] ERROR: Missing authentication.
|
|
1184
|
+
"[MCP] ERROR: Missing authentication. Set PPM_PROJECT_TOKEN environment variable."
|
|
1227
1185
|
);
|
|
1228
1186
|
console.error(
|
|
1229
|
-
"[MCP]
|
|
1187
|
+
"[MCP] Example:"
|
|
1230
1188
|
);
|
|
1231
|
-
console.error("[MCP] export
|
|
1232
|
-
console.error("[MCP] export PPM_PROJECT_TOKEN=ppt_... # Scoped access to one project (tickets only)");
|
|
1189
|
+
console.error("[MCP] export PPM_PROJECT_TOKEN=wst_... # Get from project settings");
|
|
1233
1190
|
process.exit(1);
|
|
1234
1191
|
}
|
|
1235
|
-
|
|
1236
|
-
console.error("[MCP] Auth mode: API Key (full access)");
|
|
1237
|
-
if (projectToken) {
|
|
1238
|
-
console.error("[MCP] Note: PPM_PROJECT_TOKEN ignored when PPM_API_KEY is set");
|
|
1239
|
-
}
|
|
1240
|
-
if (ppmAgent) {
|
|
1241
|
-
console.error(`[MCP] Agent identity: ${ppmAgent}`);
|
|
1242
|
-
}
|
|
1243
|
-
} else {
|
|
1244
|
-
console.error("[MCP] Auth mode: Project Token (scoped access, tickets only)");
|
|
1245
|
-
if (ppmAgent) {
|
|
1246
|
-
console.error("[MCP] Note: PPM_AGENT ignored when using PPM_PROJECT_TOKEN directly");
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1192
|
+
console.error("[MCP] Auth mode: Project Token");
|
|
1249
1193
|
const convexClient = createConvexClient(isDev);
|
|
1250
1194
|
const convexUrl = isDev ? "https://hallowed-shrimp-344.convex.cloud" : "https://trustworthy-squirrel-735.convex.cloud";
|
|
1251
1195
|
const config = {
|
|
1252
|
-
|
|
1253
|
-
// Use undefined if not set (not empty string)
|
|
1254
|
-
projectToken: apiKey ? void 0 : projectToken,
|
|
1255
|
-
// Only use token if no API key
|
|
1196
|
+
projectToken,
|
|
1256
1197
|
isDev,
|
|
1257
|
-
convexUrl
|
|
1258
|
-
selectedProjects,
|
|
1259
|
-
// Project slugs to filter (empty = all projects)
|
|
1260
|
-
agentName: apiKey && ppmAgent ? ppmAgent : void 0
|
|
1261
|
-
// Only use agent name with API key auth
|
|
1198
|
+
convexUrl
|
|
1262
1199
|
};
|
|
1263
1200
|
await startServer(config, convexClient);
|
|
1264
1201
|
console.error("[MCP] WARNING: startServer promise resolved unexpectedly!");
|