brioright-mcp 1.0.0 → 1.2.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 +13 -1
- package/index.js +53 -38
- package/package.json +2 -2
- package/setup.js +105 -0
package/README.md
CHANGED
|
@@ -7,7 +7,19 @@ This server supports two transport modes:
|
|
|
7
7
|
1. **Stdio mode (Local)**: Runs as a subprocess for local clients like Claude Desktop or Cursor.
|
|
8
8
|
2. **HTTP/SSE mode (Cloud)**: Runs as a persistent web server for cloud-based AI assistants (like Antigravity).
|
|
9
9
|
|
|
10
|
-
## Quick Setup
|
|
10
|
+
## Quick Setup (Easiest Way)
|
|
11
|
+
|
|
12
|
+
Instead of manually editing JSON configuration files, you can use our interactive setup wizard to automatically connect your IDE to Brioright:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx -y brioright-mcp connect
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
You will simply be prompted for your API Key and Workspace ID. The wizard supports injecting settings into VS Code (Roo/Cline), Cursor, Windsurf, and Antigravity automatically.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Manual Setup
|
|
11
23
|
|
|
12
24
|
### 1. Generate an API Key
|
|
13
25
|
Log in to Brioright, then run from your browser console:
|
package/index.js
CHANGED
|
@@ -22,30 +22,37 @@ import { config } from 'dotenv'
|
|
|
22
22
|
import { fileURLToPath } from 'url'
|
|
23
23
|
import { dirname, join } from 'path'
|
|
24
24
|
|
|
25
|
+
// Handle setup flow `npx brioright-mcp connect`
|
|
26
|
+
if (process.argv[2] === 'connect') {
|
|
27
|
+
const { runSetup } = await import('./setup.js')
|
|
28
|
+
await runSetup()
|
|
29
|
+
process.exit(0)
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
// Load .env from mcp-server directory
|
|
26
33
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
27
34
|
config({ path: join(__dirname, '.env') })
|
|
28
35
|
|
|
29
36
|
const API_URL = process.env.BRIORIGHT_API_URL || 'http://localhost:3001/api'
|
|
30
|
-
const
|
|
37
|
+
const ENV_API_KEY = process.env.BRIORIGHT_API_KEY
|
|
31
38
|
const DEFAULT_WORKSPACE = process.env.BRIORIGHT_WORKSPACE_ID
|
|
32
39
|
const MCP_PORT = parseInt(process.env.MCP_PORT || '4040')
|
|
33
40
|
const MCP_SECRET = process.env.MCP_SECRET // Optional bearer token for HTTP mode
|
|
34
41
|
const USE_HTTP = process.env.MCP_TRANSPORT === 'http'
|
|
35
42
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
// ── Axios client factory ──────────────────────────────────────────────────────
|
|
44
|
+
async function call(method, path, data, overrideApiKey) {
|
|
45
|
+
const key = overrideApiKey || ENV_API_KEY;
|
|
46
|
+
if (!key) {
|
|
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
|
+
}
|
|
40
49
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
})
|
|
50
|
+
const api = axios.create({
|
|
51
|
+
baseURL: API_URL,
|
|
52
|
+
headers: { 'X-API-Key': key, 'Content-Type': 'application/json' },
|
|
53
|
+
timeout: 10000,
|
|
54
|
+
})
|
|
47
55
|
|
|
48
|
-
async function call(method, path, data) {
|
|
49
56
|
try {
|
|
50
57
|
const res = await api({ method, url: path, data })
|
|
51
58
|
return res.data.data
|
|
@@ -60,9 +67,10 @@ function buildServer() {
|
|
|
60
67
|
const server = new McpServer({ name: 'brioright', version: '1.0.0' })
|
|
61
68
|
|
|
62
69
|
// ── list_workspaces ───────────────────────────────────────────────────────
|
|
63
|
-
server.tool('list_workspaces', 'List all Brioright workspaces the API key has access to',
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
server.tool('list_workspaces', 'List all Brioright workspaces the API key has access to',
|
|
71
|
+
{ apiKey: z.string().optional().describe('Brioright API Key') },
|
|
72
|
+
async ({ apiKey }) => {
|
|
73
|
+
const data = await call('GET', '/workspaces', null, apiKey)
|
|
66
74
|
const workspaces = data.workspaces || data
|
|
67
75
|
return { content: [{ type: 'text', text: JSON.stringify(workspaces.map(w => ({ id: w.id, slug: w.slug, name: w.name })), null, 2) }] }
|
|
68
76
|
}
|
|
@@ -70,11 +78,14 @@ function buildServer() {
|
|
|
70
78
|
|
|
71
79
|
// ── list_projects ─────────────────────────────────────────────────────────
|
|
72
80
|
server.tool('list_projects', 'List all projects in a workspace',
|
|
73
|
-
{
|
|
74
|
-
|
|
81
|
+
{
|
|
82
|
+
workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
|
|
83
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
84
|
+
},
|
|
85
|
+
async ({ workspaceId, apiKey }) => {
|
|
75
86
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
76
87
|
if (!ws) throw new Error('workspaceId is required')
|
|
77
|
-
const data = await call('GET', `/workspaces/${ws}/projects
|
|
88
|
+
const data = await call('GET', `/workspaces/${ws}/projects`, null, apiKey)
|
|
78
89
|
const projects = data.projects || data
|
|
79
90
|
return { content: [{ type: 'text', text: JSON.stringify(projects.map(p => ({ id: p.id, name: p.name, status: p.status })), null, 2) }] }
|
|
80
91
|
}
|
|
@@ -88,15 +99,16 @@ function buildServer() {
|
|
|
88
99
|
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
89
100
|
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
90
101
|
limit: z.number().optional().default(20),
|
|
102
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
91
103
|
},
|
|
92
|
-
async ({ projectId, workspaceId, status, priority, limit }) => {
|
|
104
|
+
async ({ projectId, workspaceId, status, priority, limit, apiKey }) => {
|
|
93
105
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
94
106
|
if (!ws) throw new Error('workspaceId is required')
|
|
95
107
|
const params = new URLSearchParams()
|
|
96
108
|
if (status) params.set('status', status)
|
|
97
109
|
if (priority) params.set('priority', priority)
|
|
98
110
|
params.set('limit', String(limit || 20))
|
|
99
|
-
const data = await call('GET', `/workspaces/${ws}/projects/${projectId}/tasks?${params}
|
|
111
|
+
const data = await call('GET', `/workspaces/${ws}/projects/${projectId}/tasks?${params}`, null, apiKey)
|
|
100
112
|
const tasks = data.tasks || data
|
|
101
113
|
return { content: [{ type: 'text', text: JSON.stringify(tasks.map(t => ({ id: t.id, title: t.title, status: t.status, priority: t.priority, dueDate: t.dueDate, assignee: t.assignee?.name })), null, 2) }] }
|
|
102
114
|
}
|
|
@@ -104,11 +116,11 @@ function buildServer() {
|
|
|
104
116
|
|
|
105
117
|
// ── get_task ──────────────────────────────────────────────────────────────
|
|
106
118
|
server.tool('get_task', 'Get full details of a single task',
|
|
107
|
-
{ taskId: z.string(), workspaceId: z.string().optional() },
|
|
108
|
-
async ({ taskId, workspaceId }) => {
|
|
119
|
+
{ taskId: z.string(), workspaceId: z.string().optional(), apiKey: z.string().optional().describe('Brioright API Key') },
|
|
120
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
109
121
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
110
122
|
if (!ws) throw new Error('workspaceId is required')
|
|
111
|
-
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}
|
|
123
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}`, null, apiKey)
|
|
112
124
|
return { content: [{ type: 'text', text: JSON.stringify(data.task || data, null, 2) }] }
|
|
113
125
|
}
|
|
114
126
|
)
|
|
@@ -124,15 +136,16 @@ function buildServer() {
|
|
|
124
136
|
dueDate: z.string().optional().describe('ISO date string e.g. 2026-03-15'),
|
|
125
137
|
assigneeId: z.string().optional(),
|
|
126
138
|
workspaceId: z.string().optional(),
|
|
139
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
127
140
|
},
|
|
128
|
-
async ({ projectId, title, description, status, priority, dueDate, assigneeId, workspaceId }) => {
|
|
141
|
+
async ({ projectId, title, description, status, priority, dueDate, assigneeId, workspaceId, apiKey }) => {
|
|
129
142
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
130
143
|
if (!ws) throw new Error('workspaceId is required')
|
|
131
144
|
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks`, {
|
|
132
145
|
title, description, status, priority,
|
|
133
146
|
dueDate: dueDate ? new Date(dueDate).toISOString() : undefined,
|
|
134
147
|
assigneeId,
|
|
135
|
-
})
|
|
148
|
+
}, apiKey)
|
|
136
149
|
const task = data.task || data
|
|
137
150
|
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)}` }] }
|
|
138
151
|
}
|
|
@@ -149,13 +162,14 @@ function buildServer() {
|
|
|
149
162
|
dueDate: z.string().optional(),
|
|
150
163
|
assigneeId: z.string().optional(),
|
|
151
164
|
workspaceId: z.string().optional(),
|
|
165
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
152
166
|
},
|
|
153
|
-
async ({ taskId, workspaceId, ...fields }) => {
|
|
167
|
+
async ({ taskId, workspaceId, apiKey, ...fields }) => {
|
|
154
168
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
155
169
|
if (!ws) throw new Error('workspaceId is required')
|
|
156
170
|
const updates = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined))
|
|
157
171
|
if (updates.dueDate) updates.dueDate = new Date(updates.dueDate).toISOString()
|
|
158
|
-
const data = await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, updates)
|
|
172
|
+
const data = await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, updates, apiKey)
|
|
159
173
|
const task = data.task || data
|
|
160
174
|
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)}` }] }
|
|
161
175
|
}
|
|
@@ -163,11 +177,11 @@ function buildServer() {
|
|
|
163
177
|
|
|
164
178
|
// ── complete_task ─────────────────────────────────────────────────────────
|
|
165
179
|
server.tool('complete_task', 'Mark a task as completed',
|
|
166
|
-
{ taskId: z.string(), workspaceId: z.string().optional() },
|
|
167
|
-
async ({ taskId, workspaceId }) => {
|
|
180
|
+
{ taskId: z.string(), workspaceId: z.string().optional(), apiKey: z.string().optional().describe('Brioright API Key') },
|
|
181
|
+
async ({ taskId, workspaceId, apiKey }) => {
|
|
168
182
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
169
183
|
if (!ws) throw new Error('workspaceId is required')
|
|
170
|
-
await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, { status: 'done' })
|
|
184
|
+
await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, { status: 'done' }, apiKey)
|
|
171
185
|
return { content: [{ type: 'text', text: `✅ Task ${taskId} marked as done.` }] }
|
|
172
186
|
}
|
|
173
187
|
)
|
|
@@ -179,11 +193,12 @@ function buildServer() {
|
|
|
179
193
|
description: z.string().optional(),
|
|
180
194
|
color: z.string().optional().default('#6366f1'),
|
|
181
195
|
workspaceId: z.string().optional(),
|
|
196
|
+
apiKey: z.string().optional().describe('Brioright API Key')
|
|
182
197
|
},
|
|
183
|
-
async ({ name, description, color, workspaceId }) => {
|
|
198
|
+
async ({ name, description, color, workspaceId, apiKey }) => {
|
|
184
199
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
185
200
|
if (!ws) throw new Error('workspaceId is required')
|
|
186
|
-
const data = await call('POST', `/workspaces/${ws}/projects`, { name, description, color })
|
|
201
|
+
const data = await call('POST', `/workspaces/${ws}/projects`, { name, description, color }, apiKey)
|
|
187
202
|
const project = data.project || data
|
|
188
203
|
return { content: [{ type: 'text', text: `✅ Project created!\n\n${JSON.stringify({ id: project.id, name: project.name }, null, 2)}` }] }
|
|
189
204
|
}
|
|
@@ -191,11 +206,11 @@ function buildServer() {
|
|
|
191
206
|
|
|
192
207
|
// ── list_members ──────────────────────────────────────────────────────────
|
|
193
208
|
server.tool('list_members', 'List workspace members (useful for finding assignee IDs)',
|
|
194
|
-
{ workspaceId: z.string().optional() },
|
|
195
|
-
async ({ workspaceId }) => {
|
|
209
|
+
{ workspaceId: z.string().optional(), apiKey: z.string().optional().describe('Brioright API Key') },
|
|
210
|
+
async ({ workspaceId, apiKey }) => {
|
|
196
211
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
197
212
|
if (!ws) throw new Error('workspaceId is required')
|
|
198
|
-
const data = await call('GET', `/workspaces/${ws}/members
|
|
213
|
+
const data = await call('GET', `/workspaces/${ws}/members`, null, apiKey)
|
|
199
214
|
const members = data.members || data
|
|
200
215
|
return { content: [{ type: 'text', text: JSON.stringify(members.map(m => ({ id: m.user?.id || m.id, name: m.user?.name || m.name, email: m.user?.email || m.email, role: m.role })), null, 2) }] }
|
|
201
216
|
}
|
|
@@ -203,11 +218,11 @@ function buildServer() {
|
|
|
203
218
|
|
|
204
219
|
// ── get_workspace_summary ─────────────────────────────────────────────────
|
|
205
220
|
server.tool('get_workspace_summary', 'Dashboard stats: task counts by status and priority',
|
|
206
|
-
{ workspaceId: z.string().optional() },
|
|
207
|
-
async ({ workspaceId }) => {
|
|
221
|
+
{ workspaceId: z.string().optional(), apiKey: z.string().optional().describe('Brioright API Key') },
|
|
222
|
+
async ({ workspaceId, apiKey }) => {
|
|
208
223
|
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
209
224
|
if (!ws) throw new Error('workspaceId is required')
|
|
210
|
-
const data = await call('GET', `/workspaces/${ws}/dashboard/stats
|
|
225
|
+
const data = await call('GET', `/workspaces/${ws}/dashboard/stats`, null, apiKey)
|
|
211
226
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
|
|
212
227
|
}
|
|
213
228
|
)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brioright-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "MCP server for Brioright — lets AI assistants create and manage tasks via natural language",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -22,4 +22,4 @@
|
|
|
22
22
|
"engines": {
|
|
23
23
|
"node": ">=18.0.0"
|
|
24
24
|
}
|
|
25
|
-
}
|
|
25
|
+
}
|
package/setup.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import readline from 'readline'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import path from 'path'
|
|
6
|
+
import os from 'os'
|
|
7
|
+
|
|
8
|
+
export async function runSetup() {
|
|
9
|
+
console.log('\n🌟 Welcome to the Brioright MCP Setup!\n')
|
|
10
|
+
console.log('This will configure your local IDE to connect to your Brioright workspace.')
|
|
11
|
+
|
|
12
|
+
const rl = readline.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const ask = (query) => new Promise(resolve => rl.question(query, resolve))
|
|
18
|
+
|
|
19
|
+
// 1. Gather credentials
|
|
20
|
+
const apiKey = await ask('Enter your Brioright API Key: ')
|
|
21
|
+
if (!apiKey.trim()) {
|
|
22
|
+
console.log('❌ API Key is required. Exiting.')
|
|
23
|
+
rl.close()
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const workspaceId = await ask('Enter your Workspace ID (slug): ')
|
|
28
|
+
if (!workspaceId.trim()) {
|
|
29
|
+
console.log('❌ Workspace ID is required. Exiting.')
|
|
30
|
+
rl.close()
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
rl.close()
|
|
35
|
+
|
|
36
|
+
// 2. The config block to inject
|
|
37
|
+
const newConfig = {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "brioright-mcp"],
|
|
40
|
+
"env": {
|
|
41
|
+
"BRIORIGHT_API_URL": "https://brioright.online/api",
|
|
42
|
+
"BRIORIGHT_API_KEY": apiKey.trim(),
|
|
43
|
+
"BRIORIGHT_WORKSPACE_ID": workspaceId.trim()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. Search for known IDE configs
|
|
48
|
+
const home = os.homedir()
|
|
49
|
+
|
|
50
|
+
// Antigravity
|
|
51
|
+
const antigravityPath = path.join(home, '.gemini', 'antigravity', 'mcp_config.json')
|
|
52
|
+
// VS Code / Cursor generic
|
|
53
|
+
const vscodeMcpPath = path.join(home, '.vscode', 'mcp.json')
|
|
54
|
+
// Cline / Roo Code (Windows)
|
|
55
|
+
const clineWindowsPath = path.join(home, 'AppData', 'Roaming', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'cline_mcp_settings.json')
|
|
56
|
+
// Cline / Roo Code (Mac)
|
|
57
|
+
const clineMacPath = path.join(home, 'Library', 'Application Support', 'Code', 'User', 'globalStorage', 'rooveterinaryinc.roo-cline', 'settings', 'cline_mcp_settings.json')
|
|
58
|
+
|
|
59
|
+
const possiblePaths = [
|
|
60
|
+
{ name: 'Antigravity IDE', path: antigravityPath },
|
|
61
|
+
{ name: 'VS Code (Roo/Cline) Windows', path: clineWindowsPath },
|
|
62
|
+
{ name: 'VS Code (Roo/Cline) Mac', path: clineMacPath },
|
|
63
|
+
{ name: 'Generic VS Code', path: vscodeMcpPath }
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
let injected = false
|
|
67
|
+
|
|
68
|
+
for (const ide of possiblePaths) {
|
|
69
|
+
if (fs.existsSync(ide.path)) {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(ide.path, 'utf8')
|
|
72
|
+
const json = JSON.parse(content || '{}')
|
|
73
|
+
|
|
74
|
+
if (!json.mcpServers) json.mcpServers = {}
|
|
75
|
+
|
|
76
|
+
json.mcpServers['brioright-remote'] = newConfig
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(ide.path, JSON.stringify(json, null, 2))
|
|
79
|
+
console.log(`\n✅ Successfully added Brioright to: ${ide.name}`)
|
|
80
|
+
console.log(` 📝 File: ${ide.path}`)
|
|
81
|
+
injected = true
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.log(`\n⚠️ Found config for ${ide.name} but failed to update it: ${err.message}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (injected) {
|
|
89
|
+
console.log('\n🎉 Setup complete! Please completely close and RESTART YOUR IDE for the new tools to load.')
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 4. Fallback if no IDE config found
|
|
94
|
+
console.log('\n⚠️ We could not find a supported IDE configuration file automatically.')
|
|
95
|
+
console.log('To set up Brioright, please copy the following JSON block into your MCP Settings file (e.g. mcp_settings.json):\n')
|
|
96
|
+
|
|
97
|
+
const fallbackBlock = {
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"brioright-remote": newConfig
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(JSON.stringify(fallbackBlock, null, 2))
|
|
104
|
+
console.log('\n(Once saved, restart your IDE.)\n')
|
|
105
|
+
}
|