@rigstate/mcp 0.7.3 → 0.7.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rigstate/mcp",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Rigstate MCP Server - Model Context Protocol for AI Editors",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -7,10 +7,13 @@ import { z } from 'zod';
7
7
  export const QueryGlobalAntidotesSchema = z.object({
8
8
  categories: z.array(z.string()).optional().describe('Filter by categories (SECURITY, ARCHITECTURE, UX, PERFORMANCE, ACCESSIBILITY, MAINTAINABILITY)'),
9
9
  severities: z.array(z.string()).optional().describe('Filter by severity (CRITICAL, HIGH, MEDIUM, LOW)'),
10
- framework_tags: z.array(z.string()).optional().describe('Filter by framework tags (e.g., ["nextjs", "react", "supabase"])'),
11
- min_trust_score: z.number().optional().describe('Minimum trust score (0-100)'),
10
+ framework_tags: z.preprocess(
11
+ (val) => (typeof val === 'string' ? JSON.parse(val) : val),
12
+ z.array(z.string())
13
+ ).optional().describe('Filter by framework tags (e.g., ["nextjs", "react", "supabase"])'),
14
+ min_trust_score: z.coerce.number().optional().describe('Minimum trust score (0-100)'),
12
15
  search_text: z.string().optional().describe('Search in title and instruction'),
13
- limit: z.number().optional().describe('Max results (default: 20)')
16
+ limit: z.coerce.number().optional().describe('Max results (default: 20)')
14
17
  });
15
18
 
