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.
Files changed (4) hide show
  1. package/README.md +13 -1
  2. package/index.js +53 -38
  3. package/package.json +2 -2
  4. 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 API_KEY = process.env.BRIORIGHT_API_KEY
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
- if (!API_KEY) {
37
- process.stderr.write('[Brioright MCP] ERROR: BRIORIGHT_API_KEY is not set.\n')
38
- process.exit(1)
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
- // ── Axios client ──────────────────────────────────────────────────────────────
42
- const api = axios.create({
43
- baseURL: API_URL,
44
- headers: { 'X-API-Key': API_KEY, 'Content-Type': 'application/json' },
45
- timeout: 10000,
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
- async () => {
65
- const data = await call('GET', '/workspaces')
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
- { workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.') },
74
- async ({ workspaceId }) => {
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.0.0",
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
+ }