#!/usr/bin/env node
// Load environment variables from .env file
import dotenv from 'dotenv';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Try to load .env from multiple locations
const envPaths = [
resolve(__dirname, '../.env'), // When running from build directory
resolve(__dirname, '.env'), // When running from src directory
resolve(process.cwd(), '.env'), // Current working directory
'/Users/josuekongolo/Downloads/mcp/companyiq-mcp/.env' // Absolute fallback
];
let envLoaded = false;
for (const envPath of envPaths) {
const result = dotenv.config({ path: envPath });
if (!result.error) {
console.error(`✅ Loaded .env from: ${envPath}`);
envLoaded = true;
break;
}
}
if (!envLoaded) {
console.error('⚠️ Could not load .env file from any location');
console.error('Tried:', envPaths);
}
// Debug: Log if API key is present
if (process.env.OPENAI_API_KEY) {
console.error(`✅ OPENAI_API_KEY loaded: ${process.env.OPENAI_API_KEY.substring(0, 20)}...`);
} else {
console.error('❌ OPENAI_API_KEY not found in environment!');
}
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { CompanyDatabase } from './database/db.js';
import { BrregClient } from './apis/brreg.js';
import { SSBClient } from './apis/ssb.js';
import { searchCompanies } from './tools/search_companies.js';
import { analyzeGrowth } from './tools/analyze_growth.js';
import { analyzeOwnership } from './tools/ownership_analysis.js';
import { trackBoard } from './tools/board_tracking.js';
import { analyzeFinancials } from './tools/financial_analysis.js';
import { analyzeMarketLandscape } from './tools/market_landscape.js';
import { analyzeConsolidation } from './tools/consolidation_trends.js';
import { getEconomicContext } from './tools/economic_context.js';
import { importFinancials, importFinancialsFromFile } from './tools/import_financials.js';
import { getCompany } from './tools/get_company.js';
import { getFinancialLink } from './tools/get_financial_link.js';
import { fetchFinancials } from './tools/fetch_financials.js';
import { searchBankruptCompanies } from './tools/search_bankrupt_companies.js';
import { buildFinancialHistory } from './tools/build_financial_history.js';
import { autoScrapeFinancials } from './tools/auto_scrape_financials.js';
// Initialize database and API clients
const db = new CompanyDatabase();
const brreg = new BrregClient();
const ssb = new SSBClient(db); // Pass database for caching
// Create MCP server
const server = new Server(
{
name: "companyiq-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_company",
description: "Hent komplett informasjon om et spesifikt selskap. Søk direkte med org.nr eller selskapsnavn. Alltid henter fra Brønnøysund - garantert oppdatert!",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer (9 siffer)" },
name: { type: "string", description: "Selskapsnavn (hvis org.nr ikke kjent)" }
}
}
},
{
name: "search_companies",
description: "Søk etter norske selskaper basert på bransje, størrelse, region, etableringsår, og daglig leder alder. Henter data fra Brønnøysund. Perfekt for å finne aktører i et marked.",
inputSchema: {
type: "object",
properties: {
industry: { type: "string", description: "Bransje/NACE-kode (f.eks. '62' for IT, '45' for bygg)" },
name: { type: "string", description: "Selskapsnavn eller del av navn" },
region: { type: "string", description: "Region/kommune (f.eks. 'Bergen', 'Oslo', 'Vestland')" },
min_employees: { type: "number", description: "Minimum antall ansatte" },
max_employees: { type: "number", description: "Maksimum antall ansatte" },
min_established_year: { type: "number", description: "Minimum etableringsår (f.eks. 2020)" },
max_established_year: { type: "number", description: "Maksimum etableringsår (f.eks. 2024)" },
min_ceo_age: { type: "number", description: "Minimum alder for daglig leder (f.eks. 30)" },
max_ceo_age: { type: "number", description: "Maksimum alder for daglig leder (f.eks. 60)" },
exclude_bankrupt: { type: "boolean", description: "Ekskluder konkursrammede selskaper", default: true },
limit: { type: "number", description: "Maksimalt antall resultater", default: 50 }
}
}
},
{
name: "analyze_growth",
description: "Identifiser høyvekstselskaper i en bransje eller region. Analyserer omsetnings- og ansattvekst. Bruk dette for å finne emerging players.",
inputSchema: {
type: "object",
properties: {
industry: { type: "string", description: "NACE-kode for bransje" },
region: { type: "string", description: "Region/fylke" },
min_growth_percent: { type: "number", description: "Minimum vekstprosent", default: 20 },
time_period: { type: "string", enum: ["1_year", "3_years", "5_years"], default: "3_years" },
limit: { type: "number", description: "Antall resultater", default: 50 }
}
}
},
{
name: "analyze_ownership",
description: "Analyser eierstruktur og datterselskaper. Vis hvem som eier et selskap og hvilke underenheter det har. Nyttig for M&A due diligence.",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer (9 siffer)" },
include_subunits: { type: "boolean", description: "Inkluder datterselskaper/underenheter", default: true },
depth: { type: "number", description: "Dybde i eierskapstreet", default: 2 }
},
required: ["org_nr"]
}
},
{
name: "track_board",
description: "Track styresammensetning og ledelse i et selskap. Vis hvem som sitter i styret og deres roller.",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
company_name: { type: "string", description: "Selskapsnavn (alternativ til org.nr)" }
}
}
},
{
name: "analyze_financials",
description: "🤖 AUTO-SCRAPING ENABLED: Finansiell analyse med automatisk nedlasting av ALLE årsregnskap hvis ikke i database. Første gang tar 2-5 min (auto-download), deretter instant (fra database).",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
include_risk_assessment: { type: "boolean", description: "Inkluder konkursrisikoanalyse", default: true },
auto_fetch: { type: "boolean", description: "Automatisk hent ALL regnskapsdata hvis mangler", default: true }
},
required: ["org_nr"]
}
},
{
name: "market_landscape",
description: "Analyser competitive landscape i en bransje. Vis alle aktører, markedsstørrelse, konsentrasjon og dominerende players.",
inputSchema: {
type: "object",
properties: {
industry: { type: "string", description: "NACE-kode for bransje (f.eks. '62' for IT)" },
region: { type: "string", description: "Region/fylke" },
include_stats: { type: "boolean", description: "Inkluder statistikk", default: true },
limit: { type: "number", description: "Maksimalt antall selskaper", default: 100 }
},
required: ["industry"]
}
},
{
name: "consolidation_trends",
description: "Analyser konsolideringstrends og M&A-aktivitet i en bransje. Vis markedskonsentrasjon, konkurser, og dominerende aktører.",
inputSchema: {
type: "object",
properties: {
industry: { type: "string", description: "NACE-kode for bransje" },
time_period: { type: "string", enum: ["1_year", "3_years", "5_years"], default: "3_years" },
region: { type: "string", description: "Region/fylke" }
},
required: ["industry"]
}
},
{
name: "economic_context",
description: "Hent økonomisk kontekst og makrostatistikk fra SSB. Vis industritrender, sysselsetting, innovasjon, og økonomiske indikatorer.",
inputSchema: {
type: "object",
properties: {
industry: { type: "string", description: "NACE-kode for spesifikk bransje" },
region: { type: "string", description: "Region/fylke" },
include_innovation: { type: "boolean", description: "Inkluder innovasjonsstatistikk", default: false }
}
}
},
{
name: "fetch_financials",
description: "🚀 AUTOMATISK HENTING: Hent regnskapsdata fra Brønnøysund Regnskapsregisteret API - INGEN manuell nedlasting! Henter siste tilgjengelige årsregnskap (2018+). Kjør årlig for å bygge historikk.",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
auto_import: { type: "boolean", description: "Lagre automatisk til database", default: true },
all_years: { type: "boolean", description: "Forsøk å hente alle år (standard: true)", default: true }
},
required: ["org_nr"]
}
},
{
name: "get_financial_link",
description: "Få direkte link og steg-for-steg guide til manuell nedlasting av årsregnskap. Bruk dette hvis fetch_financials ikke finner data (f.eks. eldre enn 2018).",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
year: { type: "number", description: "Regnskapsår (valgfritt)" }
},
required: ["org_nr"]
}
},
{
name: "import_financials",
description: "Importer regnskapsdata manuelt. Last ned årsregnskap gratis fra Brønnøysund og importer tallene her. Gjør CompanyIQ sin finansanalyse brukbar med reelle tall.",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
year: { type: "number", description: "Regnskapsår (f.eks. 2023)" },
revenue: { type: "number", description: "Omsetning i NOK (f.eks. 1500000000)" },
profit: { type: "number", description: "Resultat/overskudd i NOK" },
assets: { type: "number", description: "Sum eiendeler i NOK" },
equity: { type: "number", description: "Sum egenkapital i NOK" },
employees: { type: "number", description: "Antall ansatte i regnskapsåret" },
source: { type: "string", description: "Datakilde (manual, proff, bronnoysund)", default: "manual" }
},
required: ["org_nr", "year"]
}
},
{
name: "import_financials_from_file",
description: "Importer regnskapsdata fra CSV eller JSON fil. Støtter bulk-import av flere selskaper samtidig.",
inputSchema: {
type: "object",
properties: {
file_path: { type: "string", description: "Absolutt sti til fil" },
format: { type: "string", enum: ["csv", "json"], description: "Filformat" }
},
required: ["file_path", "format"]
}
},
{
name: "search_bankrupt_companies",
description: "Finn konkursrammede selskaper i en bransje eller region. Nyttig for markedsanalyse, risikovurdering, eller oppkjøpsmuligheter (asset deals).",
inputSchema: {
type: "object",
properties: {
industry: { type: "string", description: "NACE-kode for bransje" },
region: { type: "string", description: "Region/kommune" },
limit: { type: "number", description: "Maksimalt antall resultater", default: 50 }
}
}
},
{
name: "build_financial_history",
description: "💡 SMART HELPER: Auto-henter siste år + gir nedlastingslenker og CSV-template for historiske år. Raskeste vei til komplett historikk!",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
years_needed: { type: "number", description: "Totalt antall år ønsket (standard: 5)", default: 5 }
},
required: ["org_nr"]
}
},
{
name: "auto_scrape_financials",
description: "🤖 100% AUTOMATISK: Bruker headless browser til å laste ned og parse ALLE tilgjengelige årsregnskap fra Brønnøysund. Ingen manuelt arbeid! Tar 30-60 sek.",
inputSchema: {
type: "object",
properties: {
org_nr: { type: "string", description: "Organisasjonsnummer" },
auto_import: { type: "boolean", description: "Lagre automatisk til database", default: true },
use_api_first: { type: "boolean", description: "Bruk API for siste år først (raskere)", default: true }
},
required: ["org_nr"]
}
}
]
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
console.error(`Executing tool: ${name}`);
switch (name) {
case "get_company":
return await getCompany(args, db, brreg);
case "search_companies":
return await searchCompanies(args, db, brreg);
case "analyze_growth":
return await analyzeGrowth(args, db, ssb);
case "analyze_ownership":
return await analyzeOwnership(args, db, brreg);
case "track_board":
return await trackBoard(args, db, brreg);
case "analyze_financials":
return await analyzeFinancials(args, db, brreg);
case "market_landscape":
return await analyzeMarketLandscape(args, db, brreg, ssb);
case "consolidation_trends":
return await analyzeConsolidation(args, db, ssb);
case "economic_context":
return await getEconomicContext(args, ssb);
case "fetch_financials":
return await fetchFinancials(args, db, brreg);
case "get_financial_link":
return await getFinancialLink(args, db, brreg);
case "import_financials":
return await importFinancials(args, db);
case "import_financials_from_file":
return await importFinancialsFromFile(args, db);
case "search_bankrupt_companies":
return await searchBankruptCompanies(args, db, brreg);
case "build_financial_history":
return await buildFinancialHistory(args, db, brreg);
case "auto_scrape_financials":
return await autoScrapeFinancials(args, db, brreg);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
console.error("Tool execution error:", error);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [{
type: "text",
text: `❌ Feil: ${errorMessage}`
}],
isError: true
};
}
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("CompanyIQ MCP Server running on stdio");
console.error("Ready to provide company intelligence! 🏢");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});