16
19
  export const SubmitSignalSchema = z.object({
@@ -21,7 +24,10 @@ export const SubmitSignalSchema = z.object({
21
24
  severity: z.enum(['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']).describe('Severity level'),
22
25
  example: z.string().optional().describe('Good example demonstrating the instruction'),
23
26
  anti_example: z.string().optional().describe('Bad example showing what NOT to do'),
24
- framework_tags: z.array(z.string()).optional().describe('Relevant framework tags'),
27
+ framework_tags: z.preprocess(
28
+ (val) => (typeof val === 'string' ? [val] : val),
29
+ z.array(z.string())
30
+ ).optional().describe('Relevant framework tags'),
25
31
  reasoning: z.string().optional().describe('Why this signal should be added'),
26
32
  source_type: z.string().optional().describe('Internal source type override (e.g. TEACHER_MODE)')
27
33
  });
@@ -7,8 +7,8 @@ import { z } from 'zod';
7
7
  export const QueryBrainInputSchema = z.object({
8
8
  projectId: z.string().uuid('Invalid project ID'),
9
9
  query: z.string().min(1, 'Query is required'),
10
- limit: z.number().min(1).max(20).optional().default(8),
11
- threshold: z.number().min(0).max(1).optional().default(0.1)
10
+ limit: z.coerce.number().min(1).max(20).optional().default(8),
11
+ threshold: z.coerce.number().min(0).max(1).optional().default(0.1)
12
12
  });
13
13
 
14
14
  export const GetProjectContextInputSchema = z.object({
@@ -17,7 +17,7 @@ export const GetProjectContextInputSchema = z.object({
17
17
 
18
18
  export const GetLatestDecisionsInputSchema = z.object({
19
19
  projectId: z.string().uuid('Invalid project ID'),
20
- limit: z.number().min(1).max(10).optional().default(5)
20
+ limit: z.coerce.number().min(1).max(10).optional().default(5)
21
21
  });
22
22
 
23
23
  export const SaveDecisionInputSchema = z.object({
@@ -26,7 +26,7 @@ export const SaveDecisionInputSchema = z.object({
26
26
  decision: z.string().min(1, 'Decision content is required'),
27
27
  rationale: z.string().optional(),
28
28
  category: z.enum(['decision', 'architecture', 'constraint', 'tech_stack', 'design_rule']).optional().default('decision'),
29
- tags: z.array(z.string()).optional().default([])
29
+ tags: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string())).optional().default([])
30
30
  });
31
31
 
32
32
  export const SubmitIdeaInputSchema = z.object({
@@ -34,7 +34,7 @@ export const SubmitIdeaInputSchema = z.object({
34
34
  title: z.string().min(1, 'Title is required').max(200, 'Title too long'),
35
35
  description: z.string().min(1, 'Description is required'),
36
36
  category: z.enum(['feature', 'improvement', 'experiment', 'pivot']).optional().default('feature'),
37
- tags: z.array(z.string()).optional().default([])
37
+ tags: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string())).optional().default([])
38
38
  });
39
39
 
40
40
  export const UpdateRoadmapInputSchema = z.object({
@@ -104,7 +104,7 @@ export const GenerateProfessionalPDFInputSchema = z.object({
104
104
  export const ArchaeologicalScanInputSchema = z.object({
105
105
  projectId: z.string().uuid('Invalid project ID'),
106
106
  gitLog: z.string().describe('Git log output'),
107
- fileTree: z.array(z.string()).describe('File paths')
107
+ fileTree: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string())).describe('File paths')
108
108
  });
109
109
 
110
110
  export const ImportGhostFeaturesInputSchema = z.object({
@@ -124,7 +124,7 @@ export const AuditSecurityIntegrityInputSchema = z.object({
124
124
  projectId: z.string().uuid(),
125
125
  filePath: z.string().min(1),
126
126
  content: z.string().min(1),
127
- rules: z.array(z.string()).optional()
127
+ rules: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string())).optional()
128
128
  });
129
129
 
130
130
  export const QueryProjectBrainInputSchema = QueryBrainInputSchema;
@@ -138,7 +138,7 @@ export const SaveToProjectBrainInputSchema = z.object({
138
138
  title: z.string().min(1),
139
139
  content: z.string().min(1),
140
140
  category: z.enum(['DECISION', 'ARCHITECTURE', 'NOTE', 'LESSON_LEARNED']).default('NOTE'),
141
- tags: z.array(z.string()).optional().default([])
141
+ tags: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string())).optional().default([])
142
142
  });
143
143
 
144
144
  export const UpdateRoadmapStatusInputSchema = z.object({
@@ -174,12 +174,12 @@ export const GenerateCursorRulesInputSchema = z.object({
174
174
 
175
175
  export const AnalyzeDatabasePerformanceInputSchema = z.object({
176
176
  projectId: z.string().uuid(),
177
- filePaths: z.array(z.string())
177
+ filePaths: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string()))
178
178
  });
179
179
 
180
180
  export const AuditIntegrityGateInputSchema = z.object({
181
181
  projectId: z.string().uuid(),
182
- filePaths: z.array(z.string()).optional().default([])
182
+ filePaths: z.preprocess((val) => (typeof val === 'string' ? [val] : val), z.array(z.string())).optional().default([])
183
183
  });
184
184
 
185
185
  export const CompleteRoadmapTaskInputSchema = z.object({
@@ -16,6 +16,7 @@ Useful for transitioning between tasks.`,
16
16
  handler: async (args, context) => {
17
17
  const result = await getNextRoadmapStep(
18
18
  context.supabase,
19
+ context.userId,
19
20
  args.projectId,
20
21
  args.currentStepId
21
22
  );
@@ -30,58 +31,62 @@ export interface GetNextRoadmapStepResponse {
30
31
 
31
32
  export async function getNextRoadmapStep(
32
33
  supabase: SupabaseClient,
34
+ userId: string,
33
35
  projectId: string,
34
36
  currentStepId?: string
35
37
  ): Promise<GetNextRoadmapStepResponse> {
36
38
 
39
+ // 1. Fetch all tasks via secure RPC
40
+ const { data: allTasks, error: fetchError } = await supabase
41
+ .rpc('get_roadmap_chunks_secure', {
42
+ p_project_id: projectId,
43
+ p_user_id: userId
44
+ });
45
+
46
+ if (fetchError) {
47
+ throw new Error(`Failed to fetch roadmap data: ${fetchError.message}`);
48
+ }
49
+
50
+ const tasks = (allTasks || []) as any[];
51
+
37
52
  // Strategy:
38
53
  // 1. If currentStepId is provided, look for the step with the next higher step_number.
39
- // 2. If not, look for the first 'ACTIVE' step.
40
- // 3. If no ACTIVE, look for the first 'LOCKED' step.
54
+ // 2. If not, look for the first 'ACTIVE' or 'IN_PROGRESS' step. Baseline.
55
+ // 3. If no ACTIVE, current baseline is 0.
41
56
 
42
57
  let currentStepNumber = 0;
43
58
 
44
59
  if (currentStepId) {
45
- const { data: current } = await supabase
46
- .from('roadmap_chunks')
47
- .select('step_number')
48
- .eq('id', currentStepId)
49
- .single();
50
-
60
+ const current = tasks.find(t => t.id === currentStepId);
51
61
  if (current) {
52
62
  currentStepNumber = current.step_number;
53
63
  }
54
64
  } else {
55
- // Try to find currently active step to establish baseline
56
- const { data: active } = await supabase
57
- .from('roadmap_chunks')
58
- .select('step_number')
59
- .eq('project_id', projectId)
60
- .in('status', ['ACTIVE', 'IN_PROGRESS'])
61
- .order('step_number', { ascending: true })
62
- .limit(1)
63
- .single();
65
+ // Find baseline: first ACTIVE/IN_PROGRESS
66
+ const active = tasks
67
+ .filter(t => ['ACTIVE', 'IN_PROGRESS'].includes(t.status))
68
+ .sort((a, b) => a.step_number - b.step_number)[0];
64
69
 
65
70
  if (active) {
66
- currentStepNumber = active.step_number;
71
+ // If we are looking for the "next" step and we have an active one,
72
+ // we usually want to start *from* that active one or its successor.
73
+ // For now, let's keep the baseline logic: if we have an active one, we are *on* it.
74
+ // But get_next is often called to FIND what to do.
75
+ // If we have an active one, THAT is the next thing to work on.
76
+
77
+ // Logic change: if no currentStepId provided, and we have an ACTIVE one,
78
+ // that ACTIVE one IS the next step.
79
+ return {
80
+ nextStep: active as RoadmapChunk,
81
+ message: `Current active step: [Step ${active.step_number}] ${active.title}`
82
+ };
67
83
  }
68
84
  }
69
85
 
70
- // Find the next step > currentStepNumber
71
- // We prioritize LOCKED or PENDING steps that come *after* the current one.
72
- const { data: nextStep, error } = await supabase
73
- .from('roadmap_chunks')
74
- .select('*')
75
- .eq('project_id', projectId)
76
- .gt('step_number', currentStepNumber)
77
- .neq('status', 'COMPLETED') // We want the next *workable* step
78
- .order('step_number', { ascending: true })
79
- .limit(1)
80
- .single();
81
-
82
- if (error && error.code !== 'PGRST116') { // PGRST116 is "Row not found" which is fine
83
- throw new Error(`Failed to fetch next roadmap step: ${error.message}`);
84
- }
86
+ // Find the next step > currentStepNumber that is not COMPLETED
87
+ const nextStep = tasks
88
+ .filter(t => t.step_number > currentStepNumber && t.status !== 'COMPLETED')
89
+ .sort((a, b) => a.step_number - b.step_number)[0];
85
90
 
86
91
  if (!nextStep) {
87
92
  return {
@@ -94,43 +94,32 @@ export async function getProjectContext(
94
94
 
95
95
 
96
96
 
97
- // Continuity Loop: Fetch Active & Next Tasks
98
- const [activeTaskResult, nextTaskResult] = await Promise.all([
99
- supabase
100
- .from('roadmap_chunks')
101
- .select('id, title, step_number, role, instruction_set')
102
- .eq('project_id', projectId)
103
- .in('status', ['IN_PROGRESS', 'ACTIVE'])
104
- .limit(1)
105
- .maybeSingle(),
106
- supabase
107
- .from('roadmap_chunks')
108
- .select('id, title, step_number, role')
109
- .eq('project_id', projectId)
110
- .in('status', ['PENDING', 'LOCKED'])
111
- .order('step_number', { ascending: true })
112
- .limit(1)
113
- .maybeSingle()
114
- ]);
115
-
116
- const activeTask = activeTaskResult.data;
117
- const nextTask = nextTaskResult.data;
118
-
119
- // Fetch Digest Data (Reduced to 2 to save tokens)
97
+ // Continuity Loop: Fetch Active & Next Tasks via secure RPC
98
+ const { data: allChunks, error: chunksError } = await supabase
99
+ .rpc('get_roadmap_chunks_secure', {
100
+ p_project_id: projectId,
101
+ p_user_id: userId
102
+ });
103
+
104
+ const tasks = (allChunks || []) as any[];
105
+
106
+ // In-memory filtering to match original logic
107
+ const activeTask = tasks.find(t => ['IN_PROGRESS', 'ACTIVE'].includes(t.status));
108
+ const nextTask = tasks
109
+ .filter(t => ['PENDING', 'LOCKED'].includes(t.status))
110
+ .sort((a, b) => a.step_number - b.step_number)[0];
111
+
112
+ // Fetch Digest Data via secure RPC
120
113
  const { data: agentTasks } = await supabase
121
- .from('agent_bridge')
122
- .select('id, roadmap_chunks(title), execution_summary, completed_at')
123
- .eq('project_id', projectId)
124
- .eq('status', 'COMPLETED')
125
- .order('completed_at', { ascending: false })
126
- .limit(2);
127
-
128
- const { data: roadmapItems } = await supabase
129
- .from('roadmap_chunks')
130
- .select('title, status, updated_at')
131
- .eq('project_id', projectId)
132
- .order('updated_at', { ascending: false })
133
- .limit(2);
114
+ .rpc('get_agent_bridge_secure', {
115
+ p_project_id: projectId,
116
+ p_user_id: userId,
117
+ p_limit: 2
118
+ });
119
+
120
+ const roadmapItems = tasks
121
+ .sort((a, b) => new Date(b.updated_at || b.created_at).getTime() - new Date(a.updated_at || a.created_at).getTime())
122
+ .slice(0, 2);
134
123
 
135
124
  // Parse tech stack from detected_stack
136
125
  const techStack: TechStackInfo = {
@@ -239,7 +228,7 @@ export async function getProjectContext(
239
228
  summaryParts.push('\nLatest AI Executions:');
240
229
  agentTasks.forEach((t: any) => {
241
230
  const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : 'Recently';
242
- summaryParts.push(`- [${time}] ${t.roadmap_chunks?.title || 'Task'}: ${t.execution_summary || 'Completed'}`);
231
+ summaryParts.push(`- [${time}] ${t.roadmap_title || 'Task'}: ${t.execution_summary || 'Completed'}`);
243
232
  });
244
233
  }
245
234
 
@@ -33,42 +33,42 @@ export async function listRoadmapTasks(
33
33
  userId: string,
34
34
  projectId: string
35
35
  ): Promise<ListRoadmapTasksResponse> {
36
- // 1. Verify project access
37
- const { data: hasAccess, error: accessError } = await supabase
38
- .rpc('check_project_access_secure', {
36
+ // 1. Fetch tasks via secure RPC (bypasses RLS)
37
+ const { data: tasks, error } = await supabase
38
+ .rpc('get_roadmap_chunks_secure', {
39
39
  p_project_id: projectId,
40
40
  p_user_id: userId
41
41
  });
42
42
 
43
- if (accessError || !hasAccess) {
44
- throw new Error('Project not found or access denied');
45
- }
46
-
47
- // 2. Fetch non-completed tasks
48
- const { data: tasks, error } = await supabase
49
- .from('roadmap_chunks')
50
- .select('id, title, priority, status, step_number, prompt_content')
51
- .eq('project_id', projectId)
52
- .neq('status', 'COMPLETED')
53
- .order('priority', { ascending: false })
54
- .order('step_number', { ascending: true });
55
-
56
43
  if (error) {
44
+ if (error.message.includes('Access Denied')) {
45
+ throw new Error('Project not found or access denied');
46
+ }
57
47
  console.error('Failed to fetch roadmap tasks:', error);
58
48
  throw new Error('Failed to fetch roadmap tasks');
59
49
  }
60
50
 
51
+ // 2. Filter non-completed in JS (to preserve original logic)
52
+ const activeTasks = (tasks || [])
53
+ .filter((t: any) => t.status !== 'COMPLETED')
54
+ .sort((a: any, b: any) => {
55
+ if (a.priority !== b.priority) {
56
+ const priorityOrder: any = { 'CRITICAL': 3, 'HIGH': 2, 'MEDIUM': 1, 'LOW': 0 };
57
+ return (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0);
58
+ }
59
+ return a.step_number - b.step_number;
60
+ });
61
+
61
62
  // 3. Format response
62
- const formatted = (tasks || []).length > 0
63
- ? (tasks || []).map(t => {
63
+ const formatted = activeTasks.length > 0
64
+ ? activeTasks.map((t: any) => {
64
65
  const statusEmoji = t.status === 'ACTIVE' ? '🔵' : '🔒';
65
- const priorityStr = t.priority ? `[${t.priority}]` : '';
66
66
  return `${statusEmoji} Step ${t.step_number}: ${t.title} (ID: ${t.id})`;
67
67
  }).join('\n')
68
68
  : 'No active or locked tasks found in the roadmap.';
69
69
 
70
70
  return {
71
- tasks: (tasks || []).map(t => ({
71
+ tasks: activeTasks.map((t: any) => ({
72
72
  id: t.id,
73
73
  title: t.title,
74
74
  priority: t.priority,