@mgsoftwarebv/mcp-server-bridge 2.20.0 → 2.22.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
@@ -207,7 +207,7 @@ var TOOLS = [
207
207
  priority: { type: "string", enum: ["low", "medium", "high", "critical"] },
208
208
  projectId: { type: "string" },
209
209
  customerId: { type: "string" },
210
- q: { type: "string", description: "Search query for title or description" },
210
+ q: { type: "string", description: "Search query for ticket number, title, or description" },
211
211
  pageSize: { type: "number", default: 20, maximum: 100 }
212
212
  },
213
213
  required: []
@@ -296,28 +296,16 @@ var TOOLS = [
296
296
  // === NEW AI SESSION TOOLS ===
297
297
  {
298
298
  name: "start-ai-session-smart",
299
- description: "Start a new AI development session with automatic tracking (time breakdown provided by Cursor AI)",
299
+ description: "Start a new AI development session with automatic tracking",
300
300
  inputSchema: {
301
301
  type: "object",
302
302
  properties: {
303
303
  ticketId: { type: "string" },
304
304
  ticketUrl: { type: "string", description: "URL to the ticket" },
305
305
  cursorSessionId: { type: "string", description: "Cursor session identifier" },
306
- codebaseContext: {
307
- type: "array",
308
- items: { type: "string" },
309
- description: "Relevant files for complexity analysis"
310
- },
311
- timeBreakdown: {
312
- type: "object",
313
- description: "Time estimate breakdown by development phase (REQUIRED)",
314
- properties: {
315
- analysisMinutes: { type: "number", description: "Ticket/context analysis time" },
316
- investigationMinutes: { type: "number", description: "Bug investigation/reproduction (0 if not a bug)" },
317
- developmentMinutes: { type: "number", description: "Actual coding/fixing time" },
318
- communicationMinutes: { type: "number", description: "Customer response writing time" }
319
- },
320
- required: ["analysisMinutes", "investigationMinutes", "developmentMinutes", "communicationMinutes"]
306
+ totalEstimatedMinutes: {
307
+ type: "number",
308
+ description: "Total estimated time in minutes (senior dev WITHOUT AI, rounded to 15 min)"
321
309
  },
322
310
  complexityScore: {
323
311
  type: "number",
@@ -326,7 +314,7 @@ var TOOLS = [
326
314
  description: "Estimated complexity from 1-10"
327
315
  }
328
316
  },
329
- required: ["ticketId", "timeBreakdown"]
317
+ required: ["ticketId", "totalEstimatedMinutes"]
330
318
  }
331
319
  },
332
320
  {
@@ -647,7 +635,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
647
635
  if (priority) query = query.eq("priority", priority);
648
636
  if (projectId) query = query.eq("project_id", projectId);
649
637
  if (customerId) query = query.eq("customer_id", customerId);
650
- if (q) query = query.or(`title.ilike.%${q}%,description.ilike.%${q}%`);
638
+ if (q) query = query.or(`ticket_number.ilike.%${q}%,title.ilike.%${q}%,description.ilike.%${q}%`);
651
639
  const { data, error } = await query.order("created_at", { ascending: false });
652
640
  if (error) throw error;
653
641
  return {
@@ -807,10 +795,48 @@ ${attachments && attachments.length > 0 ? `
807
795
  case "create-ticket": {
808
796
  const { title, description, status = "open", priority = "medium", type = "task", projectId, customerId } = args2;
809
797
  const year = (/* @__PURE__ */ new Date()).getFullYear();
810
- const { count } = await supabase.from("tickets").select("*", { count: "exact", head: true }).eq("team_id", authContext.teamId);
811
- const ticketNumber = `${year}-${String((count || 0) + 1).padStart(3, "0")}`;
798
+ let resolvedTeamId = authContext.teamId;
799
+ let resolvedCustomerId = customerId;
800
+ let projectAbbreviation = "";
801
+ if (projectId) {
802
+ const { data: project } = await supabase.from("projects").select("name, team_id, customer_id").eq("id", projectId).single();
803
+ if (project) {
804
+ if (project.team_id) {
805
+ resolvedTeamId = project.team_id;
806
+ }
807
+ if (!resolvedCustomerId && project.customer_id) {
808
+ resolvedCustomerId = project.customer_id;
809
+ }
810
+ if (project.name) {
811
+ const name2 = project.name.toUpperCase().replace(/[^A-Z0-9\s]/g, "");
812
+ const words = name2.split(/\s+/).filter(Boolean);
813
+ if (words.length >= 2) {
814
+ projectAbbreviation = words.slice(0, 2).map((w) => w.substring(0, 3)).join("").substring(0, 5);
815
+ } else if (words.length === 1 && words[0]) {
816
+ projectAbbreviation = words[0].substring(0, 5);
817
+ }
818
+ }
819
+ }
820
+ }
821
+ let ticketNumber;
822
+ if (projectId && projectAbbreviation) {
823
+ const pattern = `${year}-${projectAbbreviation}-%`;
824
+ const { data: existingTickets } = await supabase.from("tickets").select("ticket_number").eq("project_id", projectId).like("ticket_number", pattern).order("ticket_number", { ascending: false }).limit(1);
825
+ let nextSequence = 1;
826
+ if (existingTickets && existingTickets.length > 0 && existingTickets[0]?.ticket_number) {
827
+ const parts = existingTickets[0].ticket_number.split("-");
828
+ if (parts.length === 3) {
829
+ const lastSeq = parseInt(parts[2], 10);
830
+ if (!isNaN(lastSeq)) nextSequence = lastSeq + 1;
831
+ }
832
+ }
833
+ ticketNumber = `${year}-${projectAbbreviation}-${String(nextSequence).padStart(3, "0")}`;
834
+ } else {
835
+ const { count } = await supabase.from("tickets").select("*", { count: "exact", head: true }).eq("team_id", resolvedTeamId);
836
+ ticketNumber = `${year}-${String((count || 0) + 1).padStart(3, "0")}`;
837
+ }
812
838
  const { data, error } = await supabase.from("tickets").insert({
813
- team_id: authContext.teamId,
839
+ team_id: resolvedTeamId,
814
840
  ticket_number: ticketNumber,
815
841
  title,
816
842
  description,
@@ -818,7 +844,7 @@ ${attachments && attachments.length > 0 ? `
818
844
  priority,
819
845
  type,
820
846
  project_id: projectId || null,
821
- customer_id: customerId || null,
847
+ customer_id: resolvedCustomerId || null,
822
848
  requester_id: authContext.userId
823
849
  }).select().single();
824
850
  if (error) throw error;
@@ -949,91 +975,34 @@ ${description ? `Description: ${description}
949
975
  }
950
976
  // === AI SESSION TOOLS ===
951
977
  case "start-ai-session-smart": {
952
- const { ticketId, ticketUrl, cursorSessionId, codebaseContext, timeBreakdown, complexityScore } = args2;
953
- if (!timeBreakdown || !timeBreakdown.analysisMinutes || timeBreakdown.developmentMinutes === void 0) {
954
- throw new Error("timeBreakdown is required with all phases: analysisMinutes, investigationMinutes, developmentMinutes, communicationMinutes");
955
- }
956
- const roundedTimeBreakdown = {
957
- analysisMinutes: roundToNearest15Minutes(timeBreakdown.analysisMinutes || 0),
958
- investigationMinutes: roundToNearest15Minutes(timeBreakdown.investigationMinutes || 0),
959
- developmentMinutes: roundToNearest15Minutes(timeBreakdown.developmentMinutes || 0),
960
- communicationMinutes: roundToNearest15Minutes(timeBreakdown.communicationMinutes || 0)
961
- };
962
- const totalEstimateMinutes = roundedTimeBreakdown.analysisMinutes + roundedTimeBreakdown.investigationMinutes + roundedTimeBreakdown.developmentMinutes + roundedTimeBreakdown.communicationMinutes;
978
+ const { ticketId, ticketUrl, cursorSessionId, totalEstimatedMinutes, complexityScore } = args2;
979
+ if (!totalEstimatedMinutes) {
980
+ throw new Error("totalEstimatedMinutes is required");
981
+ }
982
+ const roundedMinutes = roundToNearest15Minutes(totalEstimatedMinutes);
963
983
  const sessionStartTime = /* @__PURE__ */ new Date();
964
984
  const { data: sessionData, error } = await supabase.from("ai_sessions").insert({
965
985
  ticket_id: ticketId,
966
986
  provider_user_id: authContext.userId,
967
987
  team_id: authContext.teamId,
968
988
  cursor_session_id: cursorSessionId || null,
969
- ai_time_estimate_minutes: totalEstimateMinutes,
989
+ ai_time_estimate_minutes: roundedMinutes,
970
990
  complexity_score: complexityScore || null,
971
991
  status: "in_progress"
972
992
  }).select("id, ticket_id, cursor_session_id, created_at").single();
973
993
  if (error) throw error;
974
- const phaseActivities = [
975
- {
976
- ai_session_id: sessionData.id,
977
- activity_type: "analysis",
978
- description: "Ticket analysis and context understanding",
979
- duration_seconds: 0,
980
- estimated_duration_seconds: roundedTimeBreakdown.analysisMinutes * 60,
981
- status: "in_progress",
982
- // Analysis starts immediately
983
- productivity_score: 8,
984
- started_at: sessionStartTime.toISOString()
985
- },
986
- {
987
- ai_session_id: sessionData.id,
988
- activity_type: "bug_investigation",
989
- description: "Bug investigation and root cause analysis",
990
- duration_seconds: 0,
991
- estimated_duration_seconds: roundedTimeBreakdown.investigationMinutes * 60,
992
- status: "pending",
993
- productivity_score: null
994
- },
995
- {
996
- ai_session_id: sessionData.id,
997
- activity_type: "development",
998
- description: "Implementation and coding",
999
- duration_seconds: 0,
1000
- estimated_duration_seconds: roundedTimeBreakdown.developmentMinutes * 60,
1001
- status: "pending",
1002
- productivity_score: null
1003
- },
1004
- {
1005
- ai_session_id: sessionData.id,
1006
- activity_type: "communication",
1007
- description: "Customer response and documentation",
1008
- duration_seconds: 0,
1009
- estimated_duration_seconds: roundedTimeBreakdown.communicationMinutes * 60,
1010
- status: "pending",
1011
- productivity_score: null
1012
- }
1013
- ];
1014
- await supabase.from("ai_time_logs").insert(phaseActivities);
1015
994
  const sessionId = `ai-sess-${sessionData.id.substring(0, 8)}`;
1016
995
  return {
1017
996
  content: [{
1018
997
  type: "text",
1019
- text: `\u{1F680} **AI Session Started Successfully!**
998
+ text: `\u{1F680} **AI Session Started!**
1020
999
 
1021
1000
  \u{1F194} Session ID: **${sessionId}**
1022
1001
  \u{1F3AB} Ticket: ${ticketId}
1023
-
1024
- \u{1F4CA} **Time Breakdown:**
1025
- \u2022 Analysis: ${roundedTimeBreakdown.analysisMinutes} min
1026
- \u2022 Investigation: ${roundedTimeBreakdown.investigationMinutes} min
1027
- \u2022 Development: ${roundedTimeBreakdown.developmentMinutes} min
1028
- \u2022 Communication: ${roundedTimeBreakdown.communicationMinutes} min
1029
- \u2022 **Total: ${totalEstimateMinutes} min**
1030
-
1002
+ \u23F1\uFE0F Estimated: ${roundedMinutes} min
1031
1003
  ${complexityScore ? `\u{1F3AF} Complexity: ${complexityScore}/10
1032
- ` : ""}\u23F1\uFE0F **Phase Tracking Started** (Analysis in progress)
1033
- ${cursorSessionId ? `\u{1F517} Cursor Session: ${cursorSessionId}
1034
1004
  ` : ""}\u{1F4C5} Started: ${sessionStartTime.toLocaleString()}
1035
1005
 
1036
- \u2705 Session initialized with phase breakdown!
1037
1006
  \u{1F4DD} Timetrack entry will be created when you complete the session.`
1038
1007
  }]
1039
1008
  };
@@ -1092,10 +1061,21 @@ ${cursorSessionId ? `\u{1F517} Cursor Session: ${cursorSessionId}
1092
1061
  efficiency_score: currentEfficiency.toFixed(2),
1093
1062
  actual_time_minutes: totalMinutesElapsed
1094
1063
  }).eq("id", session.id);
1095
- const { data: existingEntry, error: entryError } = await supabase.from("agenda_events").select("id, tracked_duration, title, description, start_time").eq("ai_session_id", session.id).eq("status", "draft").single();
1064
+ const { data: existingEntries, error: entryError } = await supabase.from("agenda_events").select("id, tracked_duration, title, description, start_time").eq("ai_session_id", session.id).eq("status", "draft").order("created_at", { ascending: false });
1096
1065
  let trackerAction = "";
1097
1066
  let trackerDetails = "";
1098
- if (existingEntry && !entryError) {
1067
+ const existingEntry = existingEntries && existingEntries.length > 0 ? existingEntries[0] : null;
1068
+ if (existingEntries && existingEntries.length > 1) {
1069
+ const totalExistingDuration = existingEntries.reduce((sum, entry) => sum + (entry.tracked_duration || 0), 0);
1070
+ const duplicateIds = existingEntries.slice(1).map((e) => e.id);
1071
+ await supabase.from("agenda_events").delete().in("id", duplicateIds);
1072
+ if (totalExistingDuration > (existingEntry?.tracked_duration || 0)) {
1073
+ await supabase.from("agenda_events").update({ tracked_duration: totalExistingDuration }).eq("id", existingEntry.id);
1074
+ existingEntry.tracked_duration = totalExistingDuration;
1075
+ }
1076
+ trackerAction = `Consolidated ${existingEntries.length} duplicate entries`;
1077
+ }
1078
+ if (existingEntry) {
1099
1079
  const newDuration = (existingEntry.tracked_duration || 0) + roundedFollowUpMinutes * 60;
1100
1080
  await supabase.from("agenda_events").update({
1101
1081
  tracked_duration: newDuration,
@@ -1103,7 +1083,7 @@ ${cursorSessionId ? `\u{1F517} Cursor Session: ${cursorSessionId}
1103
1083
  title: workDescription,
1104
1084
  description: workDescription
1105
1085
  }).eq("id", existingEntry.id);
1106
- trackerAction = "Updated existing tracker";
1086
+ trackerAction = trackerAction || "Updated existing tracker";
1107
1087
  trackerDetails = ` \u2022 Total tracked time: ${Math.round(newDuration / 60)} minutes (+${roundedFollowUpMinutes} min)
1108
1088
  \u2022 Description: ${workDescription}
1109
1089
  `;
@@ -1526,10 +1506,17 @@ ${workDescription}`;
1526
1506
  const estimatedMinutes = session.ai_time_estimate_minutes || timeSpentMinutes;
1527
1507
  const sessionStart = new Date(session.created_at);
1528
1508
  const estimatedEnd = new Date(sessionStart.getTime() + estimatedMinutes * 6e4);
1529
- const { data: existingAgendaEntry } = await supabase.from("agenda_events").select("id, tracked_duration").eq("ai_session_id", session.id).eq("status", "draft").single();
1509
+ const { data: existingAgendaEntries } = await supabase.from("agenda_events").select("id, tracked_duration").eq("ai_session_id", session.id).eq("status", "draft").order("created_at", { ascending: false });
1530
1510
  let agendaEvent = null;
1531
1511
  let agendaError = null;
1532
1512
  let wasUpdated = false;
1513
+ let consolidatedCount = 0;
1514
+ const existingAgendaEntry = existingAgendaEntries && existingAgendaEntries.length > 0 ? existingAgendaEntries[0] : null;
1515
+ if (existingAgendaEntries && existingAgendaEntries.length > 1) {
1516
+ const duplicateIds = existingAgendaEntries.slice(1).map((e) => e.id);
1517
+ await supabase.from("agenda_events").delete().in("id", duplicateIds);
1518
+ consolidatedCount = existingAgendaEntries.length - 1;
1519
+ }
1533
1520
  if (existingAgendaEntry) {
1534
1521
  const { data: updated, error: updateError } = await supabase.from("agenda_events").update({
1535
1522
  title: ticketInfo?.title || "Development Work",
@@ -1569,6 +1556,9 @@ ${workDescription}`;
1569
1556
  if (agendaError) {
1570
1557
  console.error(`\u26A0\uFE0F Failed to ${wasUpdated ? "update" : "create"} agenda event:`, agendaError);
1571
1558
  }
1559
+ if (consolidatedCount > 0) {
1560
+ console.log(`\u{1F9F9} Cleaned up ${consolidatedCount} duplicate agenda entries for session ${aiSessionId}`);
1561
+ }
1572
1562
  let responseText = `\u{1F389} **AI Session Completed Successfully!**
1573
1563
 
1574
1564
  `;
@@ -1689,29 +1679,77 @@ ${efficiencyNotes}
1689
1679
  }
