@rigstate/mcp 0.7.6 → 0.7.7

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.7",
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
 
@@ -165,7 +165,6 @@ export async function getProjectContext(
165
165
  )].slice(0, 10) as string[];
166
166
  }
167
167
 
168
- // Build summary via utility
169
168
  const summary = await buildProjectSummary(
170
169
  project,
171
170
  techStack,
@@ -174,7 +173,8 @@ export async function getProjectContext(
174
173
  agentTasks || [],
175
174
  roadmapItems || [],
176
175
  stackDef,
177
- supabase
176
+ supabase,
177
+ userId
178
178
  );
179
179
 
180
180
  const response: ProjectContextResponse = {
@@ -17,53 +17,41 @@ 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', {
20
+ // 1. Verify project access and fetch context
21
+ const { data: projectRow, error: projectError } = await supabase
22
+ .rpc('get_project_context_secure', {
23
23
  p_project_id: projectId,
24
24
  p_user_id: userId
25
- });
25
+ })
26
+ .single() as any;
26
27
 
27
- if (accessError || !hasAccess) {
28
- throw new Error('Project not found or access denied');
28
+ if (projectError || !projectRow) {
29
+ throw new Error('Project details not found or access denied');
29
30
  }
30
31
 
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
-
38
- if (projectError || !project) {
39
- throw new Error('Project details not found');
40
- }
41
-
42
- // 2. Primary Strategy: Fetch from 'project_features'
32
+ // 2. Primary Strategy: Fetch from 'project_features' via secure RPC
43
33
  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 });
34
+ .rpc('get_project_features_secure', {
35
+ p_project_id: projectId,
36
+ p_user_id: userId
37
+ });
49
38
 
50
39
  let featuresList: any[] = [];
51
40
  let source = 'DB';
52
41
 
