@projora/mcp-server 1.0.0 → 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 (3) hide show
  1. package/README.md +21 -6
  2. package/build/index.js +348 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,18 +4,27 @@ A Model Context Protocol (MCP) server for [Projora](https://projora.app) task ma
4
4
 
5
5
  ## Features
6
6
 
7
+ ### Task Management
8
+ - **create_task** - Create a new task in a project (specify project key, title, and optional details)
7
9
  - **start_task** - Start working on a task (sets status to "In Progress", adds comment, returns full context)
8
10
  - **complete_task** - Mark a task as done with an optional summary
9
11
  - **get_task** - Get a specific task by its key (e.g., "PRJ-123")
10
- - **list_projects** - List all projects
12
+ - **update_task** - Update task fields (title, description, priority, assignee, due date)
13
+ - **update_task_status** - Update task status
14
+
15
+ ### Task Lists
11
16
  - **list_tasks** - List tasks with optional filters (project, status, priority, assignee)
12
17
  - **my_tasks** - Get tasks assigned to you
13
18
  - **overdue_tasks** - Get all overdue tasks
14
19
  - **search_tasks** - Search for tasks and projects by keyword
15
- - **update_task** - Update task fields (title, description, priority, assignee, due date)
16
- - **update_task_status** - Update task status
20
+
21
+ ### Collaboration
17
22
  - **add_comment** - Add a comment to a task
18
23
  - **log_time** - Log time worked on a task
24
+ - **link_branch** - Link a git branch or pull request to a task
25
+
26
+ ### Projects & Config
27
+ - **list_projects** - List all projects
19
28
  - **get_project** - Get project details including linked GitHub repository
20
29
  - **get_config** - Get available statuses, priorities, and task types
21
30
  - **dashboard_stats** - Get dashboard statistics
@@ -82,6 +91,8 @@ Add to your OpenCode configuration (`opencode.json`):
82
91
 
83
92
  Once configured, use prompts like:
84
93
 
94
+ - "Create a task in project PRJ: Fix the login button"
95
+ - "Create a task in PRJ with title 'Add unit tests' and assign it to me"
85
96
  - "Start working on task PRJ-123"
86
97
  - "Show me my tasks"
87
98
  - "What are the overdue tasks?"
@@ -92,14 +103,18 @@ Once configured, use prompts like:
92
103
 
93
104
  ## Workflow Example
94
105
 
95
- 1. **Start a task**: "Use projora to start working on task PRJ-123"
106
+ 1. **Create a task**: "Use projora to create a task in project PRJ: Implement user authentication"
107
+ - Task is created in the specified project
108
+ - Returns the task key (e.g., PRJ-123) for reference
109
+
110
+ 2. **Start the task**: "Use projora to start working on task PRJ-123"
96
111
  - Status automatically changes to "In Progress"
97
112
  - A comment is added noting work has started
98
113
  - You get the full task context including description, GitHub repo, and suggested branch name
99
114
 
100
- 2. **Work on the task**: Make your changes, commit code, etc.
115
+ 3. **Work on the task**: Make your changes, commit code, etc.
101
116
 
102
- 3. **Complete the task**: "Use projora to complete task PRJ-123 with summary: Implemented the new feature with tests"
117
+ 4. **Complete the task**: "Use projora to complete task PRJ-123 with summary: Implemented the new feature with tests"
103
118
  - Status changes to "Done"
104
119
  - A completion comment is added with your summary
105
120
 
package/build/index.js CHANGED
@@ -471,13 +471,71 @@ async function addCommentToTask(taskId, body) {
471
471
  const result = await graphqlQuery(mutation, { taskId, body });
472
472
  return !!result?.createComment;
473
473
  }
474
+ // Helper function to update task branch/PR info
475
+ async function updateTaskBranchInfo(taskId, branchName, prUrl, prNumber) {
476
+ const input = {};
477
+ if (branchName !== undefined)
478
+ input.branchName = branchName;
479
+ if (prUrl !== undefined)
480
+ input.prUrl = prUrl;
481
+ if (prNumber !== undefined)
482
+ input.prNumber = prNumber;
483
+ if (Object.keys(input).length === 0)
484
+ return true;
485
+ const mutation = `
486
+ mutation UpdateTaskBranch($id: ID!, $input: UpdateTaskInput!) {
487
+ updateTask(id: $id, input: $input) {
488
+ id
489
+ branchName
490
+ prUrl
491
+ prNumber
492
+ }
493
+ }
494
+ `;
495
+ const result = await graphqlQuery(mutation, { id: taskId, input });
496
+ return !!result?.updateTask;
497
+ }
498
+ // Helper function to log time on a task
499
+ async function logTimeOnTask(taskId, startedAt, endedAt, description) {
500
+ const mutation = `
501
+ mutation LogTime($taskId: ID!, $input: LogTimeInput!) {
502
+ logTime(taskId: $taskId, input: $input) {
503
+ id
504
+ durationMinutes
505
+ formattedDuration
506
+ }
507
+ }
508
+ `;
509
+ const result = await graphqlQuery(mutation, {
510
+ taskId,
511
+ input: {
512
+ description: description || null,
513
+ startedAt,
514
+ endedAt,
515
+ },
516
+ });
517
+ return result?.logTime || null;
518
+ }
519
+ // Helper to format duration in human-readable form
520
+ function formatDuration(minutes) {
521
+ if (minutes < 60) {
522
+ return `${minutes} minute${minutes !== 1 ? 's' : ''}`;
523
+ }
524
+ const hours = Math.floor(minutes / 60);
525
+ const mins = minutes % 60;
526
+ if (mins === 0) {
527
+ return `${hours} hour${hours !== 1 ? 's' : ''}`;
528
+ }
529
+ return `${hours} hour${hours !== 1 ? 's' : ''} ${mins} minute${mins !== 1 ? 's' : ''}`;
530
+ }
474
531
  // 9. Start working on a task - get full context and set status to In Progress
475
532
  server.registerTool("start_task", {
476
- description: "Start working on a task. This will: 1) Set the task status to 'In Progress', 2) Add a comment noting work has started, 3) Return comprehensive task details including description, comments, GitHub repo info, and suggested branch name.",
533
+ description: "Start working on a task. This will: 1) Set the task status to 'In Progress', 2) Set the branch name on the task (if provided), 3) Add a comment noting work has started, 4) Return comprehensive task details including description, comments, GitHub repo info, and suggested branch name.",
477
534
  inputSchema: {
478
535
  taskKey: z.string().describe("The task key (e.g., 'PRJ-123')"),
536
+ branchName: z.string().optional().describe("The git branch name being used for this task. If provided, it will be saved to the task for tracking."),
479
537
  },
480
- }, async ({ taskKey }) => {
538
+ }, async ({ taskKey, branchName: providedBranchName }) => {
481
539
  const query = `
482
540
  query StartTask($taskKey: String!) {
483
541
  taskByKey(taskKey: $taskKey) {
@@ -544,9 +602,10 @@ server.registerTool("start_task", {
544
602
  }
545
603
  }
546
604
  }
547
- // Add a comment noting that work has started
548
- const timestamp = new Date().toISOString().split('T')[0];
549
- await addCommentToTask(task.id, `Started working on this task via Claude Code on ${timestamp}`);
605
+ // Record the start time for time tracking
606
+ const startTime = new Date();
607
+ const startTimestamp = startTime.toISOString();
608
+ const dateStr = startTimestamp.split('T')[0];
550
609
  // Generate suggested branch name
551
610
  const slugifiedTitle = task.title
552
611
  .toLowerCase()
@@ -554,10 +613,24 @@ server.registerTool("start_task", {
554
613
  .replace(/^-|-$/g, '')
555
614
  .substring(0, 50);
556
615
  const suggestedBranch = `feature/${task.taskKey.toLowerCase()}-${slugifiedTitle}`;
616
+ // Use provided branch name or suggested one
617
+ const branchToUse = providedBranchName || suggestedBranch;
618
+ // Set the branch name on the task
619
+ let branchSetMessage = "";
620
+ if (providedBranchName) {
621
+ await updateTaskBranchInfo(task.id, providedBranchName);
622
+ branchSetMessage = `\n**Branch set:** \`${providedBranchName}\`\n`;
623
+ }
624
+ // Add a comment noting that work has started
625
+ const branchComment = providedBranchName ? ` on branch \`${providedBranchName}\`` : '';
626
+ await addCommentToTask(task.id, `Started working on this task via AI assistant on ${dateStr}${branchComment}`);
557
627
  let output = `# Task: ${task.taskKey} - ${task.title}\n`;
558
628
  if (statusUpdateMessage) {
559
629
  output += statusUpdateMessage;
560
630
  }
631
+ if (branchSetMessage) {
632
+ output += branchSetMessage;
633
+ }
561
634
  output += `\n`;
562
635
  output += `## Overview\n`;
563
636
  output += `- **Project**: ${project.name} (${project.key})\n`;
@@ -613,19 +686,22 @@ server.registerTool("start_task", {
613
686
  }
614
687
  }
615
688
  output += `\n---\n`;
616
- output += `**Tip:** When you're done, use \`complete_task\` to mark this task as Done.\n`;
689
+ output += `**Started at:** ${startTimestamp}\n`;
690
+ output += `**Tip:** When you're done, use \`complete_task\` with \`startedAt: "${startTimestamp}"\` to automatically log time spent.\n`;
617
691
  return {
618
692
  content: [{ type: "text", text: output }],
619
693
  };
620
694
  });
