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 +20 -0
- package/LICENSE +21 -0
- package/README.md +158 -0
- package/SYSTEM_PROMPT.md +30 -0
- package/package.json +51 -0
- package/smithery.yaml +41 -0
- package/src/server.js +263 -0
- package/test.js +137 -0
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
|
+
[](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
|
package/SYSTEM_PROMPT.md
ADDED
|
@@ -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();
|