@rigstate/mcp 0.7.6 → 0.7.8

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.6",
3
+ "version": "0.7.8",
4
4
  "description": "Rigstate MCP Server - Model Context Protocol for AI Editors",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,4 +1,4 @@
1
- import { ProjectContextResponse, TechStackInfo } from '../lib/types.js';
1
+ import { TechStackInfo } from '../lib/types.js';
2
2
 
3
3
  export async function buildProjectSummary(
4
4
  project: any,
@@ -8,14 +8,15 @@ export async function buildProjectSummary(
8
8
  agentTasks: any[],
9
9
  roadmapItems: any[],
10
10
  stackDef: any,
11
- supabase: any
11
+ supabase: any,
12
+ userId: string
12
13
  ): Promise<string> {
13
14
  const summaryParts: string[] = [];
14
15
 
15
16
  summaryParts.push(`Project Type: ${project.project_type?.toUpperCase() || 'UNKNOWN'}`);
16
17
 
17
18
  // Active Mission Parameters
18
- if (stackDef) {
19
+ if (stackDef || activeTask || nextTask) {
19
20
  summaryParts.push('\n=== ACTIVE MISSION PARAMETERS ===');
20
21
  if (activeTask) {
21
22
  summaryParts.push(`⚠️ CURRENT OBJECTIVE: T-${activeTask.step_number}: ${activeTask.title}`);
@@ -44,17 +45,23 @@ export async function buildProjectSummary(
44
45
  summaryParts.push(` Tags: ${activeTask.tags.join(', ')}`);
45
46
  }
46
47
 
47
- // Enhanced Feature Context
48
+ // Enhanced Feature Context (Secure)
48
49
  if (activeTask.feature_id) {
49
- const { data: feature } = await supabase
50
- .from('project_features')
51
- .select('name, description')
52
- .eq('id', activeTask.feature_id)
53
- .single();
54
-
55
- if (feature) {
56
- summaryParts.push(`\n Parent Feature: ${feature.name}`);
57
- summaryParts.push(` Feature Vision: ${feature.description}`);
50
+ try {
51
+ const { data: dbFeatures } = await supabase
52
+ .rpc('get_project_features_secure', {
53
+ p_project_id: project.id,
54
+ p_user_id: userId
55
+ });
56
+
57
+ const feature = dbFeatures?.find((f: any) => f.id === activeTask.feature_id);
58
+
59
+ if (feature) {
60
+ summaryParts.push(`\n Parent Feature: ${feature.name}`);
61
+ summaryParts.push(` Feature Vision: ${feature.description}`);
62
+ }
63
+ } catch (e) {
64
+ console.warn('Feature context fetch failed', e);
58
65
  }
59
66
  }
60
67
 
@@ -80,13 +87,15 @@ export async function buildProjectSummary(
80
87
  }
81
88
 
82
89
  summaryParts.push('\n=== CURRENT STACK ===');
83
- if (stackDef.frontend) summaryParts.push(`Frontend: ${stackDef.frontend.framework} (${stackDef.frontend.language})`);
84
- if (stackDef.backend) summaryParts.push(`Backend: ${stackDef.backend.service} (${stackDef.backend.database})`);
85
- if (stackDef.styling) summaryParts.push(`Styling: ${stackDef.styling.framework} ${stackDef.styling.library || ''}`);
86
- if (stackDef.hosting) summaryParts.push(`Infrastructure: ${stackDef.hosting.provider}`);
87
- } else {
88
- if (techStack.framework) summaryParts.push(`Framework: ${techStack.framework}`);
89
- if (techStack.orm) summaryParts.push(`ORM: ${techStack.orm}`);
90
+ if (stackDef) {
91
+ if (stackDef.frontend) summaryParts.push(`Frontend: ${stackDef.frontend.framework} (${stackDef.frontend.language})`);
92
+ if (stackDef.backend) summaryParts.push(`Backend: ${stackDef.backend.service} (${stackDef.backend.database})`);
93
+ if (stackDef.styling) summaryParts.push(`Styling: ${stackDef.styling.framework} ${stackDef.styling.library || ''}`);
94
+ if (stackDef.hosting) summaryParts.push(`Infrastructure: ${stackDef.hosting.provider}`);
95
+ } else {
96
+ if (techStack.framework) summaryParts.push(`Framework: ${techStack.framework}`);
97
+ if (techStack.orm) summaryParts.push(`ORM: ${techStack.orm}`);
98
+ }
90
99
  }
91
100
 
92
101
  if (project.description) {
@@ -103,8 +112,8 @@ export async function buildProjectSummary(
103
112
 
104
113
  if (spec.featureList && Array.isArray(spec.featureList)) {
105
114
  summaryParts.push('\nKey Features & Nuances:');
106
- spec.featureList.filter((f: any) => f.priority === 'MVP').forEach((f: any) => {
107
- summaryParts.push(`- ${f.name}: ${f.description}`);
115
+ spec.featureList.filter((f: any) => f.priority === 'MVP' || f.priority === 'HIGH').slice(0, 10).forEach((f: any) => {
116
+ summaryParts.push(`- ${f.name}: ${f.description?.substring(0, 200)}`);
108
117
  });
109
118
  }
110
119
  }
@@ -124,7 +133,7 @@ export async function buildProjectSummary(
124
133
  summaryParts.push('\nLatest AI Executions:');
125
134
  agentTasks.forEach((t: any) => {
126
135
  const time = t.completed_at ? new Date(t.completed_at).toLocaleString() : 'Recently';
127
- summaryParts.push(`- [${time}] ${t.roadmap_title || 'Task'}: ${t.execution_summary || 'Completed'}`);
136
+ summaryParts.push(`- [${time}] ${t.roadmap_title || 'Task'}: ${t.execution_summary?.substring(0, 100) || 'Completed'}`);
128
137
  });
129
138
  }
130
139
 
@@ -50,52 +50,54 @@ export async function getProjectContext(
50
50
  userId: string,
51
51
  projectId: string
52
52
  ): Promise<ProjectContextResponse> {
53
- // Define RPC response type
54
- interface ProjectContextRow {
55
- id: string;
56
- name: string;
57
- description: string;
58
- project_type: string;
59
- created_at: string;
60
- last_indexed_at: string;
61
- detected_stack: any;
62
- repository_tree: any;
63
- architectural_dna: any;
64
- functional_spec: any;
65
- }
66
-
67
- // Fetch project securely via RPC (bypasses RLS with strict checks)
68
- const { data: rawData, error: projectError } = await supabase
69
- .rpc('get_project_context_secure', {
70
- p_project_id: projectId,
71
- p_user_id: userId
72
- })
73
- .single();
53
+ // 1. Try secure RPC first
54
+ let projectRow: any = null;
55
+ let projectError: any = null;
74
56
 
75
- const projectRow = rawData as ProjectContextRow;
76
-
77
- if (projectError || !projectRow) {
78
- console.error('Project fetch failed:', projectError);
79
- throw new Error('Project not found or access denied');
57
+ try {
58
+ const { data, error } = await supabase
59
+ .rpc('get_project_context_secure', {
60
+ p_project_id: projectId,
61
+ p_user_id: userId
62
+ })
63
+ .single();
64
+
65
+ if (data && !error) {
66
+ projectRow = data;
67
+ } else {
68
+ projectError = error;
69
+ }
70
+ } catch (e) {
71
+ projectError = e;
80
72
  }
81
73
 
82
- // Map RPC result to tool structure
83
- const project = {
84
- id: projectRow.id,
85
- name: projectRow.name,
86
- description: projectRow.description,
87
- project_type: projectRow.project_type,
88
- created_at: projectRow.created_at,
89
- last_indexed_at: projectRow.last_indexed_at,
90
- detected_stack: projectRow.detected_stack,
91
- repository_tree: projectRow.repository_tree,
92
- functional_spec: projectRow.functional_spec
93
- };
74
+ // 2. Fallback to direct table query if RPC failed (Policies were updated to allow members)
75
+ if (!projectRow) {
76
+ console.error(`RPC get_project_context_secure failed for ${projectId}, falling back to direct query. Error:`, projectError);
77
+ const { data, error } = await supabase
78
+ .from('projects')
79
+ .select(`
80
+ id, name, description, project_type, created_at, last_indexed_at, detected_stack, repository_tree, functional_spec,
81
+ architectural_dna(stack_definition)
82
+ `)
83
+ .eq('id', projectId)
84
+ .single();
85
+
86
+ if (data) {
87
+ projectRow = {
88
+ ...data,
89
+ architectural_dna: Array.isArray(data.architectural_dna) ? data.architectural_dna[0] : data.architectural_dna
90
+ };
91
+ } else {
92
+ console.error('Project fetch failed completely:', error);
93
+ throw new Error(`Project ${projectId} not found or access denied. (User: ${userId})`);
94
+ }
95
+ }
94
96
 
95
- // DNA is now directly available
96
- const stackDef = (projectRow.architectural_dna as any)?.stack_definition;
97
+ const project = projectRow;
98
+ const stackDef = projectRow.architectural_dna?.stack_definition;
97
99
 
98
- // Continuity Loop: Fetch Active & Next Tasks via secure RPC
100
+ // 3. Continuity Loop: Fetch Active & Next Tasks
99
101
  const { data: allChunks } = await supabase
100
102
  .rpc('get_roadmap_chunks_secure', {
101
103
  p_project_id: projectId,
@@ -103,14 +105,12 @@ export async function getProjectContext(
103
105
  });
104
106
 
105
107
  const tasks = (allChunks || []) as any[];
106
-
107
- // In-memory filtering to match original logic
108
108
  const activeTask = tasks.find(t => ['IN_PROGRESS', 'ACTIVE'].includes(t.status));
109
109
  const nextTask = tasks
110
110
  .filter(t => ['PENDING', 'LOCKED'].includes(t.status))
111
111
  .sort((a, b) => a.step_number - b.step_number)[0];
112
112
 
113
- // Fetch Digest Data via secure RPC
113
+ // 4. Fetch Digest Data
114
114
  const { data: agentTasks } = await supabase
115
115
  .rpc('get_agent_bridge_secure', {
116
116
  p_project_id: projectId,
@@ -122,7 +122,7 @@ export async function getProjectContext(
122
122
  .sort((a, b) => new Date(b.updated_at || b.created_at).getTime() - new Date(a.updated_at || a.created_at).getTime())
123
123
  .slice(0, 2);
124
124
 
125
- // Parse tech stack from detected_stack
125
+ // 5. Build tech stack info
126
126
  const techStack: TechStackInfo = {
127
127
  framework: null,
128
128
  orm: null,
@@ -132,31 +132,21 @@ export async function getProjectContext(
132
132
  };
133
133
 
134
134
  if (project.detected_stack) {
135
- const stack = project.detected_stack as {
136
- dependencies?: Record<string, string>;
137
- devDependencies?: Record<string, string>;
138
- };
135
+ const stack = project.detected_stack as any;
139
136
  const allDeps = { ...stack.dependencies, ...stack.devDependencies };
140
137
 
141
138
  for (const [name, version] of Object.entries(allDeps)) {
142
139
  const cleanVersion = (version as string).replace(/[\^~]/g, '');
143
-
144
- // Categorize
145
140
  if (name === 'next' || name === 'react' || name === 'vue' || name === 'angular' || name === 'svelte') {
146
- if (!techStack.framework) {
147
- techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
148
- }
141
+ if (!techStack.framework) techStack.framework = `${KEY_LIBS[name] || name} ${cleanVersion}`;
149
142
  } else if (name.includes('prisma') || name.includes('drizzle') || name.includes('typeorm')) {
150
- if (!techStack.orm) {
151
- techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
152
- }
143
+ if (!techStack.orm) techStack.orm = `${KEY_LIBS[name] || name} ${cleanVersion}`;
153
144
  } else if (KEY_LIBS[name]) {
154
145
  techStack.keyLibraries.push(`${KEY_LIBS[name]} ${cleanVersion}`);
155
146
  }
156
147
  }
157
148
  }
158
149
 
159
- // Extract top folders from repository tree
160
150
  if (project.repository_tree && Array.isArray(project.repository_tree)) {
161
151
  techStack.topFolders = [...new Set(
162
152
  project.repository_tree
@@ -165,7 +155,6 @@ export async function getProjectContext(
165
155
  )].slice(0, 10) as string[];
166
156
  }
167
157
 
168
- // Build summary via utility
169
158
  const summary = await buildProjectSummary(
170
159
  project,
171
160
  techStack,
@@ -174,7 +163,8 @@ export async function getProjectContext(
174
163
  agentTasks || [],
175
164
  roadmapItems || [],
176
165
  stackDef,
177
- supabase
166
+ supabase,
167
+ userId
178
168
  );
179
169
 
180
170
  const response: ProjectContextResponse = {
@@ -190,18 +180,14 @@ export async function getProjectContext(
190
180
  summary
191
181
  };
192
182
 
193
- // Phase 8.5.5: Inject Global Context (Curator)
183
+ // Inject Global Context
194
184
  try {
195
185
  const curatorContext = await injectGlobalContext(supabase, userId, {
196
186
  frameworks: techStack.framework ? [techStack.framework] : [],
197
187
  libraries: techStack.keyLibraries
198
188
  });
199
-
200
- if (curatorContext) {
201
- response.summary += `\n\n=== CURATOR INTELLIGENCE ===${curatorContext}`;
202
- }
189
+ if (curatorContext) response.summary += `\n\n=== CURATOR INTELLIGENCE ===${curatorContext}`;
203
190
  } catch (e: any) {
204
- console.error('Failed to inject global context:', e);
205
191
  response.summary += `\n\n(Curator Context Unavailable: ${e.message})`;
206
192
  }
207
193
 
@@ -17,53 +17,45 @@ export const listFeaturesTool: ToolDefinition<typeof InputSchema> = {
17
17
  Useful for understanding the strategic context and major milestones.`,
18
18
  schema: InputSchema,
19
19
  handler: async ({ projectId }, { supabase, userId }) => {
20
- // 1. Verify project access
21
- const { data: hasAccess, error: accessError } = await supabase
22
- .rpc('check_project_access_secure', {
23
- p_project_id: projectId,
24
- p_user_id: userId
25
- });
20
+ // 1. Fetch project and features
21
+ let projectRow: any = null;
22
+ let dbFeatures: any[] = [];
23
+ let source = 'DB';
26
24
 
27
- if (accessError || !hasAccess) {
28
- throw new Error('Project not found or access denied');
25
+ try {
26
+ // Try secure gateway for project
27
+ const { data } = await supabase
28
+ .rpc('get_project_context_secure', { p_project_id: projectId, p_user_id: userId })
29
+ .single() as any;
30
+ projectRow = data;
31
+ } catch (e) {
32
+ // Fallback
33
+ const { data } = await supabase.from('projects').select('id, functional_spec').eq('id', projectId).single();
34
+ projectRow = data;
29
35
  }
30
36
 
31
- // 2. Fetch project details
32
- const { data: project, error: projectError } = await supabase
33
- .from('projects')
34
- .select('id, functional_spec')
35
- .eq('id', projectId)
36
- .single();
37
+ if (!projectRow) throw new Error(`Project ${projectId} not found or access denied.`);
37
38
 
38
- if (projectError || !project) {
39
- throw new Error('Project details not found');
39
+ try {
40
+ // Try secure gateway for features
41
+ const { data } = await supabase.rpc('get_project_features_secure', { p_project_id: projectId, p_user_id: userId });
42
+ dbFeatures = data || [];
43
+ } catch (e) {
44
+ // Fallback to direct table
45
+ const { data } = await supabase.from('project_features').select('id, name, description, status').eq('project_id', projectId);
46
+ dbFeatures = data || [];
40
47
  }
41
48
 
42
- // 2. Primary Strategy: Fetch from 'project_features'
43
- const { data: dbFeatures, error: dbError } = await supabase
44
- .from('project_features')
45
- .select('id, name, description, status')
46
- .eq('project_id', projectId)
47
- .neq('status', 'shadow') // Exclude shadow features by default unless asked
48
- .order('created_at', { ascending: false });
49
-
50
49
  let featuresList: any[] = [];
51
- let source = 'DB';
52
-
53
- if (!dbError && dbFeatures && dbFeatures.length > 0) {
54
- featuresList = dbFeatures.map(f => ({
55
- ...f,
56
- title: f.name // Map back to title specifically for uniform handling below
57
- }));
50
+ if (dbFeatures && dbFeatures.length > 0) {
51
+ featuresList = dbFeatures.map((f: any) => ({ ...f, title: f.name }));
58
52
  } else {
59
- // 3. Fallback Strategy: Extract from 'functional_spec'
53
+ // Fallback to spec
60
54
  source = 'FALLBACK_SPEC';
61
- // Log warning (In a real system, use a structured logger. Here we console.error to stderr)
62
- console.error(`[WARN] Project ${projectId}: 'project_features' empty or missing. Falling back to 'functional_spec'.`);
63
-
64
- const spec = project.functional_spec as any;
65
- if (spec && typeof spec === 'object' && Array.isArray(spec.features)) {
66
- featuresList = spec.features.map((f: any) => ({
55
+ const spec = projectRow.functional_spec as any;
56
+ const features = spec?.featureList || spec?.features;
57
+ if (Array.isArray(features)) {
58
+ featuresList = features.map((f: any) => ({
67
59
  id: 'legacy',
68
60
  title: f.name || f.title,
69
61
  description: f.description,
@@ -72,22 +64,15 @@ Useful for understanding the strategic context and major milestones.`,
72
64
  }
73
65
  }
74
66
 
75
- // 4. Format Response
76
67
  if (featuresList.length === 0) {
77
- return { content: [{ type: 'text', text: 'No active features found (checked DB and Spec).' }] };
68
+ return { content: [{ type: 'text', text: 'No active features found.' }] };
78
69
  }
79
70
 
80
71
  const formatted = `=== PROJECT FEATURES (Source: ${source}) ===\n` +
81
- featuresList.map(f => {
82
- return `- ${f.title} [${f.status}]`;
83
- }).join('\n');
72
+ featuresList.map(f => `- ${f.title} [${f.status}]`).join('\n');
84
73
 
85
- return {
86
- content: [{ type: 'text', text: formatted }]
87
- };
74
+ return { content: [{ type: 'text', text: formatted }] };
88
75
  }
89
76
  };
90
77
 
91
78
  registry.register(listFeaturesTool);
92
-
93
-
@@ -60,25 +60,56 @@ export async function saveToProjectBrain(
60
60
 
61
61
  const fullContent = `# ${title}\n\n${content}`;
62
62
 
63
- const { data, error } = await supabase
64
- .from('project_memories')
65
- .insert({
66
- project_id: projectId,
67
- content: fullContent,
68
- category: category.toLowerCase(),
69
- tags: tags,
70
- importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5,
71
- is_active: true,
72
- source_type: 'chat_manual'
73
- })
74
- .select('id')
75
- .single();
63
+ let memoryId: string | null = null;
64
+ let saveError: any = null;
65
+
66
+ try {
67
+ // 1. Try secure RPC
68
+ const { data, error } = await supabase.rpc('save_project_memory_secure', {
69
+ p_project_id: projectId,
70
+ p_user_id: userId,
71
+ p_content: fullContent,
72
+ p_category: category.toLowerCase(),
73
+ p_tags: tags || [],
74
+ p_importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5
75
+ });
76
+ if (data && !error) {
77
+ memoryId = data;
78
+ } else {
79
+ saveError = error;
80
+ }
81
+ } catch (e) {
82
+ saveError = e;
83
+ }
76
84
 
77
- if (error) throw new Error(`Failed to save memory: ${error.message}`);
85
+ // 2. Fallback to direct insert (RLS was updated to allow members)
86
+ if (!memoryId) {
87
+ console.warn('RPC save_project_memory_secure failed, trying direct insert. Error:', saveError);
88
+ const { data, error } = await supabase
89
+ .from('project_memories')
90
+ .insert({
91
+ project_id: projectId,
92
+ content: fullContent,
93
+ category: category.toLowerCase(),
94
+ tags: tags || [],
95
+ importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5,
96
+ is_active: true,
97
+ source_type: 'chat_manual'
98
+ })
99
+ .select('id')
100
+ .single();
101
+
102
+ if (data) {
103
+ memoryId = data.id;
104
+ } else {
105
+ console.error('Direct insert failed:', error);
106
+ throw new Error(`Failed to save memory: ${error?.message || 'Unknown error'}`);
107
+ }
108
+ }
78
109
 
79
110
  return {
80
111
  success: true,
81
- memoryId: data.id,
112
+ memoryId: memoryId,
82
113
  message: `✅ Saved [${category}] "${title}" to Project Brain.`
83
114
  };
84
115
  }