@rigstate/mcp 0.7.7 → 0.7.9

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.7",
3
+ "version": "0.7.9",
4
4
  "description": "Rigstate MCP Server - Model Context Protocol for AI Editors",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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
@@ -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,39 +17,46 @@ 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 and fetch context
21
- const { data: projectRow, error: projectError } = await supabase
22
- .rpc('get_project_context_secure', {
23
- p_project_id: projectId,
24
- p_user_id: userId
25
- })
26
- .single() as any;
20
+ // 1. Fetch project and features
21
+ let projectRow: any = null;
22
+ let dbFeatures: any[] = [];
23
+ let source = 'DB';
24
+
25
+ try {
26
+ // 1. Try secure gateway for project context
27
+ const { data, error } = await supabase
28
+ .rpc('get_project_context_secure', { p_project_id: projectId, p_user_id: userId })
29
+ .single();
27
30
 
28
- if (projectError || !projectRow) {
29
- throw new Error('Project details not found or access denied');
31
+ if (error || !data) throw error || new Error('Project not found');
32
+ projectRow = data;
33
+ } catch (e) {
34
+ console.error('[list_features] Secure Project RPC failed:', e);
35
+ // Fallback: Direct table query (Requires proper RLS)
36
+ const { data } = await supabase.from('projects').select('id, functional_spec').eq('id', projectId).single();
37
+ projectRow = data;
30
38
  }
31
39
 
32
- // 2. Primary Strategy: Fetch from 'project_features' via secure RPC
33
- const { data: dbFeatures, error: dbError } = await supabase
34
- .rpc('get_project_features_secure', {
35
- p_project_id: projectId,
36
- p_user_id: userId
37
- });
40
+ if (!projectRow) throw new Error(`Project ${projectId} not found or access denied.`);
38
41
 
39
- let featuresList: any[] = [];
40
- let source = 'DB';
42
+ try {
43
+ // Try secure gateway for features
44
+ const { data } = await supabase.rpc('get_project_features_secure', { p_project_id: projectId, p_user_id: userId });
45
+ dbFeatures = data || [];
46
+ } catch (e) {
47
+ // Fallback to direct table
48
+ const { data } = await supabase.from('project_features').select('id, name, description, status').eq('project_id', projectId);
49
+ dbFeatures = data || [];
50
+ }
41
51
 
42
- if (!dbError && dbFeatures && dbFeatures.length > 0) {
43
- featuresList = dbFeatures.map((f: any) => ({
44
- ...f,
45
- title: f.name
46
- }));
52
+ let featuresList: any[] = [];
53
+ if (dbFeatures && dbFeatures.length > 0) {
54
+ featuresList = dbFeatures.map((f: any) => ({ ...f, title: f.name }));
47
55
  } else {
48
- // 3. Fallback Strategy: Extract from 'functional_spec'
56
+ // Fallback to spec
49
57
  source = 'FALLBACK_SPEC';
50
58
  const spec = projectRow.functional_spec as any;
51
59
  const features = spec?.featureList || spec?.features;
52
-
53
60
  if (Array.isArray(features)) {
54
61
  featuresList = features.map((f: any) => ({
55
62
  id: 'legacy',
@@ -60,19 +67,14 @@ Useful for understanding the strategic context and major milestones.`,
60
67
  }
61
68
  }
62
69
 
63
- // 4. Format Response
64
70
  if (featuresList.length === 0) {
65
- return { content: [{ type: 'text', text: 'No active features found (checked DB and Spec).' }] };
71
+ return { content: [{ type: 'text', text: 'No active features found.' }] };
66
72
  }
67
73
 
68
74
  const formatted = `=== PROJECT FEATURES (Source: ${source}) ===\n` +
69
- featuresList.map(f => {
70
- return `- ${f.title} [${f.status}]`;
71
- }).join('\n');
75
+ featuresList.map(f => `- ${f.title} [${f.status}]`).join('\n');
72
76
 
73
- return {
74
- content: [{ type: 'text', text: formatted }]
75
- };
77
+ return { content: [{ type: 'text', text: formatted }] };
76
78
  }
77
79
  };
78
80
 
@@ -60,8 +60,12 @@ export async function saveToProjectBrain(
60
60
 
61
61
  const fullContent = `# ${title}\n\n${content}`;
62
62
 
63
- const { data: memoryId, error } = await supabase
64
- .rpc('save_project_memory_secure', {
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', {
65
69
  p_project_id: projectId,
66
70
  p_user_id: userId,
67
71
  p_content: fullContent,
@@ -70,7 +74,42 @@ export async function saveToProjectBrain(
70
74
  p_importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5
71
75
  });
72
76
 
73
- if (error) throw new Error(`Failed to save memory: ${error.message}`);
77
+ if (error) {
78
+ console.error('[save_to_project_brain] RPC Error:', error);
79
+ saveError = error;
80
+ } else if (data) {
81
+ memoryId = data;
82
+ }
83
+ } catch (e) {
84
+ console.error('[save_to_project_brain] RPC Exception:', e);
85
+ saveError = e;
86
+ }
87
+
88
+ // 2. Fallback to direct insert (Requires proper RLS)
89
+ if (!memoryId) {
90
+ console.warn('[save_to_project_brain] Falling back to direct insert. RPC Error/Response was:', saveError);
91
+ const { data, error } = await supabase
92
+ .from('project_memories')
93
+ .insert({
94
+ project_id: projectId,
95
+ content: fullContent,
96
+ category: category.toLowerCase(),
97
+ tags: tags || [],
98
+ importance: (category === 'DECISION' || category === 'ARCHITECTURE') ? 9 : 5,
99
+ is_active: true,
100
+ source_type: 'chat_manual'
101
+ })
102
+ .select('id')
103
+ .single();
104
+
105
+ if (data) {
106
+ memoryId = (data as any).id;
107
+ } else {
108
+ console.error('[save_to_project_brain] Direct insert failed:', error);
109
+ const detail = error?.message || (saveError as any)?.message || 'Unknown error';
110
+ throw new Error(`Failed to save memory: ${detail}. (Note: secure RPC failed first)`);
111
+ }
112
+ }
74
113
 
75
114
  return {
76
115
  success: true,
@@ -2,8 +2,7 @@
2
2
  * Tool: query_brain
3
3
  *
4
4
  * Takes a natural language query and performs semantic search
5
- * against the project's memories (RAG), returning relevant
6
- * architecture rules, decisions, and constraints.
5
+ * against the project's memories (RAG).
7
6
  */
8
7
 
9
8
  import { SupabaseClient } from '@supabase/supabase-js';
@@ -11,116 +10,76 @@ import type { BrainQueryResponse, MemoryRecord } from '../lib/types.js';
11
10
  import { registry } from '../lib/tool-registry.js';
12
11
  import { QueryBrainInputSchema } from '../lib/schemas.js';
13
12
 
14
- // ============================================
15
- // Tool Registration
16
- // ============================================
17
-
18
13
  registry.register({
19
14
  name: 'query_brain',
20
- description: `Takes a natural language query and performs semantic search
21
- against the project's memories (RAG), returning relevant
22
- architecture rules, decisions, and constraints.`,
15
+ description: `Takes a natural language query and performs semantic search against the project's memories (RAG).`,
23
16
  schema: QueryBrainInputSchema,
24
17
  handler: async (args, context) => {
25
- const result = await queryBrain(
26
- context.supabase,
27
- context.userId,
28
- args.projectId,
29
- args.query,
30
- args.limit,
31
- args.threshold
32
- );
18
+ const result = await queryBrain(context.supabase, context.userId, args.projectId, args.query, args.limit);
33
19
  return { content: [{ type: 'text', text: result.formatted }] };
34
20
  }
35
21
  });
36
22
 
37
- // Generate embedding using the Rigstate Intelligence API (Proxy)
38
- async function generateQueryEmbedding(query: string): Promise<number[] | null> {
39
- const apiKey = process.env.RIGSTATE_API_KEY;
40
- const apiUrl = process.env.RIGSTATE_API_URL || 'http://localhost:3000/api/v1';
41
-
42
- if (!apiKey) {
43
- return null;
44
- }
45
-
46
- try {
47
- const response = await fetch(`${apiUrl}/intelligence/embed`, {
48
- method: 'POST',
49
- headers: {
50
- 'Content-Type': 'application/json',
51
- 'Authorization': `Bearer ${apiKey}`
52
- },
53
- body: JSON.stringify({ text: query })
54
- });
55
-
56
- if (!response.ok) {
57
- const errorText = await response.text();
58
- console.error(`Embedding API error (${response.status}):`, errorText);
59
- return null;
60
- }
61
-
62
- const result = await response.json() as any;
63
- return result.data?.embedding || null;
64
- } catch (error) {
65
- console.error('Failed to generate embedding via Proxy:', error);
66
- return null;
67
- }
68
- }
69
-
70
23
  export async function queryBrain(
71
24
  supabase: SupabaseClient,
72
25
  userId: string,
73
26
  projectId: string,
74
27
  query: string,
75
- limit: number = 8,
76
- threshold: number = 0.5
28
+ limit: number = 8
77
29
  ): Promise<BrainQueryResponse> {
78
- // First, verify project access
79
- const { data: hasAccess, error: accessError } = await supabase
80
- .rpc('check_project_access_secure', {
81
- p_project_id: projectId,
82
- p_user_id: userId
83
- });
84
-
85
- if (accessError || !hasAccess) {
86
- throw new Error('Project not found or access denied');
87
- }
88
-
89
- // Try semantic search first using the match_memories RPC
90
- const embedding = await generateQueryEmbedding(query);
91
30
  let memories: MemoryRecord[] = [];
31
+ let searchType = 'SECURE-GATEWAY';
92
32
 
93
- // Use the secure search RPC
94
- const { data: searchResults, error: searchError } = await supabase
95
- .rpc('query_project_brain_secure', {
33
+ try {
34
+ // Try secure search RPC
35
+ const { data, error } = await supabase.rpc('query_project_brain_secure', {
96
36
  p_project_id: projectId,
97
37
  p_user_id: userId,
98
38
  p_query: query,
99
39
  p_limit: limit
100
40
  });
101
41
 
102
- if (searchError || !searchResults) {
103
- console.error('Brain query failed:', searchError);
104
- memories = [];
105
- } else {
106
- memories = searchResults.map((m: any) => ({
107
- id: m.id,
108
- content: m.content,
109
- category: m.category,
110
- tags: m.tags,
111
- netVotes: m.importance,
112
- createdAt: m.created_at
113
- }));
42
+ if (!error && data) {
43
+ memories = data.map((m: any) => ({
44
+ id: m.id,
45
+ content: m.content,
46
+ category: m.category,
47
+ tags: m.tags,
48
+ netVotes: m.importance,
49
+ createdAt: m.created_at
50
+ }));
51
+ } else {
52
+ throw error || new Error('No data');
53
+ }
54
+ } catch (e) {
55
+ // Fallback to direct table query (Policies allow members)
56
+ searchType = 'DIRECT-FALLBACK';
57
+ const { data } = await supabase
58
+ .from('project_memories')
59
+ .select('id, content, category, tags, importance, created_at')
60
+ .eq('project_id', projectId)
61
+ .eq('is_active', true)
62
+ .or(`content.ilike.%${query}%,category.ilike.%${query}%`)
63
+ .order('created_at', { ascending: false })
64
+ .limit(limit);
65
+
66
+ if (data) {
67
+ memories = data.map((m: any) => ({
68
+ id: m.id,
69
+ content: m.content,
70
+ category: m.category,
71
+ tags: m.tags,
72
+ netVotes: m.importance,
73
+ createdAt: m.created_at
74
+ }));
75
+ }
114
76
  }
115
77
 
116
- // --- FETCH RELEVANT FEATURES FROM SPEC ---
78
+ // 3. Related Features from Spec (Use Secure Gateway)
117
79
  let relevantFeatures: any[] = [];
118
80
  try {
119
81
  const { data: projectRow } = await supabase
120
- .rpc('get_project_context_secure', {
121
- p_project_id: projectId,
122
- p_user_id: userId
123
- })
82
+ .rpc('get_project_context_secure', { p_project_id: projectId, p_user_id: userId })
124
83
  .single() as any;
125
84
 
126
85
  if (projectRow?.functional_spec) {
@@ -134,40 +93,19 @@ export async function queryBrain(
134
93
  .slice(0, 3);
135
94
  }
136
95
  } catch (e) {
137
- console.warn('Feature fetch failed in brain query', e);
96
+ console.error('[query_brain] Failed to fetch project spec for features:', e);
138
97
  }
139
98
 
140
- // Format memories into a readable context block
141
- const contextLines = memories.map((m: any) => {
142
- const voteIndicator = m.netVotes && m.netVotes < 0 ? ` [⚠️ POORLY RATED: ${m.netVotes}]` : '';
143
- const tagStr = m.tags && m.tags.length > 0 ? ` [${m.tags.join(', ')}]` : '';
144
- const category = m.category ? m.category.toUpperCase() : 'GENERAL';
145
- return `- [${category}]${tagStr}${voteIndicator}: ${m.content}`;
146
- });
147
-
148
- const searchType = embedding ? 'TRIPLE-HYBRID (Vector + FTS + Fuzzy)' : 'SECURE-GATEWAY (Fuzzy)';
149
-
150
- let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===
151
- Search Mode: ${searchType}
152
- Query: "${query}"`;
153
-
99
+ const contextLines = memories.map((m: any) => `- [${(m.category || 'GENERAL').toUpperCase()}]: ${m.content}`);
100
+ let formatted = `=== PROJECT BRAIN: RELEVANT MEMORIES ===\nSearch Mode: ${searchType}\nQuery: "${query}"`;
154
101
  if (relevantFeatures.length > 0) {
155
- formatted += `\n\n=== RELATED FEATURES ===\n` +
156
- relevantFeatures.map((f: any) => `- ${f.name || f.title} [${f.status || 'Active'}]`).join('\n');
102
+ formatted += `\n\n=== RELATED FEATURES ===\n` + relevantFeatures.map((f: any) => `- ${f.name || f.title} [${f.status || 'Active'}]`).join('\n');
157
103
  }
158
-
159
- formatted += `\n\nFound ${memories.length} relevant memories:\n\n${contextLines.join('\n')}\n\n==========================================`;
104
+ formatted += `\n\nFound ${memories.length} relevant memories:\n\n${contextLines.join('\n')}`;
160
105
 
161
106
  if (memories.length === 0 && relevantFeatures.length === 0) {
162
- formatted = `=== PROJECT BRAIN ===
163
- Query: "${query}"
164
- No relevant memories or features found.
165
- =======================`;
107
+ formatted = `=== PROJECT BRAIN ===\nNo relevant info found for "${query}".`;
166
108
  }
167
109
 
168
- return {
169
- query,
170
- memories,
171
- formatted
172
- };
110
+ return { query, memories, formatted };
173
111
  }