Skip to main content
Glama

Agent MCP

project_context.ts26.9 kB
// Project Context Tools for Agent-MCP Node.js // Ported from Python project_context_tools.py to match API exactly import { z } from 'zod'; import { registerTool } from './registry.js'; import { getDbConnection } from '../db/connection.js'; import { MCP_DEBUG } from '../core/config.js'; import { verifyToken, getAgentId } from '../core/auth.js'; // Schemas matching Python implementation const ViewProjectContextSchema = z.object({ token: z.string().describe("Authentication token"), context_key: z.string().optional().describe("Exact key to view (optional). If provided, search_query is ignored."), search_query: z.string().optional().describe("Keyword search query (optional). Searches keys, descriptions, and values."), show_health_analysis: z.boolean().optional().default(false).describe("Include comprehensive health metrics and analysis"), show_stale_entries: z.boolean().optional().default(false).describe("Show only entries older than 30 days needing review"), include_backup_info: z.boolean().optional().default(false).describe("Include backup recommendations and info"), max_results: z.number().int().min(1).max(200).optional().default(50).describe("Maximum number of entries to return"), sort_by: z.enum(['key', 'last_updated', 'size']).optional().default('last_updated').describe("Sort entries by specified field") }); const UpdateProjectContextSchema = z.object({ token: z.string().describe("Authentication token"), context_key: z.string().describe("Key for the context entry"), context_value: z.any().describe("Value to store (will be JSON stringified)"), description: z.string().optional().describe("Optional description of what this context represents") }); const BulkUpdateProjectContextSchema = z.object({ token: z.string().describe("Authentication token"), updates: z.array(z.object({ context_key: z.string(), context_value: z.any(), description: z.string().optional() })).describe("Array of context updates to perform") }); const DeleteProjectContextSchema = z.object({ token: z.string().describe("Authentication token"), context_key: z.string().describe("Context key to delete") }); const BackupProjectContextSchema = z.object({ token: z.string().describe("Authentication token"), backup_name: z.string().optional().describe("Custom name for the backup") }); const ValidateContextConsistencySchema = z.object({ token: z.string().describe("Authentication token"), fix_issues: z.boolean().optional().default(false).describe("Automatically fix found issues") }); /** * Analyze context health - ported from Python _analyze_context_health */ function analyzeContextHealth(contextEntries: any[]) { if (!contextEntries || contextEntries.length === 0) { return { status: "no_data", total: 0 }; } const total = contextEntries.length; const issues: string[] = []; const warnings: string[] = []; let staleCount = 0; let jsonErrors = 0; let largeEntries = 0; const currentTime = new Date(); for (const entry of contextEntries) { const contextKey = entry.context_key || "unknown"; const value = entry.value || ""; const lastUpdated = entry.last_updated; // Check for JSON parsing issues try { if (typeof value === 'string') { JSON.parse(value); } } catch { jsonErrors += 1; issues.push(`JSON parse error in '${contextKey}'`); } // Check for stale entries (30+ days old) if (lastUpdated) { try { const updatedTime = new Date(lastUpdated); const daysOld = Math.floor((currentTime.getTime() - updatedTime.getTime()) / (1000 * 60 * 60 * 24)); if (daysOld > 30) { staleCount += 1; if (daysOld > 90) { warnings.push(`'${contextKey}' is ${daysOld} days old`); } } } catch { warnings.push(`Invalid timestamp for '${contextKey}'`); } } // Check for oversized entries (>10KB) const entrySize = String(value).length; if (entrySize > 10240) { // 10KB largeEntries += 1; warnings.push(`'${contextKey}' is large (${Math.floor(entrySize/1024)}KB)`); } } // Calculate health score const staleRatio = staleCount / total; const errorRatio = jsonErrors / total; const largeRatio = largeEntries / total; const healthScore = Math.max( 0, Math.min(100, 100 - (staleRatio * 40) - (errorRatio * 50) - (largeRatio * 10)) ); const healthStatus = healthScore >= 90 ? "excellent" : healthScore >= 70 ? "good" : healthScore >= 50 ? "needs_attention" : "critical"; return { status: healthStatus, health_score: Math.round(healthScore * 10) / 10, total, stale_entries: staleCount, json_errors: jsonErrors, large_entries: largeEntries, issues: issues.slice(0, 5), // Limit to first 5 warnings: warnings.slice(0, 5), // Limit to first 5 recommendations: generateContextRecommendations(staleCount, jsonErrors, largeEntries, total) }; } /** * Generate context recommendations - ported from Python */ function generateContextRecommendations(staleCount: number, jsonErrors: number, largeEntries: number, total: number): string[] { const recommendations: string[] = []; if (staleCount > 0) { recommendations.push(`Review ${staleCount} stale entries that haven't been updated in 30+ days`); } if (jsonErrors > 0) { recommendations.push(`Fix ${jsonErrors} entries with JSON parsing errors`); } if (largeEntries > 0) { recommendations.push(`Consider splitting ${largeEntries} large entries (>10KB) into smaller pieces`); } if (total > 100) { recommendations.push("Consider creating context backups due to large number of entries"); } return recommendations; } /** * View project context - ported from Python view_project_context_tool_impl */ async function viewProjectContext(args: Record<string, any>) { const { token, context_key, search_query, show_health_analysis = false, show_stale_entries = false, include_backup_info = false, max_results = 50, sort_by = 'last_updated' } = args; // Authenticate const agentId = getAgentId(token); if (!agentId) { return { content: [{ type: 'text' as const, text: "Unauthorized: Valid token required" }], isError: true }; } const db = getDbConnection(); try { // Build query based on filters const whereConditions: string[] = []; const queryParams: any[] = []; if (context_key) { whereConditions.push("context_key = ?"); queryParams.push(context_key); } else if (search_query) { const likePattern = `%${search_query}%`; whereConditions.push("(context_key LIKE ? OR description LIKE ? OR value LIKE ?)"); queryParams.push(likePattern, likePattern, likePattern); } if (show_stale_entries) { // Show entries older than 30 days const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(); whereConditions.push("last_updated < ?"); queryParams.push(thirtyDaysAgo); } // Build query with smart sorting let baseQuery = "SELECT context_key, value, description, updated_by, last_updated, LENGTH(value) as value_size FROM project_context"; if (whereConditions.length > 0) { baseQuery += " WHERE " + whereConditions.join(" AND "); } // Smart sorting if (sort_by === "size") { baseQuery += " ORDER BY LENGTH(value) DESC"; } else if (sort_by === "key") { baseQuery += " ORDER BY context_key ASC"; } else { // last_updated (default) baseQuery += " ORDER BY last_updated DESC"; } baseQuery += ` LIMIT ${max_results}`; const stmt = db.prepare(baseQuery); const rows = stmt.all(...queryParams); if (rows.length === 0) { const message = context_key ? `Context key '${context_key}' not found` : 'No project context entries found'; return { content: [{ type: 'text' as const, text: JSON.stringify({ message, total_entries: 0 }, null, 2) }] }; } // Process results with enhanced information const processedResults = []; for (const row of rows as any[]) { try { let valueParsed; let jsonValid = true; try { valueParsed = JSON.parse(row.value); } catch { valueParsed = row.value; jsonValid = false; } // Calculate additional metadata const entrySize = String(row.value).length; const lastUpdated = row.last_updated; let daysOld = null; if (lastUpdated) { try { const updatedTime = new Date(lastUpdated); daysOld = Math.floor((Date.now() - updatedTime.getTime()) / (1000 * 60 * 60 * 24)); } catch { // Invalid date } } processedResults.push({ context_key: row.context_key, value: valueParsed, description: row.description, updated_by: row.updated_by, last_updated: lastUpdated, metadata: { size_bytes: entrySize, size_kb: Math.round(entrySize / 1024 * 10) / 10, json_valid: jsonValid, days_old: daysOld, is_stale: daysOld !== null && daysOld > 30 } }); } catch (error) { // Skip problematic entries but log if (MCP_DEBUG) { console.error(`Error processing context entry ${row.context_key}:`, error); } } } // Build response const response: any = { total_entries: processedResults.length, max_results_limit: max_results, sort_by, entries: processedResults }; // Add health analysis if requested if (show_health_analysis) { response.health_analysis = analyzeContextHealth(rows); } // Add backup info if requested if (include_backup_info) { // Check for existing backups const backupStmt = db.prepare("SELECT context_key, last_updated FROM project_context WHERE context_key LIKE '__backup__%' ORDER BY last_updated DESC LIMIT 5"); const backups = backupStmt.all(); response.backup_info = { recent_backups: backups, recommendation: backups.length === 0 ? "No backups found - consider creating one" : `${backups.length} backups available` }; } // Log action const actionStmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); actionStmt.run(agentId, 'view_project_context', new Date().toISOString(), JSON.stringify({ context_key, search_query, results_count: processedResults.length })); return { content: [{ type: 'text' as const, text: JSON.stringify(response, null, 2) }] }; } catch (error) { console.error('Error viewing project context:', error); return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Failed to view project context', details: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } /** * Update project context - ported from Python update_project_context_tool_impl */ async function updateProjectContext(args: Record<string, any>) { const { token, context_key, context_value, description } = args; // Authenticate const agentId = getAgentId(token); if (!agentId) { return { content: [{ type: 'text' as const, text: "Unauthorized: Valid token required" }], isError: true }; } const db = getDbConnection(); try { // Ensure value is JSON serializable let valueJsonStr: string; try { valueJsonStr = JSON.stringify(context_value); } catch (error) { return { content: [{ type: 'text' as const, text: `Error: Provided context_value is not JSON serializable: ${error}` }], isError: true }; } const timestamp = new Date().toISOString(); // Use INSERT OR REPLACE (UPSERT) const upsertStmt = db.prepare(` INSERT OR REPLACE INTO project_context (context_key, value, last_updated, updated_by, description) VALUES (?, ?, ?, ?, ?) `); upsertStmt.run(context_key, valueJsonStr, timestamp, agentId, description || null); // Log action const actionStmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); actionStmt.run(agentId, 'updated_context', timestamp, JSON.stringify({ context_key, action: 'set/update' })); if (MCP_DEBUG) { console.log(`📝 Project context '${context_key}' updated by '${agentId}'`); } return { content: [{ type: 'text' as const, text: JSON.stringify({ success: true, context_key, updated_by: agentId, timestamp, value_size_bytes: valueJsonStr.length, description }, null, 2) }] }; } catch (error) { console.error('Error updating project context:', error); return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Failed to update project context', details: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } /** * Bulk update project context - ported from Python bulk_update_project_context_tool_impl */ async function bulkUpdateProjectContext(args: Record<string, any>) { const { token, updates } = args; // Authenticate const agentId = getAgentId(token); if (!agentId) { return { content: [{ type: 'text' as const, text: "Unauthorized: Valid token required" }], isError: true }; } const db = getDbConnection(); try { const timestamp = new Date().toISOString(); const results: any[] = []; // Use transaction for bulk update const transaction = db.transaction(() => { for (const update of updates) { const { context_key, context_value, description } = update; // Ensure value is JSON serializable let valueJsonStr: string; try { valueJsonStr = JSON.stringify(context_value); } catch (error) { results.push({ context_key, success: false, error: `Value not JSON serializable: ${error}` }); continue; } // Insert or update const upsertStmt = db.prepare(` INSERT OR REPLACE INTO project_context (context_key, value, last_updated, updated_by, description) VALUES (?, ?, ?, ?, ?) `); upsertStmt.run(context_key, valueJsonStr, timestamp, agentId, description || null); results.push({ context_key, success: true, value_size_bytes: valueJsonStr.length }); } // Log bulk action const actionStmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); actionStmt.run(agentId, 'bulk_update_context', timestamp, JSON.stringify({ updates_count: updates.length })); }); transaction(); if (MCP_DEBUG) { console.log(`📝 Bulk context update: ${updates.length} entries by ${agentId}`); } return { content: [{ type: 'text' as const, text: JSON.stringify({ success: true, updates_processed: results.length, updated_by: agentId, timestamp, results }, null, 2) }] }; } catch (error) { console.error('Error bulk updating project context:', error); return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Failed to bulk update project context', details: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } /** * Delete project context - ported from Python delete_project_context_tool_impl */ async function deleteProjectContext(args: Record<string, any>) { const { token, context_key } = args; // Authenticate const agentId = getAgentId(token); if (!agentId) { return { content: [{ type: 'text' as const, text: "Unauthorized: Valid token required" }], isError: true }; } const db = getDbConnection(); try { // Check if entry exists const existingStmt = db.prepare('SELECT context_key, value FROM project_context WHERE context_key = ?'); const existing = existingStmt.get(context_key); if (!existing) { return { content: [{ type: 'text' as const, text: JSON.stringify({ success: false, error: `Context key '${context_key}' not found` }, null, 2) }], isError: true }; } // Delete the entry const deleteStmt = db.prepare('DELETE FROM project_context WHERE context_key = ?'); const result = deleteStmt.run(context_key); // Log action const timestamp = new Date().toISOString(); const actionStmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); actionStmt.run(agentId, 'delete_context', timestamp, JSON.stringify({ context_key, deleted_value_size: String((existing as any).value).length })); if (MCP_DEBUG) { console.log(`🗑️ Context deleted: ${context_key} by ${agentId}`); } return { content: [{ type: 'text' as const, text: JSON.stringify({ success: true, context_key, deleted_by: agentId, timestamp, deleted: result.changes > 0 }, null, 2) }] }; } catch (error) { console.error('Error deleting project context:', error); return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Failed to delete project context', details: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } /** * Create backup of project context - ported from Python backup_project_context_tool_impl */ async function backupProjectContext(args: Record<string, any>) { const { token, backup_name } = args; // Authenticate const agentId = getAgentId(token); if (!agentId) { return { content: [{ type: 'text' as const, text: "Unauthorized: Valid token required" }], isError: true }; } const db = getDbConnection(); try { const timestamp = new Date().toISOString(); const backupId = backup_name || `backup_${timestamp.replace(/[:.]/g, '-')}`; // Get all context entries const stmt = db.prepare('SELECT * FROM project_context WHERE context_key NOT LIKE "__backup__%" ORDER BY context_key'); const entries = stmt.all(); const backup = { backup_id: backupId, created_at: timestamp, created_by: agentId, entry_count: entries.length, entries: entries.map((entry: any) => ({ context_key: entry.context_key, value: JSON.parse(entry.value), description: entry.description, last_updated: entry.last_updated, updated_by: entry.updated_by })) }; // Store backup as a special context entry const backupKey = `__backup__${backupId}`; const backupJson = JSON.stringify(backup); const insertStmt = db.prepare(` INSERT INTO project_context (context_key, value, last_updated, updated_by, description) VALUES (?, ?, ?, ?, ?) `); insertStmt.run(backupKey, backupJson, timestamp, agentId, `Backup created by ${agentId}`); // Log action const actionStmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); actionStmt.run(agentId, 'backup_context', timestamp, JSON.stringify({ backup_id: backupId, entry_count: entries.length })); if (MCP_DEBUG) { console.log(`💾 Context backup created: ${backupId} by ${agentId}`); } return { content: [{ type: 'text' as const, text: JSON.stringify({ success: true, backup_id: backupId, backup_key: backupKey, entry_count: entries.length, backup_size_bytes: backupJson.length, created_by: agentId, timestamp }, null, 2) }] }; } catch (error) { console.error('Error creating context backup:', error); return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Failed to create context backup', details: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } /** * Validate context consistency - ported from Python validate_context_consistency_tool_impl */ async function validateContextConsistency(args: Record<string, any>) { const { token, fix_issues = false } = args; // Authenticate const agentId = getAgentId(token); if (!agentId) { return { content: [{ type: 'text' as const, text: "Unauthorized: Valid token required" }], isError: true }; } const db = getDbConnection(); try { const issues: any[] = []; const fixes: any[] = []; // Get all context entries const stmt = db.prepare('SELECT * FROM project_context ORDER BY context_key'); const entries = stmt.all(); // Check for JSON parsing issues for (const entry of entries as any[]) { try { JSON.parse(entry.value); } catch { issues.push({ type: 'invalid_json', context_key: entry.context_key, error: 'Value is not valid JSON' }); if (fix_issues) { // Try to fix by re-stringifying try { const fixedValue = JSON.stringify(entry.value); const updateStmt = db.prepare('UPDATE project_context SET value = ? WHERE context_key = ?'); updateStmt.run(fixedValue, entry.context_key); fixes.push({ type: 'fixed_json', context_key: entry.context_key, action: 'Re-stringified value' }); } catch { issues.push({ type: 'unfixable_json', context_key: entry.context_key, error: 'Could not fix JSON' }); } } } } // Check for orphaned references (agents that no longer exist) const agentStmt = db.prepare('SELECT DISTINCT updated_by FROM project_context'); const contextAgents = agentStmt.all() as { updated_by: string }[]; for (const contextAgent of contextAgents) { if (contextAgent.updated_by === 'admin' || contextAgent.updated_by === 'server_startup') { continue; // Skip system entries } const checkAgentStmt = db.prepare('SELECT agent_id FROM agents WHERE agent_id = ?'); const agentExists = checkAgentStmt.get(contextAgent.updated_by); if (!agentExists) { issues.push({ type: 'orphaned_agent_reference', agent_id: contextAgent.updated_by, error: 'Context references non-existent agent' }); } } // Analyze context health const health = analyzeContextHealth(entries); // Log validation const timestamp = new Date().toISOString(); const actionStmt = db.prepare(` INSERT INTO agent_actions (agent_id, action_type, timestamp, details) VALUES (?, ?, ?, ?) `); actionStmt.run(agentId, 'validate_context', timestamp, JSON.stringify({ issues_found: issues.length, fixes_applied: fixes.length, fix_issues })); if (MCP_DEBUG) { console.log(`🔍 Context validation: ${issues.length} issues, ${fixes.length} fixes by ${agentId}`); } return { content: [{ type: 'text' as const, text: JSON.stringify({ validation_summary: { total_entries: entries.length, issues_found: issues.length, fixes_applied: fixes.length, validation_date: timestamp, validated_by: agentId }, context_health: health, issues, fixes, recommendations: health.recommendations }, null, 2) }] }; } catch (error) { console.error('Error validating context consistency:', error); return { content: [{ type: 'text' as const, text: JSON.stringify({ error: 'Failed to validate context consistency', details: error instanceof Error ? error.message : String(error) }, null, 2) }], isError: true }; } } // Register all project context tools registerTool( 'view_project_context', 'Smart project context viewer with health analysis, stale entry detection, and advanced filtering. Provides comprehensive insights into context quality and usage.', ViewProjectContextSchema, viewProjectContext ); registerTool( 'update_project_context', 'Store project context information that all agents can access. Context is stored as JSON and can be retrieved by any authenticated agent.', UpdateProjectContextSchema, updateProjectContext ); registerTool( 'bulk_update_project_context', 'Update multiple project context entries in a single transaction for efficiency and consistency.', BulkUpdateProjectContextSchema, bulkUpdateProjectContext ); registerTool( 'delete_project_context', 'Delete a specific project context entry. This action cannot be undone, so use with caution.', DeleteProjectContextSchema, deleteProjectContext ); registerTool( 'backup_project_context', 'Create a backup snapshot of all project context entries for disaster recovery and version control.', BackupProjectContextSchema, backupProjectContext ); registerTool( 'validate_context_consistency', 'Validate project context integrity, check for issues like invalid JSON or orphaned references, and optionally fix problems automatically.', ValidateContextConsistencySchema, validateContextConsistency ); if (MCP_DEBUG) { console.log('✅ Project context tools registered'); } export { viewProjectContext, updateProjectContext, bulkUpdateProjectContext, deleteProjectContext, backupProjectContext, validateContextConsistency };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rinadelph/Agent-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server