getregdata-mcp 1.1.0 → 1.2.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/README.md +19 -2
- package/actors.js +1 -1
- package/index.js +107 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,11 +13,27 @@ every beneficial owner, returned as a single verdict. The per-registry tools are
|
|
|
13
13
|
depth behind that answer, and the way to run the same jobs elsewhere.
|
|
14
14
|
|
|
15
15
|
Each registry is exposed as its own tool (e.g. `regdata_crbr_beneficial_owners`,
|
|
16
|
-
`regdata_germany_handelsregister`, `regdata_adverse_media`), plus
|
|
16
|
+
`regdata_germany_handelsregister`, `regdata_adverse_media`), plus three helpers:
|
|
17
17
|
|
|
18
18
|
- `regdata_catalog` - list every registry tool and what it returns.
|
|
19
19
|
- `regdata_describe` - fetch a specific actor's live input schema so the agent
|
|
20
20
|
builds a correct call.
|
|
21
|
+
- `regdata_run_result` - collect a check that was still running when its tool call
|
|
22
|
+
returned.
|
|
23
|
+
|
|
24
|
+
## Long checks return a handle, not a failure
|
|
25
|
+
|
|
26
|
+
Most calls return dataset items directly. If a run is still going after ~45 seconds,
|
|
27
|
+
the tool returns `{status, runId, datasetId, note}` instead. That run is still going
|
|
28
|
+
and is **already billed**, so call `regdata_run_result` with the `runId` to collect it -
|
|
29
|
+
re-running the check to "retry" charges the user twice.
|
|
30
|
+
|
|
31
|
+
This matters most for the Poland KYB composite: 20-30 seconds typically, up to ~2.5
|
|
32
|
+
minutes for a company with many beneficial owners. Set `"timeout": 600000` on the
|
|
33
|
+
server in your MCP config so the common case still returns inline.
|
|
34
|
+
|
|
35
|
+
Treat a `runId` as a secret: Apify resolves run and dataset IDs without checking
|
|
36
|
+
ownership, so anyone holding the ID can read that check's result.
|
|
21
37
|
|
|
22
38
|
## Setup
|
|
23
39
|
|
|
@@ -34,7 +50,8 @@ Add to your MCP config (`claude_desktop_config.json`, or `.mcp.json` for Claude
|
|
|
34
50
|
"getregdata": {
|
|
35
51
|
"command": "npx",
|
|
36
52
|
"args": ["-y", "getregdata-mcp"],
|
|
37
|
-
"env": { "APIFY_TOKEN": "apify_api_xxxxx" }
|
|
53
|
+
"env": { "APIFY_TOKEN": "apify_api_xxxxx" },
|
|
54
|
+
"timeout": 600000
|
|
38
55
|
}
|
|
39
56
|
}
|
|
40
57
|
}
|
package/actors.js
CHANGED
|
@@ -6,7 +6,7 @@ export const ACTORS = [
|
|
|
6
6
|
"slug": "poland-kyb-check",
|
|
7
7
|
"tool": "regdata_poland_kyb_check",
|
|
8
8
|
"title": "Poland KYB Risk Check - one call, one verdict",
|
|
9
|
-
"description": "Run a COMPLETE Polish KYB check from a single NIP or KRS - start here for Poland instead of chaining the individual registry tools. Resolves official company identity, pulls beneficial owners from the national UBO register, and screens the national debtor register for insolvency, restructuring and enforcement proceedings against the company AND every beneficial owner by exact identifier. Returns one normalized verdict - clear, findings, not_found or partial - plus machine-readable risk flags and the underlying registry entries (case signature, classification, court, dates, source). A \"partial\" verdict means a source could not be reached, not that the company is clean, and is not billed. Poland only; use the per-registry tools for other jurisdictions or for deeper single-source detail.",
|
|
9
|
+
"description": "Run a COMPLETE Polish KYB check from a single NIP or KRS - start here for Poland instead of chaining the individual registry tools. Resolves official company identity, pulls beneficial owners from the national UBO register, and screens the national debtor register for insolvency, restructuring and enforcement proceedings against the company AND every beneficial owner by exact identifier. Returns one normalized verdict - clear, findings, not_found or partial - plus machine-readable risk flags and the underlying registry entries (case signature, classification, court, dates, source). A \"partial\" verdict means a source could not be reached, not that the company is clean, and is not billed. Poland only; use the per-registry tools for other jurisdictions or for deeper single-source detail. TIMING: this check typically takes 20-30 seconds, but scales with the number of beneficial owners and has been measured up to ~2.5 minutes. If it has not finished in time, the tool returns {status, runId, note} instead of results - the check is running and already billed, so call regdata_run_result with that runId to collect it. Never re-run the check to 'retry': that bills twice.",
|
|
10
10
|
"inputSchema": {
|
|
11
11
|
"type": "object",
|
|
12
12
|
"properties": {
|
package/index.js
CHANGED
|
@@ -36,21 +36,89 @@ function requireToken() {
|
|
|
36
36
|
return t;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
//
|
|
39
|
+
// How long we hold a tool call open before handing back a run handle. Must stay
|
|
40
|
+
// under the MCP client's default request timeout (60s) with margin: if the CLIENT
|
|
41
|
+
// gives up first, the caller sees an error for a run that is still billing.
|
|
42
|
+
const WAIT_SECS = 45;
|
|
43
|
+
const TERMINAL = new Set(["SUCCEEDED", "FAILED", "ABORTED", "TIMED-OUT"]);
|
|
44
|
+
|
|
45
|
+
// Retrying a pending check runs - and bills - a second time. This wording is
|
|
46
|
+
// deliberately blunt: it is the only thing between a slow check and a double charge.
|
|
47
|
+
const PENDING_NOTE =
|
|
48
|
+
"This check is STILL RUNNING and is already being billed. Do NOT retry it and do NOT call this tool again - " +
|
|
49
|
+
"call regdata_run_result with the runId above to collect the result. Retrying charges the user twice for the same check.";
|
|
50
|
+
|
|
51
|
+
async function apiJson(url, init) {
|
|
52
|
+
const res = await fetch(url, init);
|
|
53
|
+
const text = await res.text();
|
|
54
|
+
if (!res.ok) throw new Error(`Apify API ${res.status}: ${text.slice(0, 300)}`);
|
|
55
|
+
return JSON.parse(text).data;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function datasetItems(datasetId, maxItems) {
|
|
59
|
+
const url = new URL(`${APIFY_BASE}/datasets/${datasetId}/items`);
|
|
60
|
+
url.searchParams.set("token", requireToken());
|
|
61
|
+
if (maxItems) url.searchParams.set("limit", String(maxItems));
|
|
62
|
+
const res = await fetch(url);
|
|
63
|
+
const text = await res.text();
|
|
64
|
+
if (!res.ok) throw new Error(`Could not read dataset ${datasetId} (${res.status})`);
|
|
65
|
+
return text; // JSON array of dataset items
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Either the items, or a redeemable handle. Never a lost run.
|
|
69
|
+
async function settle(run, label, maxItems) {
|
|
70
|
+
if (run.status === "SUCCEEDED") return await datasetItems(run.defaultDatasetId, maxItems);
|
|
71
|
+
if (TERMINAL.has(run.status)) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`regdata/${label} run ${run.id} ended as ${run.status}` +
|
|
74
|
+
(run.statusMessage ? `: ${run.statusMessage}` : "")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return JSON.stringify(
|
|
78
|
+
{ status: run.status, runId: run.id, datasetId: run.defaultDatasetId, note: PENDING_NOTE },
|
|
79
|
+
null,
|
|
80
|
+
2
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Start the actor and wait briefly.
|
|
85
|
+
//
|
|
86
|
+
// Started ASYNC on purpose. This previously used run-sync-get-dataset-items, which
|
|
87
|
+
// holds one connection open until the run ends. Per Apify's docs, "if the connection
|
|
88
|
+
// breaks, you will not receive any information about the run and its output" and
|
|
89
|
+
// "this won't abort the run itself" - so a client timeout destroyed the only handle
|
|
90
|
+
// to a run that then completed and BILLED. Measured durations for the Poland KYB
|
|
91
|
+
// composite were 21-148s against a 60s client default, so this was routine, not rare.
|
|
92
|
+
// Starting async means the runId exists from the first response and a disconnect is
|
|
93
|
+
// always recoverable.
|
|
40
94
|
async function runActor(slug, input, { maxItems } = {}) {
|
|
41
|
-
const url = new URL(`${APIFY_BASE}/acts/regdata~${slug}/
|
|
95
|
+
const url = new URL(`${APIFY_BASE}/acts/regdata~${slug}/runs`);
|
|
42
96
|
url.searchParams.set("token", requireToken());
|
|
97
|
+
url.searchParams.set("waitForFinish", String(WAIT_SECS)); // Apify caps this at 60
|
|
43
98
|
if (maxItems) url.searchParams.set("maxItems", String(maxItems));
|
|
44
|
-
const
|
|
99
|
+
const run = await apiJson(url, {
|
|
45
100
|
method: "POST",
|
|
46
101
|
headers: { "Content-Type": "application/json" },
|
|
47
102
|
body: JSON.stringify(input ?? {}),
|
|
48
103
|
});
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
104
|
+
return await settle(run, slug, maxItems);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Collect a run that was still going when its tool call returned.
|
|
108
|
+
//
|
|
109
|
+
// NOTE on access: we send the caller's token, but Apify does NOT scope runs or
|
|
110
|
+
// datasets to it - verified 2026-07-23, `GET /actor-runs/{id}` and
|
|
111
|
+
// `/datasets/{id}/items` both return 200 with a foreign token AND with no token
|
|
112
|
+
// at all, on a dataset whose isPublic is unset. The storage ID is itself the
|
|
113
|
+
// capability. So this tool grants nothing a runId holder could not already fetch
|
|
114
|
+
// straight from the public API; it is convenience, not an access boundary.
|
|
115
|
+
// Treat a runId as a secret and do not log or forward it.
|
|
116
|
+
async function collectRun(runId, { waitSecs, maxItems } = {}) {
|
|
117
|
+
const url = new URL(`${APIFY_BASE}/actor-runs/${runId}`);
|
|
118
|
+
url.searchParams.set("token", requireToken());
|
|
119
|
+
url.searchParams.set("waitForFinish", String(Math.max(0, Math.min(waitSecs ?? WAIT_SECS, 60))));
|
|
120
|
+
const run = await apiJson(url);
|
|
121
|
+
return await settle(run, run.actId ?? "run", maxItems);
|
|
54
122
|
}
|
|
55
123
|
|
|
56
124
|
// Fetch an actor's live input schema so the model can construct correct input.
|
|
@@ -70,7 +138,7 @@ async function describeActor(slug) {
|
|
|
70
138
|
const bySlug = Object.fromEntries(ACTORS.map((a) => [a.slug, a]));
|
|
71
139
|
const byTool = Object.fromEntries(ACTORS.map((a) => [a.tool, a]));
|
|
72
140
|
|
|
73
|
-
// One tool per actor +
|
|
141
|
+
// One tool per actor + three helpers (catalog, run_result, describe).
|
|
74
142
|
const MAX_ITEMS_PROP = {
|
|
75
143
|
type: "integer",
|
|
76
144
|
description: "Optional cap on billed dataset items returned.",
|
|
@@ -110,6 +178,30 @@ function buildToolList() {
|
|
|
110
178
|
`Use this catalog to find the right single-registry tool for other jurisdictions, or when you need one specific source in depth.`,
|
|
111
179
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
112
180
|
},
|
|
181
|
+
{
|
|
182
|
+
name: "regdata_run_result",
|
|
183
|
+
description:
|
|
184
|
+
"Collect the result of a regdata check that was still running when its tool call returned. " +
|
|
185
|
+
"When any regdata tool returns {status, runId, note} instead of results, the check is running and already billed - " +
|
|
186
|
+
"call THIS tool with that runId rather than re-running the check, which would charge the user twice. " +
|
|
187
|
+
"Returns the dataset items once the run finishes; if it is still going, returns the same handle so you can call again.",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
runId: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "The runId returned by the pending tool call, e.g. NAMNqpPch7RFSY1oQ",
|
|
194
|
+
},
|
|
195
|
+
waitSecs: {
|
|
196
|
+
type: "integer",
|
|
197
|
+
description: "Seconds to wait for the run to finish before returning (0-60, default 45).",
|
|
198
|
+
},
|
|
199
|
+
maxItems: MAX_ITEMS_PROP,
|
|
200
|
+
},
|
|
201
|
+
required: ["runId"],
|
|
202
|
+
additionalProperties: false,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
113
205
|
{
|
|
114
206
|
name: "regdata_describe",
|
|
115
207
|
description:
|
|
@@ -143,6 +235,11 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
143
235
|
const list = ACTORS.map((a) => ({ tool: a.tool, slug: a.slug, title: a.title }));
|
|
144
236
|
return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
|
|
145
237
|
}
|
|
238
|
+
if (name === "regdata_run_result") {
|
|
239
|
+
if (!args.runId) throw new Error("runId is required - it is returned by the pending tool call.");
|
|
240
|
+
const out = await collectRun(args.runId, { waitSecs: args.waitSecs, maxItems: args.maxItems });
|
|
241
|
+
return { content: [{ type: "text", text: out }] };
|
|
242
|
+
}
|
|
146
243
|
if (name === "regdata_describe") {
|
|
147
244
|
const slug = args.slug || byTool[args.tool]?.slug;
|
|
148
245
|
if (!slug || !bySlug[slug]) throw new Error(`Unknown actor: ${args.slug || args.tool}`);
|
|
@@ -164,4 +261,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
164
261
|
|
|
165
262
|
const transport = new StdioServerTransport();
|
|
166
263
|
await server.connect(transport);
|
|
167
|
-
console.error(`getregdata MCP server ready - ${ACTORS.length} registry tools + catalog/describe.`);
|
|
264
|
+
console.error(`getregdata MCP server ready - ${ACTORS.length} registry tools + catalog/run_result/describe.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "getregdata-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"mcpName": "io.github.Nolpak14/getregdata",
|
|
5
5
|
"description": "MCP server for official business-registry data - KYC/AML, beneficial owners, credit-risk, adverse media. One-call Poland KYB verdict, plus per-registry tools for Germany, Italy, Spain, Austria, France, Belgium, Czechia, Slovakia, Cyprus, Ireland, Portugal, Nigeria, Colombia, the US (California) and the UAE.",
|
|
6
6
|
"type": "module",
|