@vibetasks/mcp-server 0.5.2 → 0.5.4

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/dist/index.js +569 -158
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -12,6 +12,12 @@ import {
12
12
  import { AuthManager, TaskOperations } from "@vibetasks/core";
13
13
 
14
14
  // src/tools/index.ts
15
+ import {
16
+ parseError,
17
+ generateErrorTaskTitle,
18
+ detectProjectFromError,
19
+ containsError
20
+ } from "@vibetasks/shared";
15
21
  import { z } from "zod";
16
22
  function setupTools(taskOps) {
17
23
  return [
@@ -124,7 +130,8 @@ function setupTools(taskOps) {
124
130
  inputSchema: z.object({
125
131
  task_id: z.string().describe("Task ID"),
126
132
  title: z.string().optional().describe("New title"),
127
- notes: z.string().optional().describe("New notes"),
133
+ notes: z.string().optional().describe("New notes (user-facing description)"),
134
+ context_notes: z.string().optional().describe("AI context notes - progress, blockers, next steps"),
128
135
  due_date: z.string().optional().describe("New due date (ISO 8601)"),
129
136
  priority: z.enum(["none", "low", "medium", "high"]).optional().describe("New priority"),
130
137
  completed: z.boolean().optional().describe("Completion status")
@@ -133,6 +140,7 @@ function setupTools(taskOps) {
133
140
  const updates = {};
134
141
  if (args.title !== void 0) updates.title = args.title;
135
142
  if (args.notes !== void 0) updates.notes = args.notes;
143
+ if (args.context_notes !== void 0) updates.context_notes = args.context_notes;
136
144
  if (args.due_date !== void 0) updates.due_date = args.due_date;
137
145
  if (args.priority !== void 0) updates.priority = args.priority;
138
146
  if (args.completed !== void 0) updates.completed = args.completed;
@@ -148,7 +156,8 @@ function setupTools(taskOps) {
148
156
  id: task.id,
149
157
  title: task.title,
150
158
  completed: task.completed,
151
- priority: task.priority
159
+ priority: task.priority,
160
+ context_notes: task.context_notes
152
161
  }
153
162
  },
154
163
  null,
@@ -162,11 +171,15 @@ function setupTools(taskOps) {
162
171
  // complete_task
163
172
  {
164
173
  name: "complete_task",
165
- description: "Mark a task as complete",
174
+ description: "Mark a task as complete, optionally adding completion notes",
166
175
  inputSchema: z.object({
167
- task_id: z.string().describe("Task ID")
176
+ task_id: z.string().describe("Task ID"),
177
+ context_notes: z.string().optional().describe("Completion notes - what was done, any final notes")
168
178
  }),
169
179
  handler: async (args, taskOps2) => {
180
+ if (args.context_notes) {
181
+ await taskOps2.updateTask(args.task_id, { context_notes: args.context_notes });
182
+ }
170
183
  const task = await taskOps2.completeTask(args.task_id);
171
184
  return {
172
185
  content: [
@@ -179,7 +192,8 @@ function setupTools(taskOps) {
179
192
  id: task.id,
180
193
  title: task.title,
181
194
  completed: true,
182
- completed_at: task.completed_at
195
+ completed_at: task.completed_at,
196
+ context_notes: args.context_notes || task.context_notes
183
197
  }
184
198
  },
185
199
  null,
@@ -670,11 +684,28 @@ Generated by TaskFlow MCP Server`;
670
684
  description: "Parse error text from clipboard, terminal, or direct paste. Extracts structured information from common error formats (Node.js, Python, Expo, webpack, TypeScript, etc.) for AI consumption.",
671
685
  inputSchema: z.object({
672
686
  error_text: z.string().describe("The raw error text to parse"),
673
- source: z.enum(["terminal", "browser", "expo", "other"]).default("other").describe("Source of the error (helps with parsing)")
687
+ source: z.enum(["terminal", "browser", "paste", "clipboard", "sentry", "ci"]).default("paste").describe("Source of the error (helps with parsing)")
674
688
  }),
675
689
  handler: async (args, _taskOps) => {
676
690
  const { error_text, source } = args;
677
691
  const parsed = parseError(error_text, source);
692
+ if (!parsed) {
693
+ return {
694
+ content: [
695
+ {
696
+ type: "text",
697
+ text: JSON.stringify(
698
+ {
699
+ success: false,
700
+ message: "Could not parse error. Text does not appear to be an error."
701
+ },
702
+ null,
703
+ 2
704
+ )
705
+ }
706
+ ]
707
+ };
708
+ }
678
709
  return {
679
710
  content: [
680
711
  {
@@ -682,7 +713,9 @@ Generated by TaskFlow MCP Server`;
682
713
  text: JSON.stringify(
683
714
  {
684
715
  success: true,
685
- ...parsed
716
+ error_context: parsed,
717
+ suggested_title: generateErrorTaskTitle(parsed),
718
+ detected_project: detectProjectFromError(parsed)
686
719
  },
687
720
  null,
688
721
  2
@@ -702,7 +735,27 @@ Generated by TaskFlow MCP Server`;
702
735
  }),
703
736
  handler: async (args, _taskOps) => {
704
737
  const { log_text, max_lines = 100 } = args;
705
- const result = summarizeErrors(log_text, max_lines);
738
+ if (!containsError(log_text)) {
739
+ return {
740
+ content: [
741
+ {
742
+ type: "text",
743
+ text: JSON.stringify(
744
+ {
745
+ success: true,
746
+ summary: "No errors detected in log output.",
747
+ error_count: 0,
748
+ unique_errors: 0,
749
+ grouped_errors: []
750
+ },
751
+ null,
752
+ 2
753
+ )
754
+ }
755
+ ]
756
+ };
757
+ }
758
+ const result = summarizeErrorsLocal(log_text, max_lines);
706
759
  return {
707
760
  content: [
708
761
  {
@@ -733,7 +786,7 @@ Generated by TaskFlow MCP Server`;
733
786
  }),
734
787
  handler: async (args, taskOps2) => {
735
788
  const task = await taskOps2.getTask(args.task_id);
736
- const subtasks = task.subtasks_json || task.subtasks || [];
789
+ const subtasks = task.subtasks_json || [];
737
790
  const subtaskIndex = subtasks.findIndex((st) => st.id === args.subtask_id);
738
791
  if (subtaskIndex === -1) {
739
792
  return {
@@ -780,7 +833,7 @@ Generated by TaskFlow MCP Server`;
780
833
  }),
781
834
  handler: async (args, taskOps2) => {
782
835
  const task = await taskOps2.getTask(args.task_id);
783
- const subtasks = task.subtasks_json || task.subtasks || [];
836
+ const subtasks = task.subtasks_json || [];
784
837
  const newSubtask = {
785
838
  id: crypto.randomUUID(),
786
839
  title: args.title,
@@ -905,161 +958,352 @@ Generated by TaskFlow MCP Server`;
905
958
  ]
906
959
  };
907
960
  }
908
- }
909
- ];
910
- }
911
- function parseError(errorText, source) {
912
- const lines = errorText.split("\n");
913
- let errorType = "Unknown";
914
- let file = null;
915
- let line = null;
916
- let column = null;
917
- let message = "";
918
- const stackFrames = [];
919
- const patterns = {
920
- // Node.js/JavaScript: "TypeError: Cannot read property 'x' of undefined"
921
- jsError: /^(\w+Error):\s*(.+)$/,
922
- // Python: "TypeError: unsupported operand type(s)"
923
- pythonError: /^(\w+Error|Exception):\s*(.+)$/,
924
- // Stack frame patterns
925
- // Node.js: " at functionName (file.js:10:5)"
926
- nodeStackFrame: /^\s*at\s+(?:(.+?)\s+)?\(?(.+?):(\d+):(\d+)\)?$/,
927
- // Python: ' File "script.py", line 10, in function_name'
928
- pythonStackFrame: /^\s*File\s+"([^"]+)",\s*line\s*(\d+)(?:,\s*in\s+(.+))?$/,
929
- // Webpack/bundler: "ERROR in ./src/file.tsx:10:5"
930
- webpackError: /^ERROR\s+in\s+\.?(.+?):(\d+):(\d+)$/,
931
- // TypeScript: "src/file.ts(10,5): error TS2345:"
932
- tsError: /^(.+?)\((\d+),(\d+)\):\s*(error\s+TS\d+):\s*(.+)$/,
933
- // Expo/React Native: "Error: ..." with component stack
934
- expoError: /^Error:\s*(.+)$/,
935
- // Generic file:line:col pattern
936
- genericFileLine: /([^\s:]+\.[a-zA-Z]+):(\d+)(?::(\d+))?/,
937
- // ESLint/Prettier style: " 10:5 error Message"
938
- lintError: /^\s*(\d+):(\d+)\s+(error|warning)\s+(.+)$/
939
- };
940
- for (let i = 0; i < lines.length; i++) {
941
- const trimmedLine = lines[i].trim();
942
- if (!trimmedLine) continue;
943
- const tsMatch = trimmedLine.match(patterns.tsError);
944
- if (tsMatch) {
945
- file = tsMatch[1];
946
- line = parseInt(tsMatch[2], 10);
947
- column = parseInt(tsMatch[3], 10);
948
- errorType = tsMatch[4];
949
- message = tsMatch[5];
950
- continue;
951
- }
952
- const webpackMatch = trimmedLine.match(patterns.webpackError);
953
- if (webpackMatch) {
954
- file = webpackMatch[1];
955
- line = parseInt(webpackMatch[2], 10);
956
- column = parseInt(webpackMatch[3], 10);
957
- errorType = "WebpackError";
958
- continue;
959
- }
960
- const jsMatch = trimmedLine.match(patterns.jsError);
961
- if (jsMatch && !message) {
962
- errorType = jsMatch[1];
963
- message = jsMatch[2];
964
- continue;
965
- }
966
- const pythonMatch = trimmedLine.match(patterns.pythonError);
967
- if (pythonMatch && !message) {
968
- errorType = pythonMatch[1];
969
- message = pythonMatch[2];
970
- continue;
971
- }
972
- if (source === "expo") {
973
- const expoMatch = trimmedLine.match(patterns.expoError);
974
- if (expoMatch && !message) {
975
- errorType = "ExpoError";
976
- message = expoMatch[1];
977
- continue;
961
+ },
962
+ // report_error - AI can report errors it encounters
963
+ {
964
+ name: "report_error",
965
+ description: "Report an error you encountered while working. Creates a high-priority task to track and fix it. Use this when you hit a build error, runtime error, or any issue that blocks progress.",
966
+ inputSchema: z.object({
967
+ error_text: z.string().describe("The full error message including stack trace"),
968
+ context: z.string().optional().describe("What you were trying to do when this error occurred"),
969
+ file_path: z.string().optional().describe("File path where the error occurred (if known)"),
970
+ project: z.string().optional().describe("Project name (auto-detected from file path if not provided)")
971
+ }),
972
+ handler: async (args, taskOps2) => {
973
+ const errorText = args.error_text;
974
+ const errorContext = parseError(errorText, "terminal");
975
+ let title = errorContext ? generateErrorTaskTitle(errorContext) : "Error to fix";
976
+ let project = args.project;
977
+ if (!project && errorContext) {
978
+ project = detectProjectFromError(errorContext);
979
+ }
980
+ if (!project && args.file_path) {
981
+ const fileMatch = args.file_path.match(/(?:apps|packages)\/([^\/\s]+)\//);
982
+ if (fileMatch) project = fileMatch[1];
983
+ }
984
+ let notes = "";
985
+ if (args.context) {
986
+ notes += `**Context:** ${args.context}
987
+
988
+ `;
989
+ }
990
+ if (args.file_path || errorContext?.file_path) {
991
+ notes += `**File:** \`${args.file_path || errorContext?.file_path}\``;
992
+ if (errorContext?.line_number) {
993
+ notes += `:${errorContext.line_number}`;
994
+ if (errorContext.column) notes += `:${errorContext.column}`;
995
+ }
996
+ notes += "\n\n";
997
+ }
998
+ if (errorContext?.suggestion) {
999
+ notes += `**Suggestion:** ${errorContext.suggestion}
1000
+
1001
+ `;
1002
+ }
1003
+ notes += `**Error:**
1004
+ \`\`\`
1005
+ ${errorText}
1006
+ \`\`\``;
1007
+ const task = await taskOps2.createTask({
1008
+ title: title.length > 80 ? title.slice(0, 77) + "..." : title,
1009
+ notes,
1010
+ priority: "high",
1011
+ status: "todo",
1012
+ project_tag: project,
1013
+ created_by: "ai",
1014
+ error_context: errorContext || void 0
1015
+ });
1016
+ return {
1017
+ content: [
1018
+ {
1019
+ type: "text",
1020
+ text: JSON.stringify(
1021
+ {
1022
+ success: true,
1023
+ message: "Error reported and task created",
1024
+ task: {
1025
+ id: task.id,
1026
+ title: task.title,
1027
+ priority: task.priority,
1028
+ project: task.project_tag,
1029
+ error_category: errorContext?.category
1030
+ },
1031
+ parsed: errorContext ? {
1032
+ category: errorContext.category,
1033
+ error_type: errorContext.error_type,
1034
+ file: errorContext.file_path,
1035
+ line: errorContext.line_number
1036
+ } : null
1037
+ },
1038
+ null,
1039
+ 2
1040
+ )
1041
+ }
1042
+ ]
1043
+ };
978
1044
  }
979
- }
980
- const nodeFrameMatch = trimmedLine.match(patterns.nodeStackFrame);
981
- if (nodeFrameMatch) {
982
- const framePath = nodeFrameMatch[2];
983
- const frameLine = nodeFrameMatch[3];
984
- const frameCol = nodeFrameMatch[4];
985
- const funcName = nodeFrameMatch[1] || "<anonymous>";
986
- if (!isLibraryFrame(framePath)) {
987
- stackFrames.push(`${funcName} at ${framePath}:${frameLine}:${frameCol}`);
988
- if (!file) {
989
- file = framePath;
990
- line = parseInt(frameLine, 10);
991
- column = parseInt(frameCol, 10);
1045
+ },
1046
+ // get_errors - DEPRECATED: Use get_inbox(source: 'errors') instead
1047
+ {
1048
+ name: "get_errors",
1049
+ description: '[DEPRECATED - use get_inbox with source="errors" instead] Get current errors/bugs. This is a legacy tool - prefer get_inbox for unified inbox access.',
1050
+ inputSchema: z.object({
1051
+ priority: z.enum(["critical", "high", "all"]).default("all").describe("Filter by priority"),
1052
+ source: z.enum(["sentry", "ci", "manual", "all"]).default("all").describe("Filter by source"),
1053
+ unacknowledged_only: z.boolean().default(true).describe("Only show errors you haven't acknowledged yet"),
1054
+ limit: z.number().default(5).describe("Max errors to return")
1055
+ }),
1056
+ handler: async (args, taskOps2) => {
1057
+ const sourceTypes = args.source === "all" ? ["sentry", "ci"] : args.source === "sentry" ? ["sentry"] : args.source === "ci" ? ["ci"] : [];
1058
+ const inboxItems = await taskOps2.getInboxItems({
1059
+ source_type: sourceTypes.length > 0 ? sourceTypes : void 0,
1060
+ priority: args.priority === "all" ? void 0 : args.priority,
1061
+ unacknowledged_only: args.unacknowledged_only,
1062
+ limit: args.limit
1063
+ });
1064
+ return {
1065
+ content: [
1066
+ {
1067
+ type: "text",
1068
+ text: JSON.stringify(
1069
+ {
1070
+ deprecated: true,
1071
+ message: 'This tool is deprecated. Use get_inbox(source: "errors") for better results.',
1072
+ errors: inboxItems.map((t) => ({
1073
+ id: t.id,
1074
+ title: t.title,
1075
+ project: t.project_tag,
1076
+ status: t.status,
1077
+ source: t.source_type || (t.sentry_issue_id ? "sentry" : "manual"),
1078
+ created: t.created_at,
1079
+ error_category: t.error_context?.category
1080
+ })),
1081
+ total_matching: inboxItems.length,
1082
+ showing: inboxItems.length,
1083
+ hint: 'Migrate to get_inbox(source: "errors") for unified inbox access.'
1084
+ },
1085
+ null,
1086
+ 2
1087
+ )
1088
+ }
1089
+ ]
1090
+ };
1091
+ }
1092
+ },
1093
+ // get_error_details - Full context for specific error
1094
+ {
1095
+ name: "get_error_details",
1096
+ description: "Get full details for a specific error including parsed error context, notes, stack trace, and file context. Use after get_inbox to dive into a specific issue.",
1097
+ inputSchema: z.object({
1098
+ error_id: z.string().describe("Error task ID")
1099
+ }),
1100
+ handler: async (args, taskOps2) => {
1101
+ const task = await taskOps2.getTask(args.error_id);
1102
+ const errorContext = task.error_context;
1103
+ return {
1104
+ content: [
1105
+ {
1106
+ type: "text",
1107
+ text: JSON.stringify(
1108
+ {
1109
+ id: task.id,
1110
+ title: task.title,
1111
+ description: task.description,
1112
+ notes: task.notes,
1113
+ project: task.project_tag,
1114
+ status: task.status,
1115
+ priority: task.priority,
1116
+ created_at: task.created_at,
1117
+ created_by: task.created_by,
1118
+ context_notes: task.context_notes,
1119
+ // Parsed error context from shared parser
1120
+ error_context: errorContext ? {
1121
+ category: errorContext.category,
1122
+ error_type: errorContext.error_type,
1123
+ message: errorContext.message,
1124
+ file_path: errorContext.file_path,
1125
+ line_number: errorContext.line_number,
1126
+ column: errorContext.column,
1127
+ stack_trace: errorContext.stack_trace,
1128
+ suggestion: errorContext.suggestion,
1129
+ captured_at: errorContext.captured_at,
1130
+ capture_source: errorContext.capture_source
1131
+ } : null,
1132
+ // Sentry-specific
1133
+ sentry: task.sentry_issue_id ? {
1134
+ issue_id: task.sentry_issue_id,
1135
+ project: task.sentry_project,
1136
+ error_count: task.sentry_error_count,
1137
+ first_seen: task.sentry_first_seen,
1138
+ last_seen: task.sentry_last_seen,
1139
+ level: task.sentry_level,
1140
+ url: task.sentry_url
1141
+ } : null,
1142
+ subtasks: task.subtasks_json || [],
1143
+ tags: task.tags?.map((t) => t.name) || []
1144
+ },
1145
+ null,
1146
+ 2
1147
+ )
1148
+ }
1149
+ ]
1150
+ };
1151
+ }
1152
+ },
1153
+ // acknowledge_error - Mark error as seen/addressed
1154
+ {
1155
+ name: "acknowledge_error",
1156
+ description: "Mark an error as acknowledged so it doesn't keep appearing. Use this after you've investigated or fixed an error.",
1157
+ inputSchema: z.object({
1158
+ error_id: z.string().describe("Error task ID"),
1159
+ action: z.enum(["investigating", "fixed", "wont_fix", "need_info", "delegated"]).describe("What action was taken"),
1160
+ notes: z.string().optional().describe("Optional notes about what was done")
1161
+ }),
1162
+ handler: async (args, taskOps2) => {
1163
+ const task = await taskOps2.getTask(args.error_id);
1164
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1165
+ const ackNote = `[AI_ACKNOWLEDGED:${args.action}:${timestamp}]`;
1166
+ const existingNotes = task.context_notes || "";
1167
+ const newNotes = args.notes ? `${existingNotes}
1168
+
1169
+ ${ackNote}
1170
+ ${args.notes}` : `${existingNotes}
1171
+
1172
+ ${ackNote}`;
1173
+ let newStatus = task.status;
1174
+ if (args.action === "fixed") {
1175
+ newStatus = "done";
1176
+ } else if (args.action === "investigating") {
1177
+ newStatus = "vibing";
992
1178
  }
1179
+ await taskOps2.updateTask(args.error_id, {
1180
+ context_notes: newNotes.trim(),
1181
+ status: newStatus
1182
+ });
1183
+ return {
1184
+ content: [
1185
+ {
1186
+ type: "text",
1187
+ text: JSON.stringify(
1188
+ {
1189
+ success: true,
1190
+ error_id: args.error_id,
1191
+ action: args.action,
1192
+ new_status: newStatus,
1193
+ message: `Error acknowledged as '${args.action}'. It won't appear in unacknowledged lists.`
1194
+ },
1195
+ null,
1196
+ 2
1197
+ )
1198
+ }
1199
+ ]
1200
+ };
993
1201
  }
994
- continue;
995
- }
996
- const pythonFrameMatch = trimmedLine.match(patterns.pythonStackFrame);
997
- if (pythonFrameMatch) {
998
- const framePath = pythonFrameMatch[1];
999
- const frameLine = pythonFrameMatch[2];
1000
- const funcName = pythonFrameMatch[3] || "<module>";
1001
- if (!isLibraryFrame(framePath)) {
1002
- stackFrames.push(`${funcName} at ${framePath}:${frameLine}`);
1003
- file = framePath;
1004
- line = parseInt(frameLine, 10);
1202
+ },
1203
+ // get_inbox - Unified inbox for all external sources
1204
+ {
1205
+ name: "get_inbox",
1206
+ description: "Get inbox items from all external sources (Sentry errors, GitHub issues, email, Slack, CI failures). Use this to see what needs attention.",
1207
+ inputSchema: z.object({
1208
+ source: z.enum(["all", "sentry", "ci", "github", "email", "slack", "errors", "feedback"]).default("all").describe("Filter by source: all, sentry, ci, github, email, slack, errors (sentry+ci), feedback (github+email+slack)"),
1209
+ priority: z.enum(["all", "high", "medium", "low"]).default("all").describe("Filter by priority"),
1210
+ unacknowledged_only: z.boolean().default(true).describe("Only show items not yet acknowledged"),
1211
+ limit: z.number().default(10).describe("Maximum items to return")
1212
+ }),
1213
+ handler: async (args, taskOps2) => {
1214
+ let sourceTypes;
1215
+ switch (args.source) {
1216
+ case "sentry":
1217
+ sourceTypes = ["sentry"];
1218
+ break;
1219
+ case "ci":
1220
+ sourceTypes = ["ci"];
1221
+ break;
1222
+ case "github":
1223
+ sourceTypes = ["github_bug", "github_feedback"];
1224
+ break;
1225
+ case "email":
1226
+ sourceTypes = ["email_bug", "email_feedback"];
1227
+ break;
1228
+ case "slack":
1229
+ sourceTypes = ["slack_bug", "slack_feedback"];
1230
+ break;
1231
+ case "errors":
1232
+ sourceTypes = ["sentry", "ci"];
1233
+ break;
1234
+ case "feedback":
1235
+ sourceTypes = ["github_bug", "github_feedback", "email_bug", "email_feedback", "slack_bug", "slack_feedback"];
1236
+ break;
1237
+ default:
1238
+ sourceTypes = void 0;
1239
+ }
1240
+ const inboxItems = await taskOps2.getInboxItems({
1241
+ source_type: sourceTypes,
1242
+ priority: args.priority === "all" ? void 0 : args.priority,
1243
+ unacknowledged_only: args.unacknowledged_only,
1244
+ limit: args.limit
1245
+ });
1246
+ return {
1247
+ content: [
1248
+ {
1249
+ type: "text",
1250
+ text: JSON.stringify(
1251
+ {
1252
+ success: true,
1253
+ inbox_items: inboxItems.map((t) => ({
1254
+ id: t.id,
1255
+ title: t.title,
1256
+ source: t.source_type || (t.sentry_issue_id ? "sentry" : t.github_issue_id ? "github" : t.tags?.some((tag) => tag.name === "ci") ? "ci" : "manual"),
1257
+ priority: t.priority,
1258
+ status: t.status,
1259
+ project: t.project_tag,
1260
+ created_at: t.created_at,
1261
+ acknowledged: t.context_notes?.includes("[AI_ACKNOWLEDGED]") || false,
1262
+ // Include relevant integration details
1263
+ sentry_count: t.sentry_error_count,
1264
+ github_issue: t.github_issue_number,
1265
+ github_repo: t.github_repo
1266
+ })),
1267
+ count: inboxItems.length,
1268
+ filter: { source: args.source, priority: args.priority, unacknowledged_only: args.unacknowledged_only },
1269
+ hint: inboxItems.length > 0 ? "Use get_error_details(id) for full context. Use acknowledge_error(id) when addressed." : "Inbox is empty!"
1270
+ },
1271
+ null,
1272
+ 2
1273
+ )
1274
+ }
1275
+ ]
1276
+ };
1005
1277
  }
1006
- continue;
1007
- }
1008
- const lintMatch = trimmedLine.match(patterns.lintError);
1009
- if (lintMatch) {
1010
- line = parseInt(lintMatch[1], 10);
1011
- column = parseInt(lintMatch[2], 10);
1012
- errorType = lintMatch[3] === "error" ? "LintError" : "LintWarning";
1013
- message = lintMatch[4];
1014
- continue;
1015
- }
1016
- if (!file) {
1017
- const genericMatch = trimmedLine.match(patterns.genericFileLine);
1018
- if (genericMatch && !isLibraryFrame(genericMatch[1])) {
1019
- file = genericMatch[1];
1020
- line = parseInt(genericMatch[2], 10);
1021
- column = genericMatch[3] ? parseInt(genericMatch[3], 10) : null;
1278
+ },
1279
+ // get_inbox_stats - Quick summary of inbox status
1280
+ {
1281
+ name: "get_inbox_stats",
1282
+ description: "Get a quick summary of inbox status - counts by source and priority. Check this first to understand what needs attention.",
1283
+ inputSchema: z.object({}),
1284
+ handler: async (_args, taskOps2) => {
1285
+ const stats = await taskOps2.getInboxStats();
1286
+ return {
1287
+ content: [
1288
+ {
1289
+ type: "text",
1290
+ text: JSON.stringify(
1291
+ {
1292
+ success: true,
1293
+ ...stats,
1294
+ hint: stats.needs_attention ? "Use get_inbox() to see items that need attention." : "Inbox is under control!"
1295
+ },
1296
+ null,
1297
+ 2
1298
+ )
1299
+ }
1300
+ ]
1301
+ };
1022
1302
  }
1023
1303
  }
1024
- if (errorType !== "Unknown" && !message && trimmedLine && !trimmedLine.startsWith("at ")) {
1025
- message = trimmedLine;
1026
- }
1027
- }
1028
- if (!message) {
1029
- message = lines.find((l) => l.trim())?.trim() || "No error message found";
1030
- }
1031
- const rawExcerpt = lines.slice(0, 10).join("\n");
1032
- return {
1033
- error_type: errorType,
1034
- file,
1035
- line,
1036
- column,
1037
- message,
1038
- stack_summary: stackFrames.slice(0, 5),
1039
- // Limit to 5 frames
1040
- raw_excerpt: rawExcerpt
1041
- };
1042
- }
1043
- function isLibraryFrame(filePath) {
1044
- const libraryPatterns = [
1045
- /node_modules/,
1046
- /site-packages/,
1047
- /dist-packages/,
1048
- /\.pnpm/,
1049
- /\.yarn/,
1050
- /internal\//,
1051
- /^node:/,
1052
- /<anonymous>/,
1053
- /webpack-internal/,
1054
- /react-dom/,
1055
- /react-native/,
1056
- /expo/,
1057
- /metro/,
1058
- /hermes/
1059
1304
  ];
1060
- return libraryPatterns.some((pattern) => pattern.test(filePath));
1061
1305
  }
1062
- function summarizeErrors(logText, maxLines = 100) {
1306
+ function summarizeErrorsLocal(logText, maxLines = 100) {
1063
1307
  const errorTypePatterns = [
1064
1308
  { type: "TypeError", pattern: /TypeError:\s*(.+)/i },
1065
1309
  { type: "SyntaxError", pattern: /SyntaxError:\s*(.+)/i },
@@ -1206,6 +1450,12 @@ function summarizeErrors(logText, maxLines = 100) {
1206
1450
  }
1207
1451
 
1208
1452
  // src/resources/index.ts
1453
+ function isErrorTask(task) {
1454
+ if (task.priority !== "high") return false;
1455
+ const title = task.title?.toLowerCase() || "";
1456
+ const patterns = ["error", "bug", "fix", "crash", "fail", "broken", "issue", "typescript", "ts"];
1457
+ return patterns.some((p) => title.includes(p)) || task.sentry_issue_id;
1458
+ }
1209
1459
  function setupResources(taskOps) {
1210
1460
  return [
1211
1461
  // Active tasks resource
@@ -1303,6 +1553,167 @@ function setupResources(taskOps) {
1303
1553
  ]
1304
1554
  };
1305
1555
  }
1556
+ },
1557
+ // Error Summary resource (lightweight - just counts)
1558
+ {
1559
+ uri: "vibetasks://errors/summary",
1560
+ name: "Error Summary",
1561
+ description: "Current error counts by priority. Check this before asking about specific errors.",
1562
+ mimeType: "application/json",
1563
+ annotations: {
1564
+ audience: ["assistant"],
1565
+ priority: 0.8
1566
+ // High priority for AI awareness
1567
+ },
1568
+ handler: async (taskOps2) => {
1569
+ const allTasks = await taskOps2.getTasks("all");
1570
+ const errorTasks = allTasks.filter(isErrorTask);
1571
+ const unacknowledged = errorTasks.filter((t) => !t.context_notes?.includes("[AI_ACKNOWLEDGED]"));
1572
+ const critical = errorTasks.filter((t) => t.title?.toLowerCase().includes("critical") || t.sentry_level === "fatal");
1573
+ const fromSentry = errorTasks.filter((t) => t.sentry_issue_id);
1574
+ const fromCI = errorTasks.filter((t) => t.tags?.some((tag) => tag.name === "ci"));
1575
+ return {
1576
+ contents: [
1577
+ {
1578
+ uri: "vibetasks://errors/summary",
1579
+ mimeType: "application/json",
1580
+ text: JSON.stringify(
1581
+ {
1582
+ total_errors: errorTasks.length,
1583
+ unacknowledged: unacknowledged.length,
1584
+ critical: critical.length,
1585
+ by_source: {
1586
+ sentry: fromSentry.length,
1587
+ ci: fromCI.length,
1588
+ manual: errorTasks.length - fromSentry.length - fromCI.length
1589
+ },
1590
+ needs_attention: critical.length > 0 || unacknowledged.length > 3,
1591
+ hint: unacknowledged.length > 0 ? "Use get_errors() tool to see details and acknowledge_error() when addressed." : "No unacknowledged errors."
1592
+ },
1593
+ null,
1594
+ 2
1595
+ )
1596
+ }
1597
+ ]
1598
+ };
1599
+ }
1600
+ },
1601
+ // Error List resource (more detail, still concise)
1602
+ {
1603
+ uri: "vibetasks://errors/list",
1604
+ name: "Error List",
1605
+ description: "List of current errors with basic details. Use get_error_details tool for full context.",
1606
+ mimeType: "application/json",
1607
+ annotations: {
1608
+ audience: ["assistant"],
1609
+ priority: 0.6
1610
+ },
1611
+ handler: async (taskOps2) => {
1612
+ const allTasks = await taskOps2.getTasks("all");
1613
+ const errorTasks = allTasks.filter(isErrorTask);
1614
+ const sorted = errorTasks.sort((a, b) => {
1615
+ const aAck = a.context_notes?.includes("[AI_ACKNOWLEDGED]") ? 1 : 0;
1616
+ const bAck = b.context_notes?.includes("[AI_ACKNOWLEDGED]") ? 1 : 0;
1617
+ if (aAck !== bAck) return aAck - bAck;
1618
+ return new Date(b.created_at || 0).getTime() - new Date(a.created_at || 0).getTime();
1619
+ });
1620
+ return {
1621
+ contents: [
1622
+ {
1623
+ uri: "vibetasks://errors/list",
1624
+ mimeType: "application/json",
1625
+ text: JSON.stringify(
1626
+ {
1627
+ errors: sorted.slice(0, 10).map((t) => ({
1628
+ id: t.id,
1629
+ title: t.title,
1630
+ project: t.project_tag,
1631
+ status: t.status,
1632
+ acknowledged: t.context_notes?.includes("[AI_ACKNOWLEDGED]") || false,
1633
+ source: t.sentry_issue_id ? "sentry" : t.tags?.some((tag) => tag.name === "ci") ? "ci" : "manual",
1634
+ created: t.created_at,
1635
+ sentry_count: t.sentry_error_count
1636
+ })),
1637
+ total: errorTasks.length,
1638
+ showing: Math.min(10, errorTasks.length)
1639
+ },
1640
+ null,
1641
+ 2
1642
+ )
1643
+ }
1644
+ ]
1645
+ };
1646
+ }
1647
+ },
1648
+ // Inbox Summary resource - unified view of all external sources
1649
+ {
1650
+ uri: "vibetasks://inbox/summary",
1651
+ name: "Inbox Summary",
1652
+ description: "Summary of inbox items from all sources (Sentry, GitHub, Email, Slack, CI). Check this to see what needs attention.",
1653
+ mimeType: "application/json",
1654
+ annotations: {
1655
+ audience: ["assistant"],
1656
+ priority: 0.9
1657
+ // High priority - AI should check inbox often
1658
+ },
1659
+ handler: async (taskOps2) => {
1660
+ const stats = await taskOps2.getInboxStats();
1661
+ return {
1662
+ contents: [
1663
+ {
1664
+ uri: "vibetasks://inbox/summary",
1665
+ mimeType: "application/json",
1666
+ text: JSON.stringify(
1667
+ {
1668
+ ...stats,
1669
+ hint: stats.needs_attention ? "Use get_inbox() tool to see items that need attention." : "Inbox is under control!"
1670
+ },
1671
+ null,
1672
+ 2
1673
+ )
1674
+ }
1675
+ ]
1676
+ };
1677
+ }
1678
+ },
1679
+ // Inbox List resource - detailed inbox items
1680
+ {
1681
+ uri: "vibetasks://inbox/list",
1682
+ name: "Inbox Items",
1683
+ description: "List of inbox items from all external sources. Shows up to 15 items sorted by priority and recency.",
1684
+ mimeType: "application/json",
1685
+ annotations: {
1686
+ audience: ["assistant"],
1687
+ priority: 0.7
1688
+ },
1689
+ handler: async (taskOps2) => {
1690
+ const inboxItems = await taskOps2.getInboxItems({ limit: 15 });
1691
+ return {
1692
+ contents: [
1693
+ {
1694
+ uri: "vibetasks://inbox/list",
1695
+ mimeType: "application/json",
1696
+ text: JSON.stringify(
1697
+ {
1698
+ inbox_items: inboxItems.map((t) => ({
1699
+ id: t.id,
1700
+ title: t.title,
1701
+ source: t.source_type || (t.sentry_issue_id ? "sentry" : t.github_issue_id ? "github" : t.tags?.some((tag) => tag.name === "ci") ? "ci" : "manual"),
1702
+ priority: t.priority,
1703
+ status: t.status,
1704
+ project: t.project_tag,
1705
+ created_at: t.created_at,
1706
+ acknowledged: t.context_notes?.includes("[AI_ACKNOWLEDGED]") || false
1707
+ })),
1708
+ total: inboxItems.length
1709
+ },
1710
+ null,
1711
+ 2
1712
+ )
1713
+ }
1714
+ ]
1715
+ };
1716
+ }
1306
1717
  }
1307
1718
  ];
1308
1719
  }
@@ -1321,7 +1732,7 @@ if (hookType === "SessionStart") {
1321
1732
  async function main() {
1322
1733
  try {
1323
1734
  const authManager = new AuthManager();
1324
- const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || await authManager.getConfig("supabase_url") || "https://cbkkztbcoitrfcleghfd.supabase.co";
1735
+ const supabaseUrl = process.env.TASKFLOW_SUPABASE_URL || await authManager.getConfig("supabase_url") || "https://ihmayqzxqyednchbezya.supabase.co";
1325
1736
  const supabaseKey = process.env.TASKFLOW_SUPABASE_KEY || await authManager.getConfig("supabase_key") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImNia2t6dGJjb2l0cmZjbGVnaGZkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc3NTc0MjgsImV4cCI6MjA4MzMzMzQyOH0.G7ILx-nntP0NbxO1gKt5yASb7nt7OmpJ8qtykeGYbQA";
1326
1737
  const accessToken = await authManager.getAccessToken();
1327
1738
  if (!accessToken) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetasks/mcp-server",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "VibeTasks MCP Server for Claude Code, Cursor, and AI coding tools. Status-based task management: todo → vibing → done.",
5
5
  "author": "Vyas",
6
6
  "license": "MIT",
@@ -45,7 +45,8 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@modelcontextprotocol/sdk": "^0.5.0",
48
- "@vibetasks/core": "^0.5.2",
48
+ "@vibetasks/core": "^0.5.4",
49
+ "@vibetasks/shared": "*",
49
50
  "zod": "^3.22.0"
50
51
  },
51
52
  "devDependencies": {