projoflow-mcp-server 1.1.4 → 1.2.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.
Files changed (2) hide show
  1. package/index.js +449 -4
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -79,7 +79,26 @@ async function authenticate() {
79
79
  }
80
80
  console.error(`Raw projectIds response: ${JSON.stringify(projectIds)}`);
81
81
 
82
- authContext.accessible_project_ids = Array.isArray(projectIds) ? projectIds : [];
82
+ // Handle different return formats from Supabase RPC:
83
+ // - Direct array: ["uuid1", "uuid2"]
84
+ // - Wrapped: [{"get_client_project_ids": ["uuid1"]}]
85
+ // - Single value: "uuid1"
86
+ // - Null: null
87
+ let parsedProjectIds = [];
88
+ if (Array.isArray(projectIds)) {
89
+ if (projectIds.length > 0 && typeof projectIds[0] === 'object' && projectIds[0].get_client_project_ids) {
90
+ // Wrapped format from SQL query
91
+ parsedProjectIds = projectIds[0].get_client_project_ids || [];
92
+ } else {
93
+ // Direct array format
94
+ parsedProjectIds = projectIds;
95
+ }
96
+ } else if (typeof projectIds === 'string') {
97
+ parsedProjectIds = [projectIds];
98
+ }
99
+
100
+ authContext.accessible_project_ids = parsedProjectIds;
101
+ console.error(`Parsed project IDs: ${JSON.stringify(parsedProjectIds)}`);
83
102
  console.error(`Authenticated as CLIENT via API key (${authContext.accessible_project_ids.length} projects accessible)`);
84
103
  } else {
85
104
  console.error(`Authenticated via API key for workspace ${authContext.workspace_id}`);
@@ -111,7 +130,20 @@ async function authenticate() {
111
130
  p_user_id: authContext.user_id,
112
131
  p_client_id: authContext.client_id
113
132
  });
114
- authContext.accessible_project_ids = projectIds || [];
133
+
134
+ // Handle different return formats (same as above)
135
+ let parsedProjectIds = [];
136
+ if (Array.isArray(projectIds)) {
137
+ if (projectIds.length > 0 && typeof projectIds[0] === 'object' && projectIds[0].get_client_project_ids) {
138
+ parsedProjectIds = projectIds[0].get_client_project_ids || [];
139
+ } else {
140
+ parsedProjectIds = projectIds;
141
+ }
142
+ } else if (typeof projectIds === 'string') {
143
+ parsedProjectIds = [projectIds];
144
+ }
145
+
146
+ authContext.accessible_project_ids = parsedProjectIds;
115
147
  console.error(`Authenticated as CLIENT via API key (${authContext.accessible_project_ids.length} projects accessible)`);
