@projora/mcp-server 1.2.0 → 1.3.1
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 +31 -31
- package/build/index.js +9 -161
- package/build/setup.d.ts +2 -0
- package/build/setup.js +171 -0
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -4,27 +4,18 @@ 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)
|
|
9
7
|
- **start_task** - Start working on a task (sets status to "In Progress", adds comment, returns full context)
|
|
10
8
|
- **complete_task** - Mark a task as done with an optional summary
|
|
11
9
|
- **get_task** - Get a specific task by its key (e.g., "PRJ-123")
|
|
12
|
-
- **
|
|
13
|
-
- **update_task_status** - Update task status
|
|
14
|
-
|
|
15
|
-
### Task Lists
|
|
10
|
+
- **list_projects** - List all projects
|
|
16
11
|
- **list_tasks** - List tasks with optional filters (project, status, priority, assignee)
|
|
17
12
|
- **my_tasks** - Get tasks assigned to you
|
|
18
13
|
- **overdue_tasks** - Get all overdue tasks
|
|
19
14
|
- **search_tasks** - Search for tasks and projects by keyword
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
- **update_task** - Update task fields (title, description, priority, assignee, due date)
|
|
16
|
+
- **update_task_status** - Update task status
|
|
22
17
|
- **add_comment** - Add a comment to a task
|
|
23
18
|
- **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
|
|
28
19
|
- **get_project** - Get project details including linked GitHub repository
|
|
29
20
|
- **get_config** - Get available statuses, priorities, and task types
|
|
30
21
|
- **dashboard_stats** - Get dashboard statistics
|
|
@@ -41,18 +32,27 @@ Or install globally:
|
|
|
41
32
|
npm install -g @projora/mcp-server
|
|
42
33
|
```
|
|
43
34
|
|
|
44
|
-
##
|
|
35
|
+
## Quick Setup
|
|
36
|
+
|
|
37
|
+
Run this single command to automatically configure Claude Code or OpenCode:
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
```bash
|
|
40
|
+
npx @projora/mcp-server setup
|
|
41
|
+
```
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
The setup wizard will:
|
|
44
|
+
1. Prompt for your API token (get it from [Projora Settings](https://projora.app/settings/integrations))
|
|
45
|
+
2. Automatically configure your AI coding assistant
|
|
46
|
+
3. No manual editing of config files required!
|
|
50
47
|
|
|
51
|
-
|
|
48
|
+
### Manual Configuration
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
If you prefer to manually configure, see below:
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
<details>
|
|
53
|
+
<summary>Claude Code Manual Setup</summary>
|
|
54
|
+
|
|
55
|
+
Add to your `~/.claude.json`:
|
|
56
56
|
|
|
57
57
|
```json
|
|
58
58
|
{
|
|
@@ -65,10 +65,12 @@ Add to your Claude Code configuration (`~/.claude.json`):
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
```
|
|
68
|
+
</details>
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
<details>
|
|
71
|
+
<summary>OpenCode Manual Setup</summary>
|
|
70
72
|
|
|
71
|
-
Add to your
|
|
73
|
+
Add to your `opencode.json`:
|
|
72
74
|
|
|
73
75
|
```json
|
|
74
76
|
{
|
|
@@ -84,15 +86,17 @@ Add to your OpenCode configuration (`opencode.json`):
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
```
|
|
89
|
+
</details>
|
|
90
|
+
|
|
91
|
+
### Environment Variables
|
|
87
92
|
|
|
88
|
-
|
|
93
|
+
- `PROJORA_AUTH_TOKEN` - Your API token (required)
|
|
94
|
+
- `PROJORA_GRAPHQL_URL` - GraphQL endpoint (defaults to `https://api.projora.app/graphql`, only set for self-hosted)
|
|
89
95
|
|
|
90
96
|
## Example Prompts
|
|
91
97
|
|
|
92
98
|
Once configured, use prompts like:
|
|
93
99
|
|
|
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"
|
|
96
100
|
- "Start working on task PRJ-123"
|
|
97
101
|
- "Show me my tasks"
|
|
98
102
|
- "What are the overdue tasks?"
|
|
@@ -103,18 +107,14 @@ Once configured, use prompts like:
|
|
|
103
107
|
|
|
104
108
|
## Workflow Example
|
|
105
109
|
|
|
106
|
-
1. **
|
|
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"
|
|
110
|
+
1. **Start a task**: "Use projora to start working on task PRJ-123"
|
|
111
111
|
- Status automatically changes to "In Progress"
|
|
112
112
|
- A comment is added noting work has started
|
|
113
113
|
- You get the full task context including description, GitHub repo, and suggested branch name
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
2. **Work on the task**: Make your changes, commit code, etc.
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
3. **Complete the task**: "Use projora to complete task PRJ-123 with summary: Implemented the new feature with tests"
|
|
118
118
|
- Status changes to "Done"
|
|
119
119
|
- A completion comment is added with your summary
|
|
120
120
|
|
package/build/index.js
CHANGED
|
@@ -1066,167 +1066,7 @@ server.registerTool("update_task", {
|
|
|
1066
1066
|
content: [{ type: "text", text: `Updated ${result.updateTask.taskKey}: ${updatedFields}` }],
|
|
1067
1067
|
};
|
|
1068
1068
|
});
|
|
1069
|
-
// 14.
|
|
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
|
|
1069
|
+
// 14. Get project with GitHub info
|
|
1230
1070
|
server.registerTool("get_project", {
|
|
1231
1071
|
description: "Get project details including linked GitHub repository",
|
|
1232
1072
|
inputSchema: {
|
|
@@ -1281,6 +1121,14 @@ server.registerTool("get_project", {
|
|
|
1281
1121
|
});
|
|
1282
1122
|
// Main function to run the server
|
|
1283
1123
|
async function main() {
|
|
1124
|
+
// Check if "setup" command was passed
|
|
1125
|
+
const args = process.argv.slice(2);
|
|
1126
|
+
if (args.includes('setup') || args.includes('--setup')) {
|
|
1127
|
+
// Dynamically import and run the setup script
|
|
1128
|
+
const setupModule = await import('./setup.js');
|
|
1129
|
+
return; // Setup script handles its own execution
|
|
1130
|
+
}
|
|
1131
|
+
// Otherwise, start the MCP server
|
|
1284
1132
|
const transport = new StdioServerTransport();
|
|
1285
1133
|
await server.connect(transport);
|
|
1286
1134
|
console.error("Projora MCP Server running on stdio");
|
package/build/setup.d.ts
ADDED
package/build/setup.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import * as readline from 'readline';
|
|
6
|
+
const CLAUDE_CONFIG_PATH = path.join(os.homedir(), '.claude.json');
|
|
7
|
+
const OPENCODE_CONFIG_PATH = path.join(os.homedir(), 'opencode.json');
|
|
8
|
+
// Colors for terminal output
|
|
9
|
+
const colors = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bold: '\x1b[1m',
|
|
12
|
+
green: '\x1b[32m',
|
|
13
|
+
blue: '\x1b[34m',
|
|
14
|
+
yellow: '\x1b[33m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
};
|
|
17
|
+
function log(message, color = colors.reset) {
|
|
18
|
+
console.log(`${color}${message}${colors.reset}`);
|
|
19
|
+
}
|
|
20
|
+
function promptUser(question) {
|
|
21
|
+
const rl = readline.createInterface({
|
|
22
|
+
input: process.stdin,
|
|
23
|
+
output: process.stdout,
|
|
24
|
+
});
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(question, (answer) => {
|
|
27
|
+
rl.close();
|
|
28
|
+
resolve(answer.trim());
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function setupClaude(authToken) {
|
|
33
|
+
log('\n📝 Setting up Claude Code configuration...', colors.blue);
|
|
34
|
+
const config = {
|
|
35
|
+
projora: {
|
|
36
|
+
command: 'npx',
|
|
37
|
+
args: ['-y', '@projora/mcp-server'],
|
|
38
|
+
env: {
|
|
39
|
+
PROJORA_AUTH_TOKEN: authToken,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
let existingConfig = {};
|
|
44
|
+
// Read existing config if it exists
|
|
45
|
+
if (fs.existsSync(CLAUDE_CONFIG_PATH)) {
|
|
46
|
+
try {
|
|
47
|
+
const fileContent = fs.readFileSync(CLAUDE_CONFIG_PATH, 'utf-8');
|
|
48
|
+
existingConfig = JSON.parse(fileContent);
|
|
49
|
+
log(' ✓ Found existing Claude Code configuration', colors.green);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
log(' ⚠ Warning: Could not parse existing config, will create new one', colors.yellow);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Merge the configs
|
|
56
|
+
const updatedConfig = {
|
|
57
|
+
...existingConfig,
|
|
58
|
+
...config,
|
|
59
|
+
};
|
|
60
|
+
// Write the updated config
|
|
61
|
+
try {
|
|
62
|
+
fs.writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(updatedConfig, null, 2), 'utf-8');
|
|
63
|
+
log(` ✓ Successfully updated ${CLAUDE_CONFIG_PATH}`, colors.green);
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
log(` ✗ Failed to write config: ${error}`, colors.red);
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function setupOpenCode(authToken) {
|
|
72
|
+
log('\n📝 Setting up OpenCode configuration...', colors.blue);
|
|
73
|
+
const mcpConfig = {
|
|
74
|
+
projora: {
|
|
75
|
+
type: 'local',
|
|
76
|
+
command: ['npx', '-y', '@projora/mcp-server'],
|
|
77
|
+
enabled: true,
|
|
78
|
+
environment: {
|
|
79
|
+
PROJORA_AUTH_TOKEN: authToken,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
let existingConfig = {};
|
|
84
|
+
// Read existing config if it exists
|
|
85
|
+
if (fs.existsSync(OPENCODE_CONFIG_PATH)) {
|
|
86
|
+
try {
|
|
87
|
+
const fileContent = fs.readFileSync(OPENCODE_CONFIG_PATH, 'utf-8');
|
|
88
|
+
existingConfig = JSON.parse(fileContent);
|
|
89
|
+
log(' ✓ Found existing OpenCode configuration', colors.green);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
log(' ⚠ Warning: Could not parse existing config, will create new one', colors.yellow);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Ensure mcp key exists
|
|
96
|
+
if (!existingConfig.mcp) {
|
|
97
|
+
existingConfig.mcp = {};
|
|
98
|
+
}
|
|
99
|
+
// Add/update Projora MCP config
|
|
100
|
+
existingConfig.mcp.projora = mcpConfig.projora;
|
|
101
|
+
// Write the updated config
|
|
102
|
+
try {
|
|
103
|
+
fs.writeFileSync(OPENCODE_CONFIG_PATH, JSON.stringify(existingConfig, null, 2), 'utf-8');
|
|
104
|
+
log(` ✓ Successfully updated ${OPENCODE_CONFIG_PATH}`, colors.green);
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
log(` ✗ Failed to write config: ${error}`, colors.red);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function main() {
|
|
113
|
+
log('\n╔═══════════════════════════════════════════╗', colors.bold);
|
|
114
|
+
log('║ Projora MCP Server Setup ║', colors.bold);
|
|
115
|
+
log('╚═══════════════════════════════════════════╝', colors.bold);
|
|
116
|
+
log('\nThis wizard will configure Projora integration for your AI coding assistant.', colors.blue);
|
|
117
|
+
log('You can get your API token from: https://projora.app/settings/integrations\n');
|
|
118
|
+
// Prompt for auth token
|
|
119
|
+
const authToken = await promptUser('Enter your Projora API token: ');
|
|
120
|
+
if (!authToken) {
|
|
121
|
+
log('\n✗ Error: API token is required', colors.red);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
// Ask which assistant to configure
|
|
125
|
+
log('\n' + colors.bold + 'Which AI coding assistant do you want to configure?' + colors.reset);
|
|
126
|
+
log(' 1. Claude Code');
|
|
127
|
+
log(' 2. OpenCode');
|
|
128
|
+
log(' 3. Both');
|
|
129
|
+
const choice = await promptUser('\nEnter your choice (1, 2, or 3): ');
|
|
130
|
+
let claudeSuccess = false;
|
|
131
|
+
let opencodeSuccess = false;
|
|
132
|
+
switch (choice) {
|
|
133
|
+
case '1':
|
|
134
|
+
claudeSuccess = await setupClaude(authToken);
|
|
135
|
+
break;
|
|
136
|
+
case '2':
|
|
137
|
+
opencodeSuccess = await setupOpenCode(authToken);
|
|
138
|
+
break;
|
|
139
|
+
case '3':
|
|
140
|
+
claudeSuccess = await setupClaude(authToken);
|
|
141
|
+
opencodeSuccess = await setupOpenCode(authToken);
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
log('\n✗ Invalid choice', colors.red);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
// Summary
|
|
148
|
+
log('\n' + colors.bold + '═'.repeat(45) + colors.reset);
|
|
149
|
+
log(colors.bold + 'Setup Complete!' + colors.reset, colors.green);
|
|
150
|
+
log('═'.repeat(45) + '\n', colors.bold);
|
|
151
|
+
if (claudeSuccess) {
|
|
152
|
+
log('✓ Claude Code is configured', colors.green);
|
|
153
|
+
log(' Restart Claude Code to start using Projora tools\n');
|
|
154
|
+
}
|
|
155
|
+
if (opencodeSuccess) {
|
|
156
|
+
log('✓ OpenCode is configured', colors.green);
|
|
157
|
+
log(' Restart OpenCode to start using Projora tools\n');
|
|
158
|
+
}
|
|
159
|
+
log('Available commands:', colors.blue);
|
|
160
|
+
log(' • start_task - Start working on a task');
|
|
161
|
+
log(' • complete_task - Mark task as done');
|
|
162
|
+
log(' • my_tasks - List your tasks');
|
|
163
|
+
log(' • add_comment - Add a comment to a task');
|
|
164
|
+
log(' • log_time - Log time worked');
|
|
165
|
+
log(' • search_tasks - Search tasks\n');
|
|
166
|
+
log('Documentation: https://docs.projora.app/integrations/mcp\n');
|
|
167
|
+
}
|
|
168
|
+
main().catch((error) => {
|
|
169
|
+
log(`\n✗ Setup failed: ${error.message}`, colors.red);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
});
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@projora/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
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": {
|
|
7
|
-
"
|
|
7
|
+
"mcp-server": "./build/index.js",
|
|
8
|
+
"projora-mcp": "./build/index.js",
|
|
9
|
+
"projora-mcp-setup": "./build/setup.js"
|
|
8
10
|
},
|
|
9
11
|
"main": "./build/index.js",
|
|
10
12
|
"scripts": {
|
|
11
|
-
"build": "tsc && chmod 755 build/index.js",
|
|
13
|
+
"build": "tsc && chmod 755 build/index.js build/setup.js",
|
|
12
14
|
"dev": "tsc --watch",
|
|
13
15
|
"start": "node build/index.js",
|
|
16
|
+
"setup": "node build/setup.js",
|
|
14
17
|
"prepublishOnly": "npm run build"
|
|
15
18
|
},
|
|
16
19
|
"files": [
|