brioright-mcp 1.2.1 → 1.4.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 +6 -0
- package/index.js +146 -4
- package/package.json +1 -1
- package/test-axios.mjs +0 -20
- package/test-axios2.mjs +0 -20
package/README.md
CHANGED
|
@@ -88,3 +88,9 @@ And pass the Authorization Header: `Bearer your_secure_bearer_token`.
|
|
|
88
88
|
| `create_project` | Create a new project |
|
|
89
89
|
| `list_members` | List workspace members (for finding assignee IDs) |
|
|
90
90
|
| `get_workspace_summary` | Dashboard stats: task counts by status/priority |
|
|
91
|
+
| `bulk_create_tasks` | Create multiple tasks at once |
|
|
92
|
+
| `add_comment` | Add a comment to a task |
|
|
93
|
+
| `get_task_comments` | Get all comments for a given task |
|
|
94
|
+
| `log_time` | Log time entry for a project/task |
|
|
95
|
+
| `add_task_attachment` | Upload a file attachment to a task using a Base64 encoded string |
|
|
96
|
+
| `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 {
|
|
@@ -129,6 +134,49 @@ function buildServer() {
|
|
|
129
134
|
}
|
|
130
135
|
)
|
|
131
136
|
|
|
137
|
+
// ── get_task_attachments ──────────────────────────────────────────────────
|
|
138
|
+
server.tool('get_task_attachments', 'List all file attachments for a specific task',
|
|
139
|
+
{
|
|
140
|
+
taskId: z.string(),
|
|
141
|
+
workspaceId: z.string().optional(),
|
|
142
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
143
|
+
},
|
|
144
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
145
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
146
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
147
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}/attachments`, null, apiKey)
|
|
148
|
+
const attachments = data.attachments || data
|
|
149
|
+
return { content: [{ type: 'text', text: JSON.stringify(attachments, null, 2) }] }
|
|
150
|
+
}
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
// ── add_task_attachment ───────────────────────────────────────────────────
|
|
154
|
+
server.tool('add_task_attachment', 'Upload a file attachment to a task using a Base64 encoded string',
|
|
155
|
+
{
|
|
156
|
+
taskId: z.string(),
|
|
157
|
+
fileName: z.string().describe('Name of the file to attach (e.g. image.png)'),
|
|
158
|
+
fileContent: z.string().describe('Base64 encoded string of the file content'),
|
|
159
|
+
mimeType: z.string().optional().describe('MIME type of the file (e.g. image/png)'),
|
|
160
|
+
workspaceId: z.string().optional(),
|
|
161
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
162
|
+
},
|
|
163
|
+
async ({ taskId, fileName, fileContent, mimeType, workspaceId, apiKey }) => {
|
|
164
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
165
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
166
|
+
|
|
167
|
+
// Convert base64 to Blob
|
|
168
|
+
const buffer = Buffer.from(fileContent, 'base64')
|
|
169
|
+
const blob = new Blob([buffer], { type: mimeType || 'application/octet-stream' })
|
|
170
|
+
|
|
171
|
+
const formData = new FormData()
|
|
172
|
+
formData.append('file', blob, fileName)
|
|
173
|
+
|
|
174
|
+
const data = await call('POST', `/workspaces/${ws}/tasks/${taskId}/attachments`, formData, apiKey, { 'Content-Type': 'multipart/form-data' })
|
|
175
|
+
const attachment = data.attachment || data
|
|
176
|
+
return { content: [{ type: 'text', text: `✅ File attached successfully!\n\n${JSON.stringify({ id: attachment.id, name: attachment.name, url: attachment.url }, null, 2)}` }] }
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
|
|
132
180
|
// ── create_task ───────────────────────────────────────────────────────────
|
|
133
181
|
server.tool('create_task', 'Create a new task in a Brioright project',
|
|
134
182
|
{
|
|
@@ -155,6 +203,41 @@ function buildServer() {
|
|
|
155
203
|
}
|
|
156
204
|
)
|
|
157
205
|
|
|
206
|
+
// ── bulk_create_tasks ─────────────────────────────────────────────────────
|
|
207
|
+
server.tool('bulk_create_tasks', 'Create multiple tasks at once in a Brioright project',
|
|
208
|
+
{
|
|
209
|
+
projectId: z.string().describe('Project ID'),
|
|
210
|
+
tasks: z.array(z.object({
|
|
211
|
+
title: z.string().describe('Task title'),
|
|
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-03-15'),
|
|
216
|
+
assigneeId: z.string().optional(),
|
|
217
|
+
parentTaskId: z.string().optional(),
|
|
218
|
+
tags: z.array(z.string()).optional()
|
|
219
|
+
})).describe('Array of tasks to create'),
|
|
220
|
+
workspaceId: z.string().optional(),
|
|
221
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
222
|
+
},
|
|
223
|
+
async ({ projectId, tasks, workspaceId, apiKey }) => {
|
|
224
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
225
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
226
|
+
|
|
227
|
+
// Format dates
|
|
228
|
+
const formattedTasks = tasks.map(t => ({
|
|
229
|
+
...t,
|
|
230
|
+
dueDate: t.dueDate ? new Date(t.dueDate).toISOString() : undefined
|
|
231
|
+
}))
|
|
232
|
+
|
|
233
|
+
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks/bulk`, {
|
|
234
|
+
tasks: formattedTasks
|
|
235
|
+
}, apiKey)
|
|
236
|
+
|
|
237
|
+
return { content: [{ type: 'text', text: `✅ Successfully created ${data.count} tasks!` }] }
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
|
|
158
241
|
// ── update_task ───────────────────────────────────────────────────────────
|
|
159
242
|
server.tool('update_task', 'Update fields on an existing task',
|
|
160
243
|
{
|
|
@@ -175,7 +258,7 @@ function buildServer() {
|
|
|
175
258
|
if (updates.dueDate) updates.dueDate = new Date(updates.dueDate).toISOString()
|
|
176
259
|
const data = await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, updates, apiKey)
|
|
177
260
|
const task = data.task || data
|
|
178
|
-
return { content: [{ type: 'text', text: `✅ Task updated!\n\n${JSON.stringify({ id: task.id, title: task.title, status: task.status, priority: task.priority }, null, 2)}` }] }
|
|
261
|
+
return { content: [{ type: 'text', text: `✅ Task updated!\n\n${JSON.stringify({ id: task.id, title: task.title, status: task.status, priority: task.priority, description: task.description, dueDate: task.dueDate }, null, 2)}` }] }
|
|
179
262
|
}
|
|
180
263
|
)
|
|
181
264
|
|
|
@@ -231,6 +314,65 @@ function buildServer() {
|
|
|
231
314
|
}
|
|
232
315
|
)
|
|
233
316
|
|
|
317
|
+
// ── add_comment ───────────────────────────────────────────────────────────
|
|
318
|
+
server.tool('add_comment', 'Add a comment to a task',
|
|
319
|
+
{
|
|
320
|
+
taskId: z.string(),
|
|
321
|
+
text: z.string().describe('The content of the comment'),
|
|
322
|
+
workspaceId: z.string().optional(),
|
|
323
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
324
|
+
},
|
|
325
|
+
async ({ taskId, text, workspaceId, apiKey }) => {
|
|
326
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
327
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
328
|
+
const data = await call('POST', `/workspaces/${ws}/tasks/${taskId}/comments`, { text }, apiKey)
|
|
329
|
+
const comment = data.comment || data
|
|
330
|
+
return { content: [{ type: 'text', text: `✅ Comment added!\n\n${JSON.stringify(comment, null, 2)}` }] }
|
|
331
|
+
}
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
// ── get_task_comments ─────────────────────────────────────────────────────
|
|
335
|
+
server.tool('get_task_comments', 'Get all comments for a given task',
|
|
336
|
+
{
|
|
337
|
+
taskId: z.string(),
|
|
338
|
+
workspaceId: z.string().optional(),
|
|
339
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
340
|
+
},
|
|
341
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
342
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
343
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
344
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}/comments`, null, apiKey)
|
|
345
|
+
const comments = data.comments || data
|
|
346
|
+
return { content: [{ type: 'text', text: JSON.stringify(comments.map(c => ({ id: c.id, text: c.text, author: c.author?.name, createdAt: c.createdAt })), null, 2) }] }
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
// ── log_time ──────────────────────────────────────────────────────────────
|
|
351
|
+
server.tool('log_time', 'Log time entry for a project/task',
|
|
352
|
+
{
|
|
353
|
+
projectId: z.string(),
|
|
354
|
+
taskId: z.string().optional(),
|
|
355
|
+
description: z.string().optional(),
|
|
356
|
+
startTime: z.string().optional().describe('ISO date string for start time. Defaults to now.'),
|
|
357
|
+
endTime: z.string().optional().describe('ISO date string for end time. If omitted, starts a running timer.'),
|
|
358
|
+
workspaceId: z.string().optional(),
|
|
359
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
360
|
+
},
|
|
361
|
+
async ({ projectId, taskId, description, startTime, endTime, workspaceId, apiKey }) => {
|
|
362
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
363
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
364
|
+
const start = startTime ? new Date(startTime).toISOString() : new Date().toISOString()
|
|
365
|
+
const end = endTime ? new Date(endTime).toISOString() : undefined
|
|
366
|
+
|
|
367
|
+
const payload = { projectId, description, startTime: start, endTime: end }
|
|
368
|
+
if (taskId) payload.taskId = taskId
|
|
369
|
+
|
|
370
|
+
const data = await call('POST', `/workspaces/${ws}/time-entries`, payload, apiKey)
|
|
371
|
+
const entry = data.entry || data
|
|
372
|
+
return { content: [{ type: 'text', text: `✅ Time logged (Duration: ${entry.duration ? entry.duration + ' seconds' : 'Running timer ...'})!\n\n${JSON.stringify({ id: entry.id, description: entry.description, startTime: entry.startTime, endTime: entry.endTime, duration: entry.duration }, null, 2)}` }] }
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
|
|
234
376
|
return server
|
|
235
377
|
}
|
|
236
378
|
|
package/package.json
CHANGED
package/test-axios.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
|
|
3
|
-
async function test() {
|
|
4
|
-
const api = axios.create({
|
|
5
|
-
baseURL: 'https://brioright.online/api',
|
|
6
|
-
headers: { 'X-API-Key': 'brio_9847f9c385632d4d1c888afc42c8c3906ffad22bf7713c062a2d1c6db90adadf', 'Content-Type': 'application/json' },
|
|
7
|
-
timeout: 10000,
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
const res = await api({ method: 'GET', url: '/workspaces', data: null });
|
|
12
|
-
console.log("SUCCESS:");
|
|
13
|
-
console.log(res.data);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.log("ERROR:");
|
|
16
|
-
console.log(err.response?.data || err.message);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
test();
|
package/test-axios2.mjs
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
2
|
-
|
|
3
|
-
async function test() {
|
|
4
|
-
const api = axios.create({
|
|
5
|
-
baseURL: 'https://brioright.online/api',
|
|
6
|
-
headers: { 'X-API-Key': 'brio_9847f9c385632d4d1c888afc42c8c3906ffad22bf7713c062a2d1c6db90adadf', 'Content-Type': 'application/json' },
|
|
7
|
-
timeout: 10000,
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
const res = await api({ method: 'GET', url: '/workspaces' }); // data: undefined
|
|
12
|
-
console.log("SUCCESS:");
|
|
13
|
-
console.log(res.data);
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.log("ERROR:");
|
|
16
|
-
console.log(err.response?.data || err.message);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
test();
|