bottlenecks-mcp-server
Version:
Model Context Protocol server for Bottlenecks database - enables AI agents like Claude to interact with bottleneck data
465 lines ⢠22 kB
JavaScript
/**
* MCP Validation Operations Tools
* Provides AI agents with validation and completeness checking capabilities
*/
/**
* Get validation status for a specific card
*/
export function createGetCardValidationTool(client) {
return {
type: 'call_tool',
name: 'get_card_validation',
description: 'Get detailed validation status and completeness for a specific card',
inputSchema: {
type: 'object',
properties: {
card_id: {
type: 'string',
description: 'The ID of the card to validate',
format: 'uuid',
},
include_structure_details: {
type: 'boolean',
description: 'Include detailed MDX structure analysis',
default: true,
},
},
required: ['card_id'],
},
handler: async (params) => {
try {
const { card_id, include_structure_details = true } = params;
// Call the validation API
const response = await client.request('/api/admin/validate-schema', {
method: 'POST',
body: {
type: 'single',
cardId: card_id,
},
});
if (!response.success) {
return {
content: [
{
type: 'text',
text: `Failed to validate card: ${response.error || 'Unknown error'}`,
},
],
isError: true,
};
}
const validationReport = response.data.result;
// Format the response for AI consumption
let resultText = `š Card Validation Report\n\n`;
resultText += `Card ID: ${card_id}\n`;
resultText += `Title: ${validationReport.cardTitle}\n`;
resultText += `Valid: ${validationReport.isValid ? 'ā
' : 'ā'}\n`;
resultText += `Completeness: ${validationReport.completeness}%\n`;
resultText += `Validation Date: ${validationReport.validatedAt}\n\n`;
// Add field validation issues
const errors = validationReport.results.filter((r) => r.severity === 'error');
const warnings = validationReport.results.filter((r) => r.severity === 'warning');
if (errors.length > 0) {
resultText += `ā Errors (${errors.length}):\n`;
errors.forEach((error) => {
resultText += ` ⢠${error.field}: ${error.error}\n`;
if (error.suggestion) {
resultText += ` š” ${error.suggestion}\n`;
}
});
resultText += '\n';
}
if (warnings.length > 0) {
resultText += `ā ļø Warnings (${warnings.length}):\n`;
warnings.forEach((warning) => {
resultText += ` ⢠${warning.field}: ${warning.error || 'Needs attention'}\n`;
if (warning.suggestion) {
resultText += ` š” ${warning.suggestion}\n`;
}
});
resultText += '\n';
}
// Include structure details if requested
if (include_structure_details && validationReport.mdxStructure) {
const structure = validationReport.mdxStructure;
resultText += `š MDX Structure Analysis:\n`;
resultText += `Structure Valid: ${structure.isStructureValid ? 'ā
' : 'ā'}\n`;
resultText += `Structure Completeness: ${structure.structureCompleteness}%\n`;
resultText += `Sections Found: ${structure.sectionsFound.join(', ')}\n`;
if (structure.sectionsMissing.length > 0) {
resultText += `Missing Sections: ${structure.sectionsMissing.join(', ')}\n`;
}
const missingRequired = structure.results
.filter((r) => r.severity === 'error' && !r.subsection)
.map((r) => r.section);
if (missingRequired.length > 0) {
resultText += `ā Missing Required Sections: ${missingRequired.join(', ')}\n`;
}
resultText += '\nš Recommendations:\n';
const recommendations = structure.results
.filter((r) => r.suggestion)
.slice(0, 5); // Limit to top 5 recommendations
recommendations.forEach((rec) => {
const sectionName = rec.subsection
? `${rec.section}.${rec.subsection}`
: rec.section;
resultText += ` ⢠${sectionName}: ${rec.suggestion}\n`;
});
}
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Validation check failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
},
};
}
/**
* Validate multiple cards and get completeness summary
*/
export function createBulkValidationTool(client) {
return {
type: 'call_tool',
name: 'bulk_validate_cards',
description: 'Validate multiple cards and get completeness statistics',
inputSchema: {
type: 'object',
properties: {
card_ids: {
type: 'array',
items: { type: 'string', format: 'uuid' },
description: 'Array of card IDs to validate (max 50)',
maxItems: 50,
},
validation_type: {
type: 'string',
enum: ['selected', 'bulk'],
description: 'Type of validation - selected for specific cards, bulk for all cards',
default: 'selected',
},
include_details: {
type: 'boolean',
description: 'Include detailed validation results for each card',
default: false,
},
},
},
handler: async (params) => {
try {
const { card_ids, validation_type = 'selected', include_details = false, } = params;
const requestBody = { type: validation_type };
if (validation_type === 'selected' && card_ids) {
requestBody.cardIds = card_ids;
}
const response = await client.request('/api/admin/validate-schema', {
method: 'POST',
body: requestBody,
});
if (!response.success) {
return {
content: [
{
type: 'text',
text: `Failed to validate cards: ${response.error || 'Unknown error'}`,
},
],
isError: true,
};
}
const bulkResult = response.data.result;
// Format summary statistics
let resultText = `š Bulk Validation Summary\n\n`;
resultText += `Total Cards: ${bulkResult.totalCards}\n`;
resultText += `Valid Cards: ${bulkResult.validCards}\n`;
resultText += `Invalid Cards: ${bulkResult.invalidCards}\n`;
resultText += `Average Completeness: ${bulkResult.averageCompleteness}%\n`;
resultText += `Validation Date: ${bulkResult.validatedAt}\n\n`;
// Completeness distribution
const highCompleteness = bulkResult.cardReports.filter((r) => r.completeness >= 80).length;
const mediumCompleteness = bulkResult.cardReports.filter((r) => r.completeness >= 50 && r.completeness < 80).length;
const lowCompleteness = bulkResult.cardReports.filter((r) => r.completeness < 50).length;
resultText += `š Completeness Distribution:\n`;
resultText += ` High (ā„80%): ${highCompleteness} cards\n`;
resultText += ` Medium (50-79%): ${mediumCompleteness} cards\n`;
resultText += ` Low (<50%): ${lowCompleteness} cards\n\n`;
// Common issues
const allErrors = bulkResult.cardReports.flatMap((r) => r.results.filter((res) => res.severity === 'error'));
const errorCounts = {};
allErrors.forEach((error) => {
errorCounts[error.field] = (errorCounts[error.field] || 0) + 1;
});
if (Object.keys(errorCounts).length > 0) {
resultText += `ā Most Common Issues:\n`;
Object.entries(errorCounts)
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.forEach(([field, count]) => {
resultText += ` ⢠${field}: ${count} cards\n`;
});
resultText += '\n';
}
// Include detailed results if requested
if (include_details) {
resultText += `š Detailed Results:\n`;
bulkResult.cardReports.slice(0, 10).forEach((report) => {
const errorCount = report.results.filter((r) => r.severity === 'error').length;
const warningCount = report.results.filter((r) => r.severity === 'warning').length;
const status = report.isValid ? 'ā
' : 'ā';
resultText += ` ${status} ${report.cardTitle} (${report.completeness}%)`;
if (errorCount > 0)
resultText += ` - ${errorCount} errors`;
if (warningCount > 0)
resultText += ` - ${warningCount} warnings`;
resultText += '\n';
});
}
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Bulk validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
},
};
}
/**
* Find cards that need validation attention
*/
export function createFindCardsNeedingValidationTool(client) {
return {
type: 'call_tool',
name: 'find_cards_needing_validation',
description: 'Find cards that need validation attention (low completeness, missing sections, etc.)',
inputSchema: {
type: 'object',
properties: {
completeness_threshold: {
type: 'number',
description: 'Minimum completeness percentage (cards below this need attention)',
default: 70,
minimum: 0,
maximum: 100,
},
include_valid_but_incomplete: {
type: 'boolean',
description: 'Include cards that are valid but have low completeness',
default: true,
},
limit: {
type: 'number',
description: 'Maximum number of cards to return',
default: 20,
minimum: 1,
maximum: 100,
},
},
},
handler: async (params) => {
try {
const { completeness_threshold = 70, include_valid_but_incomplete = true, limit = 20, } = params;
// First get all cards with validation status
const cardsResponse = await client.request('/api/admin/cards', {
method: 'GET',
});
if (!cardsResponse.success) {
return {
content: [
{
type: 'text',
text: 'Failed to fetch cards for validation analysis',
},
],
isError: true,
};
}
const cards = cardsResponse.data?.data || [];
// Filter cards that need attention
const cardsNeedingAttention = cards
.filter((card) => {
const validation = card.validation_status;
if (!validation)
return true; // No validation data = needs validation
const isIncomplete = validation.completeness < completeness_threshold;
const hasErrors = !validation.is_valid;
return hasErrors || (include_valid_but_incomplete && isIncomplete);
})
.slice(0, limit);
let resultText = `š Cards Needing Validation Attention\n\n`;
resultText += `Found ${cardsNeedingAttention.length} cards needing attention\n`;
resultText += `Completeness threshold: ${completeness_threshold}%\n\n`;
const highPriority = cardsNeedingAttention.filter((card) => !card.validation_status || !card.validation_status.is_valid);
const mediumPriority = cardsNeedingAttention.filter((card) => card.validation_status?.is_valid &&
card.validation_status.completeness < completeness_threshold);
resultText += `š Priority Breakdown:\n`;
resultText += ` High Priority (invalid): ${highPriority.length} cards\n`;
resultText += ` Medium Priority (valid but incomplete): ${mediumPriority.length} cards\n\n`;
resultText += `šÆ Cards to Focus On:\n`;
cardsNeedingAttention.forEach((card, index) => {
const validation = card.validation_status;
const priority = validation?.is_valid ? 'Medium' : 'High';
const completeness = validation?.completeness || 0;
const status = validation?.is_valid ? 'ā ļø' : 'ā';
resultText += `${index + 1}. ${status} ${card.title}\n`;
resultText += ` Priority: ${priority} | Completeness: ${completeness}%\n`;
resultText += ` ID: ${card.id}\n`;
if (!validation) {
resultText += ` Action: Run validation to assess current status\n`;
}
else if (!validation.is_valid) {
resultText += ` Action: Fix validation errors first\n`;
}
else {
resultText += ` Action: Improve completeness by adding optional sections\n`;
}
resultText += '\n';
});
resultText += `š” Next Steps:\n`;
resultText += `⢠Use get_card_validation on high-priority cards to see specific issues\n`;
resultText += `⢠Focus on fixing validation errors before improving completeness\n`;
resultText += `⢠Missing required sections like "impact" should be added first\n`;
resultText += `⢠Consider bulk validation for cards without validation data\n`;
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Failed to find cards needing validation: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
},
};
}
/**
* Get validation statistics and insights
*/
export function createGetValidationStatsTool(client) {
return {
type: 'call_tool',
name: 'get_validation_stats',
description: 'Get overall validation statistics and insights for the database',
inputSchema: {
type: 'object',
properties: {
include_recent_only: {
type: 'boolean',
description: 'Only include cards validated in the last 7 days',
default: false,
},
},
},
handler: async (params) => {
try {
const { include_recent_only = false } = params;
// Get validation statistics from the API
const response = await client.request('/api/admin/validate-schema', {
method: 'GET',
});
if (!response.success) {
return {
content: [
{
type: 'text',
text: `Failed to get validation statistics: ${response.error || 'Unknown error'}`,
},
],
isError: true,
};
}
const { history, summary } = response.data;
let resultText = `š Validation Statistics & Insights\n\n`;
if (summary) {
resultText += `šÆ Overall Database Health:\n`;
resultText += ` Total Cards with Validation: ${summary.total_cards_with_validation}\n`;
resultText += ` Valid Cards: ${summary.valid_cards}\n`;
resultText += ` Invalid Cards: ${summary.invalid_cards}\n`;
resultText += ` Average Completeness: ${summary.average_completeness}%\n`;
resultText += ` High Completeness (ā„80%): ${summary.high_completeness_cards}\n`;
resultText += ` Low Completeness (<50%): ${summary.low_completeness_cards}\n\n`;
}
if (history && history.length > 0) {
resultText += `š Recent Validation Activity:\n`;
const recentHistory = include_recent_only
? history.filter((h) => new Date(h.created_at) >
new Date(Date.now() - 7 * 24 * 60 * 60 * 1000))
: history.slice(0, 10);
recentHistory.forEach((h, index) => {
resultText += ` ${index + 1}. ${new Date(h.created_at).toLocaleDateString()}\n`;
resultText += ` Type: ${h.validation_type || 'Unknown'}\n`;
if (h.total_cards)
resultText += ` Cards: ${h.total_cards}\n`;
if (h.average_completeness)
resultText += ` Avg Completeness: ${h.average_completeness}%\n`;
});
resultText += '\n';
}
resultText += `š” Recommendations:\n`;
resultText += `⢠Use bulk_validate_cards to check multiple cards at once\n`;
resultText += `⢠Focus on cards with completeness < 50% for maximum impact\n`;
resultText += `⢠Missing required sections (like "impact") should be prioritized\n`;
resultText += `⢠Use get_card_validation for detailed analysis of specific cards\n`;
resultText += `⢠Use find_cards_needing_validation to identify problem cards\n`;
return {
content: [
{
type: 'text',
text: resultText,
},
],
};
}
catch (error) {
return {
content: [
{
type: 'text',
text: `Failed to get validation stats: ${error instanceof Error ? error.message : 'Unknown error'}`,
},
],
isError: true,
};
}
},
};
}
//# sourceMappingURL=validation-operations.js.map