1690
1680
  const durationSeconds = Math.round(estimatedHours * 3600);
1691
1681
  const now = /* @__PURE__ */ new Date();
1692
- const startTime = new Date(now.getTime() - durationSeconds * 1e3);
1693
- const { data: agendaEntry, error: agendaError } = await supabase.from("agenda_events").insert({
1694
- team_id: authContext.teamId,
1695
- user_id: authContext.userId,
1696
- project_id: project?.id || null,
1697
- ticket_id: ticket?.id || null,
1698
- ai_session_id: aiSession?.id || null,
1699
- title: workDescription,
1700
- description: chatContextSummary || workDescription,
1701
- start_time: startTime.toISOString(),
1702
- end_time: now.toISOString(),
1703
- type: "work",
1704
- status: "draft",
1705
- all_day: false,
1706
- is_tracked: true,
1707
- tracked_duration: durationSeconds
1708
- }).select("id, tracked_duration, project_id, ticket_id, ai_session_id").single();
1682
+ let agendaEntry = null;
1683
+ let agendaError = null;
1684
+ let wasUpdated = false;
1685
+ let consolidatedCount = 0;
1686
+ if (aiSession?.id || ticket?.id) {
1687
+ let query = supabase.from("agenda_events").select("id, tracked_duration, project_id, ticket_id, ai_session_id").eq("status", "draft").eq("user_id", authContext.userId).order("created_at", { ascending: false });
1688
+ if (aiSession?.id) {
1689
+ query = query.eq("ai_session_id", aiSession.id);
1690
+ } else if (ticket?.id) {
1691
+ query = query.eq("ticket_id", ticket.id);
1692
+ }
1693
+ const { data: existingEntries } = await query;
1694
+ if (existingEntries && existingEntries.length > 0) {
1695
+ const existingEntry = existingEntries[0];
1696
+ if (existingEntries.length > 1) {
1697
+ const duplicateIds = existingEntries.slice(1).map((e) => e.id);
1698
+ await supabase.from("agenda_events").delete().in("id", duplicateIds);
1699
+ consolidatedCount = existingEntries.length - 1;
1700
+ }
1701
+ const newDuration = (existingEntry.tracked_duration || 0) + durationSeconds;
1702
+ const { data: updated, error: updateError } = await supabase.from("agenda_events").update({
1703
+ tracked_duration: newDuration,
1704
+ end_time: now.toISOString(),
1705
+ title: workDescription,
1706
+ description: chatContextSummary || workDescription,
1707
+ project_id: project?.id || existingEntry.project_id
1708
+ }).eq("id", existingEntry.id).select("id, tracked_duration, project_id, ticket_id, ai_session_id").single();
1709
+ agendaEntry = updated;
1710
+ agendaError = updateError;
1711
+ wasUpdated = true;
1712
+ }
1713
+ }
1714
+ if (!agendaEntry) {
1715
+ const startTime = new Date(now.getTime() - durationSeconds * 1e3);
1716
+ const { data: created, error: createError } = await supabase.from("agenda_events").insert({
1717
+ team_id: authContext.teamId,
1718
+ user_id: authContext.userId,
1719
+ project_id: project?.id || null,
1720
+ ticket_id: ticket?.id || null,
1721
+ ai_session_id: aiSession?.id || null,
1722
+ title: workDescription,
1723
+ description: chatContextSummary || workDescription,
1724
+ start_time: startTime.toISOString(),
1725
+ end_time: now.toISOString(),
1726
+ type: "work",
1727
+ status: "draft",
1728
+ all_day: false,
1729
+ is_tracked: true,
1730
+ tracked_duration: durationSeconds
1731
+ }).select("id, tracked_duration, project_id, ticket_id, ai_session_id").single();
1732
+ agendaEntry = created;
1733
+ agendaError = createError;
1734
+ }
1709
1735
  if (agendaError || !agendaEntry) {
1710
- throw new Error(`Failed to create time entry: ${agendaError?.message || "Unknown error"}`);
1736
+ throw new Error(`Failed to ${wasUpdated ? "update" : "create"} time entry: ${agendaError?.message || "Unknown error"}`);
1711
1737
  }