621
- // 9b. Complete a task - mark as Done and add completion comment
695
+ // 9b. Complete a task - mark as Done, log time, and add completion comment
622
696
  server.registerTool("complete_task", {
623
- description: "Mark a task as complete/done. This will: 1) Set the task status to 'Done', 2) Add a comment with an optional summary of what was accomplished.",
697
+ description: "Mark a task as complete/done. This will: 1) Set the task status to 'Done', 2) Log the time spent (if startedAt is provided), 3) Set the PR URL (if provided), 4) Add a comment with the summary and time logged.",
624
698
  inputSchema: {
625
699
  taskKey: z.string().describe("The task key (e.g., 'PRJ-123')"),
626
- summary: z.string().optional().describe("Optional summary of what was accomplished"),
700
+ summary: z.string().optional().describe("Summary of what was accomplished"),
701
+ startedAt: z.string().optional().describe("ISO timestamp of when work started (from start_task). Used to calculate and log time spent."),
702
+ prUrl: z.string().optional().describe("URL of the pull request created for this task"),
627
703
  },
628
- }, async ({ taskKey, summary }) => {
704
+ }, async ({ taskKey, summary, startedAt, prUrl }) => {
629
705
  // Get the task
630
706
  const getTaskQuery = `
631
707
  query GetTaskId($taskKey: String!) {
@@ -645,6 +721,8 @@ server.registerTool("complete_task", {
645
721
  }
646
722
  const task = taskData.taskByKey;
647
723
  const previousStatus = task.status?.name || 'No status';
724
+ const endTime = new Date();
725
+ const endTimestamp = endTime.toISOString();
648
726
  // Find the "Done" status
649
727
  const doneStatus = await findStatusByName('done');
650
728
  if (!doneStatus) {
@@ -659,9 +737,47 @@ server.registerTool("complete_task", {
659
737
  content: [{ type: "text", text: `Failed to update task status. Check your permissions.` }],
660
738
  };
661
739
  }
740
+ // Log time if startedAt was provided
741
+ let timeLogMessage = "";
742
+ let durationMinutes = 0;
743
+ if (startedAt) {
744
+ try {
745
+ const startDate = new Date(startedAt);
746
+ durationMinutes = Math.round((endTime.getTime() - startDate.getTime()) / 60000);
747
+ if (durationMinutes > 0) {
748
+ // Format timestamps for the API (YYYY-MM-DD HH:MM:SS)
749
+ const formatForApi = (date) => {
750
+ return date.toISOString().replace('T', ' ').substring(0, 19);
751
+ };
752
+ const timeLog = await logTimeOnTask(task.id, formatForApi(startDate), formatForApi(endTime), `AI-assisted work session${summary ? `: ${summary.substring(0, 100)}` : ''}`);
753
+ if (timeLog) {
754
+ timeLogMessage = `Time logged: ${timeLog.formattedDuration}`;
755
+ }
756
+ }
757
+ }
758
+ catch (e) {
759
+ // If parsing fails, just skip time logging
760
+ console.error("Failed to parse startedAt timestamp:", e);
761
+ }
762
+ }
763
+ // Set PR URL if provided
764
+ let prSetMessage = "";
765
+ if (prUrl) {
766
+ // Extract PR number from URL if possible (e.g., https://github.com/owner/repo/pull/123)
767
+ const prMatch = prUrl.match(/\/pull\/(\d+)/);
768
+ const prNumber = prMatch ? parseInt(prMatch[1], 10) : undefined;
769
+ await updateTaskBranchInfo(task.id, undefined, prUrl, prNumber);
770
+ prSetMessage = `PR linked: ${prUrl}\n`;
771
+ }
662
772
  // Add completion comment
663
- const timestamp = new Date().toISOString().split('T')[0];
664
- let commentBody = `Task completed via Claude Code on ${timestamp}`;
773
+ const dateStr = endTimestamp.split('T')[0];
774
+ let commentBody = `Task completed via AI assistant on ${dateStr}`;
775
+ if (durationMinutes > 0) {
776
+ commentBody += `\n\n**Time spent:** ${formatDuration(durationMinutes)}`;
777
+ }
778
+ if (prUrl) {
779
+ commentBody += `\n\n**Pull Request:** ${prUrl}`;
780
+ }
665
781
  if (summary) {
666
782
  commentBody += `\n\n**Summary:**\n${summary}`;
667
783
  }
@@ -669,6 +785,12 @@ server.registerTool("complete_task", {
669
785
  let output = `# Task Completed: ${task.taskKey}\n\n`;
670
786
  output += `**${task.title}**\n\n`;
671
787
  output += `Status updated: ${previousStatus} → ${updated.status.name}\n`;
788
+ if (timeLogMessage) {
789
+ output += `${timeLogMessage}\n`;
790
+ }
791
+ if (prSetMessage) {
792
+ output += `${prSetMessage}`;
793
+ }
672
794
  if (summary) {
673
795
  output += `\nSummary added to task comments.\n`;
674
796
  }
@@ -817,6 +939,59 @@ server.registerTool("log_time", {
817
939
  content: [{ type: "text", text: `Logged ${result.logTime.formattedDuration} on ${taskKey}${description ? `: ${description}` : ''}` }],
818
940
  };
819
941
  });
942
+ // 12b. Link branch or PR to a task
943
+ server.registerTool("link_branch", {
944
+ description: "Link a git branch or pull request to a task. This helps track which branch/PR is associated with the task.",
945
+ inputSchema: {
946
+ taskKey: z.string().describe("The task key (e.g., 'PRJ-123')"),
947
+ branchName: z.string().optional().describe("The git branch name"),
948
+ prUrl: z.string().optional().describe("URL of the pull request"),
949
+ },
950
+ }, async ({ taskKey, branchName, prUrl }) => {
951
+ if (!branchName && !prUrl) {
952
+ return {
953
+ content: [{ type: "text", text: `Please provide either a branch name or PR URL.` }],
954
+ };
955
+ }
956
+ // First get the task ID
957
+ const getTaskQuery = `
958
+ query GetTaskId($taskKey: String!) {
959
+ taskByKey(taskKey: $taskKey) {
960
+ id
961
+ branchName
962
+ prUrl
963
+ }
964
+ }
965
+ `;
966
+ const taskData = await graphqlQuery(getTaskQuery, { taskKey });
967
+ if (!taskData || !taskData.taskByKey) {
968
+ return {
969
+ content: [{ type: "text", text: `Task '${taskKey}' not found.` }],
970
+ };
971
+ }
972
+ // Extract PR number from URL if possible
973
+ let prNumber;
974
+ if (prUrl) {
975
+ const prMatch = prUrl.match(/\/pull\/(\d+)/);
976
+ prNumber = prMatch ? parseInt(prMatch[1], 10) : undefined;
977
+ }
978
+ const success = await updateTaskBranchInfo(taskData.taskByKey.id, branchName, prUrl, prNumber);
979
+ if (!success) {
980
+ return {
981
+ content: [{ type: "text", text: `Failed to update task. Check your permissions.` }],
982
+ };
983
+ }
984
+ let output = `Updated ${taskKey}:\n`;
985
+ if (branchName) {
986
+ output += ` Branch: ${branchName}\n`;
987
+ }
988
+ if (prUrl) {
989
+ output += ` PR: ${prUrl}\n`;
990
+ }
991
+ return {
992
+ content: [{ type: "text", text: output }],
993
+ };
994
+ });
820
995
  // 13. Update task (general)
821
996
  server.registerTool("update_task", {
822
997
  description: "Update task fields (title, description, priority, assignee, due date, estimate)",
@@ -891,7 +1066,167 @@ server.registerTool("update_task", {
891
1066
  content: [{ type: "text", text: `Updated ${result.updateTask.taskKey}: ${updatedFields}` }],
892
1067
  };
893
1068
  });
894
- // 14. Get project with GitHub info
1069
+ // 14. Create a new task
1070
+ server.registerTool("create_task", {
1071
+ description: "Create a new task in a project. You can specify the project by key and optionally the task list name, or provide IDs directly. Returns the created task details.",
1072
+ inputSchema: {
1073
+ projectKey: z.string().optional().describe("The project key (e.g., 'PRJ'). Either projectKey or taskListId is required."),
1074
+ taskListName: z.string().optional().describe("Name of the task list to add the task to (e.g., 'To Do', 'Backlog'). If not provided, uses the first task list."),
1075
+ taskListId: z.string().optional().describe("The task list ID. If provided, projectKey and taskListName are ignored."),
1076
+ title: z.string().describe("The task title (required)"),
1077
+ description: z.string().optional().describe("Task description (supports markdown)"),
1078
+ statusId: z.string().optional().describe("Status ID (use get_config to see available statuses)"),
1079
+ priorityId: z.string().optional().describe("Priority ID (use get_config to see available priorities)"),
1080
+ taskTypeId: z.string().optional().describe("Task type ID (use get_config to see available task types)"),
1081
+ assigneeId: z.string().optional().describe("User ID to assign the task to"),
1082
+ dueDate: z.string().optional().describe("Due date in YYYY-MM-DD format"),
1083
+ startDate: z.string().optional().describe("Start date in YYYY-MM-DD format"),
1084
+ estimatedHours: z.number().optional().describe("Estimated hours to complete the task"),
1085
+ },
1086
+ }, async ({ projectKey, taskListName, taskListId, title, description, statusId, priorityId, taskTypeId, assigneeId, dueDate, startDate, estimatedHours }) => {
1087
+ let resolvedTaskListId = taskListId;
1088
+ let projectName = "";
1089
+ // If taskListId is not provided, we need to look it up from projectKey
1090
+ if (!resolvedTaskListId) {
1091
+ if (!projectKey) {
1092
+ return {
1093
+ content: [{ type: "text", text: "Either projectKey or taskListId is required to create a task." }],
1094
+ };
1095
+ }
1096
+ // Get project and its task lists
1097
+ const projectQuery = `
1098
+ query GetProjectWithTaskLists {
1099
+ projects {
1100
+ id
1101
+ name
1102
+ key
1103
+ taskLists {
1104
+ id
1105
+ name
1106
+ sortOrder
1107
+ }
1108
+ }
1109
+ }
1110
+ `;
1111
+ const projectData = await graphqlQuery(projectQuery);
1112
+ if (!projectData) {
1113
+ return {
1114
+ content: [{ type: "text", text: "Failed to fetch projects. Check your authentication token." }],
1115
+ };
1116
+ }
1117
+ const project = projectData.projects.find(p => p.key.toLowerCase() === projectKey.toLowerCase());
1118
+ if (!project) {
1119
+ const availableProjects = projectData.projects.map(p => p.key).join(", ");
1120
+ return {
1121
+ content: [{ type: "text", text: `Project '${projectKey}' not found. Available projects: ${availableProjects}` }],
1122
+ };
1123
+ }
1124
+ projectName = project.name;
1125
+ if (project.taskLists.length === 0) {
1126
+ return {
1127
+ content: [{ type: "text", text: `Project '${projectKey}' has no task lists. Please create a task list first.` }],
1128
+ };
1129
+ }
1130
+ // Find the task list by name or use the first one
1131
+ if (taskListName) {
1132
+ const taskList = project.taskLists.find(tl => tl.name.toLowerCase() === taskListName.toLowerCase());
1133
+ if (!taskList) {
1134
+ const availableLists = project.taskLists.map(tl => tl.name).join(", ");
1135
+ return {
1136
+ content: [{ type: "text", text: `Task list '${taskListName}' not found in project '${projectKey}'. Available lists: ${availableLists}` }],
1137
+ };
1138
+ }
1139
+ resolvedTaskListId = taskList.id;
1140
+ }
1141
+ else {
1142
+ // Use the first task list (sorted by sortOrder)
1143
+ const sortedLists = [...project.taskLists].sort((a, b) => a.sortOrder - b.sortOrder);
1144
+ resolvedTaskListId = sortedLists[0].id;
1145
+ }
1146
+ }
1147
+ // Build the input object
1148
+ const input = {
1149
+ taskListId: resolvedTaskListId,
1150
+ title,
1151
+ };
1152
+ if (description !== undefined)
1153
+ input.description = description;
1154
+ if (statusId !== undefined)
1155
+ input.statusId = statusId;
1156
+ if (priorityId !== undefined)
1157
+ input.priorityId = priorityId;
1158
+ if (taskTypeId !== undefined)
1159
+ input.taskTypeId = taskTypeId;
1160
+ if (assigneeId !== undefined)
1161
+ input.assigneeId = assigneeId;
1162
+ if (dueDate !== undefined)
1163
+ input.dueDate = dueDate;
1164
+ if (startDate !== undefined)
1165
+ input.startDate = startDate;
1166
+ if (estimatedHours !== undefined)
1167
+ input.estimatedHours = estimatedHours;
1168
+ // Create the task
1169
+ const mutation = `
1170
+ mutation CreateTask($input: CreateTaskInput!) {
1171
+ createTask(input: $input) {
1172
+ id
1173
+ taskKey
1174
+ title
1175
+ description
1176
+ dueDate
1177
+ startDate
1178
+ estimatedHours
1179
+ status { id name }
1180
+ priority { id name }
1181
+ taskType { id name }
1182
+ assignee { id name }
1183
+ project { id name key }
1184
+ taskList { id name }
1185
+ createdAt
1186
+ }
1187
+ }
1188
+ `;
1189
+ const result = await graphqlQuery(mutation, { input });
1190
+ if (!result || !result.createTask) {
1191
+ return {
1192
+ content: [{ type: "text", text: "Failed to create task. Check your permissions and input values." }],
1193
+ };
1194
+ }
1195
+ const task = result.createTask;
1196
+ let output = `# Task Created: ${task.taskKey}\n\n`;
1197
+ output += `**${task.title}**\n\n`;
1198
+ output += `- **Project**: ${task.project.name} (${task.project.key})\n`;
1199
+ output += `- **List**: ${task.taskList.name}\n`;
1200
+ output += `- **Status**: ${task.status?.name || 'Default'}\n`;
1201
+ if (task.priority) {
1202
+ output += `- **Priority**: ${task.priority.name}\n`;
1203
+ }
1204
+ if (task.taskType) {
1205
+ output += `- **Type**: ${task.taskType.name}\n`;
1206
+ }
1207
+ if (task.assignee) {
1208
+ output += `- **Assignee**: ${task.assignee.name}\n`;
1209
+ }
1210
+ if (task.dueDate) {
1211
+ output += `- **Due Date**: ${task.dueDate}\n`;
1212
+ }
1213
+ if (task.startDate) {
1214
+ output += `- **Start Date**: ${task.startDate}\n`;
1215
+ }
1216
+ if (task.estimatedHours) {
1217
+ output += `- **Estimate**: ${task.estimatedHours} hours\n`;
1218
+ }
1219
+ if (task.description) {
1220
+ output += `\n## Description\n${task.description}\n`;
1221
+ }
1222
+ output += `\n---\n`;
1223
+ output += `Created at: ${task.createdAt}\n`;
1224
+ output += `\nYou can now use \`start_task\` with taskKey "${task.taskKey}" to begin working on it.`;
1225
+ return {
1226
+ content: [{ type: "text", text: output }],
1227
+ };
1228
+ });
1229
+ // 15. Get project with GitHub info
895
1230
  server.registerTool("get_project", {
896
1231
  description: "Get project details including linked GitHub repository",
897
1232
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@projora/mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "MCP server for Projora task management - integrate with Claude Code and OpenCode to manage tasks, update statuses, and track work",
5
5
  "type": "module",
6
6
  "bin": {