UNPKG

mcp-tenant-credit-scorer

Version:

MCP server for tenant credit scoring based on S&P corporate methodology

370 lines (342 loc) 10.2 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { DataParser } from './data-parser.js'; import { IndustryMatcher } from './industry-matcher.js'; import { ScoreCalculator } from './score-calculator.js'; import { ReportGenerator } from './report-generator.js'; import { Validator } from './validator.js'; // Initialize server const server = new Server( { name: 'mcp-credit-scorer', version: '1.0.0', }, { capabilities: { resources: {}, tools: {}, }, } ); // Initialize modules const dataParser = new DataParser(); const industryMatcher = new IndustryMatcher(); const scoreCalculator = new ScoreCalculator(); const reportGenerator = new ReportGenerator(); const validator = new Validator(); // List available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'analyze_credit', description: 'Analyze tenant credit from financial statements and website', inputSchema: { type: 'object', properties: { companyName: { type: 'string', description: 'Name of the company to analyze', }, website: { type: 'string', description: 'Company website URL', }, financialData: { type: 'object', description: 'Financial statement data (can be PDF path or parsed data)', properties: { pdfPath: { type: 'string' }, parsedData: { type: 'object' } } }, options: { type: 'object', properties: { includeDetailedReport: { type: 'boolean', default: true }, confidenceLevel: { type: 'string', enum: ['audited', 'company-prepared', 'tax-returns', 'limited'], default: 'company-prepared' } } } }, required: ['companyName'], }, }, { name: 'score_component', description: 'Score an individual component of the credit assessment', inputSchema: { type: 'object', properties: { component: { type: 'string', enum: ['industry', 'competitive', 'financial', 'liquidity', 'management'], description: 'Component to score', }, data: { type: 'object', description: 'Data needed for scoring the component', }, }, required: ['component', 'data'], }, }, { name: 'classify_industry', description: 'Classify a company into S&P industry categories', inputSchema: { type: 'object', properties: { companyDescription: { type: 'string', description: 'Description of company business activities', }, website: { type: 'string', description: 'Company website for additional context', }, }, required: ['companyDescription'], }, }, { name: 'validate_scores', description: 'Validate score consistency and flag conflicts', inputSchema: { type: 'object', properties: { scores: { type: 'object', properties: { industryRisk: { type: 'number' }, competitivePosition: { type: 'number' }, financialRisk: { type: 'number' }, liquidity: { type: 'number' }, management: { type: 'number' } } }, }, required: ['scores'], }, } ], }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'analyze_credit': { // Full credit analysis workflow const { companyName, website, financialData, options = {} } = args; // 1. Parse financial data let parsedFinancials; if (financialData?.pdfPath) { parsedFinancials = await dataParser.parsePDF(financialData.pdfPath); } else if (financialData?.parsedData) { parsedFinancials = financialData.parsedData; } else { throw new Error('Financial data required'); } // 2. Extract company info from website const companyInfo = website ? await dataParser.parseWebsite(website) : {}; // 3. Classify industry const industryClass = await industryMatcher.classify({ description: companyInfo.description || '', website: website }); // 4. Calculate all component scores const scores = await scoreCalculator.calculateAllScores({ companyName, financials: parsedFinancials, industry: industryClass, companyInfo }); // 5. Validate scores const validation = await validator.validateScores(scores); // 6. Generate report const report = options.includeDetailedReport ? await reportGenerator.generateFullReport({ companyName, scores, validation, industryClass, financials: parsedFinancials, companyInfo, confidenceLevel: options.confidenceLevel }) : await reportGenerator.generateSummary({ companyName, scores, validation }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, scores, validation, report }, null, 2), }, ], }; } case 'score_component': { const { component, data } = args; const score = await scoreCalculator.scoreComponent(component, data); return { content: [ { type: 'text', text: JSON.stringify({ success: true, component, score }, null, 2), }, ], }; } case 'classify_industry': { const { companyDescription, website } = args; const classification = await industryMatcher.classify({ description: companyDescription, website }); return { content: [ { type: 'text', text: JSON.stringify({ success: true, classification }, null, 2), }, ], }; } case 'validate_scores': { const { scores } = args; const validation = await validator.validateScores(scores); return { content: [ { type: 'text', text: JSON.stringify({ success: true, validation }, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2), }, ], }; } }); // List available resources server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'credit-scorer://methodology', name: 'Credit Scoring Methodology', description: 'Complete methodology guide for credit scoring', mimeType: 'text/markdown', }, { uri: 'credit-scorer://scoring-tables', name: 'Scoring Reference Tables', description: 'All scoring matrices and thresholds', mimeType: 'application/json', }, { uri: 'credit-scorer://industry-data', name: 'Industry Classifications', description: 'S&P industry classifications and CPGP assignments', mimeType: 'application/json', }, ], }; }); // Read resources server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; switch (uri) { case 'credit-scorer://methodology': return { contents: [ { uri, mimeType: 'text/markdown', text: await import('fs').then(fs => fs.promises.readFile('../methodology-guide.md', 'utf-8') ), }, ], }; case 'credit-scorer://scoring-tables': return { contents: [ { uri, mimeType: 'application/json', text: await import('fs').then(fs => fs.promises.readFile('./data/scoring-tables.json', 'utf-8') ), }, ], }; case 'credit-scorer://industry-data': return { contents: [ { uri, mimeType: 'application/json', text: await import('fs').then(fs => fs.promises.readFile('./data/industry-data.json', 'utf-8') ), }, ], }; default: throw new Error(`Unknown resource: ${uri}`); } }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // Don't log to console in production - MCP uses stdio for communication } main().catch((error) => { // In MCP, errors should be sent through the protocol, not to stderr process.exit(1); });