brioright-mcp 1.0.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/.env +3 -0
- package/.env.example +14 -0
- package/README.md +78 -0
- package/index.js +283 -0
- package/package.json +25 -0
package/.env
ADDED
package/.env.example
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ── Brioright API connection ──────────────────────────────────────────────────
|
|
2
|
+
BRIORIGHT_API_URL=https://brioright.online/api
|
|
3
|
+
BRIORIGHT_API_KEY=brio_your_key_here
|
|
4
|
+
BRIORIGHT_WORKSPACE_ID=your-workspace-slug
|
|
5
|
+
|
|
6
|
+
# ── Transport mode ────────────────────────────────────────────────────────────
|
|
7
|
+
# Leave unset (or set to "stdio") for Claude Desktop / Cursor
|
|
8
|
+
# Set to "http" for cloud AI clients (Antigravity, remote access)
|
|
9
|
+
MCP_TRANSPORT=http
|
|
10
|
+
|
|
11
|
+
# ── HTTP/SSE mode settings (only used when MCP_TRANSPORT=http) ────────────────
|
|
12
|
+
MCP_PORT=4040
|
|
13
|
+
# Optional bearer token — cloud AI clients must pass: Authorization: Bearer <secret>
|
|
14
|
+
MCP_SECRET=change_me_to_a_strong_secret
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Brioright MCP Server
|
|
2
|
+
|
|
3
|
+
Connect AI assistants (Claude Desktop, Cursor, Antigravity) directly to your Brioright workspace.
|
|
4
|
+
|
|
5
|
+
## Transport Modes
|
|
6
|
+
This server supports two transport modes:
|
|
7
|
+
1. **Stdio mode (Local)**: Runs as a subprocess for local clients like Claude Desktop or Cursor.
|
|
8
|
+
2. **HTTP/SSE mode (Cloud)**: Runs as a persistent web server for cloud-based AI assistants (like Antigravity).
|
|
9
|
+
|
|
10
|
+
## Quick Setup
|
|
11
|
+
|
|
12
|
+
### 1. Generate an API Key
|
|
13
|
+
Log in to Brioright, then run from your browser console:
|
|
14
|
+
```javascript
|
|
15
|
+
const res = await fetch('/api/api-keys', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
credentials: 'include',
|
|
19
|
+
body: JSON.stringify({ name: 'My MCP Client' })
|
|
20
|
+
});
|
|
21
|
+
console.log(await res.json());
|
|
22
|
+
```
|
|
23
|
+
**Save the key**!
|
|
24
|
+
|
|
25
|
+
### 2. Configure the MCP Server
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cp .env.example .env
|
|
29
|
+
```
|
|
30
|
+
Edit `.env`:
|
|
31
|
+
```env
|
|
32
|
+
BRIORIGHT_API_URL=https://brioright.online/api
|
|
33
|
+
BRIORIGHT_API_KEY=brio_your_key_here
|
|
34
|
+
BRIORIGHT_WORKSPACE_ID=your-workspace-slug
|
|
35
|
+
|
|
36
|
+
# Leave unset (or "stdio") for Claude Desktop
|
|
37
|
+
# Set to "http" for cloud clients like Antigravity
|
|
38
|
+
MCP_TRANSPORT=http
|
|
39
|
+
MCP_PORT=4040
|
|
40
|
+
MCP_SECRET=change_me_to_a_strong_secret
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3A. Connect to Claude Desktop (Local)
|
|
44
|
+
Add to `%APPDATA%\Claude\claude_desktop_config.json`:
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"brioright": {
|
|
49
|
+
"command": "node",
|
|
50
|
+
"args": ["/absolute/path/to/mcp-server/index.js"],
|
|
51
|
+
"env": {
|
|
52
|
+
"BRIORIGHT_API_URL": "...",
|
|
53
|
+
"BRIORIGHT_API_KEY": "...",
|
|
54
|
+
"BRIORIGHT_WORKSPACE_ID": "..."
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 3B. Connect Cloud AI (Remote)
|
|
62
|
+
Deploy the server with `MCP_TRANSPORT=http` (via PM2).
|
|
63
|
+
Provide the cloud AI assistant your MCP server base URL: `http://your-server-ip:4040/sse`
|
|
64
|
+
And pass the Authorization Header: `Bearer your_secure_bearer_token`.
|
|
65
|
+
|
|
66
|
+
## Available Tools
|
|
67
|
+
| Tool | Description |
|
|
68
|
+
|------|-------------|
|
|
69
|
+
| `list_workspaces` | List all accessible workspaces |
|
|
70
|
+
| `list_projects` | List projects in a workspace |
|
|
71
|
+
| `list_tasks` | List tasks with optional status/priority filter |
|
|
72
|
+
| `get_task` | Get full task details |
|
|
73
|
+
| `create_task` | Create a task with title, priority, due date, assignee |
|
|
74
|
+
| `update_task` | Update any fields on a task |
|
|
75
|
+
| `complete_task` | Mark a task as done |
|
|
76
|
+
| `create_project` | Create a new project |
|
|
77
|
+
| `list_members` | List workspace members (for finding assignee IDs) |
|
|
78
|
+
| `get_workspace_summary` | Dashboard stats: task counts by status/priority |
|
package/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Brioright MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Supports two transport modes:
|
|
6
|
+
* 1. stdio (default) — for local clients: Claude Desktop, Cursor
|
|
7
|
+
* 2. http (MCP_TRANSPORT=http) — for cloud AI: Antigravity, remote clients
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node index.js # stdio mode
|
|
11
|
+
* MCP_TRANSPORT=http node index.js # HTTP/SSE mode (port from MCP_PORT, default 4040)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
15
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
16
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
|
|
17
|
+
import { z } from 'zod'
|
|
18
|
+
import axios from 'axios'
|
|
19
|
+
import express from 'express'
|
|
20
|
+
import cors from 'cors'
|
|
21
|
+
import { config } from 'dotenv'
|
|
22
|
+
import { fileURLToPath } from 'url'
|
|
23
|
+
import { dirname, join } from 'path'
|
|
24
|
+
|
|
25
|
+
// Load .env from mcp-server directory
|
|
26
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
27
|
+
config({ path: join(__dirname, '.env') })
|
|
28
|
+
|
|
29
|
+
const API_URL = process.env.BRIORIGHT_API_URL || 'http://localhost:3001/api'
|
|
30
|
+
const API_KEY = process.env.BRIORIGHT_API_KEY
|
|
31
|
+
const DEFAULT_WORKSPACE = process.env.BRIORIGHT_WORKSPACE_ID
|
|
32
|
+
const MCP_PORT = parseInt(process.env.MCP_PORT || '4040')
|
|
33
|
+
const MCP_SECRET = process.env.MCP_SECRET // Optional bearer token for HTTP mode
|
|
34
|
+
const USE_HTTP = process.env.MCP_TRANSPORT === 'http'
|
|
35
|
+
|
|
36
|
+
if (!API_KEY) {
|
|
37
|
+
process.stderr.write('[Brioright MCP] ERROR: BRIORIGHT_API_KEY is not set.\n')
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
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
|
+
})
|
|
47
|
+
|
|
48
|
+
async function call(method, path, data) {
|
|
49
|
+
try {
|
|
50
|
+
const res = await api({ method, url: path, data })
|
|
51
|
+
return res.data.data
|
|
52
|
+
} catch (err) {
|
|
53
|
+
const msg = err.response?.data?.message || err.message || 'Unknown error'
|
|
54
|
+
throw new Error(`Brioright API error: ${msg}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Build MCP Server (shared between both transports) ────────────────────────
|
|
59
|
+
function buildServer() {
|
|
60
|
+
const server = new McpServer({ name: 'brioright', version: '1.0.0' })
|
|
61
|
+
|
|
62
|
+
// ── 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')
|
|
66
|
+
const workspaces = data.workspaces || data
|
|
67
|
+
return { content: [{ type: 'text', text: JSON.stringify(workspaces.map(w => ({ id: w.id, slug: w.slug, name: w.name })), null, 2) }] }
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// ── list_projects ─────────────────────────────────────────────────────────
|
|
72
|
+
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 }) => {
|
|
75
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
76
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
77
|
+
const data = await call('GET', `/workspaces/${ws}/projects`)
|
|
78
|
+
const projects = data.projects || data
|
|
79
|
+
return { content: [{ type: 'text', text: JSON.stringify(projects.map(p => ({ id: p.id, name: p.name, status: p.status })), null, 2) }] }
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// ── list_tasks ────────────────────────────────────────────────────────────
|
|
84
|
+
server.tool('list_tasks', 'List tasks in a project with optional filters',
|
|
85
|
+
{
|
|
86
|
+
projectId: z.string().describe('Project ID'),
|
|
87
|
+
workspaceId: z.string().optional(),
|
|
88
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
89
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
90
|
+
limit: z.number().optional().default(20),
|
|
91
|
+
},
|
|
92
|
+
async ({ projectId, workspaceId, status, priority, limit }) => {
|
|
93
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
94
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
95
|
+
const params = new URLSearchParams()
|
|
96
|
+
if (status) params.set('status', status)
|
|
97
|
+
if (priority) params.set('priority', priority)
|
|
98
|
+
params.set('limit', String(limit || 20))
|
|
99
|
+
const data = await call('GET', `/workspaces/${ws}/projects/${projectId}/tasks?${params}`)
|
|
100
|
+
const tasks = data.tasks || data
|
|
101
|
+
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
|
+
}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
// ── get_task ──────────────────────────────────────────────────────────────
|
|
106
|
+
server.tool('get_task', 'Get full details of a single task',
|
|
107
|
+
{ taskId: z.string(), workspaceId: z.string().optional() },
|
|
108
|
+
async ({ taskId, workspaceId }) => {
|
|
109
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
110
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
111
|
+
const data = await call('GET', `/workspaces/${ws}/tasks/${taskId}`)
|
|
112
|
+
return { content: [{ type: 'text', text: JSON.stringify(data.task || data, null, 2) }] }
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
// ── create_task ───────────────────────────────────────────────────────────
|
|
117
|
+
server.tool('create_task', 'Create a new task in a Brioright project',
|
|
118
|
+
{
|
|
119
|
+
projectId: z.string().describe('Project ID'),
|
|
120
|
+
title: z.string().describe('Task title'),
|
|
121
|
+
description: z.string().optional(),
|
|
122
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done']).optional().default('todo'),
|
|
123
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium'),
|
|
124
|
+
dueDate: z.string().optional().describe('ISO date string e.g. 2026-03-15'),
|
|
125
|
+
assigneeId: z.string().optional(),
|
|
126
|
+
workspaceId: z.string().optional(),
|
|
127
|
+
},
|
|
128
|
+
async ({ projectId, title, description, status, priority, dueDate, assigneeId, workspaceId }) => {
|
|
129
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
130
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
131
|
+
const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks`, {
|
|
132
|
+
title, description, status, priority,
|
|
133
|
+
dueDate: dueDate ? new Date(dueDate).toISOString() : undefined,
|
|
134
|
+
assigneeId,
|
|
135
|
+
})
|
|
136
|
+
const task = data.task || data
|
|
137
|
+
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
|
+
}
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
// ── update_task ───────────────────────────────────────────────────────────
|
|
142
|
+
server.tool('update_task', 'Update fields on an existing task',
|
|
143
|
+
{
|
|
144
|
+
taskId: z.string(),
|
|
145
|
+
title: z.string().optional(),
|
|
146
|
+
description: z.string().optional(),
|
|
147
|
+
status: z.enum(['todo', 'in_progress', 'in_review', 'done', 'cancelled']).optional(),
|
|
148
|
+
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
|
|
149
|
+
dueDate: z.string().optional(),
|
|
150
|
+
assigneeId: z.string().optional(),
|
|
151
|
+
workspaceId: z.string().optional(),
|
|
152
|
+
},
|
|
153
|
+
async ({ taskId, workspaceId, ...fields }) => {
|
|
154
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
155
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
156
|
+
const updates = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined))
|
|
157
|
+
if (updates.dueDate) updates.dueDate = new Date(updates.dueDate).toISOString()
|
|
158
|
+
const data = await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, updates)
|
|
159
|
+
const task = data.task || data
|
|
160
|
+
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
|
+
}
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
// ── complete_task ─────────────────────────────────────────────────────────
|
|
165
|
+
server.tool('complete_task', 'Mark a task as completed',
|
|
166
|
+
{ taskId: z.string(), workspaceId: z.string().optional() },
|
|
167
|
+
async ({ taskId, workspaceId }) => {
|
|
168
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
169
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
170
|
+
await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, { status: 'done' })
|
|
171
|
+
return { content: [{ type: 'text', text: `✅ Task ${taskId} marked as done.` }] }
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
// ── create_project ────────────────────────────────────────────────────────
|
|
176
|
+
server.tool('create_project', 'Create a new project in a workspace',
|
|
177
|
+
{
|
|
178
|
+
name: z.string(),
|
|
179
|
+
description: z.string().optional(),
|
|
180
|
+
color: z.string().optional().default('#6366f1'),
|
|
181
|
+
workspaceId: z.string().optional(),
|
|
182
|
+
},
|
|
183
|
+
async ({ name, description, color, workspaceId }) => {
|
|
184
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
185
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
186
|
+
const data = await call('POST', `/workspaces/${ws}/projects`, { name, description, color })
|
|
187
|
+
const project = data.project || data
|
|
188
|
+
return { content: [{ type: 'text', text: `✅ Project created!\n\n${JSON.stringify({ id: project.id, name: project.name }, null, 2)}` }] }
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
// ── list_members ──────────────────────────────────────────────────────────
|
|
193
|
+
server.tool('list_members', 'List workspace members (useful for finding assignee IDs)',
|
|
194
|
+
{ workspaceId: z.string().optional() },
|
|
195
|
+
async ({ workspaceId }) => {
|
|
196
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
197
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
198
|
+
const data = await call('GET', `/workspaces/${ws}/members`)
|
|
199
|
+
const members = data.members || data
|
|
200
|
+
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
|
+
}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
// ── get_workspace_summary ─────────────────────────────────────────────────
|
|
205
|
+
server.tool('get_workspace_summary', 'Dashboard stats: task counts by status and priority',
|
|
206
|
+
{ workspaceId: z.string().optional() },
|
|
207
|
+
async ({ workspaceId }) => {
|
|
208
|
+
const ws = workspaceId || DEFAULT_WORKSPACE
|
|
209
|
+
if (!ws) throw new Error('workspaceId is required')
|
|
210
|
+
const data = await call('GET', `/workspaces/${ws}/dashboard/stats`)
|
|
211
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return server
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ── HTTP/SSE transport (for cloud AI clients) ─────────────────────────────────
|
|
219
|
+
if (USE_HTTP) {
|
|
220
|
+
const app = express()
|
|
221
|
+
app.use(express.json())
|
|
222
|
+
app.use(cors({
|
|
223
|
+
origin: '*', // Cloud AI clients can come from any origin
|
|
224
|
+
exposedHeaders: ['Content-Type', 'Cache-Control', 'X-Accel-Buffering'],
|
|
225
|
+
}))
|
|
226
|
+
|
|
227
|
+
// Optional bearer token auth for the HTTP endpoint
|
|
228
|
+
app.use((req, res, next) => {
|
|
229
|
+
if (MCP_SECRET) {
|
|
230
|
+
const authHeader = req.headers.authorization || ''
|
|
231
|
+
const token = authHeader.replace('Bearer ', '')
|
|
232
|
+
if (token !== MCP_SECRET) {
|
|
233
|
+
return res.status(401).json({ error: 'Unauthorized — invalid MCP_SECRET' })
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
next()
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Track active SSE transports by session
|
|
240
|
+
const transports = new Map()
|
|
241
|
+
|
|
242
|
+
// SSE endpoint — client connects here first to get a session
|
|
243
|
+
app.get('/sse', async (req, res) => {
|
|
244
|
+
process.stderr.write(`[Brioright MCP] New SSE connection from ${req.ip}\n`)
|
|
245
|
+
const transport = new SSEServerTransport('/messages', res)
|
|
246
|
+
transports.set(transport.sessionId, transport)
|
|
247
|
+
|
|
248
|
+
// Clean up on disconnect
|
|
249
|
+
res.on('close', () => {
|
|
250
|
+
process.stderr.write(`[Brioright MCP] SSE session ${transport.sessionId} closed\n`)
|
|
251
|
+
transports.delete(transport.sessionId)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const server = buildServer()
|
|
255
|
+
await server.connect(transport)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Messages endpoint — tool call requests come here
|
|
259
|
+
app.post('/messages', async (req, res) => {
|
|
260
|
+
const sessionId = req.query.sessionId
|
|
261
|
+
const transport = transports.get(sessionId)
|
|
262
|
+
if (!transport) {
|
|
263
|
+
return res.status(404).json({ error: `Session ${sessionId} not found` })
|
|
264
|
+
}
|
|
265
|
+
await transport.handlePostMessage(req, res)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
// Health check
|
|
269
|
+
app.get('/health', (req, res) => res.json({ status: 'ok', server: 'brioright-mcp', transport: 'http/sse' }))
|
|
270
|
+
|
|
271
|
+
app.listen(MCP_PORT, () => {
|
|
272
|
+
process.stderr.write(`[Brioright MCP] HTTP/SSE server running on port ${MCP_PORT}\n`)
|
|
273
|
+
process.stderr.write(`[Brioright MCP] SSE endpoint: http://0.0.0.0:${MCP_PORT}/sse\n`)
|
|
274
|
+
process.stderr.write(`[Brioright MCP] Messages endpoint: http://0.0.0.0:${MCP_PORT}/messages\n`)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// ── Stdio transport (for local clients: Claude Desktop, Cursor) ───────────────
|
|
278
|
+
} else {
|
|
279
|
+
const server = buildServer()
|
|
280
|
+
const transport = new StdioServerTransport()
|
|
281
|
+
await server.connect(transport)
|
|
282
|
+
process.stderr.write('[Brioright MCP] Stdio server running. Listening for MCP requests...\n')
|
|
283
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "brioright-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for Brioright — lets AI assistants create and manage tasks via natural language",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"brioright-mcp": "index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"dev": "node --watch index.js",
|
|
13
|
+
"inspect": "npx @modelcontextprotocol/inspector node index.js"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@modelcontextprotocol/sdk": "^1.7.0",
|
|
17
|
+
"axios": "^1.7.0",
|
|
18
|
+
"cors": "^2.8.6",
|
|
19
|
+
"dotenv": "^16.4.0",
|
|
20
|
+
"express": "^4.22.1"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|