bizfile-mcp 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.env.example ADDED
@@ -0,0 +1,20 @@
1
+ # Bizfile MCP Server — Environment Variables
2
+ # Copy this file to .env and fill in your values
3
+ # NEVER commit .env to git
4
+
5
+ # ── REQUIRED ──────────────────────────────────────────────
6
+ # Your Anthropic API key from console.anthropic.com
7
+ ANTHROPIC_API_KEY=sk-ant-your-key-here
8
+
9
+ # ── OPTIONAL (adds more data sources) ─────────────────────
10
+ # UK Companies House — free at developer.company-information.service.gov.uk
11
+ # Adds detailed UK company profiles, filing history, and officer data
12
+ COMPANIES_HOUSE_API_KEY=your-companies-house-key
13
+
14
+ # OpenCorporates — free tier at opencorporates.com/api_accounts/new
15
+ # Higher rate limits with an API token (free registration)
16
+ OPENCORPORATES_API_TOKEN=your-opencorporates-token
17
+
18
+ # ── FUTURE (v2) ───────────────────────────────────────────
19
+ # Stripe for billing (add when first customer is ready)
20
+ # STRIPE_SECRET_KEY=sk_live_your-key
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OjasKord
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # Bizfile MCP — Global Company Intelligence
2
+
3
+ > Real-time company verification, KYC, and due diligence across 130+ jurisdictions for AI agents.
4
+
5
+ [![smithery badge](https://smithery.ai/badge/OjasKord/bizfile-mcp)](https://smithery.ai/server/OjasKord/bizfile-mcp)
6
+
7
+ ## What it does
8
+
9
+ Bizfile MCP gives any AI agent instant access to verified company data from official government registries worldwide. No hallucinated data — every result comes directly from authoritative sources in real time.
10
+
11
+ Built for compliance, KYC, and due diligence workflows running inside AI agents.
12
+
13
+ ---
14
+
15
+ ## Tools
16
+
17
+ ### `search_company`
18
+ Search for any company by name across UK Companies House, Singapore ACRA, and OpenCorporates (130+ jurisdictions).
19
+
20
+ **Example:**
21
+ ```json
22
+ { "company_name": "Shell", "country": "UK" }
23
+ ```
24
+ **Returns:** List of matching companies with registration numbers, status, jurisdiction, incorporation date, and registry URLs.
25
+
26
+ ---
27
+
28
+ ### `get_company_profile`
29
+ Get a full company profile including registration status, registered address, SIC codes, filing history, accounts status, and key officers.
30
+
31
+ **Example:**
32
+ ```json
33
+ { "company_name": "Shell PLC", "registration_number": "04366849", "jurisdiction": "gb" }
34
+ ```
35
+ **Returns:** Complete company record with filing history URL, SIC codes, accounts, and officer list.
36
+
37
+ ---
38
+
39
+ ### `verify_company`
40
+ KYC-style verification of a company across multiple registries. Returns a confidence rating (HIGH / MEDIUM / LOW), verified status, and any discrepancies found.
41
+
42
+ **Example:**
43
+ ```json
44
+ { "company_name": "Accenture Singapore", "country": "SG" }
45
+ ```
46
+ **Returns:** Verification report with confidence level, confirmed identity fields, data sources checked, and verification gaps.
47
+
48
+ ---
49
+
50
+ ### `check_company_risk`
51
+ AI-powered due diligence risk assessment. Analyses registry data to produce a risk score (0–100), risk level, specific risk factors, and recommended due diligence actions.
52
+
53
+ **Example:**
54
+ ```json
55
+ { "company_name": "Acme Trading Ltd", "registration_number": "12345678", "jurisdiction": "gb" }
56
+ ```
57
+ **Returns:** Risk score, risk level (LOW / MEDIUM / HIGH / CRITICAL), list of specific risk factors with severity, positive indicators, and recommended next steps.
58
+
59
+ ---
60
+
61
+ ### `get_officers`
62
+ Get the directors and officers of a UK company including appointment dates, roles, nationalities, and resignation history. Useful for beneficial ownership analysis.
63
+
64
+ **Example:**
65
+ ```json
66
+ { "company_name": "Shell PLC", "registration_number": "04366849", "jurisdiction": "gb" }
67
+ ```
68
+ **Returns:** Active and resigned officers with roles, appointment dates, nationalities, and occupations.
69
+
70
+ ---
71
+
72
+ ## Data Sources
73
+
74
+ | Source | Coverage | Free |
75
+ |--------|----------|------|
76
+ | UK Companies House | 5M+ UK companies, full filing history | ✅ |
77
+ | Singapore ACRA | All Singapore-registered entities | ✅ |
78
+ | OpenCorporates | 210M+ companies, 130+ jurisdictions | ✅ |
79
+ | US SEC EDGAR | All US public company filings | ✅ |
80
+
81
+ All data sourced from official government registries under open data licences. AI analysis powered by Anthropic Claude.
82
+
83
+ ---
84
+
85
+ ## Quick Start
86
+
87
+ ### Connect via Smithery
88
+ ```bash
89
+ smithery mcp add OjasKord/bizfile-mcp
90
+ ```
91
+
92
+ ### Connect via Claude Desktop
93
+ Add to your `claude_desktop_config.json`:
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "bizfile": {
98
+ "command": "node",
99
+ "args": ["/path/to/bizfile-mcp/src/server.js"],
100
+ "env": {
101
+ "ANTHROPIC_API_KEY": "your-key-here",
102
+ "COMPANIES_HOUSE_API_KEY": "your-key-here"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ ### Connect via HTTP
110
+ ```
111
+ https://bizfile-mcp--ojaskord.run.tools
112
+ ```
113
+
114
+ ---
115
+
116
+ ## Environment Variables
117
+
118
+ | Variable | Required | Description |
119
+ |----------|----------|-------------|
120
+ | `ANTHROPIC_API_KEY` | ✅ Required | Powers AI risk assessment and verification |
121
+ | `COMPANIES_HOUSE_API_KEY` | Recommended | Free from developer.company-information.service.gov.uk — unlocks full UK data |
122
+ | `OPENCORPORATES_API_TOKEN` | Optional | Higher rate limits on global search |
123
+
124
+ ---
125
+
126
+ ## Use Cases
127
+
128
+ - **KYC automation** — Verify counterparties before onboarding
129
+ - **Due diligence agents** — Automated company research for M&A and investment
130
+ - **Compliance workflows** — Screen companies against registry data
131
+ - **Legal research** — Director history, filing compliance, company status
132
+ - **Trade finance** — Verify buyers, sellers, and intermediaries in commodity deals
133
+ - **Credit underwriting** — Company age, filing history, officer stability
134
+
135
+ ---
136
+
137
+ ## Pricing
138
+
139
+ | Tier | Price | Calls |
140
+ |------|-------|-------|
141
+ | Free | — | 100 calls/month |
142
+ | Pro | $299/month | 10,000 calls/month |
143
+ | Enterprise | $999/month | Unlimited + SLA |
144
+
145
+ ---
146
+
147
+ ## Support
148
+
149
+ - GitHub Issues: [github.com/OjasKord/bizfile-mcp/issues](https://github.com/OjasKord/bizfile-mcp/issues)
150
+ - Email: contact@bizfilemcp.com
151
+
152
+ ---
153
+
154
+ ## Legal
155
+
156
+ All data retrieved from official public government registries. This tool does not provide legal or financial advice. Always verify critical information with qualified professionals.
157
+
158
+ MIT License
@@ -0,0 +1,30 @@
1
+ # Bizfile MCP — System Prompt
2
+
3
+ You have access to Bizfile MCP, a company intelligence server that provides real-time data from official government registries worldwide.
4
+
5
+ ## Available Tools
6
+
7
+ - **search_company** — Find any company by name across 130+ jurisdictions
8
+ - **get_company_profile** — Get full company details including status, address, filings, and officers
9
+ - **verify_company** — KYC-style verification with confidence rating
10
+ - **check_company_risk** — AI risk assessment with score 0-100 and due diligence recommendations
11
+ - **get_officers** — Directors and officers with appointment history
12
+
13
+ ## How to use effectively
14
+
15
+ 1. Always start with `search_company` to get the registration number
16
+ 2. Use the registration number with `get_company_profile` for full details
17
+ 3. Use `check_company_risk` for due diligence workflows
18
+ 4. Use `verify_company` for KYC and onboarding workflows
19
+ 5. Use `get_officers` with jurisdiction: gb for UK beneficial ownership checks
20
+
21
+ ## Jurisdiction codes
22
+ - `gb` — United Kingdom
23
+ - `sg` — Singapore
24
+ - `us` — United States
25
+ - `us_de` — Delaware (US)
26
+ - `ie` — Ireland
27
+ - `au` — Australia
28
+
29
+ ## Data is live
30
+ All data is fetched in real time from official registries. Results reflect the current state of company records.
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "bizfile-mcp",
3
+ "version": "1.3.0",
4
+ "description": "Real-time company verification, KYC, and due diligence across 130+ jurisdictions. Search companies, verify identities, assess risk, and get officer data from UK Companies House, Singapore ACRA, and US SEC EDGAR.",
5
+ "main": "src/server.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "start": "node src/server.js",
9
+ "dev": "node --watch src/server.js"
10
+ },
11
+ "keywords": [
12
+ "mcp",
13
+ "company-intelligence",
14
+ "kyc",
15
+ "due-diligence",
16
+ "compliance",
17
+ "company-registry",
18
+ "companies-house",
19
+ "acra",
20
+ "singapore",
21
+ "uk",
22
+ "business-verification",
23
+ "corporate-intelligence",
24
+ "aml",
25
+ "sanctions",
26
+ "directors",
27
+ "officers",
28
+ "trade-finance",
29
+ "fintech",
30
+ "legal",
31
+ "risk"
32
+ ],
33
+ "author": "OjasKord",
34
+ "license": "MIT",
35
+ "homepage": "https://github.com/OjasKord/bizfile-mcp",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/OjasKord/bizfile-mcp.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/OjasKord/bizfile-mcp/issues"
42
+ },
43
+ "dependencies": {
44
+ "@anthropic-ai/sdk": "^0.39.0",
45
+ "@modelcontextprotocol/sdk": "^1.10.1",
46
+ "node-fetch": "^3.3.2"
47
+ },
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ }
51
+ }
package/smithery.yaml ADDED
@@ -0,0 +1,41 @@
1
+ # Smithery configuration for Bizfile MCP
2
+ # https://smithery.ai/docs/config
3
+
4
+ startCommand:
5
+ type: http
6
+ configSchema:
7
+ type: object
8
+ properties:
9
+ anthropicApiKey:
10
+ type: string
11
+ description: "Your Anthropic API key from console.anthropic.com"
12
+ x-from:
13
+ header: "x-anthropic-api-key"
14
+ companiesHouseApiKey:
15
+ type: string
16
+ description: "UK Companies House API key (free at developer.company-information.service.gov.uk) — unlocks full UK company data"
17
+ x-from:
18
+ header: "x-companies-house-api-key"
19
+ required: []
20
+ url: https://bizfile-mcp-production.up.railway.app
21
+
22
+ systemPrompt: |
23
+ You have access to Bizfile MCP, a company intelligence server providing real-time data from official government registries worldwide.
24
+
25
+ Available tools:
26
+ - search_company: Find any company by name across 130+ jurisdictions
27
+ - get_company_profile: Full company details including status, address, filings, and officers
28
+ - verify_company: KYC-style verification with confidence rating (HIGH/MEDIUM/LOW)
29
+ - check_company_risk: AI risk score 0-100 with due diligence recommendations
30
+ - get_officers: Directors and officers with appointment history (UK companies)
31
+
32
+ Best practice workflow:
33
+ 1. Use search_company to find the company and get its registration number
34
+ 2. Use get_company_profile with the registration number for full details
35
+ 3. Use check_company_risk for due diligence
36
+ 4. Use verify_company for KYC and onboarding
37
+ 5. Use get_officers with jurisdiction: gb for UK beneficial ownership checks
38
+
39
+ Jurisdiction codes: gb (UK), sg (Singapore), us (USA), ie (Ireland), au (Australia)
40
+
41
+ All data is fetched live from official government registries. No hallucinated company data.
package/src/server.js ADDED
@@ -0,0 +1,263 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bizfile MCP Server v1.3
4
+ * Root endpoint handles MCP JSON-RPC directly (Smithery-compatible)
5
+ */
6
+
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
10
+ import { CallToolRequestSchema, ListToolsRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
11
+ import Anthropic from "@anthropic-ai/sdk";
12
+ import http from "http";
13
+ import { randomUUID } from "crypto";
14
+
15
+ const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
16
+ const MODEL = "claude-sonnet-4-20250514";
17
+ const PORT = process.env.PORT || 3000;
18
+
19
+ // ─── DATA SOURCES ─────────────────────────────────────────────────────────────
20
+
21
+ async function searchCompaniesHouse(companyName) {
22
+ const apiKey = process.env.COMPANIES_HOUSE_API_KEY;
23
+ if (!apiKey) return null;
24
+ const auth = Buffer.from(`${apiKey}:`).toString("base64");
25
+ const res = await fetch(`https://api.company-information.service.gov.uk/search/companies?q=${encodeURIComponent(companyName)}&items_per_page=5`, { headers: { Authorization: `Basic ${auth}` } });
26
+ if (!res.ok) return null;
27
+ return await res.json();
28
+ }
29
+
30
+ async function getCompaniesHouseProfile(companyNumber) {
31
+ const apiKey = process.env.COMPANIES_HOUSE_API_KEY;
32
+ if (!apiKey) return null;
33
+ const auth = Buffer.from(`${apiKey}:`).toString("base64");
34
+ const headers = { Authorization: `Basic ${auth}` };
35
+ const [profileRes, officersRes] = await Promise.all([
36
+ fetch(`https://api.company-information.service.gov.uk/company/${companyNumber}`, { headers }),
37
+ fetch(`https://api.company-information.service.gov.uk/company/${companyNumber}/officers`, { headers }),
38
+ ]);
39
+ return { profile: profileRes.ok ? await profileRes.json() : null, officers: officersRes.ok ? await officersRes.json() : null };
40
+ }
41
+
42
+ async function searchACRA(companyName) {
43
+ const res = await fetch(`https://data.gov.sg/api/action/datastore_search?resource_id=d_3f960c10fed6145404ca7b821f263b87&q=${encodeURIComponent(companyName)}&limit=5`);
44
+ if (!res.ok) return null;
45
+ const data = await res.json();
46
+ return data.result?.records || null;
47
+ }
48
+
49
+ async function searchOpenCorporates(companyName, jurisdiction) {
50
+ const params = new URLSearchParams({ q: companyName, per_page: "5" });
51
+ if (jurisdiction) params.append("jurisdiction_code", jurisdiction);
52
+ const apiToken = process.env.OPENCORPORATES_API_TOKEN;
53
+ if (apiToken) params.append("api_token", apiToken);
54
+ const res = await fetch(`https://api.opencorporates.com/v0.4/companies/search?${params}`);
55
+ if (!res.ok) return [];
56
+ const data = await res.json();
57
+ return data.results?.companies || [];
58
+ }
59
+
60
+ async function getOpenCorporatesCompany(jurisdictionCode, companyNumber) {
61
+ const apiToken = process.env.OPENCORPORATES_API_TOKEN;
62
+ const tokenParam = apiToken ? `?api_token=${apiToken}` : "";
63
+ const res = await fetch(`https://api.opencorporates.com/v0.4/companies/${jurisdictionCode}/${companyNumber}${tokenParam}`);
64
+ if (!res.ok) return null;
65
+ const data = await res.json();
66
+ return data.results?.company || null;
67
+ }
68
+
69
+ // ─── AI ANALYSIS ─────────────────────────────────────────────────────────────
70
+
71
+ async function analyzeWithClaude(systemPrompt, userContent) {
72
+ const response = await anthropic.messages.create({
73
+ model: MODEL, max_tokens: 1500,
74
+ system: systemPrompt,
75
+ messages: [{ role: "user", content: userContent }],
76
+ });
77
+ return response.content[0].text;
78
+ }
79
+
80
+ const RISK_PROMPT = `You are a corporate KYC specialist. Return ONLY valid JSON, no markdown:
81
+ {"risk_score":0-100,"risk_level":"LOW|MEDIUM|HIGH|CRITICAL","risk_factors":[{"factor":"string","detail":"string","severity":"LOW|MEDIUM|HIGH"}],"positive_indicators":["string"],"recommended_actions":["string"],"summary":"string"}`;
82
+
83
+ const VERIFY_PROMPT = `You are a KYC verification specialist. Return ONLY valid JSON, no markdown:
84
+ {"verified":true,"confidence":"HIGH|MEDIUM|LOW","identity_confirmed":{"legal_name":"string","registration_number":"string","jurisdiction":"string","status":"ACTIVE|INACTIVE|DISSOLVED|UNKNOWN"},"data_sources_checked":["string"],"discrepancies":["string"],"verification_gaps":["string"],"summary":"string"}`;
85
+
86
+ // ─── TOOL HANDLERS ────────────────────────────────────────────────────────────
87
+
88
+ async function handleSearchCompany({ company_name, jurisdiction, country }) {
89
+ const results = { sources: {}, companies: [] };
90
+ await Promise.all([
91
+ searchCompaniesHouse(company_name).then(r => { if (r) results.sources.companies_house = r; }).catch(() => {}),
92
+ searchACRA(company_name).then(r => { if (r) results.sources.acra_singapore = r; }).catch(() => {}),
93
+ searchOpenCorporates(company_name, jurisdiction).then(r => { if (r?.length) results.sources.opencorporates = r; }).catch(() => {}),
94
+ ]);
95
+ if (results.sources.companies_house?.items) results.companies.push(...results.sources.companies_house.items.slice(0, 5).map(c => ({ source: "Companies House (UK)", name: c.title, registration_number: c.company_number, status: c.company_status, type: c.company_type, jurisdiction: "United Kingdom", incorporated: c.date_of_creation, url: `https://find-and-update.company-information.service.gov.uk/company/${c.company_number}` })));
96
+ if (results.sources.acra_singapore) results.companies.push(...results.sources.acra_singapore.slice(0, 5).map(c => ({ source: "ACRA (Singapore)", name: c.entity_name, registration_number: c.uen, status: c.uen_status, type: c.entity_type, jurisdiction: "Singapore", incorporated: c.reg_date })));
97
+ if (results.sources.opencorporates?.length) results.companies.push(...results.sources.opencorporates.slice(0, 5).map(item => { const c = item.company; return { source: "OpenCorporates", name: c.name, registration_number: c.company_number, status: c.current_status || (c.inactive ? "Inactive" : "Active"), type: c.company_type, jurisdiction: c.jurisdiction_code?.toUpperCase(), incorporated: c.incorporation_date, url: c.opencorporates_url }; }));
98
+ return { query: company_name, total_results: results.companies.length, companies: results.companies, sources_checked: Object.keys(results.sources) };
99
+ }
100
+
101
+ async function handleGetCompanyProfile({ company_name, registration_number, jurisdiction }) {
102
+ if (!registration_number) return { note: "Full profile requires a registration number. Use search_company first.", search: await handleSearchCompany({ company_name, jurisdiction }) };
103
+ let profileData = {};
104
+ if ((jurisdiction === "gb" || jurisdiction === "uk") && registration_number) { const ch = await getCompaniesHouseProfile(registration_number).catch(() => null); if (ch) profileData.companies_house = ch; }
105
+ try { if (jurisdiction && registration_number) { const oc = await getOpenCorporatesCompany(jurisdiction, registration_number); if (oc) profileData.opencorporates = oc; } } catch {}
106
+ const ch = profileData.companies_house?.profile;
107
+ const oc = profileData.opencorporates;
108
+ const officers = profileData.companies_house?.officers?.items || [];
109
+ return { legal_name: ch?.company_name || oc?.name, registration_number: ch?.company_number || oc?.company_number, status: ch?.company_status || (oc?.inactive ? "inactive" : "active"), type: ch?.type || oc?.company_type, jurisdiction: ch ? "United Kingdom" : oc?.jurisdiction_code?.toUpperCase(), incorporated: ch?.date_of_creation || oc?.incorporation_date, dissolved: ch?.date_of_cessation || oc?.dissolution_date, registered_address: ch?.registered_office_address || null, sic_codes: ch?.sic_codes || [], officers: officers.slice(0, 10).map(o => ({ name: o.name, role: o.officer_role, appointed: o.appointed_on, resigned: o.resigned_on || null, nationality: o.nationality || null })), filing_history_url: ch ? `https://find-and-update.company-information.service.gov.uk/company/${ch.company_number}/filing-history` : null, sources: Object.keys(profileData) };
110
+ }
111
+
112
+ async function handleVerifyCompany({ company_name, registration_number, jurisdiction, country }) {
113
+ const searchData = await handleSearchCompany({ company_name, jurisdiction: jurisdiction || country, country });
114
+ const analysis = await analyzeWithClaude(VERIFY_PROMPT, JSON.stringify({ company_name, registration_number, search_results: searchData }));
115
+ let parsed; try { parsed = JSON.parse(analysis); } catch { parsed = { raw_analysis: analysis }; }
116
+ return { ...parsed, raw_data: searchData };
117
+ }
118
+
119
+ async function handleCheckCompanyRisk({ company_name, registration_number, jurisdiction }) {
120
+ let companyData = {};
121
+ try { companyData.search = await handleSearchCompany({ company_name, jurisdiction }); } catch {}
122
+ if (registration_number && jurisdiction) { try { companyData.profile = await handleGetCompanyProfile({ company_name, registration_number, jurisdiction }); } catch {} }
123
+ const analysis = await analyzeWithClaude(RISK_PROMPT, JSON.stringify({ company_name, registration_number, jurisdiction, data: companyData }));
124
+ let parsed; try { parsed = JSON.parse(analysis); } catch { parsed = { raw_analysis: analysis }; }
125
+ return { company: company_name, registration_number: registration_number || null, jurisdiction: jurisdiction || "unknown", ...parsed, data_sources: companyData.search?.sources_checked || [] };
126
+ }
127
+
128
+ async function handleGetOfficers({ company_name, registration_number, jurisdiction }) {
129
+ if (!registration_number) return { note: "Registration number required. Use search_company to find it.", company: company_name };
130
+ if (jurisdiction !== "gb" && jurisdiction !== "uk") return { note: "Officer data available for UK companies (jurisdiction: gb).", company: company_name };
131
+ const ch = await getCompaniesHouseProfile(registration_number).catch(() => null);
132
+ if (!ch?.officers?.items?.length) return { note: "No officer data found.", company: company_name };
133
+ const officers = ch.officers.items.map(o => ({ name: o.name, role: o.officer_role, appointed: o.appointed_on, resigned: o.resigned_on || null, nationality: o.nationality || null, occupation: o.occupation || null }));
134
+ return { company: company_name, registration_number, source: "Companies House (UK)", total_officers: officers.length, active_officers: officers.filter(o => !o.resigned), resigned_officers: officers.filter(o => o.resigned) };
135
+ }
136
+
137
+ // ─── TOOL DEFINITIONS ─────────────────────────────────────────────────────────
138
+
139
+ const TOOLS = [
140
+ { name: "search_company", description: "Search for a company by name across UK Companies House, Singapore ACRA, and OpenCorporates (130+ jurisdictions). Returns matching companies with registration numbers and status.", inputSchema: { type: "object", properties: { company_name: { type: "string", description: "Company name to search for" }, country: { type: "string", description: "Optional country filter e.g. UK, SG, US" }, jurisdiction: { type: "string", description: "Optional jurisdiction code e.g. gb, sg" } }, required: ["company_name"] } },
141
+ { name: "get_company_profile", description: "Get detailed company profile including status, address, SIC codes, filing history. Provide registration_number and jurisdiction for best results.", inputSchema: { type: "object", properties: { company_name: { type: "string" }, registration_number: { type: "string" }, jurisdiction: { type: "string", description: "Jurisdiction code: gb (UK), sg (Singapore)" } }, required: ["company_name"] } },
142
+ { name: "verify_company", description: "KYC-style company verification. Returns confidence rating, verified status, and discrepancies found across registries.", inputSchema: { type: "object", properties: { company_name: { type: "string" }, registration_number: { type: "string" }, jurisdiction: { type: "string" }, country: { type: "string" } }, required: ["company_name"] } },
143
+ { name: "check_company_risk", description: "AI-powered due diligence risk assessment. Returns risk score 0-100, risk level, specific risk factors, and recommended due diligence actions.", inputSchema: { type: "object", properties: { company_name: { type: "string" }, registration_number: { type: "string" }, jurisdiction: { type: "string" } }, required: ["company_name"] } },
144
+ { name: "get_officers", description: "Get directors and officers of a UK company with appointment dates, roles, and nationalities. Requires registration_number and jurisdiction: gb.", inputSchema: { type: "object", properties: { company_name: { type: "string" }, registration_number: { type: "string", description: "Required" }, jurisdiction: { type: "string", description: "Use gb for UK companies" } }, required: ["company_name", "registration_number", "jurisdiction"] } },
145
+ ];
146
+
147
+ // ─── MCP SERVER FACTORY ───────────────────────────────────────────────────────
148
+
149
+ function createMCPServer() {
150
+ const server = new Server({ name: "bizfile-mcp", version: "1.3.0" }, { capabilities: { tools: {} } });
151
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
152
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
153
+ const { name, arguments: args } = request.params;
154
+ try {
155
+ let result;
156
+ switch (name) {
157
+ case "search_company": result = await handleSearchCompany(args); break;
158
+ case "get_company_profile": result = await handleGetCompanyProfile(args); break;
159
+ case "verify_company": result = await handleVerifyCompany(args); break;
160
+ case "check_company_risk": result = await handleCheckCompanyRisk(args); break;
161
+ case "get_officers": result = await handleGetOfficers(args); break;
162
+ default: throw new Error(`Unknown tool: ${name}`);
163
+ }
164
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
165
+ } catch (error) {
166
+ return { content: [{ type: "text", text: JSON.stringify({ error: error.message, tool: name }) }], isError: true };
167
+ }
168
+ });
169
+ return server;
170
+ }
171
+
172
+ // ─── HTTP SERVER ──────────────────────────────────────────────────────────────
173
+
174
+ const sessions = new Map();
175
+
176
+ const httpServer = http.createServer(async (req, res) => {
177
+ res.setHeader("Access-Control-Allow-Origin", "*");
178
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
179
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
180
+ if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
181
+
182
+ const url = new URL(req.url, `http://localhost`);
183
+
184
+ // Health check on /health only
185
+ if (url.pathname === "/health") {
186
+ res.writeHead(200, { "Content-Type": "application/json" });
187
+ res.end(JSON.stringify({ name: "bizfile-mcp", version: "1.3.0", status: "running", tools: TOOLS.map(t => t.name) }));
188
+ return;
189
+ }
190
+
191
+ // All MCP traffic goes through / and /mcp
192
+ if (url.pathname === "/" || url.pathname === "/mcp") {
193
+ if (req.method === "POST") {
194
+ let body = "";
195
+ req.on("data", chunk => { body += chunk; });
196
+ req.on("end", async () => {
197
+ try {
198
+ const message = JSON.parse(body);
199
+ const sessionId = req.headers["mcp-session-id"];
200
+ let transport;
201
+
202
+ if (sessionId && sessions.has(sessionId)) {
203
+ transport = sessions.get(sessionId);
204
+ } else if (isInitializeRequest(message)) {
205
+ const server = createMCPServer();
206
+ transport = new StreamableHTTPServerTransport({
207
+ sessionIdGenerator: () => randomUUID(),
208
+ onsessioninitialized: (id) => sessions.set(id, transport),
209
+ });
210
+ transport.onclose = () => { if (transport.sessionId) sessions.delete(transport.sessionId); };
211
+ await server.connect(transport);
212
+ } else {
213
+ // Stateless fallback — create a fresh server for each request
214
+ const server = createMCPServer();
215
+ transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
216
+ await server.connect(transport);
217
+ }
218
+
219
+ await transport.handleRequest(req, res, message);
220
+ } catch (e) {
221
+ if (!res.headersSent) { res.writeHead(500, { "Content-Type": "application/json" }); res.end(JSON.stringify({ error: e.message })); }
222
+ }
223
+ });
224
+ return;
225
+ }
226
+
227
+ if (req.method === "GET") {
228
+ const sessionId = req.headers["mcp-session-id"];
229
+ if (sessionId && sessions.has(sessionId)) {
230
+ await sessions.get(sessionId).handleRequest(req, res);
231
+ } else {
232
+ res.writeHead(400, { "Content-Type": "application/json" });
233
+ res.end(JSON.stringify({ error: "Invalid or missing session ID" }));
234
+ }
235
+ return;
236
+ }
237
+
238
+ if (req.method === "DELETE") {
239
+ const sessionId = req.headers["mcp-session-id"];
240
+ if (sessionId && sessions.has(sessionId)) { await sessions.get(sessionId).close(); sessions.delete(sessionId); }
241
+ res.writeHead(200); res.end();
242
+ return;
243
+ }
244
+ }
245
+
246
+ res.writeHead(404, { "Content-Type": "application/json" });
247
+ res.end(JSON.stringify({ error: "Not found" }));
248
+ });
249
+
250
+ // ─── START ────────────────────────────────────────────────────────────────────
251
+
252
+ if (process.env.MCP_TRANSPORT === "stdio") {
253
+ const server = createMCPServer();
254
+ const transport = new StdioServerTransport();
255
+ await server.connect(transport);
256
+ console.error("Bizfile MCP Server v1.3 running (STDIO)");
257
+ } else {
258
+ httpServer.listen(PORT, () => {
259
+ console.log(`Bizfile MCP Server v1.3 running on port ${PORT}`);
260
+ console.log(`Health: http://localhost:${PORT}/health`);
261
+ console.log(`MCP: http://localhost:${PORT}/`);
262
+ });
263
+ }
package/test.js ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Bizfile MCP — Quick Test Script
3
+ * Tests each data source independently
4
+ * Run: node test.js
5
+ */
6
+
7
+ const COMPANIES_HOUSE_API_KEY = process.env.COMPANIES_HOUSE_API_KEY;
8
+ const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
9
+
10
+ console.log("=== Bizfile MCP — Data Source Tests ===\n");
11
+
12
+ // Test 1: Companies House
13
+ async function testCompaniesHouse() {
14
+ console.log("TEST 1: UK Companies House...");
15
+ if (!COMPANIES_HOUSE_API_KEY) {
16
+ console.log(" ⚠️ COMPANIES_HOUSE_API_KEY not set — skipping\n");
17
+ return;
18
+ }
19
+ try {
20
+ const auth = Buffer.from(`${COMPANIES_HOUSE_API_KEY}:`).toString("base64");
21
+ const res = await fetch(
22
+ "https://api.company-information.service.gov.uk/search/companies?q=Shell&items_per_page=3",
23
+ { headers: { Authorization: `Basic ${auth}` } }
24
+ );
25
+ const data = await res.json();
26
+ if (data.items && data.items.length > 0) {
27
+ console.log(` ✅ Working — found ${data.total_results} results for "Shell"`);
28
+ console.log(` First result: ${data.items[0].title} (${data.items[0].company_number})\n`);
29
+ } else {
30
+ console.log(" ❌ No results returned\n");
31
+ }
32
+ } catch (e) {
33
+ console.log(` ❌ Error: ${e.message}\n`);
34
+ }
35
+ }
36
+
37
+ // Test 2: Singapore ACRA
38
+ async function testACRA() {
39
+ console.log("TEST 2: Singapore ACRA...");
40
+ try {
41
+ const res = await fetch(
42
+ "https://data.gov.sg/api/action/datastore_search?resource_id=d_3f960c10fed6145404ca7b821f263b87&q=singapore+airlines&limit=3"
43
+ );
44
+ const data = await res.json();
45
+ if (data.result?.records?.length > 0) {
46
+ console.log(` ✅ Working — found ${data.result.total} results for "Singapore Airlines"`);
47
+ console.log(` First result: ${data.result.records[0].entity_name}\n`);
48
+ } else {
49
+ console.log(" ⚠️ No results — ACRA dataset may have changed\n");
50
+ }
51
+ } catch (e) {
52
+ console.log(` ❌ Error: ${e.message}\n`);
53
+ }
54
+ }
55
+
56
+ // Test 3: SEC EDGAR
57
+ async function testEDGAR() {
58
+ console.log("TEST 3: US SEC EDGAR...");
59
+ try {
60
+ const res = await fetch(
61
+ "https://efts.sec.gov/LATEST/search-index?q=%22Apple+Inc%22&forms=10-K&hits.hits._source=display_names,file_date,form_type",
62
+ { headers: { "User-Agent": "BizfileMCP contact@bizfilemcp.com" } }
63
+ );
64
+ const data = await res.json();
65
+ if (data.hits?.hits?.length > 0) {
66
+ console.log(` ✅ Working — found SEC filings for Apple Inc`);
67
+ console.log(` Latest filing: ${data.hits.hits[0]._source.form_type} on ${data.hits.hits[0]._source.file_date}\n`);
68
+ } else {
69
+ console.log(" ⚠️ No results returned\n");
70
+ }
71
+ } catch (e) {
72
+ console.log(` ❌ Error: ${e.message}\n`);
73
+ }
74
+ }
75
+
76
+ // Test 4: OpenCorporates (no key needed for basic search)
77
+ async function testOpenCorporates() {
78
+ console.log("TEST 4: OpenCorporates (global)...");
79
+ try {
80
+ const res = await fetch(
81
+ "https://api.opencorporates.com/v0.4/companies/search?q=Accenture&per_page=3"
82
+ );
83
+ const data = await res.json();
84
+ if (data.results?.companies?.length > 0) {
85
+ console.log(` ✅ Working — found ${data.results.total_count} results for "Accenture"`);
86
+ console.log(` First result: ${data.results.companies[0].company.name} (${data.results.companies[0].company.jurisdiction_code})\n`);
87
+ } else {
88
+ console.log(" ⚠️ No results returned\n");
89
+ }
90
+ } catch (e) {
91
+ console.log(` ❌ Error: ${e.message}\n`);
92
+ }
93
+ }
94
+
95
+ // Test 5: Anthropic API
96
+ async function testAnthropic() {
97
+ console.log("TEST 5: Anthropic API...");
98
+ if (!ANTHROPIC_API_KEY) {
99
+ console.log(" ⚠️ ANTHROPIC_API_KEY not set — skipping\n");
100
+ return;
101
+ }
102
+ try {
103
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
104
+ method: "POST",
105
+ headers: {
106
+ "x-api-key": ANTHROPIC_API_KEY,
107
+ "anthropic-version": "2023-06-01",
108
+ "content-type": "application/json",
109
+ },
110
+ body: JSON.stringify({
111
+ model: "claude-haiku-4-5-20251001",
112
+ max_tokens: 50,
113
+ messages: [{ role: "user", content: "Reply with just: API working" }],
114
+ }),
115
+ });
116
+ const data = await res.json();
117
+ if (data.content?.[0]?.text) {
118
+ console.log(` ✅ Working — ${data.content[0].text}\n`);
119
+ } else {
120
+ console.log(` ❌ Unexpected response: ${JSON.stringify(data)}\n`);
121
+ }
122
+ } catch (e) {
123
+ console.log(` ❌ Error: ${e.message}\n`);
124
+ }
125
+ }
126
+
127
+ // Run all tests
128
+ async function runTests() {
129
+ await testCompaniesHouse();
130
+ await testACRA();
131
+ await testEDGAR();
132
+ await testOpenCorporates();
133
+ await testAnthropic();
134
+ console.log("=== Tests complete ===");
135
+ }
136
+
137
+ runTests();