@projora/mcp-server 1.3.3 → 1.5.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/README.md +20 -9
- package/build/index.js +149 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,21 +4,30 @@ 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 with optional fields (priority, assignee, due date, etc.)
|
|
7
9
|
- **start_task** - Start working on a task (sets status to "In Progress", adds comment, returns full context)
|
|
8
|
-
- **complete_task** - Mark a task as done with
|
|
9
|
-
- **get_task** - Get
|
|
10
|
-
- **
|
|
10
|
+
- **complete_task** - Mark a task as done with optional summary and automatic time logging
|
|
11
|
+
- **get_task** - Get detailed task information including comments, sub-tasks, and dependencies
|
|
12
|
+
- **update_task** - Update task fields (title, description, priority, assignee, due date, estimate)
|
|
13
|
+
- **update_task_status** - Update task status
|
|
14
|
+
- **link_branch** - Link a git branch or pull request to a task
|
|
15
|
+
|
|
16
|
+
### Time Tracking
|
|
17
|
+
- **log_time** - Log time worked on a task with optional description
|
|
18
|
+
- **add_comment** - Add a comment to a task
|
|
19
|
+
|
|
20
|
+
### Queries & Search
|
|
21
|
+
- **list_projects** - List all projects with task counts
|
|
11
22
|
- **list_tasks** - List tasks with optional filters (project, status, priority, assignee)
|
|
12
23
|
- **my_tasks** - Get tasks assigned to you
|
|
13
24
|
- **overdue_tasks** - Get all overdue tasks
|
|
14
25
|
- **search_tasks** - Search for tasks and projects by keyword
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- **add_comment** - Add a comment to a task
|
|
18
|
-
- **log_time** - Log time worked on a task
|
|
26
|
+
|
|
27
|
+
### Configuration & Stats
|
|
19
28
|
- **get_project** - Get project details including linked GitHub repository
|
|
20
29
|
- **get_config** - Get available statuses, priorities, and task types
|
|
21
|
-
- **dashboard_stats** - Get dashboard statistics
|
|
30
|
+
- **dashboard_stats** - Get dashboard statistics overview
|
|
22
31
|
|
|
23
32
|
## Installation
|
|
24
33
|
|
|
@@ -40,7 +49,7 @@ Run this single command (replace `YOUR_TOKEN` with your API token):
|
|
|
40
49
|
|
|
41
50
|
```bash
|
|
42
51
|
claude mcp add projora \
|
|
43
|
-
--env PROJORA_AUTH_TOKEN=YOUR_TOKEN \
|
|
52
|
+
--env PROJORA_AUTH_TOKEN="YOUR_TOKEN" \
|
|
44
53
|
-- npx -y projora-mcp-server
|
|
45
54
|
```
|
|
46
55
|
|
|
@@ -108,6 +117,7 @@ Add to your `opencode.json`:
|
|
|
108
117
|
|
|
109
118
|
Once configured, use prompts like:
|
|
110
119
|
|
|
120
|
+
- "Create a task in project PROJ: Fix authentication bug"
|
|
111
121
|
- "Start working on task PRJ-123"
|
|
112
122
|
- "Show me my tasks"
|
|
113
123
|
- "What are the overdue tasks?"
|
|
@@ -115,6 +125,7 @@ Once configured, use prompts like:
|
|
|
115
125
|
- "Mark task PRJ-123 as done with summary: Fixed the login bug"
|
|
116
126
|
- "Add a comment to PRJ-123: Need to review the test coverage"
|
|
117
127
|
- "Log 2 hours on task PRJ-123"
|
|
128
|
+
- "Link branch feature/auth-fix to task PRJ-123"
|
|
118
129
|
|
|
119
130
|
## Workflow Example
|
|
120
131
|
|
package/build/index.js
CHANGED
|
@@ -1119,6 +1119,155 @@ server.registerTool("get_project", {
|
|
|
1119
1119
|
content: [{ type: "text", text: output }],
|
|
1120
1120
|
};
|
|
1121
1121
|
});
|
|
1122
|
+
// 15. Create a new task
|
|
1123
|
+
server.registerTool("create_task", {
|
|
1124
|
+
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.",
|
|
1125
|
+
inputSchema: {
|
|
1126
|
+
title: z.string().describe("The task title (required)"),
|
|
1127
|
+
projectKey: z.string().optional().describe("The project key (e.g., 'PRJ'). Either projectKey or taskListId is required."),
|
|
1128
|
+
taskListId: z.string().optional().describe("The task list ID. If provided, projectKey and taskListName are ignored."),
|
|
1129
|
+
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."),
|
|
1130
|
+
description: z.string().optional().describe("Task description (supports markdown)"),
|
|
1131
|
+
statusId: z.string().optional().describe("Status ID (use get_config to see available statuses)"),
|
|
1132
|
+
priorityId: z.string().optional().describe("Priority ID (use get_config to see available priorities)"),
|
|
1133
|
+
taskTypeId: z.string().optional().describe("Task type ID (use get_config to see available task types)"),
|
|
1134
|
+
assigneeId: z.string().optional().describe("User ID to assign the task to"),
|
|
1135
|
+
dueDate: z.string().optional().describe("Due date in YYYY-MM-DD format"),
|
|
1136
|
+
startDate: z.string().optional().describe("Start date in YYYY-MM-DD format"),
|
|
1137
|
+
estimatedHours: z.number().optional().describe("Estimated hours to complete the task"),
|
|
1138
|
+
},
|
|
1139
|
+
}, async ({ title, projectKey, taskListId, taskListName, description, statusId, priorityId, taskTypeId, assigneeId, dueDate, startDate, estimatedHours }) => {
|
|
1140
|
+
// If taskListId is not provided, we need to look it up from the project
|
|
1141
|
+
let finalTaskListId = taskListId;
|
|
1142
|
+
if (!finalTaskListId && projectKey) {
|
|
1143
|
+
// Get the project's task lists
|
|
1144
|
+
const projectQuery = `
|
|
1145
|
+
query GetProject($projectKey: String!) {
|
|
1146
|
+
projects {
|
|
1147
|
+
id
|
|
1148
|
+
key
|
|
1149
|
+
name
|
|
1150
|
+
taskLists {
|
|
1151
|
+
id
|
|
1152
|
+
name
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
`;
|
|
1157
|
+
const projectData = await graphqlQuery(projectQuery);
|
|
1158
|
+
if (!projectData) {
|
|
1159
|
+
return {
|
|
1160
|
+
content: [{ type: "text", text: "Failed to fetch project. Check your authentication token." }],
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
const project = projectData.projects.find(p => p.key.toLowerCase() === projectKey.toLowerCase());
|
|
1164
|
+
if (!project) {
|
|
1165
|
+
return {
|
|
1166
|
+
content: [{ type: "text", text: `Project with key '${projectKey}' not found.` }],
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
if (project.taskLists.length === 0) {
|
|
1170
|
+
return {
|
|
1171
|
+
content: [{ type: "text", text: `Project '${projectKey}' has no task lists.` }],
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
// Find the task list by name, or use the first one
|
|
1175
|
+
if (taskListName) {
|
|
1176
|
+
const taskList = project.taskLists.find(tl => tl.name.toLowerCase() === taskListName.toLowerCase());
|
|
1177
|
+
if (!taskList) {
|
|
1178
|
+
const availableLists = project.taskLists.map(tl => tl.name).join(', ');
|
|
1179
|
+
return {
|
|
1180
|
+
content: [{ type: "text", text: `Task list '${taskListName}' not found in project '${projectKey}'. Available lists: ${availableLists}` }],
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
finalTaskListId = taskList.id;
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
finalTaskListId = project.taskLists[0].id;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (!finalTaskListId) {
|
|
1190
|
+
return {
|
|
1191
|
+
content: [{ type: "text", text: "Either taskListId or projectKey must be provided." }],
|
|
1192
|
+
};
|
|
1193
|
+
}
|
|
1194
|
+
// Create the task
|
|
1195
|
+
const input = {
|
|
1196
|
+
taskListId: finalTaskListId,
|
|
1197
|
+
title,
|
|
1198
|
+
};
|
|
1199
|
+
if (description !== undefined)
|
|
1200
|
+
input.description = description;
|
|
1201
|
+
if (statusId !== undefined)
|
|
1202
|
+
input.statusId = statusId;
|
|
1203
|
+
if (priorityId !== undefined)
|
|
1204
|
+
input.priorityId = priorityId;
|
|
1205
|
+
if (taskTypeId !== undefined)
|
|
1206
|
+
input.taskTypeId = taskTypeId;
|
|
1207
|
+
if (assigneeId !== undefined)
|
|
1208
|
+
input.assigneeId = assigneeId;
|
|
1209
|
+
if (dueDate !== undefined)
|
|
1210
|
+
input.dueDate = dueDate;
|
|
1211
|
+
if (startDate !== undefined)
|
|
1212
|
+
input.startDate = startDate;
|
|
1213
|
+
if (estimatedHours !== undefined)
|
|
1214
|
+
input.estimatedHours = estimatedHours;
|
|
1215
|
+
const mutation = `
|
|
1216
|
+
mutation CreateTask($input: CreateTaskInput!) {
|
|
1217
|
+
createTask(input: $input) {
|
|
1218
|
+
id
|
|
1219
|
+
taskKey
|
|
1220
|
+
title
|
|
1221
|
+
description
|
|
1222
|
+
dueDate
|
|
1223
|
+
startDate
|
|
1224
|
+
estimatedHours
|
|
1225
|
+
createdAt
|
|
1226
|
+
status { id name }
|
|
1227
|
+
priority { id name }
|
|
1228
|
+
taskType { id name }
|
|
1229
|
+
assignee { id name }
|
|
1230
|
+
project { id name key }
|
|
1231
|
+
taskList { id name }
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
`;
|
|
1235
|
+
const result = await graphqlQuery(mutation, { input });
|
|
1236
|
+
if (!result) {
|
|
1237
|
+
return {
|
|
1238
|
+
content: [{ type: "text", text: "Failed to create task. Check your permissions and input data." }],
|
|
1239
|
+
};
|
|
1240
|
+
}
|
|
1241
|
+
const task = result.createTask;
|
|
1242
|
+
let output = `# Task Created: ${task.taskKey}\n\n`;
|
|
1243
|
+
output += `**${task.title}**\n\n`;
|
|
1244
|
+
output += `- **Project**: ${task.project.name} (${task.project.key})\n`;
|
|
1245
|
+
output += `- **List**: ${task.taskList.name}\n`;
|
|
1246
|
+
output += `- **Status**: ${task.status?.name || 'No status'}\n`;
|
|
1247
|
+
if (task.priority) {
|
|
1248
|
+
output += `- **Priority**: ${task.priority.name}\n`;
|
|
1249
|
+
}
|
|
1250
|
+
if (task.taskType) {
|
|
1251
|
+
output += `- **Type**: ${task.taskType.name}\n`;
|
|
1252
|
+
}
|
|
1253
|
+
if (task.assignee) {
|
|
1254
|
+
output += `- **Assignee**: ${task.assignee.name}\n`;
|
|
1255
|
+
}
|
|
1256
|
+
if (task.dueDate) {
|
|
1257
|
+
output += `- **Due Date**: ${task.dueDate}\n`;
|
|
1258
|
+
}
|
|
1259
|
+
if (task.estimatedHours) {
|
|
1260
|
+
output += `- **Estimate**: ${task.estimatedHours} hours\n`;
|
|
1261
|
+
}
|
|
1262
|
+
if (task.description) {
|
|
1263
|
+
output += `\n## Description\n${task.description}\n`;
|
|
1264
|
+
}
|
|
1265
|
+
output += `\n---\nCreated at: ${task.createdAt}\n\n`;
|
|
1266
|
+
output += `You can now use \`start_task\` with taskKey "${task.taskKey}" to begin working on it.`;
|
|
1267
|
+
return {
|
|
1268
|
+
content: [{ type: "text", text: output }],
|
|
1269
|
+
};
|
|
1270
|
+
});
|
|
1122
1271
|
// Main function to run the server
|
|
1123
1272
|
async function main() {
|
|
1124
1273
|
// Check if "setup" command was passed
|
package/package.json
CHANGED