brioright-mcp 1.4.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 +1 -0
- package/index.js +222 -91
- package/package.json +3 -2
- package/.env +0 -3
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ 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 |
|
package/index.js
CHANGED
|
@@ -76,8 +76,9 @@ function buildServer() {
|
|
|
76
76
|
const server = new McpServer({ name: 'brioright', version: '1.0.0' })
|
|
77
77
|
|
|
78
78
|
// ── list_workspaces ───────────────────────────────────────────────────────
|
|
79
|
-
server.tool('list_workspaces',
|
|
80
|
-
|
|
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.') },
|
|
81
82
|
async ({ apiKey }) => {
|
|
82
83
|
const data = await call('GET', '/workspaces', null, apiKey)
|
|
83
84
|
const workspaces = data.workspaces || data
|
|
@@ -86,10 +87,11 @@ function buildServer() {
|
|
|
86
87
|
)
|
|
87
88
|
|
|
88
89
|
// ── list_projects ─────────────────────────────────────────────────────────
|
|
89
|
-
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.',
|
|
90
92
|
{
|
|
91
|
-
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
92
|
-
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.')
|
|
93
95
|
},
|
|
94
96
|
async ({ workspaceId, apiKey }) => {
|
|
95
97
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -101,14 +103,15 @@ function buildServer() {
|
|
|
101
103
|
)
|
|
102
104
|
|
|
103
105
|
// ── list_tasks ────────────────────────────────────────────────────────────
|
|
104
|
-
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.',
|
|
105
108
|
{
|
|
106
|
-
projectId: z.string().describe('Project ID'),
|
|
107
|
-
workspaceId: z.string().optional(),
|
|
108
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
109
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
110
|
-
limit: z.number().optional().default(20),
|
|
111
|
-
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.')
|
|
112
115
|
},
|
|
113
116
|
async ({ projectId, workspaceId, status, priority, limit, apiKey }) => {
|
|
114
117
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -124,8 +127,9 @@ function buildServer() {
|
|
|
124
127
|
)
|
|
125
128
|
|
|
126
129
|
// ── get_task ──────────────────────────────────────────────────────────────
|
|
127
|
-
server.tool('get_task',
|
|
128
|
-
|
|
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.') },
|
|
129
133
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
130
134
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
131
135
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -135,11 +139,12 @@ function buildServer() {
|
|
|
135
139
|
)
|
|
136
140
|
|
|
137
141
|
// ── get_task_attachments ──────────────────────────────────────────────────
|
|
138
|
-
server.tool('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.',
|
|
139
144
|
{
|
|
140
|
-
taskId: z.string(),
|
|
141
|
-
workspaceId: z.string().optional(),
|
|
142
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
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.')
|
|
143
148
|
},
|
|
144
149
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
145
150
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -151,14 +156,15 @@ function buildServer() {
|
|
|
151
156
|
)
|
|
152
157
|
|
|
153
158
|
// ── add_task_attachment ───────────────────────────────────────────────────
|
|
154
|
-
server.tool('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.',
|
|
155
161
|
{
|
|
156
|
-
taskId: z.string(),
|
|
157
|
-
fileName: z.string().describe('
|
|
158
|
-
fileContent: z.string().describe('Base64
|
|
159
|
-
mimeType: z.string().optional().describe('MIME type
|
|
160
|
-
workspaceId: z.string().optional(),
|
|
161
|
-
apiKey: z.string().optional().describe('Brioright API
|
|
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.')
|
|
162
168
|
},
|
|
163
169
|
async ({ taskId, fileName, fileContent, mimeType, workspaceId, apiKey }) => {
|
|
164
170
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -178,17 +184,18 @@ function buildServer() {
|
|
|
178
184
|
)
|
|
179
185
|
|
|
180
186
|
// ── create_task ───────────────────────────────────────────────────────────
|
|
181
|
-
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.',
|
|
182
189
|
{
|
|
183
|
-
projectId: z.string().describe('Project ID'),
|
|
184
|
-
title: z.string().describe('
|
|
185
|
-
description: z.string().optional(),
|
|
186
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo'),
|
|
187
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium'),
|
|
188
|
-
dueDate: z.string().optional().describe('
|
|
189
|
-
assigneeId: z.string().optional(),
|
|
190
|
-
workspaceId: z.string().optional(),
|
|
191
|
-
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.')
|
|
192
199
|
},
|
|
193
200
|
async ({ projectId, title, description, status, priority, dueDate, assigneeId, workspaceId, apiKey }) => {
|
|
194
201
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -196,38 +203,63 @@ function buildServer() {
|
|
|
196
203
|
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks`, {
|
|
197
204
|
title, description, status, priority,
|
|
198
205
|
dueDate: dueDate ? new Date(dueDate).toISOString() : undefined,
|
|
199
|
-
assigneeId,
|
|
206
|
+
assigneeId: assigneeId || undefined,
|
|
200
207
|
}, apiKey)
|
|
201
208
|
const task = data.task || data
|
|
202
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)}` }] }
|
|
203
210
|
}
|
|
204
211
|
)
|
|
205
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
|
+
|
|
206
235
|
// ── bulk_create_tasks ─────────────────────────────────────────────────────
|
|
207
|
-
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.',
|
|
208
238
|
{
|
|
209
|
-
projectId: z.string().describe('Project ID'),
|
|
239
|
+
projectId: z.string().describe('Project ID — use list_projects first if unknown.'),
|
|
210
240
|
tasks: z.array(z.object({
|
|
211
|
-
title: z.string().describe('
|
|
212
|
-
description: z.string().optional(),
|
|
213
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo'),
|
|
214
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium'),
|
|
215
|
-
dueDate: z.string().optional().describe('ISO date string e.g. 2026-
|
|
216
|
-
assigneeId: z.string().optional(),
|
|
217
|
-
parentTaskId: z.string().optional(),
|
|
218
|
-
tags: z.array(z.string()).optional()
|
|
219
|
-
})).describe('Array of
|
|
220
|
-
workspaceId: z.string().optional(),
|
|
221
|
-
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.')
|
|
222
252
|
},
|
|
223
253
|
async ({ projectId, tasks, workspaceId, apiKey }) => {
|
|
224
254
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
225
255
|
if (!ws) throw new Error('workspaceId is required')
|
|
226
256
|
|
|
227
|
-
// Format dates
|
|
257
|
+
// Format dates and sanitise strings
|
|
228
258
|
const formattedTasks = tasks.map(t => ({
|
|
229
259
|
...t,
|
|
230
|
-
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
|
|
231
263
|
}))
|
|
232
264
|
|
|
233
265
|
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks/bulk`, {
|
|
@@ -239,22 +271,23 @@ function buildServer() {
|
|
|
239
271
|
)
|
|
240
272
|
|
|
241
273
|
// ── update_task ───────────────────────────────────────────────────────────
|
|
242
|
-
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.',
|
|
243
276
|
{
|
|
244
|
-
taskId: z.string(),
|
|
245
|
-
title: z.string().optional(),
|
|
246
|
-
description: z.string().optional(),
|
|
247
|
-
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
248
|
-
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
249
|
-
dueDate: z.string().optional(),
|
|
250
|
-
assigneeId: z.string().optional(),
|
|
251
|
-
workspaceId: z.string().optional(),
|
|
252
|
-
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.')
|
|
253
286
|
},
|
|
254
287
|
async ({ taskId, workspaceId, apiKey, ...fields }) => {
|
|
255
288
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
256
289
|
if (!ws) throw new Error('workspaceId is required')
|
|
257
|
-
const updates = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined))
|
|
290
|
+
const updates = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined && v !== ''))
|
|
258
291
|
if (updates.dueDate) updates.dueDate = new Date(updates.dueDate).toISOString()
|
|
259
292
|
const data = await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, updates, apiKey)
|
|
260
293
|
const task = data.task || data
|
|
@@ -263,8 +296,9 @@ function buildServer() {
|
|
|
263
296
|
)
|
|
264
297
|
|
|
265
298
|
// ── complete_task ─────────────────────────────────────────────────────────
|
|
266
|
-
server.tool('complete_task',
|
|
267
|
-
|
|
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.') },
|
|
268
302
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
269
303
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
270
304
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -274,13 +308,14 @@ function buildServer() {
|
|
|
274
308
|
)
|
|
275
309
|
|
|
276
310
|
// ── create_project ────────────────────────────────────────────────────────
|
|
277
|
-
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.',
|
|
278
313
|
{
|
|
279
|
-
name: z.string(),
|
|
280
|
-
description: z.string().optional(),
|
|
281
|
-
color: z.string().optional().default('#6366f1'),
|
|
282
|
-
workspaceId: z.string().optional(),
|
|
283
|
-
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.')
|
|
284
319
|
},
|
|
285
320
|
async ({ name, description, color, workspaceId, apiKey }) => {
|
|
286
321
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -292,8 +327,9 @@ function buildServer() {
|
|
|
292
327
|
)
|
|
293
328
|
|
|
294
329
|
// ── list_members ──────────────────────────────────────────────────────────
|
|
295
|
-
server.tool('list_members',
|
|
296
|
-
|
|
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.') },
|
|
297
333
|
async ({ workspaceId, apiKey }) => {
|
|
298
334
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
299
335
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -304,8 +340,9 @@ function buildServer() {
|
|
|
304
340
|
)
|
|
305
341
|
|
|
306
342
|
// ── get_workspace_summary ─────────────────────────────────────────────────
|
|
307
|
-
server.tool('get_workspace_summary',
|
|
308
|
-
|
|
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.') },
|
|
309
346
|
async ({ workspaceId, apiKey }) => {
|
|
310
347
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
311
348
|
if (!ws) throw new Error('workspaceId is required')
|
|
@@ -315,12 +352,13 @@ function buildServer() {
|
|
|
315
352
|
)
|
|
316
353
|
|
|
317
354
|
// ── add_comment ───────────────────────────────────────────────────────────
|
|
318
|
-
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.',
|
|
319
357
|
{
|
|
320
|
-
taskId: z.string(),
|
|
321
|
-
text: z.string().describe('
|
|
322
|
-
workspaceId: z.string().optional(),
|
|
323
|
-
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.')
|
|
324
362
|
},
|
|
325
363
|
async ({ taskId, text, workspaceId, apiKey }) => {
|
|
326
364
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -332,11 +370,12 @@ function buildServer() {
|
|
|
332
370
|
)
|
|
333
371
|
|
|
334
372
|
// ── get_task_comments ─────────────────────────────────────────────────────
|
|
335
|
-
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.',
|
|
336
375
|
{
|
|
337
|
-
taskId: z.string(),
|
|
338
|
-
workspaceId: z.string().optional(),
|
|
339
|
-
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.')
|
|
340
379
|
},
|
|
341
380
|
async ({ taskId, workspaceId, apiKey }) => {
|
|
342
381
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -347,16 +386,34 @@ function buildServer() {
|
|
|
347
386
|
}
|
|
348
387
|
)
|
|
349
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
|
+
|
|
350
406
|
// ── log_time ──────────────────────────────────────────────────────────────
|
|
351
|
-
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.',
|
|
352
409
|
{
|
|
353
|
-
projectId: z.string(),
|
|
354
|
-
taskId: z.string().optional(),
|
|
355
|
-
description: z.string().optional(),
|
|
356
|
-
startTime: z.string().optional().describe('
|
|
357
|
-
endTime: z.string().optional().describe('
|
|
358
|
-
workspaceId: z.string().optional(),
|
|
359
|
-
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.')
|
|
360
417
|
},
|
|
361
418
|
async ({ projectId, taskId, description, startTime, endTime, workspaceId, apiKey }) => {
|
|
362
419
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
@@ -373,7 +430,81 @@ function buildServer() {
|
|
|
373
430
|
}
|
|
374
431
|
)
|
|
375
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
|
+
|
|
376
506
|
return server
|
|
507
|
+
|
|
377
508
|
}
|
|
378
509
|
|
|
379
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