53
42
  if (!dbError && dbFeatures && dbFeatures.length > 0) {
54
- featuresList = dbFeatures.map(f => ({
43
+ featuresList = dbFeatures.map((f: any) => ({
55
44
  ...f,
56
- title: f.name // Map back to title specifically for uniform handling below
45
+ title: f.name
57
46
  }));
58
47
  } else {
59
48
  // 3. Fallback Strategy: Extract from 'functional_spec'
60
49
  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'.`);
50
+ const spec = projectRow.functional_spec as any;
51
+ const features = spec?.featureList || spec?.features;
63
52
 
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) => ({
53
+ if (Array.isArray(features)) {
54
+ featuresList = features.map((f: any) => ({
67
55
  id: 'legacy',
68
56
  title: f.name || f.title,
69
57
  description: f.description,
@@ -89,5 +77,3 @@ Useful for understanding the strategic context and major milestones.`,
89
77
  };
90
78
 
91
79
  registry.register(listFeaturesTool);
92
-
93
-
@@ -60,25 +60,21 @@ 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
+ const { data: memoryId, error } = await supabase
64
+ .rpc('save_project_memory_secure', {
65
+ p_project_id: projectId,
66
+ p_user_id: userId,
67
+ p_content: fullContent,
68
+ p_category: category.toLowerCase(),
69
+ p_tags: tags || [],
70
+ p_importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5
71
+ });
76
72
 
77
73
  if (error) throw new Error(`Failed to save memory: ${error.message}`);
78
74
 
79
75
  return {
80
76
  success: true,
81
- memoryId: data.id,
77
+ memoryId: memoryId,
82
78
  message: `✅ Saved [${category}] "${title}" to Project Brain.`
83
79
  };
84
80
  }
@@ -87,42 +87,22 @@ export async function queryBrain(
87
87
  }
88
88
 
89
89
  // Try semantic search first using the match_memories RPC
90
- // This requires the embedding to be generated, so we'll try
91
- // Generate embedding if possible for semantic search
92
90
  const embedding = await generateQueryEmbedding(query);
93
91
  let memories: MemoryRecord[] = [];
94
92
 
95
- // Use the hybrid search RPC
93
+ // Use the secure search RPC
96
94
  const { data: searchResults, error: searchError } = await supabase
97
- .rpc('hybrid_search_memories', {
95
+ .rpc('query_project_brain_secure', {
98
96
  p_project_id: projectId,
97
+ p_user_id: userId,
99
98
  p_query: query,
100
- p_embedding: embedding || null,
101
- p_limit: limit,
102
- p_similarity_threshold: threshold || 0.1
99
+ p_limit: limit
103
100
  });
104
101
 
105
- if (searchError) {
106
- // Fallback to basic recent fetch if RPC fails
107
- const { data: recentMemories } = await supabase
108
- .from('project_memories')
109
- .select('id, content, category, tags, importance, created_at')
110
- .eq('project_id', projectId)
111
- .eq('is_active', true)
112
- .order('created_at', { ascending: false })
113
- .limit(limit);
114
-
115
- if (recentMemories) {
116
- memories = recentMemories.map(m => ({
117
- id: m.id,
118
- content: m.content,
119
- category: m.category || 'general',
120
- tags: m.tags || [],
121
- netVotes: m.importance || 0,
122
- createdAt: m.created_at
123
- }));
124
- }
125
- } else if (searchResults) {
102
+ if (searchError || !searchResults) {
103
+ console.error('Brain query failed:', searchError);
104
+ memories = [];
105
+ } else {
126
106
  memories = searchResults.map((m: any) => ({
127
107
  id: m.id,
128
108
  content: m.content,
@@ -133,31 +113,39 @@ export async function queryBrain(
133
113
  }));
134
114
  }
135
115
 
136
- // --- NEW: FETCH RELEVANT FEATURES ---
137
- // Simple fuzzy search until we vector embedding for features.
116
+ // --- FETCH RELEVANT FEATURES FROM SPEC ---
138
117
  let relevantFeatures: any[] = [];
139
118
  try {
140
- const { data: features } = await supabase
141
- .from('project_features')
142
- .select('name, status, description')
143
- .eq('project_id', projectId)
144
- .or(`name.ilike.%${query}%,description.ilike.%${query}%`)
145
- .limit(3);
146
-
147
- if (features) relevantFeatures = features;
119
+ const { data: projectRow } = await supabase
120
+ .rpc('get_project_context_secure', {
121
+ p_project_id: projectId,
122
+ p_user_id: userId
123
+ })
124
+ .single() as any;
125
+
126
+ if (projectRow?.functional_spec) {
127
+ const spec = typeof projectRow.functional_spec === 'string'
128
+ ? JSON.parse(projectRow.functional_spec)
129
+ : projectRow.functional_spec;
130
+
131
+ const features = spec.featureList || spec.features || [];
132
+ relevantFeatures = features
133
+ .filter((f: any) => (f.name || f.title)?.toLowerCase().includes(query.toLowerCase()))
134
+ .slice(0, 3);
135
+ }
148
136
  } catch (e) {
149
137
  console.warn('Feature fetch failed in brain query', e);
150
138
  }
151
139
 
152
140
  // Format memories into a readable context block
153
- const contextLines = memories.map((m) => {
141
+ const contextLines = memories.map((m: any) => {
154
142
  const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [⚠️ POORLY RATED: ${m.netVotes}]` : '';
155
143
  const tagStr = m.tags && m.tags.length > 0 ? ` [${m.tags.join(', ')}]` : '';
156
144
  const category = m.category ? m.category.toUpperCase() : 'GENERAL';
157
145
  return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
158
146
  });
159
147
 
160
- const searchType = embedding ? 'TRIPLE-HYBRID (Vector + FTS + Fuzzy)' : 'HYBRID (FTS + Fuzzy)';
148
+ const searchType = embedding ? 'TRIPLE-HYBRID (Vector + FTS + Fuzzy)' : 'SECURE-GATEWAY (Fuzzy)';
161
149
 
162
150
  let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
163
151
  Search Mode: ${searchType}
@@ -165,7 +153,7 @@ Query: "${query}"`;
165
153
 
166
154
  if (relevantFeatures.length > 0) {
167
155
  formatted += `\n\n=== RELATED FEATURES ===\n` +
168
- relevantFeatures.map((f: any) => `- ${f.name} [${f.status}]`).join('\n');
156
+ relevantFeatures.map((f: any) => `- ${f.name || f.title} [${f.status || 'Active'}]`).join('\n');
169
157
  }
170
158
 
171
159
  formatted += `\n\nFound ${memories.length} relevant memories:\n\n${contextLines.join('\n')}\n\n==========================================`;