116
148
  } else {
117
149
  // Update last_used_at
@@ -243,7 +275,7 @@ const TOOLS = [
243
275
  },
244
276
  {
245
277
  name: "update_task",
246
- description: "Update a task's details or status",
278
+ description: "Update a task's details, status, or assignment. Use list_workspace_members to find user IDs.",
247
279
  inputSchema: {
248
280
  type: "object",
249
281
  properties: {
@@ -252,7 +284,9 @@ const TOOLS = [
252
284
  description: { type: "string" },
253
285
  status: { type: "string", enum: ["todo", "in_progress", "review", "done"] },
254
286
  priority: { type: "string", enum: ["low", "medium", "high", "urgent"] },
255
- due_date: { type: "string" }
287
+ due_date: { type: "string", description: "Due date (YYYY-MM-DD)" },
288
+ assigned_to: { type: "string", description: "User ID to assign/reassign the task to (get from list_workspace_members)" },
289
+ estimated_hours: { type: "number", description: "Estimated hours to complete" }
256
290
  },
257
291
  required: ["task_id"]
258
292
  }
@@ -457,6 +491,218 @@ const TOOLS = [
457
491
  },
458
492
  required: ["file_path"]
459
493
  }
494
+ },
495
+ {
496
+ name: "get_task",
497
+ description: "Get full details of a specific task including assignee info",
498
+ inputSchema: {
499
+ type: "object",
500
+ properties: {
501
+ task_id: { type: "string", description: "Task ID" }
502
+ },
503
+ required: ["task_id"]
504
+ }
505
+ },
506
+ {
507
+ name: "delete_task",
508
+ description: "Delete a task permanently",
509
+ inputSchema: {
510
+ type: "object",
511
+ properties: {
512
+ task_id: { type: "string", description: "Task ID" }
513
+ },
514
+ required: ["task_id"]
515
+ }
516
+ },
517
+ {
518
+ name: "get_client",
519
+ description: "Get full details of a specific client",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: {
523
+ client_id: { type: "string", description: "Client ID" }
524
+ },
525
+ required: ["client_id"]
526
+ }
527
+ },
528
+ {
529
+ name: "update_client",
530
+ description: "Update a client's details",
531
+ inputSchema: {
532
+ type: "object",
533
+ properties: {
534
+ client_id: { type: "string", description: "Client ID" },
535
+ name: { type: "string" },
536
+ contact_name: { type: "string" },
537
+ email: { type: "string" },
538
+ phone: { type: "string" },
539
+ company: { type: "string" },
540
+ notes: { type: "string" }
541
+ },
542
+ required: ["client_id"]
543
+ }
544
+ },
545
+ {
546
+ name: "delete_client",
547
+ description: "Delete a client permanently. Will fail if client has linked projects.",
548
+ inputSchema: {
549
+ type: "object",
550
+ properties: {
551
+ client_id: { type: "string", description: "Client ID" }
552
+ },
553
+ required: ["client_id"]
554
+ }
555
+ },
556
+ {
557
+ name: "create_lead",
558
+ description: "Create a new lead/inquiry",
559
+ inputSchema: {
560
+ type: "object",
561
+ properties: {
562
+ company_name: { type: "string", description: "Company or person name" },
563
+ contact_name: { type: "string", description: "Contact person name" },
564
+ email: { type: "string" },
565
+ phone: { type: "string" },
566
+ budget_range: { type: "string", enum: ["under_5k", "5k_10k", "10k_25k", "25k_50k", "50k_plus", "not_sure"] },
567
+ timeline: { type: "string", enum: ["asap", "1_month", "2_3_months", "3_6_months", "flexible"] },
568
+ project_description: { type: "string", description: "What the lead needs" },
569
+ source: { type: "string", description: "How they found you" },
570
+ notes: { type: "string" },
571
+ status: { type: "string", enum: ["new", "contacted", "qualified", "converted", "lost"], default: "new" }
572
+ },
573
+ required: ["company_name", "email"]
574
+ }
575
+ },
576
+ {
577
+ name: "get_lead",
578
+ description: "Get full details of a specific lead",
579
+ inputSchema: {
580
+ type: "object",
581
+ properties: {
582
+ lead_id: { type: "string", description: "Lead ID" }
583
+ },
584
+ required: ["lead_id"]
585
+ }
586
+ },
587
+ {
588
+ name: "delete_lead",
589
+ description: "Delete a lead permanently",
590
+ inputSchema: {
591
+ type: "object",
592
+ properties: {
593
+ lead_id: { type: "string", description: "Lead ID" }
594
+ },
595
+ required: ["lead_id"]
596
+ }
597
+ },
598
+ {
599
+ name: "list_notes",
600
+ description: "List notes for a project, optionally filtered by type",
601
+ inputSchema: {
602
+ type: "object",
603
+ properties: {
604
+ project_id: { type: "string", description: "Project ID" },
605
+ note_type: { type: "string", enum: ["general", "meeting", "technical", "decision"], description: "Filter by note type" }
606
+ },
607
+ required: ["project_id"]
608
+ }
609
+ },
610
+ {
611
+ name: "update_note",
612
+ description: "Update a project note",
613
+ inputSchema: {
614
+ type: "object",
615
+ properties: {
616
+ note_id: { type: "string", description: "Note ID" },
617
+ title: { type: "string" },
618
+ content: { type: "string" },
619
+ note_type: { type: "string", enum: ["general", "meeting", "technical", "decision"] }
620
+ },
621
+ required: ["note_id"]
622
+ }
623
+ },
624
+ {
625
+ name: "delete_note",
626
+ description: "Delete a project note permanently",
627
+ inputSchema: {
628
+ type: "object",
629
+ properties: {
630
+ note_id: { type: "string", description: "Note ID" }
631
+ },
632
+ required: ["note_id"]
633
+ }
634
+ },
635
+ {
636
+ name: "delete_document",
637
+ description: "Delete a project document from storage and database",
638
+ inputSchema: {
639
+ type: "object",
640
+ properties: {
641
+ document_id: { type: "string", description: "Document ID from list_project_documents" }
642
+ },
643
+ required: ["document_id"]
644
+ }
645
+ },
646
+ {
647
+ name: "update_time_entry",
648
+ description: "Update a time entry",
649
+ inputSchema: {
650
+ type: "object",
651
+ properties: {
652
+ time_entry_id: { type: "string", description: "Time entry ID" },
653
+ duration_minutes: { type: "number" },
654
+ description: { type: "string" },
655
+ date: { type: "string", description: "Date (YYYY-MM-DD)" },
656
+ billable: { type: "boolean" },
657
+ task_id: { type: "string" }
658
+ },
659
+ required: ["time_entry_id"]
660
+ }
661
+ },
662
+ {
663
+ name: "delete_time_entry",
664
+ description: "Delete a time entry permanently",
665
+ inputSchema: {
666
+ type: "object",
667
+ properties: {
668
+ time_entry_id: { type: "string", description: "Time entry ID" }
669
+ },
670
+ required: ["time_entry_id"]
671
+ }
672
+ },
673
+ {
674
+ name: "update_comment",
675
+ description: "Update a task comment's content",
676
+ inputSchema: {
677
+ type: "object",
678
+ properties: {
679
+ comment_id: { type: "string", description: "Comment ID" },
680
+ content: { type: "string", description: "New comment content" }
681
+ },
682
+ required: ["comment_id", "content"]
683
+ }
684
+ },
685
+ {
686
+ name: "delete_comment",
687
+ description: "Delete a task comment permanently",
688
+ inputSchema: {
689
+ type: "object",
690
+ properties: {
691
+ comment_id: { type: "string", description: "Comment ID" }
692
+ },
693
+ required: ["comment_id"]
694
+ }
695
+ },
696
+ {
697
+ name: "delete_project",
698
+ description: "Delete a project and all its related data (tasks, time entries, notes, documents) permanently",
699
+ inputSchema: {
700
+ type: "object",
701
+ properties: {
702
+ project_id: { type: "string", description: "Project ID" }
703
+ },
704
+ required: ["project_id"]
705
+ }
460
706
  }
