Skip to main content
Glama
yoda-digital

Cerebra Legal MCP Server

by yoda-digital
LegalAttemptCompletionTool.ts11.6 kB
import { DomainDetector } from '../shared/DomainDetector.js'; import { LegalKnowledgeBase } from '../shared/LegalKnowledgeBase.js'; import { CitationFormatter } from '../shared/CitationFormatter.js'; import { LegalAttemptCompletionInput, LegalAttemptCompletionResponse, ToolResponse } from '../shared/types.js'; import { logger } from '../utils/logger.js'; /** * LegalAttemptCompletionTool class for implementing the legal_attempt_completion tool */ export class LegalAttemptCompletionTool { private domainDetector: DomainDetector; private legalKnowledgeBase: LegalKnowledgeBase; private citationFormatter: CitationFormatter; /** * Constructor * @param domainDetector - Domain detector instance * @param legalKnowledgeBase - Legal knowledge base instance * @param citationFormatter - Citation formatter instance */ constructor( domainDetector: DomainDetector, legalKnowledgeBase: LegalKnowledgeBase, citationFormatter: CitationFormatter ) { this.domainDetector = domainDetector; this.legalKnowledgeBase = legalKnowledgeBase; this.citationFormatter = citationFormatter; logger.info('LegalAttemptCompletionTool initialized'); } /** * Process a completion request * @param input - The input data * @returns Tool response */ public processCompletion(input: unknown): ToolResponse { try { logger.debug('Processing completion request', input); // Validate input const validatedInput = this.validateCompletionData(input as LegalAttemptCompletionInput); // Detect domain from result and context const textToAnalyze = validatedInput.context ? `${validatedInput.result} ${validatedInput.context}` : validatedInput.result; const domain = this.domainDetector.detectDomain(textToAnalyze); // Format the result with proper legal structure const formattedResult = this.formatLegalResult(validatedInput.result, domain); // Extract and format citations in the result const citations = this.citationFormatter.extractCitations(validatedInput.result); const formattedCitations = citations.map(citation => this.citationFormatter.formatLegalCitation(citation, domain) ); // Prepare response const response: LegalAttemptCompletionResponse = { result: formattedResult, command: validatedInput.command, detectedDomain: domain, formattedCitations: formattedCitations }; logger.debug('Completion processed successfully', response); return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] }; } catch (error) { // Log and handle errors logger.error('Error processing completion', error as Error); return { content: [{ type: "text", text: JSON.stringify({ error: error instanceof Error ? error.message : String(error), status: 'failed' }, null, 2) }], isError: true }; } } /** * Validate completion data * @param input - The input to validate * @returns Validated input * @throws Error if validation fails */ private validateCompletionData(input: LegalAttemptCompletionInput): LegalAttemptCompletionInput { if (!input) { throw new Error('Input is required'); } if (!input.result || typeof input.result !== 'string') { throw new Error('Result must be a non-empty string'); } // Validate command if provided if (input.command !== undefined && (typeof input.command !== 'string' || input.command.trim() === '')) { throw new Error('Command must be a non-empty string if provided'); } return { result: input.result, command: input.command, context: input.context }; } /** * Format a result with appropriate legal structure * @param result - The original result * @param domain - The detected domain * @returns Formatted result */ private formatLegalResult(result: string, domain: string): string { // If result already has a good structure, return it as is if (this.hasGoodStructure(result)) { return result; } // Get domain-specific completion template const template = this.legalKnowledgeBase.getCompletionTemplate(domain); // Try to apply the template structure while preserving content return this.applyTemplateStructure(result, template, domain); } /** * Check if the result already has a good structure * @param result - The result to check * @returns Whether the result has a good structure */ private hasGoodStructure(result: string): boolean { // Check for section headers (numbered or with clear titles) const hasNumberedSections = /\n\s*\d+\.\s+[A-Z]/.test(result); const hasTitledSections = /\n\s*[A-Z][a-zA-Z\s]+:/.test(result); // Check for bullet points const hasBulletPoints = /\n\s*[-•*]\s+/.test(result); // Check for clear paragraphs const hasClearParagraphs = result.split('\n\n').length >= 3; return (hasNumberedSections || hasTitledSections) && (hasBulletPoints || hasClearParagraphs); } /** * Apply a template structure to the result while preserving content * @param result - The original result * @param template - The template to apply * @param domain - The detected domain * @returns Formatted result */ private applyTemplateStructure(result: string, template: string, domain: string): string { // Extract sections from the template const templateSections = this.extractTemplateSections(template); // Try to extract content for each section from the result const contentSections: Record<string, string> = {}; for (const section of templateSections) { const content = this.extractContentForSection(result, section, domain); contentSections[section] = content; } // Build the formatted result let formattedResult = ''; // Add a title based on domain switch (domain) { case 'ansc_contestation': formattedResult += 'Legal Analysis Summary:\n\n'; break; case 'consumer_protection': formattedResult += 'Consumer Protection Analysis:\n\n'; break; case 'contract_analysis': formattedResult += 'Contract Analysis Report:\n\n'; break; default: formattedResult += 'Legal Analysis Report:\n\n'; } // Add a brief introduction if we can extract one const introMatch = result.match(/^([^.!?]+[.!?])/); if (introMatch) { formattedResult += `${introMatch[1]}\n\n`; } // Add each section with its content for (let i = 0; i < templateSections.length; i++) { const section = templateSections[i]; const content = contentSections[section]; formattedResult += `${i + 1}. ${section}:\n${content}\n\n`; } return formattedResult.trim(); } /** * Extract sections from a template * @param template - The template * @returns Array of section names */ private extractTemplateSections(template: string): string[] { const sections: string[] = []; // Look for numbered sections const sectionMatches = template.match(/\d+\.\s+([A-Z][^:]+):/g); if (sectionMatches) { for (const match of sectionMatches) { const sectionName = match.replace(/\d+\.\s+/, '').replace(':', ''); sections.push(sectionName); } } // If no sections found, use default sections if (sections.length === 0) { sections.push('Key Findings', 'Legal Assessment', 'Recommended Action', 'Next Steps'); } return sections; } /** * Extract content for a specific section from the result * @param result - The original result * @param section - The section name * @param domain - The detected domain * @returns Content for the section */ private extractContentForSection(result: string, section: string, domain: string): string { // Define keywords for each section by domain const sectionKeywords: Record<string, Record<string, string[]>> = { 'ansc_contestation': { 'Key Findings': ['finding', 'claim', 'specification', 'requirement', 'challenge'], 'Legal Assessment': ['assessment', 'analysis', 'law', 'article', 'provision', 'legal', 'compliance'], 'Recommended Action': ['recommend', 'action', 'strategy', 'approach', 'response'], 'Next Steps': ['next', 'step', 'timeline', 'procedure', 'document', 'submit'] }, 'consumer_protection': { 'Factual Assessment': ['fact', 'claim', 'product', 'warranty', 'consumer', 'retailer'], 'Legal Position': ['position', 'law', 'right', 'obligation', 'protection', 'legal'], 'Recommended Action': ['recommend', 'action', 'strategy', 'approach', 'response'], 'Next Steps': ['next', 'step', 'timeline', 'communication', 'document'] }, 'contract_analysis': { 'Key Provisions Assessment': ['provision', 'clause', 'term', 'condition', 'agreement'], 'Legal Implications': ['implication', 'consequence', 'effect', 'risk', 'liability'], 'Recommended Action': ['recommend', 'action', 'strategy', 'approach', 'modification'], 'Implementation Guidance': ['implement', 'guidance', 'step', 'procedure', 'monitor'] }, 'legal_reasoning': { 'Factual Summary': ['fact', 'summary', 'background', 'situation', 'circumstance'], 'Legal Assessment': ['assessment', 'analysis', 'law', 'legal', 'principle', 'precedent'], 'Recommended Position': ['position', 'recommendation', 'conclusion', 'opinion'], 'Action Plan': ['action', 'plan', 'step', 'timeline', 'resource'] } }; // Get keywords for this section and domain const domainKeywords = sectionKeywords[domain] || sectionKeywords['legal_reasoning']; const keywords = domainKeywords[section] || []; // Split result into paragraphs const paragraphs = result.split('\n').filter(p => p.trim().length > 0); // Score each paragraph for relevance to this section const scoredParagraphs = paragraphs.map(paragraph => { let score = 0; // Check for section name in paragraph if (paragraph.toLowerCase().includes(section.toLowerCase())) { score += 5; } // Check for keywords for (const keyword of keywords) { if (paragraph.toLowerCase().includes(keyword.toLowerCase())) { score += 1; } } return { paragraph, score }; }); // Sort by score (descending) scoredParagraphs.sort((a, b) => b.score - a.score); // Get top paragraphs const topParagraphs = scoredParagraphs.slice(0, 3).filter(p => p.score > 0); // If no relevant paragraphs found, create a placeholder if (topParagraphs.length === 0) { return ` - [${section} information not found in the original text]`; } // Format paragraphs as bullet points return topParagraphs.map(p => { const paragraph = p.paragraph.trim(); // If paragraph already starts with a bullet point or dash, use it as is if (paragraph.startsWith('-') || paragraph.startsWith('•') || paragraph.startsWith('*')) { return ` ${paragraph}`; } // Otherwise, add a dash return ` - ${paragraph}`; }).join('\n'); } }

Latest Blog Posts

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/yoda-digital/mcp-cerebra-legal-server'

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