@equilateral_ai/mindmeld 3.3.0 → 3.4.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.
- package/README.md +1 -10
- package/hooks/pre-compact.js +213 -25
- package/hooks/session-start.js +636 -42
- package/hooks/subagent-start.js +150 -0
- package/hooks/subagent-stop.js +184 -0
- package/package.json +8 -7
- package/scripts/init-project.js +74 -33
- package/scripts/mcp-bridge.js +220 -0
- package/src/core/CorrelationAnalyzer.js +157 -0
- package/src/core/LLMPatternDetector.js +198 -0
- package/src/core/RelevanceDetector.js +123 -36
- package/src/core/StandardsIngestion.js +119 -18
- package/src/handlers/activity/activityGetMe.js +1 -1
- package/src/handlers/activity/activityGetTeam.js +100 -55
- package/src/handlers/admin/adminSetup.js +216 -0
- package/src/handlers/alerts/alertsAcknowledge.js +6 -6
- package/src/handlers/alerts/alertsGet.js +11 -11
- package/src/handlers/analytics/activitySummaryGet.js +34 -35
- package/src/handlers/analytics/coachingGet.js +11 -11
- package/src/handlers/analytics/convergenceGet.js +236 -0
- package/src/handlers/analytics/developerScoreGet.js +41 -111
- package/src/handlers/collaborators/collaboratorInvite.js +1 -1
- package/src/handlers/company/companyUsersDelete.js +141 -0
- package/src/handlers/company/companyUsersGet.js +90 -0
- package/src/handlers/company/companyUsersPost.js +267 -0
- package/src/handlers/company/companyUsersPut.js +76 -0
- package/src/handlers/correlations/correlationsDeveloperGet.js +12 -12
- package/src/handlers/correlations/correlationsGet.js +8 -8
- package/src/handlers/correlations/correlationsProjectGet.js +5 -5
- package/src/handlers/enterprise/controlTowerGet.js +224 -0
- package/src/handlers/enterprise/enterpriseOnboardingSetup.js +48 -9
- package/src/handlers/enterprise/enterpriseOnboardingStatus.js +1 -3
- package/src/handlers/github/githubConnectionStatus.js +1 -1
- package/src/handlers/github/githubDiscoverPatterns.js +4 -2
- package/src/handlers/github/githubPatternsReview.js +7 -36
- package/src/handlers/health/healthGet.js +55 -0
- package/src/handlers/helpers/checkSuperAdmin.js +13 -14
- package/src/handlers/helpers/subscriptionTiers.js +27 -27
- package/src/handlers/mcp/mcpHandler.js +569 -0
- package/src/handlers/mcp/mindmeldMcpHandler.js +689 -0
- package/src/handlers/notifications/sendNotification.js +18 -18
- package/src/handlers/patterns/patternEvaluatePromotionPost.js +173 -0
- package/src/handlers/projects/projectCreate.js +124 -10
- package/src/handlers/projects/projectDelete.js +4 -4
- package/src/handlers/projects/projectGet.js +8 -8
- package/src/handlers/projects/projectUpdate.js +4 -4
- package/src/handlers/reports/aiLeverage.js +34 -30
- package/src/handlers/reports/engineeringInvestment.js +16 -16
- package/src/handlers/reports/riskForecast.js +41 -21
- package/src/handlers/reports/standardsRoi.js +101 -9
- package/src/handlers/scheduled/maturityUpdateJob.js +166 -0
- package/src/handlers/sessions/sessionStandardsPost.js +43 -7
- package/src/handlers/standards/discoveriesGet.js +93 -0
- package/src/handlers/standards/projectStandardsGet.js +2 -2
- package/src/handlers/standards/projectStandardsPut.js +2 -2
- package/src/handlers/standards/standardsRelevantPost.js +107 -12
- package/src/handlers/standards/standardsTransition.js +112 -15
- package/src/handlers/stripe/billingPortalPost.js +1 -1
- package/src/handlers/stripe/enterpriseCheckoutPost.js +2 -2
- package/src/handlers/stripe/subscriptionCreatePost.js +2 -2
- package/src/handlers/stripe/webhookPost.js +42 -14
- package/src/handlers/user/apiTokenCreate.js +71 -0
- package/src/handlers/user/apiTokenList.js +64 -0
- package/src/handlers/user/userSplashGet.js +90 -73
- package/src/handlers/users/cognitoPostConfirmation.js +37 -1
- package/src/handlers/users/cognitoPreSignUp.js +114 -0
- package/src/handlers/users/userGet.js +12 -8
- package/src/handlers/webhooks/githubWebhook.js +117 -125
- package/src/index.js +46 -51
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JARVIS MCP Server - Lambda Handler
|
|
3
|
+
*
|
|
4
|
+
* Implements MCP (Model Context Protocol) JSON-RPC directly over HTTP.
|
|
5
|
+
* No SDK transport layer needed — Lambda's request/response model maps
|
|
6
|
+
* cleanly to stateless JSON-RPC.
|
|
7
|
+
*
|
|
8
|
+
* POST /mcp - JSON-RPC messages (initialize, tools/list, tools/call, etc.)
|
|
9
|
+
* GET /mcp - 405 (SSE not supported in stateless Lambda)
|
|
10
|
+
* DELETE /mcp - 200 (no-op session close)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { executeQuery } = require('./dbOperations');
|
|
14
|
+
|
|
15
|
+
const SERVER_INFO = {
|
|
16
|
+
name: 'jarvis-mcp',
|
|
17
|
+
version: '1.0.0'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const PROTOCOL_VERSION = '2025-03-26';
|
|
21
|
+
|
|
22
|
+
const CORS_HEADERS = {
|
|
23
|
+
'Access-Control-Allow-Origin': '*',
|
|
24
|
+
'Access-Control-Allow-Methods': 'POST, GET, DELETE, OPTIONS',
|
|
25
|
+
'Access-Control-Allow-Headers': 'Content-Type, Accept, Mcp-Session-Id',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// ============================================================
|
|
29
|
+
// Tool Definitions
|
|
30
|
+
// ============================================================
|
|
31
|
+
|
|
32
|
+
const TOOLS = [
|
|
33
|
+
{
|
|
34
|
+
name: 'list_agents',
|
|
35
|
+
description: 'Discover active agents in the JARVIS mesh and their capabilities, success rates, and circuit breaker status',
|
|
36
|
+
inputSchema: {
|
|
37
|
+
type: 'object',
|
|
38
|
+
properties: {
|
|
39
|
+
capability: { type: 'string', description: 'Filter by capability (optional)' }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'dispatch_task',
|
|
45
|
+
description: 'Dispatch a task to the JARVIS orchestration engine for routing to the best agent',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object',
|
|
48
|
+
properties: {
|
|
49
|
+
description: { type: 'string', description: 'Natural language task description' },
|
|
50
|
+
parameters: { type: 'object', description: 'Task parameters (optional)' },
|
|
51
|
+
constraints: { type: 'object', description: 'Constraints: minSuccessRate, maxCost, maxDuration (optional)' }
|
|
52
|
+
},
|
|
53
|
+
required: ['description']
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'query_decision_frames',
|
|
58
|
+
description: 'Query governance decision frames - the audit trail of every agent routing decision with full reasoning context',
|
|
59
|
+
inputSchema: {
|
|
60
|
+
type: 'object',
|
|
61
|
+
properties: {
|
|
62
|
+
limit: { type: 'number', description: 'Max results (default 20, max 100)' },
|
|
63
|
+
agentName: { type: 'string', description: 'Filter by agent name (optional)' },
|
|
64
|
+
timeWindow: { type: 'string', description: 'Time window: 1d, 7d, 30d, 90d (default 7d)' }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: 'get_standards',
|
|
70
|
+
description: 'Search the Equilateral standards library for relevant rules, anti-patterns, and examples',
|
|
71
|
+
inputSchema: {
|
|
72
|
+
type: 'object',
|
|
73
|
+
properties: {
|
|
74
|
+
context: { type: 'string', description: 'Search context (e.g., "Lambda database", "CORS", "cost optimization")' },
|
|
75
|
+
topK: { type: 'number', description: 'Number of results (default 5, max 20)' }
|
|
76
|
+
},
|
|
77
|
+
required: ['context']
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'get_rapport_context',
|
|
82
|
+
description: 'Get Rapport invariants, purpose, notes, and active loops for a scope. These are the structural patterns that shape how agents interact.',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
scope: { type: 'string', description: 'Scope name (default: jarvis)' }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'get_mesh_status',
|
|
92
|
+
description: 'Get aggregate health status of the JARVIS agent mesh: active/inactive agents, circuit breaker states, success rates',
|
|
93
|
+
inputSchema: { type: 'object', properties: {} }
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'get_agent_stats',
|
|
97
|
+
description: 'Get detailed performance statistics for a specific agent: success rate, duration, cost, recent executions',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
agentName: { type: 'string', description: 'Name of the agent' },
|
|
102
|
+
timeWindow: { type: 'string', description: 'Time window: 1d, 7d, 30d, 90d (default 7d)' }
|
|
103
|
+
},
|
|
104
|
+
required: ['agentName']
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
// ============================================================
|
|
110
|
+
// Resource Definitions
|
|
111
|
+
// ============================================================
|
|
112
|
+
|
|
113
|
+
const RESOURCES = [
|
|
114
|
+
{ uri: 'agent://registry', name: 'Agent Registry', description: 'Complete JARVIS agent registry with capabilities and health status', mimeType: 'application/json' },
|
|
115
|
+
{ uri: 'decision://frames/recent', name: 'Decision Frames', description: 'Recent governance decision frames with audit trail', mimeType: 'application/json' },
|
|
116
|
+
{ uri: 'standards://library', name: 'Standards Library', description: 'Equilateral AI standards library - rules and anti-patterns', mimeType: 'application/json' },
|
|
117
|
+
{ uri: 'rapport://invariants/jarvis', name: 'Rapport Invariants', description: 'Rapport structural invariants for the jarvis scope', mimeType: 'application/json' },
|
|
118
|
+
{ uri: 'workflow://active', name: 'Active Workflows', description: 'Currently active agent workflows', mimeType: 'application/json' }
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
// ============================================================
|
|
122
|
+
// Tool Implementations
|
|
123
|
+
// ============================================================
|
|
124
|
+
|
|
125
|
+
async function callTool(name, args = {}) {
|
|
126
|
+
switch (name) {
|
|
127
|
+
case 'list_agents': {
|
|
128
|
+
try {
|
|
129
|
+
let query = `
|
|
130
|
+
SELECT agent_name, agent_type, capabilities, tools, status,
|
|
131
|
+
circuit_breaker_status, success_rate, total_executions
|
|
132
|
+
FROM agent_orchestration.agent_registry
|
|
133
|
+
WHERE status = 'active'
|
|
134
|
+
`;
|
|
135
|
+
const params = [];
|
|
136
|
+
if (args.capability) {
|
|
137
|
+
query += ` AND $1 = ANY(capabilities)`;
|
|
138
|
+
params.push(args.capability);
|
|
139
|
+
}
|
|
140
|
+
query += ` ORDER BY success_rate DESC, total_executions DESC`;
|
|
141
|
+
const result = await executeQuery(query, params);
|
|
142
|
+
return { content: [{ type: 'text', text: JSON.stringify({ agents: result.rows, count: result.rows.length }, null, 2) }] };
|
|
143
|
+
} catch (e) {
|
|
144
|
+
if (e.message.includes('does not exist')) {
|
|
145
|
+
return { content: [{ type: 'text', text: 'Agent orchestration schema not available in this database. The agent_orchestration schema is on the JARVIS Mac Mini database. Use get_rapport_context or get_standards for data available in this environment.' }] };
|
|
146
|
+
}
|
|
147
|
+
throw e;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case 'dispatch_task': {
|
|
152
|
+
try {
|
|
153
|
+
const result = await executeQuery(`
|
|
154
|
+
INSERT INTO agent_orchestration.agent_routing_log
|
|
155
|
+
(task_description, task_parameters, constraints, status, created_at)
|
|
156
|
+
VALUES ($1, $2, $3, 'pending', NOW())
|
|
157
|
+
RETURNING routing_id, status, created_at
|
|
158
|
+
`, [args.description, JSON.stringify(args.parameters || {}), JSON.stringify(args.constraints || {})]);
|
|
159
|
+
return {
|
|
160
|
+
content: [{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: JSON.stringify({
|
|
163
|
+
dispatched: true,
|
|
164
|
+
routing_id: result.rows[0].routing_id,
|
|
165
|
+
status: result.rows[0].status,
|
|
166
|
+
message: `Task dispatched: "${args.description}"`
|
|
167
|
+
}, null, 2)
|
|
168
|
+
}]
|
|
169
|
+
};
|
|
170
|
+
} catch (e) {
|
|
171
|
+
if (e.message.includes('does not exist')) {
|
|
172
|
+
return { content: [{ type: 'text', text: 'Agent orchestration schema not available in this database. Task dispatch requires the JARVIS Mac Mini environment.' }] };
|
|
173
|
+
}
|
|
174
|
+
throw e;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
case 'query_decision_frames': {
|
|
179
|
+
try {
|
|
180
|
+
const effectiveLimit = Math.min(parseInt(args.limit) || 20, 100);
|
|
181
|
+
const validWindows = { '1d': '1 day', '7d': '7 days', '30d': '30 days', '90d': '90 days' };
|
|
182
|
+
const effectiveWindow = validWindows[args.timeWindow] || '7 days';
|
|
183
|
+
const params = [effectiveWindow];
|
|
184
|
+
let query = `
|
|
185
|
+
SELECT frame_id, agent_name, decision_type, reasoning,
|
|
186
|
+
governance_context, outcome, created_at
|
|
187
|
+
FROM agent_orchestration.decision_frames
|
|
188
|
+
WHERE created_at > NOW() - $1::interval
|
|
189
|
+
`;
|
|
190
|
+
if (args.agentName) {
|
|
191
|
+
params.push(args.agentName);
|
|
192
|
+
query += ` AND agent_name = $${params.length}`;
|
|
193
|
+
}
|
|
194
|
+
params.push(effectiveLimit);
|
|
195
|
+
query += ` ORDER BY created_at DESC LIMIT $${params.length}`;
|
|
196
|
+
const result = await executeQuery(query, params);
|
|
197
|
+
return { content: [{ type: 'text', text: JSON.stringify({ frames: result.rows, count: result.rows.length }, null, 2) }] };
|
|
198
|
+
} catch (e) {
|
|
199
|
+
if (e.message.includes('does not exist')) {
|
|
200
|
+
return { content: [{ type: 'text', text: 'Decision frames schema not available in this database. Governance audit trails are on the JARVIS Mac Mini environment.' }] };
|
|
201
|
+
}
|
|
202
|
+
throw e;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'get_standards': {
|
|
207
|
+
const k = Math.min(args.topK || 5, 20);
|
|
208
|
+
// Split search into individual words for AND matching
|
|
209
|
+
const words = args.context.split(/\s+/).filter(w => w.length > 1);
|
|
210
|
+
const searchFields = `COALESCE(rule,'') || ' ' || COALESCE(category,'') || ' ' || COALESCE(element,'') || ' ' || COALESCE(title,'') || ' ' || COALESCE(keywords::text,'')`;
|
|
211
|
+
const conditions = words.map((_, i) => `(${searchFields}) ILIKE $${i + 1}`);
|
|
212
|
+
const params = words.map(w => `%${w}%`);
|
|
213
|
+
params.push(k);
|
|
214
|
+
const result = await executeQuery(`
|
|
215
|
+
SELECT pattern_id, element, title, rule, category,
|
|
216
|
+
keywords, anti_patterns, examples, cost_impact,
|
|
217
|
+
maturity, correlation
|
|
218
|
+
FROM rapport.standards_patterns
|
|
219
|
+
WHERE ${conditions.join(' AND ')}
|
|
220
|
+
ORDER BY
|
|
221
|
+
CASE WHEN maturity = 'enforced' THEN 1
|
|
222
|
+
WHEN maturity = 'validated' THEN 2
|
|
223
|
+
ELSE 3 END,
|
|
224
|
+
correlation DESC
|
|
225
|
+
LIMIT $${params.length}
|
|
226
|
+
`, params);
|
|
227
|
+
return { content: [{ type: 'text', text: JSON.stringify({ standards: result.rows, count: result.rows.length }, null, 2) }] };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case 'get_rapport_context': {
|
|
231
|
+
const scope = args.scope || 'jarvis';
|
|
232
|
+
const result = await executeQuery(
|
|
233
|
+
`SELECT rapport.get_user_context($1, $2) as context`,
|
|
234
|
+
[process.env.MCP_DEFAULT_USER || 'james.ford@happyhippo.ai', scope]
|
|
235
|
+
);
|
|
236
|
+
const context = result.rows[0]?.context || {
|
|
237
|
+
invariants: { agent_level: [], relationship_level: [] },
|
|
238
|
+
purpose: null, recent_notes: [], active_loops: []
|
|
239
|
+
};
|
|
240
|
+
return { content: [{ type: 'text', text: JSON.stringify({ scope, ...context }, null, 2) }] };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
case 'get_mesh_status': {
|
|
244
|
+
try {
|
|
245
|
+
const result = await executeQuery(`
|
|
246
|
+
SELECT
|
|
247
|
+
COUNT(*) FILTER (WHERE status = 'active') as active_agents,
|
|
248
|
+
COUNT(*) FILTER (WHERE status = 'inactive') as inactive_agents,
|
|
249
|
+
COUNT(*) FILTER (WHERE circuit_breaker_status = 'open') as circuit_breakers_open,
|
|
250
|
+
AVG(success_rate) FILTER (WHERE status = 'active') as avg_success_rate,
|
|
251
|
+
SUM(total_executions) as total_executions
|
|
252
|
+
FROM agent_orchestration.agent_registry
|
|
253
|
+
`);
|
|
254
|
+
const mesh = result.rows[0];
|
|
255
|
+
return {
|
|
256
|
+
content: [{
|
|
257
|
+
type: 'text',
|
|
258
|
+
text: JSON.stringify({
|
|
259
|
+
mesh: {
|
|
260
|
+
active_agents: parseInt(mesh.active_agents) || 0,
|
|
261
|
+
inactive_agents: parseInt(mesh.inactive_agents) || 0,
|
|
262
|
+
circuit_breakers_open: parseInt(mesh.circuit_breakers_open) || 0,
|
|
263
|
+
avg_success_rate: parseFloat(mesh.avg_success_rate) || 0,
|
|
264
|
+
total_executions: parseInt(mesh.total_executions) || 0,
|
|
265
|
+
status: parseInt(mesh.circuit_breakers_open) > 0 ? 'degraded' : 'healthy',
|
|
266
|
+
timestamp: new Date().toISOString()
|
|
267
|
+
}
|
|
268
|
+
}, null, 2)
|
|
269
|
+
}]
|
|
270
|
+
};
|
|
271
|
+
} catch (e) {
|
|
272
|
+
if (e.message.includes('does not exist')) {
|
|
273
|
+
return { content: [{ type: 'text', text: 'Agent mesh schema not available in this database. Mesh status is on the JARVIS Mac Mini environment.' }] };
|
|
274
|
+
}
|
|
275
|
+
throw e;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
case 'get_agent_stats': {
|
|
280
|
+
try {
|
|
281
|
+
const validWindows = { '1d': '1 day', '7d': '7 days', '30d': '30 days', '90d': '90 days' };
|
|
282
|
+
const effectiveWindow = validWindows[args.timeWindow] || '7 days';
|
|
283
|
+
const agentResult = await executeQuery(`
|
|
284
|
+
SELECT agent_name, agent_type, total_executions, total_successes,
|
|
285
|
+
total_failures, success_rate, avg_duration_ms, avg_cost_usd,
|
|
286
|
+
circuit_breaker_status
|
|
287
|
+
FROM agent_orchestration.agent_registry
|
|
288
|
+
WHERE agent_name = $1
|
|
289
|
+
`, [args.agentName]);
|
|
290
|
+
if (agentResult.rows.length === 0) {
|
|
291
|
+
return { content: [{ type: 'text', text: JSON.stringify({ error: `Agent '${args.agentName}' not found` }) }], isError: true };
|
|
292
|
+
}
|
|
293
|
+
const executionsResult = await executeQuery(`
|
|
294
|
+
SELECT status, duration_ms, cost_usd, execution_timestamp
|
|
295
|
+
FROM agent_orchestration.agent_executions
|
|
296
|
+
WHERE agent_name = $1
|
|
297
|
+
AND execution_timestamp > NOW() - $2::interval
|
|
298
|
+
ORDER BY execution_timestamp DESC
|
|
299
|
+
LIMIT 50
|
|
300
|
+
`, [args.agentName, effectiveWindow]);
|
|
301
|
+
return {
|
|
302
|
+
content: [{
|
|
303
|
+
type: 'text',
|
|
304
|
+
text: JSON.stringify({
|
|
305
|
+
agent: agentResult.rows[0],
|
|
306
|
+
recent_executions: executionsResult.rows.length,
|
|
307
|
+
executions: executionsResult.rows
|
|
308
|
+
}, null, 2)
|
|
309
|
+
}]
|
|
310
|
+
};
|
|
311
|
+
} catch (e) {
|
|
312
|
+
if (e.message.includes('does not exist')) {
|
|
313
|
+
return { content: [{ type: 'text', text: 'Agent stats schema not available in this database. Agent performance data is on the JARVIS Mac Mini environment.' }] };
|
|
314
|
+
}
|
|
315
|
+
throw e;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
default:
|
|
320
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ============================================================
|
|
325
|
+
// Resource Implementations
|
|
326
|
+
// ============================================================
|
|
327
|
+
|
|
328
|
+
async function readResource(uri) {
|
|
329
|
+
try {
|
|
330
|
+
switch (uri) {
|
|
331
|
+
case 'agent://registry': {
|
|
332
|
+
const result = await executeQuery(`
|
|
333
|
+
SELECT agent_name, agent_type, capabilities, status,
|
|
334
|
+
circuit_breaker_status, success_rate
|
|
335
|
+
FROM agent_orchestration.agent_registry ORDER BY agent_name
|
|
336
|
+
`);
|
|
337
|
+
return { contents: [{ uri, text: JSON.stringify(result.rows, null, 2), mimeType: 'application/json' }] };
|
|
338
|
+
}
|
|
339
|
+
case 'decision://frames/recent': {
|
|
340
|
+
const result = await executeQuery(`
|
|
341
|
+
SELECT frame_id, agent_name, decision_type, reasoning, outcome, created_at
|
|
342
|
+
FROM agent_orchestration.decision_frames
|
|
343
|
+
ORDER BY created_at DESC LIMIT 20
|
|
344
|
+
`);
|
|
345
|
+
return { contents: [{ uri, text: JSON.stringify(result.rows, null, 2), mimeType: 'application/json' }] };
|
|
346
|
+
}
|
|
347
|
+
case 'standards://library': {
|
|
348
|
+
const result = await executeQuery(`
|
|
349
|
+
SELECT pattern_id, element, title, rule, category, maturity, correlation
|
|
350
|
+
FROM rapport.standards_patterns
|
|
351
|
+
WHERE maturity IN ('enforced', 'validated', 'recommended')
|
|
352
|
+
ORDER BY category, correlation DESC
|
|
353
|
+
`);
|
|
354
|
+
return { contents: [{ uri, text: JSON.stringify(result.rows, null, 2), mimeType: 'application/json' }] };
|
|
355
|
+
}
|
|
356
|
+
case 'rapport://invariants/jarvis': {
|
|
357
|
+
const result = await executeQuery(
|
|
358
|
+
`SELECT rapport.get_user_context($1, $2) as context`,
|
|
359
|
+
[process.env.MCP_DEFAULT_USER || 'james.ford@happyhippo.ai', 'jarvis']
|
|
360
|
+
);
|
|
361
|
+
const context = result.rows[0]?.context || { invariants: {} };
|
|
362
|
+
return { contents: [{ uri, text: JSON.stringify(context, null, 2), mimeType: 'application/json' }] };
|
|
363
|
+
}
|
|
364
|
+
case 'workflow://active': {
|
|
365
|
+
const result = await executeQuery(`
|
|
366
|
+
SELECT workflow_name, status, workflow_config, created_at
|
|
367
|
+
FROM agent_orchestration.agent_workflows
|
|
368
|
+
WHERE status = 'active' ORDER BY created_at DESC
|
|
369
|
+
`);
|
|
370
|
+
return { contents: [{ uri, text: JSON.stringify(result.rows, null, 2), mimeType: 'application/json' }] };
|
|
371
|
+
}
|
|
372
|
+
default:
|
|
373
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
374
|
+
}
|
|
375
|
+
} catch (e) {
|
|
376
|
+
if (e.message.includes('does not exist')) {
|
|
377
|
+
return { contents: [{ uri, text: JSON.stringify({ error: 'Schema not available in this database environment' }), mimeType: 'application/json' }] };
|
|
378
|
+
}
|
|
379
|
+
throw e;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ============================================================
|
|
384
|
+
// JSON-RPC Message Router
|
|
385
|
+
// ============================================================
|
|
386
|
+
|
|
387
|
+
async function handleJsonRpc(message) {
|
|
388
|
+
const { method, params, id } = message;
|
|
389
|
+
|
|
390
|
+
switch (method) {
|
|
391
|
+
case 'initialize':
|
|
392
|
+
return {
|
|
393
|
+
jsonrpc: '2.0',
|
|
394
|
+
id,
|
|
395
|
+
result: {
|
|
396
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
397
|
+
capabilities: {
|
|
398
|
+
tools: { listChanged: false },
|
|
399
|
+
resources: { subscribe: false, listChanged: false }
|
|
400
|
+
},
|
|
401
|
+
serverInfo: SERVER_INFO
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
case 'notifications/initialized':
|
|
406
|
+
// Client acknowledgment — no response needed for notifications
|
|
407
|
+
return null;
|
|
408
|
+
|
|
409
|
+
case 'ping':
|
|
410
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
411
|
+
|
|
412
|
+
case 'tools/list':
|
|
413
|
+
return {
|
|
414
|
+
jsonrpc: '2.0',
|
|
415
|
+
id,
|
|
416
|
+
result: { tools: TOOLS }
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
case 'tools/call': {
|
|
420
|
+
const { name, arguments: args } = params;
|
|
421
|
+
try {
|
|
422
|
+
const result = await callTool(name, args || {});
|
|
423
|
+
return { jsonrpc: '2.0', id, result };
|
|
424
|
+
} catch (error) {
|
|
425
|
+
return {
|
|
426
|
+
jsonrpc: '2.0',
|
|
427
|
+
id,
|
|
428
|
+
result: {
|
|
429
|
+
content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }],
|
|
430
|
+
isError: true
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
case 'resources/list':
|
|
437
|
+
return {
|
|
438
|
+
jsonrpc: '2.0',
|
|
439
|
+
id,
|
|
440
|
+
result: { resources: RESOURCES }
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
case 'resources/read': {
|
|
444
|
+
const { uri } = params;
|
|
445
|
+
try {
|
|
446
|
+
const result = await readResource(uri);
|
|
447
|
+
return { jsonrpc: '2.0', id, result };
|
|
448
|
+
} catch (error) {
|
|
449
|
+
return {
|
|
450
|
+
jsonrpc: '2.0',
|
|
451
|
+
id,
|
|
452
|
+
error: { code: -32602, message: error.message }
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
default:
|
|
458
|
+
return {
|
|
459
|
+
jsonrpc: '2.0',
|
|
460
|
+
id,
|
|
461
|
+
error: { code: -32601, message: `Method not found: ${method}` }
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ============================================================
|
|
467
|
+
// Lambda Handler
|
|
468
|
+
// ============================================================
|
|
469
|
+
|
|
470
|
+
exports.handler = async (event) => {
|
|
471
|
+
const method = event.httpMethod;
|
|
472
|
+
|
|
473
|
+
// CORS preflight
|
|
474
|
+
if (method === 'OPTIONS') {
|
|
475
|
+
return {
|
|
476
|
+
statusCode: 200,
|
|
477
|
+
headers: { ...CORS_HEADERS, 'Access-Control-Max-Age': '86400' },
|
|
478
|
+
body: ''
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// GET - SSE not supported in stateless Lambda
|
|
483
|
+
if (method === 'GET') {
|
|
484
|
+
return {
|
|
485
|
+
statusCode: 405,
|
|
486
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json', Allow: 'POST, DELETE, OPTIONS' },
|
|
487
|
+
body: JSON.stringify({
|
|
488
|
+
jsonrpc: '2.0',
|
|
489
|
+
error: { code: -32000, message: 'SSE streaming not supported. Use POST for JSON-RPC requests.' },
|
|
490
|
+
id: null
|
|
491
|
+
})
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// DELETE - Session close (no-op in stateless mode)
|
|
496
|
+
if (method === 'DELETE') {
|
|
497
|
+
return {
|
|
498
|
+
statusCode: 200,
|
|
499
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
500
|
+
body: ''
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// POST - JSON-RPC message processing
|
|
505
|
+
if (method !== 'POST') {
|
|
506
|
+
return {
|
|
507
|
+
statusCode: 405,
|
|
508
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json', Allow: 'POST, GET, DELETE, OPTIONS' },
|
|
509
|
+
body: JSON.stringify({ jsonrpc: '2.0', error: { code: -32000, message: 'Method not allowed' }, id: null })
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
try {
|
|
514
|
+
let body;
|
|
515
|
+
try {
|
|
516
|
+
body = typeof event.body === 'string' ? JSON.parse(event.body) : event.body;
|
|
517
|
+
} catch (e) {
|
|
518
|
+
return {
|
|
519
|
+
statusCode: 400,
|
|
520
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
521
|
+
body: JSON.stringify({ jsonrpc: '2.0', error: { code: -32700, message: 'Parse error: invalid JSON' }, id: null })
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Handle batch requests
|
|
526
|
+
if (Array.isArray(body)) {
|
|
527
|
+
const responses = [];
|
|
528
|
+
for (const msg of body) {
|
|
529
|
+
const response = await handleJsonRpc(msg);
|
|
530
|
+
if (response) responses.push(response);
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
statusCode: responses.length > 0 ? 200 : 202,
|
|
534
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
535
|
+
body: responses.length > 0 ? JSON.stringify(responses) : ''
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// Handle single request
|
|
540
|
+
const response = await handleJsonRpc(body);
|
|
541
|
+
|
|
542
|
+
// Notifications don't get responses
|
|
543
|
+
if (!response) {
|
|
544
|
+
return {
|
|
545
|
+
statusCode: 202,
|
|
546
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
547
|
+
body: ''
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return {
|
|
552
|
+
statusCode: 200,
|
|
553
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
554
|
+
body: JSON.stringify(response)
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
} catch (error) {
|
|
558
|
+
console.error('MCP handler error:', error);
|
|
559
|
+
return {
|
|
560
|
+
statusCode: 500,
|
|
561
|
+
headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
|
|
562
|
+
body: JSON.stringify({
|
|
563
|
+
jsonrpc: '2.0',
|
|
564
|
+
error: { code: -32603, message: 'Internal server error' },
|
|
565
|
+
id: null
|
|
566
|
+
})
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
};
|