brioright-mcp 1.3.0 → 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 +7 -0
- package/index.js +262 -83
- package/package.json +3 -2
- package/.env +0 -3
package/README.md
CHANGED
|
@@ -82,9 +82,16 @@ And pass the Authorization Header: `Bearer your_secure_bearer_token`.
|
|
|
82
82
|
| `list_projects` | List projects in a workspace |
|
|
83
83
|
| `list_tasks` | List tasks with optional status/priority filter |
|
|
84
84
|
| `get_task` | Get full task details |
|
|
85
|
+
| `duplicate_task` | Duplicate an existing task |
|
|
85
86
|
| `create_task` | Create a task with title, priority, due date, assignee |
|
|
86
87
|
| `update_task` | Update any fields on a task |
|
|
87
88
|
| `complete_task` | Mark a task as done |
|
|
88
89
|
| `create_project` | Create a new project |
|
|
89
90
|
| `list_members` | List workspace members (for finding assignee IDs) |
|
|
90
91
|
| `get_workspace_summary` | Dashboard stats: task counts by status/priority |
|
|
92
|
+
| `bulk_create_tasks` | Create multiple tasks at once |
|
|
93
|
+
| `add_comment` | Add a comment to a task |
|
|
94
|
+
| `get_task_comments` | Get all comments for a given task |
|
|
95
|
+
| `log_time` | Log time entry for a project/task |
|
|
96
|
+
| `add_task_attachment` | Upload a file attachment to a task using a Base64 encoded string |
|
|
97
|
+
| `get_task_attachments` | List all file attachments for a specific task |
|
package/index.js
CHANGED
|
@@ -41,16 +41,21 @@ const MCP_SECRET = process.env.MCP_SECRET // Optional bearer token for HTTP mode
|
|
|
41
41
|
const USE_HTTP = process.env.MCP_TRANSPORT === 'http'
|
|
42
42
|
|
|
43
43
|
// ── Axios client factory ──────────────────────────────────────────────────────
|
|
44
|
-
async function call(method, path, data, overrideApiKey) {
|
|
44
|
+
async function call(method, path, data, overrideApiKey, customHeaders = {}) {
|
|
45
45
|
const key = overrideApiKey || ENV_API_KEY;
|
|
46
46
|
if (!key) {
|
|
47
47
|
throw new Error('Brioright API error: No API Key provided. Either set BRIORIGHT_API_KEY environment variable or provide apiKey in the tool arguments.');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
const headers = { 'X-API-Key': key, 'Content-Type': 'application/json', ...customHeaders }
|
|
51
|
+
if (headers['Content-Type'] === 'multipart/form-data' || headers['Content-Type'] === null) {
|
|
52
|
+
delete headers['Content-Type']; // Let axios auto-generate the multipart boundary
|
|
53
|
+
}
|
|
54
|
+
|
|
50
55
|
const api = axios.create({
|
|
51
56
|
baseURL: API_URL,
|
|
52
|
-
headers
|
|
53
|
-
timeout:
|
|
57
|
+
headers,
|
|
58
|
+
timeout: 30000,
|
|
54
59
|
})
|
|
55
60
|
|
|
56
61
|
try {
|
|
@@ -71,8 +76,9 @@ function buildServer() {
|
|
|
71
76
|
const server = new McpServer({ name: 'brioright', version: '1.0.0' })
|
|
72
77
|
|
|
73
78
|
// ── list_workspaces ───────────────────────────────────────────────────────
|
|
74
|
-
server.tool('list_workspaces',
|
|
75
|
-
|
|
79
|
+
server.tool('list_workspaces',
|
|
80
|
+
'Returns all Brioright workspaces accessible via the API key. Call this FIRST when the user has not provided a workspaceId, or when they ask "what workspaces do I have?". The response includes each workspace id, slug, and name — use the slug as workspaceId in subsequent tool calls.',
|
|
81
|
+
{ apiKey: z.string().optional().describe('Brioright API key. Only needed if not set in environment.') },
|
|
76
82
|
async ({ apiKey }) => {
|
|
77
83
|
const data = await call('GET', '/workspaces', null, apiKey)
|
|
78
84
|
const workspaces = data.workspaces || data
|
|
@@ -81,10 +87,11 @@ function buildServer() {
|
|
|
81
87
|
)
|
|
82
88
|
|
|
83
89
|
// ── list_projects ─────────────────────────────────────────────────────────
|
|
84
|
-
server.tool('list_projects',
|
|
90
|
+
server.tool('list_projects',
|
|
91
|
+
'Returns all projects inside a workspace. Use this to discover project IDs before calling list_tasks, create_task, or bulk_create_tasks. Call this when the user mentions a project by name but you need its ID. Returns each project id, name, and status.',
|
|
85
92
|
{
|
|
86
|
-
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
87
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
93
|
+
workspaceId: z.string().optional().describe('Workspace slug (e.g. "my-team"). Use list_workspaces first if unknown. Defaults to BRIORIGHT_WORKSPACE_ID env var.'),
|
|
94
|
+
apiKey: z.string().optional().describe('Brioright API key. Only needed if not set in environment.')
|
|
88
95
|
},
|
|
89
96
|
async ({ workspaceId, apiKey }) => {
|
|
90
97
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -96,14 +103,15 @@ function buildServer() {
|
|
|
96
103
|
)
|
|
97
104
|
|
|
98
105
|
// ── list_tasks ────────────────────────────────────────────────────────────
|
|
99
|
-
server.tool('list_tasks',
|
|
106
|
+
server.tool('list_tasks',
|
|
107
|
+
'Fetch tasks from a project with optional filters. Use this when the user asks to see tasks, check what is in progress, find tasks by status/priority, or before updating/completing tasks. Filter by status (todo, in_progress, in_review, done, cancelled) or priority (low, medium, high, urgent). Returns task id, title, status, priority, dueDate, and assignee name.',
|
|
100
108
|
{
|
|
101
|
-
projectId: z.string().describe('Project ID'),
|
|
102
|
-
workspaceId: z.string().optional(),
|
|
103
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
104
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
105
|
-
limit: z.number().optional().default(20),
|
|
106
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
109
|
+
projectId: z.string().describe('Project ID — use list_projects first if you only know the project name.'),
|
|
110
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
111
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional().describe('Filter by task status. Omit to return all statuses.'),
|
|
112
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('Filter by priority level. Omit to return all priorities.'),
|
|
113
|
+
limit: z.number().optional().default(20).describe('Max number of tasks to return. Default 20, max recommended 100.'),
|
|
114
|
+
apiKey: z.string().optional().describe('Brioright API key. Only needed if not set in environment.')
|
|
107
115
|
},
|
|
108
116
|
async ({ projectId, workspaceId, status, priority, limit, apiKey }) => {
|
|
109
117
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -119,8 +127,9 @@ function buildServer() {
|
|
|
119
127
|
)
|
|
120
128
|
|
|
121
129
|
// ── get_task ──────────────────────────────────────────────────────────────
|
|
122
|
-
server.tool('get_task',
|
|
123
|
-
|
|
130
|
+
server.tool('get_task',
|
|
131
|
+
'Fetches complete details for a single task by its ID, including description, status, priority, assignee, due date, tags, subtasks, and dependencies. Use this when you need the full task data before updating it, summarising it, or answering detailed questions about it. Prefer this over list_tasks when you already have the task ID.',
|
|
132
|
+
{ taskId: z.string().describe('The unique task ID (UUID). Use list_tasks to find it if unknown.'), workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'), apiKey: z.string().optional().describe('Brioright API key.') },
|
|
124
133
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
125
134
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
126
135
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -129,18 +138,64 @@ function buildServer() {
|
|
|
129
138
|
}
|
|
130
139
|
)
|
|
131
140
|
|
|
141
|
+
// ── get_task_attachments ──────────────────────────────────────────────────
|
|
142
|
+
server.tool('get_task_attachments',
|
|
143
|
+
'Lists all files attached to a task. Use this when the user asks "what files are on this task?" or before deciding to add a new attachment. Returns file name, URL, size, and uploader.',
|
|
144
|
+
{
|
|
145
|
+
taskId: z.string().describe('ID of the task to list attachments for.'),
|
|
146
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
147
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
148
|
+
},
|
|
149
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
150
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
151
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
152
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}/attachments`, null, apiKey)
|
|
153
|
+
const attachments = data.attachments || data
|
|
154
|
+
return { content: [{ type: 'text', text: JSON.stringify(attachments, null, 2) }] }
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
// ── add_task_attachment ───────────────────────────────────────────────────
|
|
159
|
+
server.tool('add_task_attachment',
|
|
160
|
+
'Uploads a file to a task as an attachment. Use this when the user wants to attach a document, screenshot, or report to a task. The file must be Base64-encoded. Returns the attachment id, file name, and public URL.',
|
|
161
|
+
{
|
|
162
|
+
taskId: z.string().describe('ID of the task to attach the file to.'),
|
|
163
|
+
fileName: z.string().describe('File name with extension, e.g. "report.pdf" or "screenshot.png".'),
|
|
164
|
+
fileContent: z.string().describe('Full Base64-encoded content of the file.'),
|
|
165
|
+
mimeType: z.string().optional().describe('MIME type, e.g. "image/png", "application/pdf". Defaults to application/octet-stream.'),
|
|
166
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
167
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
168
|
+
},
|
|
169
|
+
async ({ taskId, fileName, fileContent, mimeType, workspaceId, apiKey }) => {
|
|
170
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
171
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
172
|
+
|
|
173
|
+
// Convert base64 to Blob
|
|
174
|
+
const buffer = Buffer.from(fileContent, 'base64')
|
|
175
|
+
const blob = new Blob([buffer], { type: mimeType || 'application/octet-stream' })
|
|
176
|
+
|
|
177
|
+
const formData = new FormData()
|
|
178
|
+
formData.append('file', blob, fileName)
|
|
179
|
+
|
|
180
|
+
const data = await call('POST', `/workspaces/${ws}/tasks/${taskId}/attachments`, formData, apiKey, { 'Content-Type': 'multipart/form-data' })
|
|
181
|
+
const attachment = data.attachment || data
|
|
182
|
+
return { content: [{ type: 'text', text: `✅ File attached successfully!\n\n${JSON.stringify({ id: attachment.id, name: attachment.name, url: attachment.url }, null, 2)}` }] }
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
|
|
132
186
|
// ── create_task ───────────────────────────────────────────────────────────
|
|
133
|
-
server.tool('create_task',
|
|
187
|
+
server.tool('create_task',
|
|
188
|
+
'Creates a single new task in a project. Use this when the user asks to add one task. For creating multiple tasks at once, prefer bulk_create_tasks instead. Use list_projects to get projectId and list_members to get assigneeId if needed. Returns the created task id, title, status, priority, and due date.',
|
|
134
189
|
{
|
|
135
|
-
projectId: z.string().describe('Project ID'),
|
|
136
|
-
title: z.string().describe('
|
|
137
|
-
description: z.string().optional(),
|
|
138
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo'),
|
|
139
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium'),
|
|
140
|
-
dueDate: z.string().optional().describe('
|
|
141
|
-
assigneeId: z.string().optional(),
|
|
142
|
-
workspaceId: z.string().optional(),
|
|
143
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
190
|
+
projectId: z.string().describe('Project ID — use list_projects if you only know the project name.'),
|
|
191
|
+
title: z.string().describe('Clear, concise task title describing the work to be done.'),
|
|
192
|
+
description: z.string().optional().describe('Detailed description, acceptance criteria, or context for the task. Supports markdown.'),
|
|
193
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo').describe('Initial task status. Default is todo.'),
|
|
194
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium').describe('Task priority. Use urgent only for blockers or critical issues.'),
|
|
195
|
+
dueDate: z.string().optional().describe('Due date as ISO string, e.g. "2026-04-01". Omit if no deadline.'),
|
|
196
|
+
assigneeId: z.string().optional().describe('User ID of the assignee — use list_members to find IDs. Omit to leave unassigned.'),
|
|
197
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
198
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
144
199
|
},
|
|
145
200
|
async ({ projectId, title, description, status, priority, dueDate, assigneeId, workspaceId, apiKey }) => {
|
|
146
201
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -148,38 +203,63 @@ function buildServer() {
|
|
|
148
203
|
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks`, {
|
|
149
204
|
title, description, status, priority,
|
|
150
205
|
dueDate: dueDate ? new Date(dueDate).toISOString() : undefined,
|
|
151
|
-
assigneeId,
|
|
206
|
+
assigneeId: assigneeId || undefined,
|
|
152
207
|
}, apiKey)
|
|
153
208
|
const task = data.task || data
|
|
154
209
|
return { content: [{ type: 'text', text: `✅ Task created!\n\n${JSON.stringify({ id: task.id, title: task.title, status: task.status, priority: task.priority, dueDate: task.dueDate }, null, 2)}` }] }
|
|
155
210
|
}
|
|
156
211
|
)
|
|
157
212
|
|
|
213
|
+
// ── duplicate_task ────────────────────────────────────────────────────────
|
|
214
|
+
server.tool('duplicate_task',
|
|
215
|
+
'Creates an exact copy of an existing task, including its title, description, priority, and assignee. Use this when the user wants to repeat a task, clone a template task, or create a similar task quickly. The duplicate is placed in the same project. Returns the new task id and title.',
|
|
216
|
+
{
|
|
217
|
+
taskId: z.string().describe('ID of the original task to copy.'),
|
|
218
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
219
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
220
|
+
},
|
|
221
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
222
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
223
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
224
|
+
|
|
225
|
+
// We need to fetch the task first to find its projectId, as the /duplicate endpoint requires project ID via the URL path
|
|
226
|
+
const taskData = await call('GET', `/workspaces/${ws}/tasks/${taskId}`, null, apiKey)
|
|
227
|
+
const originalTask = taskData.task || taskData
|
|
228
|
+
|
|
229
|
+
const data = await call('POST', `/workspaces/${ws}/projects/${originalTask.projectId}/tasks/${taskId}/duplicate`, null, apiKey)
|
|
230
|
+
const clonedTask = data.task || data
|
|
231
|
+
return { content: [{ type: 'text', text: `✅ Task duplicated!\n\n${JSON.stringify({ id: clonedTask.id, title: clonedTask.title, status: clonedTask.status, position: clonedTask.position }, null, 2)}` }] }
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
158
235
|
// ── bulk_create_tasks ─────────────────────────────────────────────────────
|
|
159
|
-
server.tool('bulk_create_tasks',
|
|
236
|
+
server.tool('bulk_create_tasks',
|
|
237
|
+
'Creates multiple tasks in one API call — far more efficient than calling create_task repeatedly. Use this when the user provides a list of tasks, a sprint plan, a feature breakdown, or asks you to set up a project. Each task can have its own title, description, status, priority, due date, and assignee. Use parentTaskId to create subtasks under an existing task. Returns the count of created tasks.',
|
|
160
238
|
{
|
|
161
|
-
projectId: z.string().describe('Project ID'),
|
|
239
|
+
projectId: z.string().describe('Project ID — use list_projects first if unknown.'),
|
|
162
240
|
tasks: z.array(z.object({
|
|
163
|
-
title: z.string().describe('
|
|
164
|
-
description: z.string().optional(),
|
|
165
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo'),
|
|
166
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium'),
|
|
167
|
-
dueDate: z.string().optional().describe('ISO date string e.g. 2026-
|
|
168
|
-
assigneeId: z.string().optional(),
|
|
169
|
-
parentTaskId: z.string().optional(),
|
|
170
|
-
tags: z.array(z.string()).optional()
|
|
171
|
-
})).describe('Array of
|
|
172
|
-
workspaceId: z.string().optional(),
|
|
173
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
241
|
+
title: z.string().describe('Clear task title.'),
|
|
242
|
+
description: z.string().optional().describe('Detailed context or acceptance criteria for the task.'),
|
|
243
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo').describe('Task status. Default: todo.'),
|
|
244
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium').describe('Priority level.'),
|
|
245
|
+
dueDate: z.string().optional().describe('ISO date string e.g. "2026-04-01". Omit if no deadline.'),
|
|
246
|
+
assigneeId: z.string().optional().describe('User ID from list_members. Omit to leave unassigned.'),
|
|
247
|
+
parentTaskId: z.string().optional().describe('ID of a parent task to nest this as a subtask. Omit for top-level tasks.'),
|
|
248
|
+
tags: z.array(z.string()).optional().describe('Label tags for categorisation, e.g. ["backend", "auth"].')
|
|
249
|
+
})).describe('Array of task objects to create. Minimum 1, no hard maximum.'),
|
|
250
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
251
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
174
252
|
},
|
|
175
253
|
async ({ projectId, tasks, workspaceId, apiKey }) => {
|
|
176
254
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
177
255
|
if (!ws) throw new Error('workspaceId is required')
|
|
178
256
|
|
|
179
|
-
// Format dates
|
|
257
|
+
// Format dates and sanitise strings
|
|
180
258
|
const formattedTasks = tasks.map(t => ({
|
|
181
259
|
...t,
|
|
182
|
-
dueDate: t.dueDate ? new Date(t.dueDate).toISOString() : undefined
|
|
260
|
+
dueDate: t.dueDate ? new Date(t.dueDate).toISOString() : undefined,
|
|
261
|
+
assigneeId: t.assigneeId || undefined,
|
|
262
|
+
parentTaskId: t.parentTaskId || undefined
|
|
183
263
|
}))
|
|
184
264
|
|
|
185
265
|
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks/bulk`, {
|
|
@@ -191,22 +271,23 @@ function buildServer() {
|
|
|
191
271
|
)
|
|
192
272
|
|
|
193
273
|
// ── update_task ───────────────────────────────────────────────────────────
|
|
194
|
-
server.tool('update_task',
|
|
274
|
+
server.tool('update_task',
|
|
275
|
+
'Updates one or more fields on an existing task. Use this when the user asks to change a task title, reassign it, change priority or status, update the description, or set/change a due date. Only provide the fields you want to change — omit the rest. To mark a task done, you can either use this tool with status=done or use complete_task. Returns the updated task.',
|
|
195
276
|
{
|
|
196
|
-
taskId: z.string(),
|
|
197
|
-
title: z.string().optional(),
|
|
198
|
-
description: z.string().optional(),
|
|
199
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
200
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
201
|
-
dueDate: z.string().optional(),
|
|
202
|
-
assigneeId: z.string().optional(),
|
|
203
|
-
workspaceId: z.string().optional(),
|
|
204
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
277
|
+
taskId: z.string().describe('ID of the task to update — use list_tasks or get_task to find it.'),
|
|
278
|
+
title: z.string().optional().describe('New task title. Omit to keep current.'),
|
|
279
|
+
description: z.string().optional().describe('New description. Omit to keep current. Supports markdown.'),
|
|
280
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional().describe('New status. Use cancelled for tasks that will not be done.'),
|
|
281
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().describe('New priority level.'),
|
|
282
|
+
dueDate: z.string().optional().describe('New due date as ISO string e.g. "2026-04-15". Omit to keep current.'),
|
|
283
|
+
assigneeId: z.string().optional().describe('User ID of new assignee — use list_members to find. Omit to keep current.'),
|
|
284
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
285
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
205
286
|
},
|
|
206
287
|
async ({ taskId, workspaceId, apiKey, ...fields }) => {
|
|
207
288
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
208
289
|
if (!ws) throw new Error('workspaceId is required')
|
|
209
|
-
const updates = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined))
|
|
290
|
+
const updates = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined && v !== ''))
|
|
210
291
|
if (updates.dueDate) updates.dueDate = new Date(updates.dueDate).toISOString()
|
|
211
292
|
const data = await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, updates, apiKey)
|
|
212
293
|
const task = data.task || data
|
|
@@ -215,8 +296,9 @@ function buildServer() {
|
|
|
215
296
|
)
|
|
216
297
|
|
|
217
298
|
// ── complete_task ─────────────────────────────────────────────────────────
|
|
218
|
-
server.tool('complete_task',
|
|
219
|
-
|
|
299
|
+
server.tool('complete_task',
|
|
300
|
+
'Marks a task as done (status=done). Use this as a shorthand when the user says "complete", "finish", "mark as done", or "close" a task. Equivalent to update_task with status=done but more concise. Returns a confirmation.',
|
|
301
|
+
{ taskId: z.string().describe('ID of the task to complete.'), workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'), apiKey: z.string().optional().describe('Brioright API key.') },
|
|
220
302
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
221
303
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
222
304
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -226,13 +308,14 @@ function buildServer() {
|
|
|
226
308
|
)
|
|
227
309
|
|
|
228
310
|
// ── create_project ────────────────────────────────────────────────────────
|
|
229
|
-
server.tool('create_project',
|
|
311
|
+
server.tool('create_project',
|
|
312
|
+
'Creates a new project (board) inside a workspace. Use this when the user asks to set up a new project, initiative, or area of work. After creating the project, you can immediately use bulk_create_tasks to populate it with tasks. Returns the new project id and name.',
|
|
230
313
|
{
|
|
231
|
-
name: z.string(),
|
|
232
|
-
description: z.string().optional(),
|
|
233
|
-
color: z.string().optional().default('#6366f1'),
|
|
234
|
-
workspaceId: z.string().optional(),
|
|
235
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
314
|
+
name: z.string().describe('Project name, e.g. "Q2 Marketing Campaign" or "Mobile App Redesign".'),
|
|
315
|
+
description: z.string().optional().describe('Brief summary of the project goals or scope.'),
|
|
316
|
+
color: z.string().optional().default('#6366f1').describe('Hex color for visual identification, e.g. "#f59e0b". Default is indigo (#6366f1).'),
|
|
317
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
318
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
236
319
|
},
|
|
237
320
|
async ({ name, description, color, workspaceId, apiKey }) => {
|
|
238
321
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -244,8 +327,9 @@ function buildServer() {
|
|
|
244
327
|
)
|
|
245
328
|
|
|
246
329
|
// ── list_members ──────────────────────────────────────────────────────────
|
|
247
|
-
server.tool('list_members',
|
|
248
|
-
|
|
330
|
+
server.tool('list_members',
|
|
331
|
+
'Returns all members of a workspace with their user IDs, names, emails, and roles. Call this BEFORE create_task or update_task when the user mentions assigning a task to someone by name — use this to resolve the name to a user ID. Also use it to answer "who is on this workspace?" or "what is [person]s user ID?".',
|
|
332
|
+
{ workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'), apiKey: z.string().optional().describe('Brioright API key.') },
|
|
249
333
|
async ({ workspaceId, apiKey }) => {
|
|
250
334
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
251
335
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -256,8 +340,9 @@ function buildServer() {
|
|
|
256
340
|
)
|
|
257
341
|
|
|
258
342
|
// ── get_workspace_summary ─────────────────────────────────────────────────
|
|
259
|
-
server.tool('get_workspace_summary',
|
|
260
|
-
|
|
343
|
+
server.tool('get_workspace_summary',
|
|
344
|
+
'Returns high-level dashboard statistics for a workspace: total tasks broken down by status (todo, in_progress, done, etc.) and by priority (low, medium, high, urgent). Use this to answer questions like "how many tasks are pending?", "what is our workload?", or "give me a project health overview". Does NOT return individual task details — use list_tasks for that.',
|
|
345
|
+
{ workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'), apiKey: z.string().optional().describe('Brioright API key.') },
|
|
261
346
|
async ({ workspaceId, apiKey }) => {
|
|
262
347
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
263
348
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -267,12 +352,13 @@ function buildServer() {
|
|
|
267
352
|
)
|
|
268
353
|
|
|
269
354
|
// ── add_comment ───────────────────────────────────────────────────────────
|
|
270
|
-
server.tool('add_comment',
|
|
355
|
+
server.tool('add_comment',
|
|
356
|
+
'Posts a comment on a task. Use this to leave a status update, ask a question on a task, provide context, or summarise findings. Supports markdown formatting in the text. Returns the posted comment with its ID and timestamp.',
|
|
271
357
|
{
|
|
272
|
-
taskId: z.string(),
|
|
273
|
-
text: z.string().describe('
|
|
274
|
-
workspaceId: z.string().optional(),
|
|
275
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
358
|
+
taskId: z.string().describe('ID of the task to comment on.'),
|
|
359
|
+
text: z.string().describe('Comment content. Supports markdown — use **bold**, bullet lists, code blocks etc. for clarity.'),
|
|
360
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
361
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
276
362
|
},
|
|
277
363
|
async ({ taskId, text, workspaceId, apiKey }) => {
|
|
278
364
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -284,11 +370,12 @@ function buildServer() {
|
|
|
284
370
|
)
|
|
285
371
|
|
|
286
372
|
// ── get_task_comments ─────────────────────────────────────────────────────
|
|
287
|
-
server.tool('get_task_comments',
|
|
373
|
+
server.tool('get_task_comments',
|
|
374
|
+
'Retrieves the full comment thread for a task in chronological order. Use this when the user asks "what has been discussed on this task?", wants a summary of activity, or before adding a new comment to avoid duplicating information. Returns each comment id, text, author name, and timestamp.',
|
|
288
375
|
{
|
|
289
|
-
taskId: z.string(),
|
|
290
|
-
workspaceId: z.string().optional(),
|
|
291
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
376
|
+
taskId: z.string().describe('ID of the task to fetch comments for.'),
|
|
377
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
378
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
292
379
|
},
|
|
293
380
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
294
381
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -299,16 +386,34 @@ function buildServer() {
|
|
|
299
386
|
}
|
|
300
387
|
)
|
|
301
388
|
|
|
389
|
+
// ── get_task_activities ───────────────────────────────────────────────────
|
|
390
|
+
server.tool('get_task_activities',
|
|
391
|
+
'Returns the full audit trail of changes made to a task: who changed what field, when status changed, when it was assigned, due date updates, etc. Use this when the user asks "what changed on this task?", "when was this assigned?", or "show me the task history". Different from get_task_comments — this is system-generated activity, not user comments.',
|
|
392
|
+
{
|
|
393
|
+
taskId: z.string().describe('ID of the task to fetch activity history for.'),
|
|
394
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
395
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
396
|
+
},
|
|
397
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
398
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
399
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
400
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}/activities`, null, apiKey)
|
|
401
|
+
const activities = data.activities || data
|
|
402
|
+
return { content: [{ type: 'text', text: JSON.stringify(activities.map(a => ({ id: a.id, type: a.type, message: a.message, user: a.user?.name, createdAt: a.createdAt })), null, 2) }] }
|
|
403
|
+
}
|
|
404
|
+
)
|
|
405
|
+
|
|
302
406
|
// ── log_time ──────────────────────────────────────────────────────────────
|
|
303
|
-
server.tool('log_time',
|
|
407
|
+
server.tool('log_time',
|
|
408
|
+
'Records a time entry against a project or specific task. Use this when the user says "log 2 hours on task X", "I spent 30 minutes on the auth feature", or "start a timer". If endTime is omitted, a running timer is started. If both startTime and endTime are provided, a completed time block is logged. projectId is required; taskId narrows it to a specific task.',
|
|
304
409
|
{
|
|
305
|
-
projectId: z.string(),
|
|
306
|
-
taskId: z.string().optional(),
|
|
307
|
-
description: z.string().optional(),
|
|
308
|
-
startTime: z.string().optional().describe('
|
|
309
|
-
endTime: z.string().optional().describe('
|
|
310
|
-
workspaceId: z.string().optional(),
|
|
311
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
410
|
+
projectId: z.string().describe('Project ID to log time against — use list_projects if unknown.'),
|
|
411
|
+
taskId: z.string().optional().describe('Optional task ID to associate the time entry with a specific task.'),
|
|
412
|
+
description: z.string().optional().describe('What was worked on during this time, e.g. "Fixed login bug", "Code review for PR #42".'),
|
|
413
|
+
startTime: z.string().optional().describe('Start of the work period as ISO string, e.g. "2026-03-22T09:00:00Z". Defaults to now.'),
|
|
414
|
+
endTime: z.string().optional().describe('End of the work period as ISO string. Omit to start a running timer instead of logging a completed block.'),
|
|
415
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
416
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
312
417
|
},
|
|
313
418
|
async ({ projectId, taskId, description, startTime, endTime, workspaceId, apiKey }) => {
|
|
314
419
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -325,7 +430,81 @@ function buildServer() {
|
|
|
325
430
|
}
|
|
326
431
|
)
|
|
327
432
|
|
|
433
|
+
|
|
434
|
+
// ── delete_task ───────────────────────────────────────────────────────────
|
|
435
|
+
server.tool('delete_task',
|
|
436
|
+
'Permanently deletes a task and all its subtasks, comments, and attachments. This action is IRREVERSIBLE. Use this when the user explicitly asks to delete or remove a task. Do NOT use this to cancel a task — use update_task with status=cancelled instead. Always confirm with the user before calling this if there is any ambiguity. Returns a confirmation message.',
|
|
437
|
+
{
|
|
438
|
+
taskId: z.string().describe('ID of the task to permanently delete. Use list_tasks or get_task to find it.'),
|
|
439
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
440
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
441
|
+
},
|
|
442
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
443
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
444
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
445
|
+
await call('DELETE', `/workspaces/${ws}/tasks/${taskId}`, null, apiKey)
|
|
446
|
+
return { content: [{ type: 'text', text: `🗑️ Task ${taskId} has been permanently deleted.` }] }
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
// ── search_workspace ──────────────────────────────────────────────────────
|
|
451
|
+
server.tool('search_workspace',
|
|
452
|
+
'Searches across the entire workspace and returns matching tasks, projects, and members in a single call. Use this when the user asks to find something by keyword, e.g. "find tasks about login", "search for the auth project", or "who is named John?". Returns up to 5 results per category (tasks, projects, users). Requires at least 2 characters in the query.',
|
|
453
|
+
{
|
|
454
|
+
query: z.string().describe('Search keyword or phrase — minimum 2 characters. Searches task titles/descriptions, project names/descriptions, and member names/emails.'),
|
|
455
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
456
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
457
|
+
},
|
|
458
|
+
async ({ query, workspaceId, apiKey }) => {
|
|
459
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
460
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
461
|
+
if (!query || query.trim().length < 2) throw new Error('Search query must be at least 2 characters')
|
|
462
|
+
const data = await call('GET', `/workspaces/${ws}/search?q=${encodeURIComponent(query.trim())}`, null, apiKey)
|
|
463
|
+
const { projects = [], tasks = [], users = [] } = data
|
|
464
|
+
const result = {
|
|
465
|
+
summary: `Found ${tasks.length} task(s), ${projects.length} project(s), ${users.length} member(s) for "${query}"`,
|
|
466
|
+
tasks: tasks.map(t => ({ id: t.id, title: t.title, status: t.status, priority: t.priority, projectId: t.projectId })),
|
|
467
|
+
projects: projects.map(p => ({ id: p.id, name: p.name, description: p.description })),
|
|
468
|
+
users: users.map(u => ({ id: u.id, name: u.name, email: u.email }))
|
|
469
|
+
}
|
|
470
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }
|
|
471
|
+
}
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
// ── get_workspace_analytics ───────────────────────────────────────────────
|
|
475
|
+
server.tool('get_workspace_analytics',
|
|
476
|
+
'Returns detailed analytics for a workspace including: task completion rate, tasks by status and priority, completion trends over time, task creation trends, top performers (leaderboard), member workload distribution, and per-project progress. Use this when the user asks for a report, "how are we doing?", "show me the analytics", "who completed the most tasks?", or "what is our completion rate?". Use get_workspace_summary instead for just basic task counts.',
|
|
477
|
+
{
|
|
478
|
+
range: z.enum(['7d', '30d', '90d', 'all']).optional().default('30d').describe('Time range for trend data. 7d=last week, 30d=last month (default), 90d=last quarter, all=all time.'),
|
|
479
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
480
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
481
|
+
},
|
|
482
|
+
async ({ range, workspaceId, apiKey }) => {
|
|
483
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
484
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
485
|
+
const data = await call('GET', `/workspaces/${ws}/analytics?range=${range || '30d'}`, null, apiKey)
|
|
486
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
|
|
487
|
+
}
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
// ── get_overdue_tasks ─────────────────────────────────────────────────────
|
|
491
|
+
server.tool('get_overdue_tasks',
|
|
492
|
+
'Returns all tasks that are past their due date and still not done. Use this when the user asks "what is overdue?", "what tasks are late?", or "show me missed deadlines". Results include task id, title, due date, assignee, and project. This is a dedicated endpoint — do not try to replicate this by filtering list_tasks manually.',
|
|
493
|
+
{
|
|
494
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
495
|
+
apiKey: z.string().optional().describe('Brioright API key.')
|
|
496
|
+
},
|
|
497
|
+
async ({ workspaceId, apiKey }) => {
|
|
498
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
499
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
500
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/overdue`, null, apiKey)
|
|
501
|
+
const tasks = data.tasks || data
|
|
502
|
+
return { content: [{ type: 'text', text: JSON.stringify(tasks.map(t => ({ id: t.id, title: t.title, dueDate: t.dueDate, assignee: t.assignee?.name, projectId: t.projectId, status: t.status })), null, 2) }] }
|
|
503
|
+
}
|
|
504
|
+
)
|
|
505
|
+
|
|
328
506
|
return server
|
|
507
|
+
|
|
329
508
|
}
|
|
330
509
|
|
|
331
510
|
// ── HTTP/SSE transport (for cloud AI clients) ─────────────────────────────────
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brioright-mcp",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "MCP server for Brioright — lets AI assistants create and manage tasks via natural language",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "MCP server for Brioright — lets AI assistants (Claude, Cursor, ChatGPT) create, search, analyse and manage tasks via natural language",
|
|
5
5
|
"type": "module",
|
|
6
|
+
|
|
6
7
|
"main": "index.js",
|
|
7
8
|
"bin": {
|
|
8
9
|
"brioright-mcp": "index.js"
|
package/.env
DELETED