461
707
  ];
462
708
 
@@ -908,6 +1154,205 @@ async function handleTool(name, args) {
908
1154
  };
909
1155
  }
910
1156
 
1157
+ case "get_task": {
1158
+ const { data, error } = await supabase
1159
+ .from("tasks")
1160
+ .select("*, users(id, email, name), projects(id, name)")
1161
+ .eq("id", args.task_id)
1162
+ .single();
1163
+ if (error) throw new Error(error.message);
1164
+ return data;
1165
+ }
1166
+
1167
+ case "delete_task": {
1168
+ const { error } = await supabase
1169
+ .from("tasks")
1170
+ .delete()
1171
+ .eq("id", args.task_id);
1172
+ if (error) throw new Error(error.message);
1173
+ return { success: true, message: "Task deleted" };
1174
+ }
1175
+
1176
+ case "get_client": {
1177
+ const { data, error } = await supabase
1178
+ .from("clients")
1179
+ .select("*")
1180
+ .eq("id", args.client_id)
1181
+ .single();
1182
+ if (error) throw new Error(error.message);
1183
+ return data;
1184
+ }
1185
+
1186
+ case "update_client": {
1187
+ const { client_id, ...updates } = args;
1188
+ const { data, error } = await supabase
1189
+ .from("clients")
1190
+ .update(updates)
1191
+ .eq("id", client_id)
1192
+ .select()
1193
+ .single();
1194
+ if (error) throw new Error(error.message);
1195
+ return data;
1196
+ }
1197
+
1198
+ case "delete_client": {
1199
+ const { error } = await supabase
1200
+ .from("clients")
1201
+ .delete()
1202
+ .eq("id", args.client_id);
1203
+ if (error) throw new Error(error.message);
1204
+ return { success: true, message: "Client deleted" };
1205
+ }
1206
+
1207
+ case "create_lead": {
1208
+ const { data, error } = await supabase
1209
+ .from("leads")
1210
+ .insert({
1211
+ company_name: args.company_name,
1212
+ contact_name: args.contact_name,
1213
+ email: args.email,
1214
+ phone: args.phone,
1215
+ budget_range: args.budget_range,
1216
+ timeline: args.timeline,
1217
+ project_description: args.project_description,
1218
+ source: args.source,
1219
+ notes: args.notes,
1220
+ status: args.status || "new"
1221
+ })
1222
+ .select()
1223
+ .single();
1224
+ if (error) throw new Error(error.message);
1225
+ return data;
1226
+ }
1227
+
1228
+ case "get_lead": {
1229
+ const { data, error } = await supabase
1230
+ .from("leads")
1231
+ .select("*")
1232
+ .eq("id", args.lead_id)
1233
+ .single();
1234
+ if (error) throw new Error(error.message);
1235
+ return data;
1236
+ }
1237
+
1238
+ case "delete_lead": {
1239
+ const { error } = await supabase
1240
+ .from("leads")
1241
+ .delete()
1242
+ .eq("id", args.lead_id);
1243
+ if (error) throw new Error(error.message);
1244
+ return { success: true, message: "Lead deleted" };
1245
+ }
1246
+
1247
+ case "list_notes": {
1248
+ let query = supabase
1249
+ .from("notes")
1250
+ .select("*")
1251
+ .eq("project_id", args.project_id)
1252
+ .order("created_at", { ascending: false });
1253
+ if (args.note_type) query = query.eq("note_type", args.note_type);
1254
+ const { data, error } = await query;
1255
+ if (error) throw new Error(error.message);
1256
+ return data;
1257
+ }
1258
+
1259
+ case "update_note": {
1260
+ const { note_id, ...updates } = args;
1261
+ const { data, error } = await supabase
1262
+ .from("notes")
1263
+ .update(updates)
1264
+ .eq("id", note_id)
1265
+ .select()
1266
+ .single();
1267
+ if (error) throw new Error(error.message);
1268
+ return data;
1269
+ }
1270
+
1271
+ case "delete_note": {
1272
+ const { error } = await supabase
1273
+ .from("notes")
1274
+ .delete()
1275
+ .eq("id", args.note_id);
1276
+ if (error) throw new Error(error.message);
1277
+ return { success: true, message: "Note deleted" };
1278
+ }
1279
+
1280
+ case "delete_document": {
1281
+ // Get the file path first
1282
+ const { data: doc, error: fetchError } = await supabase
1283
+ .from("project_documents")
1284
+ .select("file_path")
1285
+ .eq("id", args.document_id)
1286
+ .single();
1287
+ if (fetchError) throw new Error(fetchError.message);
1288
+
1289
+ // Delete from storage
1290
+ if (doc.file_path) {
1291
+ const { error: storageError } = await supabase.storage
1292
+ .from("project-documents")
1293
+ .remove([doc.file_path]);
1294
+ if (storageError) throw new Error(`Storage deletion failed: ${storageError.message}`);
1295
+ }
1296
+
1297
+ // Delete from database
1298
+ const { error } = await supabase
1299
+ .from("project_documents")
1300
+ .delete()
1301
+ .eq("id", args.document_id);
1302
+ if (error) throw new Error(error.message);
1303
+ return { success: true, message: "Document deleted" };
1304
+ }
1305
+
1306
+ case "update_time_entry": {
1307
+ const { time_entry_id, ...updates } = args;
1308
+ const { data, error } = await supabase
1309
+ .from("time_entries")
1310
+ .update(updates)
1311
+ .eq("id", time_entry_id)
1312
+ .select("*, projects(name), tasks(title)")
1313
+ .single();
1314
+ if (error) throw new Error(error.message);
1315
+ return data;
1316
+ }
1317
+
1318
+ case "delete_time_entry": {
1319
+ const { error } = await supabase
1320
+ .from("time_entries")
1321
+ .delete()
1322
+ .eq("id", args.time_entry_id);
1323
+ if (error) throw new Error(error.message);
1324
+ return { success: true, message: "Time entry deleted" };
1325
+ }
1326
+
1327
+ case "update_comment": {
1328
+ const { data, error } = await supabase
1329
+ .from("task_comments")
1330
+ .update({ content: args.content })
1331
+ .eq("id", args.comment_id)
1332
+ .select()
1333
+ .single();
1334
+ if (error) throw new Error(error.message);
1335
+ return data;
1336
+ }
1337
+
1338
+ case "delete_comment": {
1339
+ const { error } = await supabase
1340
+ .from("task_comments")
1341
+ .delete()
1342
+ .eq("id", args.comment_id);
1343
+ if (error) throw new Error(error.message);
1344
+ return { success: true, message: "Comment deleted" };
1345
+ }
1346
+
1347
+ case "delete_project": {
1348
+ const { error } = await supabase
1349
+ .from("projects")
1350
+ .delete()
1351
+ .eq("id", args.project_id);
1352
+ if (error) throw new Error(error.message);
1353
+ return { success: true, message: "Project and all related data deleted" };
1354
+ }
1355
+
911
1356
  default:
912
1357
  throw new Error(`Unknown tool: ${name}`);
913
1358
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "projoflow-mcp-server",
3
- "version": "1.1.4",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for ProjoFlow project management - connect AI tools to your projects",
5
5
  "main": "index.js",
6
6
  "type": "module",