brioright-mcp 1.6.0 → 1.7.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 (2) hide show
  1. package/index.js +141 -0
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -701,6 +701,147 @@ function buildServer() {
701
701
  }
702
702
  )
703
703
 
704
+ // ── start_work ─────────────────────────────────────────────────────────────
705
+ server.tool('start_work',
706
+ 'Signals that AI has STARTED working on a task. Creates a task with status=in_progress and returns its ID. Call this at the BEGINNING of any significant work session (feature, bug fix, refactor, UI change, etc.) so the work is tracked from the start. Returns a taskId — pass it to finish_work when the work is complete. Pair: start_work → finish_work.',
707
+ {
708
+ projectId: z.string().describe('Project ID to log the work against — use list_projects if unknown.'),
709
+ title: z.string().describe('Clear title for the work being started, e.g. "Refactor login page UI" or "Fix avatar upload bug".'),
710
+ description: z.string().optional().describe('Optional: what will be done, the approach, and which files/areas will be affected.'),
711
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium').describe('Priority of the work. Use urgent for blockers.'),
712
+ workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
713
+ apiKey: z.string().optional().describe('Brioright API key.')
714
+ },
715
+ async ({ projectId, title, description, priority, workspaceId, apiKey }) => {
716
+ const ws = workspaceId || DEFAULT_WORKSPACE
717
+ if (!ws) throw new Error('workspaceId is required')
718
+ const startedAt = new Date().toLocaleString()
719
+ const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks`, {
720
+ title,
721
+ description: description
722
+ ? `${description}\n\n---\n_🤖 Work started by AI agent at ${startedAt}_`
723
+ : `🤖 Work started by AI agent at ${startedAt}.\n\n_Call \`finish_work\` with this task ID when the work is complete._`,
724
+ status: 'in_progress',
725
+ priority: priority || 'medium',
726
+ }, apiKey)
727
+ const task = data.task || data
728
+ return { content: [{ type: 'text', text: `🚀 Work started and logged!\n\nTask ID: **${task.id}**\nTitle: ${task.title}\nStatus: in_progress\n\n👉 Remember to call \`finish_work\` with taskId="${task.id}" when done.` }] }
729
+ }
730
+ )
731
+
732
+ // ── finish_work ─────────────────────────────────────────────────────────────
733
+ server.tool('finish_work',
734
+ 'Signals that AI has COMPLETED a piece of work. Marks the task as done and posts a structured markdown completion summary as a comment (what changed, files modified, key decisions). Use this to close a task that was opened with start_work. If you did not call start_work, use track_work instead for a one-shot retroactive log.',
735
+ {
736
+ taskId: z.string().describe('Task ID returned by start_work. This is the task to mark as done.'),
737
+ summary: z.string().describe('What was accomplished — be specific. Describe what changed, why, and any important decisions. Markdown is supported.'),
738
+ filesChanged: z.array(z.string()).optional().describe('List of files that were created or modified, e.g. ["src/components/Login.jsx", "src/styles/auth.css"]. Will be shown in the completion comment.'),
739
+ workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
740
+ apiKey: z.string().optional().describe('Brioright API key.')
741
+ },
742
+ async ({ taskId, summary, filesChanged, workspaceId, apiKey }) => {
743
+ const ws = workspaceId || DEFAULT_WORKSPACE
744
+ if (!ws) throw new Error('workspaceId is required')
745
+
746
+ // Mark task as done
747
+ await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, { status: 'done' }, apiKey)
748
+
749
+ // Build a clean structured completion comment
750
+ const fileSection = filesChanged?.length
751
+ ? `\n\n### 📁 Files Changed\n${filesChanged.map(f => `- \`${f}\``).join('\n')}`
752
+ : ''
753
+ const comment = [
754
+ `## ✅ Work Completed`,
755
+ ``,
756
+ summary,
757
+ fileSection,
758
+ ``,
759
+ `---`,
760
+ `_🤖 Completed by AI agent at ${new Date().toLocaleString()}_`
761
+ ].join('\n')
762
+
763
+ await call('POST', `/workspaces/${ws}/tasks/${taskId}/comments`, { text: comment }, apiKey)
764
+
765
+ return { content: [{ type: 'text', text: `✅ Work finished and logged!\n\nTask ${taskId} → done\nCompletion summary posted as a comment on the task.` }] }
766
+ }
767
+ )
768
+
769
+ // ── track_work ─────────────────────────────────────────────────────────────
770
+ server.tool('track_work',
771
+ 'Smart one-shot work tracker. Call this AFTER completing any work session to automatically: (1) search for an existing matching task in the project, (2) reuse it or create a fresh one if not found, (3) mark it done, and (4) post a structured completion comment. This is the preferred tool when you did not call start_work at the beginning — it handles the full lifecycle in a single call. Ideal for retroactively logging completed work.',
772
+ {
773
+ projectId: z.string().describe('Project ID to log work against — use list_projects if unknown.'),
774
+ title: z.string().describe('Title of the work done. Used first to search for an existing task — be specific enough to match if one exists.'),
775
+ summary: z.string().describe('Detailed description of what was accomplished. Supports markdown. Include: what changed, why, key decisions, and caveats.'),
776
+ priority: z.enum(['low', 'medium', 'high', 'urgent']).optional().default('medium').describe('Priority for the task if a new one must be created.'),
777
+ filesChanged: z.array(z.string()).optional().describe('List of files created or modified. Shown in the completion comment for traceability.'),
778
+ workspaceId: z.string().optional().describe('Workspace slug. Defaults to BRIORIGHT_WORKSPACE_ID.'),
779
+ apiKey: z.string().optional().describe('Brioright API key.')
780
+ },
781
+ async ({ projectId, title, summary, priority, filesChanged, workspaceId, apiKey }) => {
782
+ const ws = workspaceId || DEFAULT_WORKSPACE
783
+ if (!ws) throw new Error('workspaceId is required')
784
+
785
+ let taskId = null
786
+ let action = 'created'
787
+
788
+ // Step 1: Search for an existing open task with a similar title
789
+ try {
790
+ const searchQuery = title.substring(0, 60).trim()
791
+ const searchData = await call('GET', `/workspaces/${ws}/search?q=${encodeURIComponent(searchQuery)}`, null, apiKey)
792
+ const { tasks = [] } = searchData
793
+ // Find a task in the same project that isn't already closed
794
+ const match = tasks.find(t =>
795
+ t.projectId === projectId &&
796
+ !['done', 'cancelled'].includes(t.status)
797
+ )
798
+ if (match) {
799
+ taskId = match.id
800
+ action = 'found_existing'
801
+ }
802
+ } catch {
803
+ // Search failed — proceed to create a new task
804
+ }
805
+
806
+ // Step 2: Create task if no match was found
807
+ if (!taskId) {
808
+ const data = await call('POST', `/workspaces/${ws}/projects/${projectId}/tasks`, {
809
+ title,
810
+ description: summary,
811
+ status: 'done',
812
+ priority: priority || 'medium',
813
+ }, apiKey)
814
+ const task = data.task || data
815
+ taskId = task.id
816
+ } else {
817
+ // Mark the found task as done
818
+ await call('PATCH', `/workspaces/${ws}/tasks/${taskId}`, { status: 'done' }, apiKey)
819
+ }
820
+
821
+ // Step 3: Post structured completion comment
822
+ const fileSection = filesChanged?.length
823
+ ? `\n\n### 📁 Files Changed\n${filesChanged.map(f => `- \`${f}\``).join('\n')}`
824
+ : ''
825
+ const comment = [
826
+ `## ✅ Work Completed`,
827
+ ``,
828
+ summary,
829
+ fileSection,
830
+ ``,
831
+ `---`,
832
+ `_🤖 Auto-tracked by AI agent via \`track_work\` at ${new Date().toLocaleString()}_`
833
+ ].join('\n')
834
+
835
+ await call('POST', `/workspaces/${ws}/tasks/${taskId}/comments`, { text: comment }, apiKey)
836
+
837
+ const actionLabel = action === 'found_existing'
838
+ ? `♻️ Found existing open task — marked it done`
839
+ : `🆕 No matching task found — created a new one`
840
+
841
+ return { content: [{ type: 'text', text: `✅ Work tracked!\n\n${actionLabel}\nTask ID: ${taskId}\nCompletion summary posted as a comment.` }] }
842
+ }
843
+ )
844
+
704
845
  return server
705
846
 
706
847
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brioright-mcp",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
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