1712
- let responseText = `\u23F1\uFE0F **Hours Logged Successfully!**
1738
+ let responseText = `\u23F1\uFE0F **Hours ${wasUpdated ? "Added to Existing Entry" : "Logged Successfully"}!**
1713
1739
 
1714
1740
  `;
1741
+ if (wasUpdated) {
1742
+ responseText += `\u{1F504} **Updated existing draft entry** (avoiding duplicates)
1743
+ `;
1744
+ responseText += ` \u2022 New total: ${Math.round((agendaEntry.tracked_duration || 0) / 3600 * 10) / 10}h
1745
+
1746
+ `;
1747
+ }
1748
+ if (consolidatedCount > 0) {
1749
+ responseText += `\u{1F9F9} **Cleaned up ${consolidatedCount} duplicate entries**
1750
+
1751
+ `;
1752
+ }
1715
1753
  responseText += `\u{1F4CB} **Entry Details:**
1716
1754
  `;
1717
1755
  if (project) {
@@ -1731,7 +1769,7 @@ ${efficiencyNotes}
1731
1769
  }
1732
1770
  responseText += ` \u2022 Description: ${workDescription}
1733
1771
  `;
1734
- responseText += ` \u2022 Estimated Hours: ${estimatedHours}h (${Math.floor(estimatedHours)}h ${Math.round(estimatedHours % 1 * 60)}m)
1772
+ responseText += ` \u2022 ${wasUpdated ? "Added" : "Estimated"} Hours: ${estimatedHours}h (${Math.floor(estimatedHours)}h ${Math.round(estimatedHours % 1 * 60)}m)
1735
1773
  `;
1736
1774
  responseText += ` \u2022 Status: DRAFT (not billed yet)
1737
1775
  `;
@@ -1745,7 +1783,7 @@ ${efficiencyNotes}
1745
1783
 
1746
1784
  `;
1747
1785
  }
1748
- responseText += `\u2705 Time entry created and ready for review in the agenda!`;
1786
+ responseText += `\u2705 Time entry ${wasUpdated ? "updated" : "created"} and ready for review in the agenda!`;
1749
1787
  return {
1750
1788
  content: [{
1751
1789
  type: "text",