ictrp-mcp-server 0.1.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/CHANGELOG.md +65 -0
- package/LICENSE +37 -0
- package/README.md +208 -0
- package/README_ZH.md +189 -0
- package/dist/cli/setup-cli.d.ts +14 -0
- package/dist/cli/setup-cli.js +230 -0
- package/dist/cli/setup-cli.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +477 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +99 -0
- package/dist/runtime/bootstrap.js +350 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/env-probe.d.ts +108 -0
- package/dist/runtime/env-probe.js +479 -0
- package/dist/runtime/env-probe.js.map +1 -0
- package/dist/runtime/sidecar-client.d.ts +50 -0
- package/dist/runtime/sidecar-client.js +120 -0
- package/dist/runtime/sidecar-client.js.map +1 -0
- package/dist/runtime/supervisor.d.ts +47 -0
- package/dist/runtime/supervisor.js +248 -0
- package/dist/runtime/supervisor.js.map +1 -0
- package/package.json +60 -0
- package/sidecar/ictrp_sidecar.py +602 -0
- package/sidecar/vendor/ictrp_mcp/__init__.py +3 -0
- package/sidecar/vendor/ictrp_mcp/cache/__init__.py +0 -0
- package/sidecar/vendor/ictrp_mcp/cache/store.py +313 -0
- package/sidecar/vendor/ictrp_mcp/data/__init__.py +0 -0
- package/sidecar/vendor/ictrp_mcp/data/columns.py +108 -0
- package/sidecar/vendor/ictrp_mcp/data/jsonio.py +213 -0
- package/sidecar/vendor/ictrp_mcp/data/normalize.py +348 -0
- package/sidecar/vendor/ictrp_mcp/data/query.py +307 -0
- package/sidecar/vendor/ictrp_mcp/errors.py +123 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/__init__.py +0 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/export_guard.py +269 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/htmlstate.py +143 -0
- package/sidecar/vendor/ictrp_mcp/ictrp/session.py +245 -0
- package/sidecar/vendor/ictrp_mcp/offline.py +133 -0
- package/sidecar/vendor/ictrp_mcp/provenance.py +182 -0
- package/sidecar/vendor/ictrp_mcp/server.py +368 -0
- package/sidecar/vendor/ictrp_mcp/tools.py +712 -0
- package/sidecar/vendor/pyproject.toml +25 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes to the npm package `ictrp-mcp-server`. The Python package
|
|
4
|
+
`ictrp-mcp-service` is versioned separately in `pyproject.toml`.
|
|
5
|
+
|
|
6
|
+
## 0.1.0
|
|
7
|
+
|
|
8
|
+
First release.
|
|
9
|
+
|
|
10
|
+
**MCP tools** — ten, matching the Python service one for one:
|
|
11
|
+
|
|
12
|
+
- `ictrp_search` — runs the three-step chain and materializes the whole result
|
|
13
|
+
set, returning a page plus a `set_id`. The only expensive call.
|
|
14
|
+
- `ictrp_filter`, `ictrp_field_query`, `ictrp_registry_summary`,
|
|
15
|
+
`ictrp_find_duplicates`, `ictrp_export`, `ictrp_cache_status`,
|
|
16
|
+
`ictrp_snapshot`, `ictrp_bundle_status` — local, no upstream request.
|
|
17
|
+
- `ictrp_check_environment` — reports what is ready, and what to do about what
|
|
18
|
+
is not.
|
|
19
|
+
|
|
20
|
+
**Packaging** — a Node MCP server with a bundled Python sidecar. The sidecar is
|
|
21
|
+
stdlib-only apart from `httpx`, so no browser and no Chromium are involved
|
|
22
|
+
anywhere in the stack; ICTRP needs no JavaScript execution to yield data. A
|
|
23
|
+
private virtualenv is created on first run rather than at install time, because
|
|
24
|
+
packaged desktop applications do not execute npm's `postinstall`.
|
|
25
|
+
|
|
26
|
+
The package is self-contained: the full Python source ships under
|
|
27
|
+
`sidecar/vendor/` together with a generated `pyproject.toml`, so `ictrp-setup
|
|
28
|
+
setup` builds the wheel from the tarball itself. It does not depend on a
|
|
29
|
+
matching release existing on PyPI, and the Python that runs is always the same
|
|
30
|
+
commit as the JavaScript around it.
|
|
31
|
+
|
|
32
|
+
**Offline path** — `ICTRP_BUNDLE_PATH` / `ICTRP_BUNDLE_DIR` let a desktop build
|
|
33
|
+
ship a snapshot so the first launch works with no network. Snapshots carry a
|
|
34
|
+
creation date and are reported stale past `ICTRP_BUNDLE_MAX_AGE_DAYS`,
|
|
35
|
+
defaulting to 28 days against WHO's weekly refresh. A snapshot-served response
|
|
36
|
+
says so: `provenance.offline_snapshot` is `true` and `snapshot_path` /
|
|
37
|
+
`snapshot_age_days` identify the file it came from, so snapshot data is never
|
|
38
|
+
mistaken for a live query.
|
|
39
|
+
|
|
40
|
+
**Refresh semantics** — `refresh: true` forces an upstream search and is always
|
|
41
|
+
attempted, even inside the failure-backoff window. A forced refresh that fails
|
|
42
|
+
does not discard the cached set; the cached data is served with the failure
|
|
43
|
+
recorded in `provenance.notes`. Automatic refreshes are the ones that back off.
|
|
44
|
+
|
|
45
|
+
**Documentation** — the README is bilingual: `README.md` in English and
|
|
46
|
+
`README_ZH.md` in Chinese, each linking to the other from the top.
|
|
47
|
+
|
|
48
|
+
**Reasons this release exists at all** — the ICTRP CSV export is incomplete and
|
|
49
|
+
fails silently, measured at 0.4% to 29.0% depending on the keyword. Accordingly:
|
|
50
|
+
|
|
51
|
+
- a failure never returns zero rows; it raises, so it cannot be read as
|
|
52
|
+
"no such trial exists";
|
|
53
|
+
- `total_matched` does not exist, only `rows_returned` and
|
|
54
|
+
`upstream_reported_total`, never merged;
|
|
55
|
+
- every response carries `records_incomplete` and an `incompleteness_notice`;
|
|
56
|
+
- absence from a result set is never evidence of nonexistence.
|
|
57
|
+
|
|
58
|
+
### Known limitations
|
|
59
|
+
|
|
60
|
+
- `rows_returned` counts retrieved rows. It is never the number of matching
|
|
61
|
+
trials, and no tool will present it as such.
|
|
62
|
+
- The sidecar's default port is 8849, deliberately not the 8848 used by other
|
|
63
|
+
trial-registry MCP servers, so both can be installed together.
|
|
64
|
+
- Materialized sets live in the sidecar process. Restarting it invalidates
|
|
65
|
+
every `set_id`; callers get `set_expired` and re-search.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ictrp-mcp-service contributors
|
|
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.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This license covers the software only.
|
|
26
|
+
|
|
27
|
+
The DATA this software retrieves is subject to WHO's terms for the ICTRP
|
|
28
|
+
database, which are separate and take precedence for any use of that data.
|
|
29
|
+
See ATTRIBUTION.md in the source repository. In summary:
|
|
30
|
+
|
|
31
|
+
- Attribute the source as WHO ICTRP.
|
|
32
|
+
- Clearly display the date the data were processed by WHO ICTRP.
|
|
33
|
+
- Do not assert proprietary rights over any portion of the ICTRP database.
|
|
34
|
+
- Do not use the WHO name or emblem in association with use of the data.
|
|
35
|
+
- No marketing, promotional or commercial use.
|
|
36
|
+
|
|
37
|
+
This project is not affiliated with or endorsed by WHO.
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# ictrp-mcp-server
|
|
2
|
+
|
|
3
|
+
[简体中文](./README_ZH.md) | English
|
|
4
|
+
|
|
5
|
+
An MCP server for the **WHO International Clinical Trials Registry Platform
|
|
6
|
+
(ICTRP)**, packaged for npm. Node MCP surface, Python sidecar, plain HTTP
|
|
7
|
+
underneath — no browser, no headless Chromium, no automation runtime.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx ictrp-mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The Python side of this package is the same code as the
|
|
14
|
+
[`ictrp-mcp-service`](https://github.com/opencare-skillhub/ictrp-mcp-service) Python
|
|
15
|
+
distribution. The npm package wraps it so a desktop host can declare one stdio
|
|
16
|
+
command and get a working trial-registry data source.
|
|
17
|
+
|
|
18
|
+
## Why the sidecar exists
|
|
19
|
+
|
|
20
|
+
The ICTRP access logic is already implemented and tested in Python, and its
|
|
21
|
+
hard-won details matter: which HTML controls to carry through the postback
|
|
22
|
+
chain, which redirect means "blocked" rather than "contract drift", how a
|
|
23
|
+
legitimately empty CSV differs from a failed one. Reimplementing that in
|
|
24
|
+
TypeScript would produce a second version that drifts from the first.
|
|
25
|
+
|
|
26
|
+
So the split is: **Node owns the MCP protocol, Python owns the domain.** They
|
|
27
|
+
talk over a small local JSON API.
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
MCP client ──stdio──▶ Node (this package) ──HTTP──▶ Python sidecar ──HTTPS──▶ ICTRP
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The sidecar is standard library only, plus `httpx`. Nothing here downloads a
|
|
34
|
+
browser.
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
The first run creates a private virtualenv and installs the Python sidecar into
|
|
39
|
+
it. This is deliberately *not* done at install time: packaged desktop
|
|
40
|
+
applications do not execute npm's `postinstall`, so anything that has to happen
|
|
41
|
+
before first use must happen at first use.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx ictrp-setup doctor # what is ready, what is not (exit 0/1/2)
|
|
45
|
+
npx ictrp-setup setup # create the venv and install the sidecar
|
|
46
|
+
npx ictrp-setup sidecar # run the sidecar in the foreground
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`doctor` uses exit codes as its contract, so an installer can drive a UI from
|
|
50
|
+
them:
|
|
51
|
+
|
|
52
|
+
| Code | Meaning | What a UI should do |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| 0 | Ready | Enable the data source |
|
|
55
|
+
| 1 | Not ready, needs the user | Show the suggested actions |
|
|
56
|
+
| 2 | Setup failed | Show the error; offer to retry |
|
|
57
|
+
|
|
58
|
+
Requires Node 20+ and Python 3.10+. The Python source ships inside this package
|
|
59
|
+
under `sidecar/vendor/`, so `setup` builds it locally — no PyPI release of
|
|
60
|
+
`ictrp-mcp-service` is needed for the tool itself, only for its dependencies.
|
|
61
|
+
|
|
62
|
+
Behind a slow or unreachable network, `setup` tries PyPI mirrors in order —
|
|
63
|
+
`aliyun`, `tencent`, `tsinghua`, then the official index — and falls back on
|
|
64
|
+
failure. Some mirrors report a missing package rather than an error when they
|
|
65
|
+
cannot serve it, so falling back is the only reliable strategy. Point it at your
|
|
66
|
+
own mirror with `--mirror=<name|url>` or `ICTRP_PIP_MIRROR`.
|
|
67
|
+
|
|
68
|
+
## Configure your MCP client
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"mcpServers": {
|
|
73
|
+
"ictrp": {
|
|
74
|
+
"command": "npx",
|
|
75
|
+
"args": ["-y", "ictrp-mcp-server"]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Tools
|
|
82
|
+
|
|
83
|
+
Network-backed:
|
|
84
|
+
|
|
85
|
+
- **`ictrp_search`** — runs the full chain and materializes the entire result
|
|
86
|
+
set locally. Returns a page plus a `set_id`. The only expensive call.
|
|
87
|
+
|
|
88
|
+
Local, no upstream request:
|
|
89
|
+
|
|
90
|
+
- **`ictrp_filter`** — filter, sort and page a materialized set.
|
|
91
|
+
- **`ictrp_field_query`** — distinct values plus population coverage for a field.
|
|
92
|
+
- **`ictrp_registry_summary`** — composition by registry/phase/status/year/country.
|
|
93
|
+
- **`ictrp_find_duplicates`** — identifier cross-reference duplicates.
|
|
94
|
+
- **`ictrp_export`** — CSV / JSON / JSONL / Markdown with a provenance header.
|
|
95
|
+
- **`ictrp_cache_status`** — list or purge cached sets.
|
|
96
|
+
- **`ictrp_snapshot`** — write a set to an offline snapshot file.
|
|
97
|
+
- **`ictrp_bundle_status`** — report which snapshot can answer a keyword.
|
|
98
|
+
- **`ictrp_check_environment`** — readiness report and next actions.
|
|
99
|
+
|
|
100
|
+
Materialize once; refine for free.
|
|
101
|
+
|
|
102
|
+
A worked example lives in the [project README](https://github.com/opencare-skillhub/ictrp-mcp-service#usage):
|
|
103
|
+
one search for `ibi343` returns 12 trials spread across **three** registries
|
|
104
|
+
(ClinicalTrials.gov 10, JPRN 1, ChiCTR 1) — the cross-registry case that a
|
|
105
|
+
single-registry search would miss.
|
|
106
|
+
|
|
107
|
+
## The finding that shapes every response
|
|
108
|
+
|
|
109
|
+
**The ICTRP CSV export is incomplete, and it fails silently.** Measured against
|
|
110
|
+
the portal's own reported match counts:
|
|
111
|
+
|
|
112
|
+
| Query | Portal reports | CSV delivers | Shortfall |
|
|
113
|
+
|---|---|---|---|
|
|
114
|
+
| `ChiCTR` | 14,197 | 14,147 | 0.4% |
|
|
115
|
+
| `pancreatic` | 10,354 | 9,273 | 10.4% |
|
|
116
|
+
| `pancreatic cancer` | 6,952 | 6,262 | 9.9% |
|
|
117
|
+
| `KRAS` | 1,243 | 882 | **29.0%** |
|
|
118
|
+
|
|
119
|
+
This is not a counting artifact: paginating the HTML results for `KRAS` yielded
|
|
120
|
+
108 distinct trial IDs, **8 of which were absent from the CSV** — records
|
|
121
|
+
visible on the results page and missing from the export. The cause is unknown;
|
|
122
|
+
duplicate records, a fixed row cap, and pagination limits have all been ruled
|
|
123
|
+
out.
|
|
124
|
+
|
|
125
|
+
So this server never presents retrieved counts as match counts:
|
|
126
|
+
|
|
127
|
+
- A failure raises; it never returns zero rows. An empty list would be
|
|
128
|
+
indistinguishable from "no such trial exists", which is exactly the mistake
|
|
129
|
+
this package exists to prevent.
|
|
130
|
+
- There is no `total_matched`. There is `rows_returned`, and separately
|
|
131
|
+
`upstream_reported_total`, and they are never merged.
|
|
132
|
+
- Every response carries `records_incomplete` and an `incompleteness_notice`.
|
|
133
|
+
|
|
134
|
+
## Environment variables
|
|
135
|
+
|
|
136
|
+
| Variable | Default | Purpose |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `ICTRP_HOME` | package root | Where writable state goes — the venv, snapshots, caches. Set this to a private, writable directory when the package itself is read-only (an asar archive, a system-path global install). It does not affect where the sidecar script is read from. |
|
|
139
|
+
| `ICTRP_VENV` | `<root>/.venv` | Where the private virtualenv lives |
|
|
140
|
+
| `ICTRP_SIDECAR_URL` | `http://127.0.0.1:8849` | Where the Node layer finds the sidecar |
|
|
141
|
+
| `ICTRP_SIDECAR_PORT` | `8849` | Sidecar listen port |
|
|
142
|
+
| `ICTRP_SIDECAR_TIMEOUT_MS` | `30000` | Per-call timeout for local tools |
|
|
143
|
+
| `ICTRP_USE_SIDECAR` | `1` | Set `0` to run without a sidecar and fail loudly instead |
|
|
144
|
+
| `ICTRP_AUTOSTART_SIDECAR` | `1` | Set `0` to require an externally managed sidecar |
|
|
145
|
+
| `ICTRP_PIP_MIRROR` | `aliyun` | First mirror for `setup`; accepts a name or URL |
|
|
146
|
+
| `ICTRP_CACHE_DIR` | `~/.cache/ictrp-mcp-service` | Where raw CSV exports are cached |
|
|
147
|
+
| `ICTRP_BUNDLE_PATH` | — | Explicit offline snapshot file |
|
|
148
|
+
| `ICTRP_BUNDLE_DIR` | — | Directory of `<keyword-slug>.json` snapshots |
|
|
149
|
+
| `ICTRP_BUNDLE_MAX_AGE_DAYS` | `28` | Age beyond which a snapshot is reported stale |
|
|
150
|
+
|
|
151
|
+
## Data providers
|
|
152
|
+
|
|
153
|
+
ICTRP does not register trials; it republishes the registries below, each on its
|
|
154
|
+
own import cadence. Which registry a record came from is always available as
|
|
155
|
+
`source_register`.
|
|
156
|
+
|
|
157
|
+
| Registry | Last data file imported |
|
|
158
|
+
|---|---|
|
|
159
|
+
| Australian New Zealand Clinical Trials Registry | 2026-09-21 |
|
|
160
|
+
| Brazilian Clinical Trials Registry (ReBec) | 2026-09-14 |
|
|
161
|
+
| Chinese Clinical Trial Registry | 2026-09-21 |
|
|
162
|
+
| Clinical Research Information Service — Republic of Korea | 2026-09-21 |
|
|
163
|
+
| Clinical Trials Information System (CTIS) | 2026-09-21 |
|
|
164
|
+
| Clinical Trials Registry — India | 2026-09-14 |
|
|
165
|
+
| ClinicalTrials.gov | 2026-09-21 |
|
|
166
|
+
| Cuban Public Registry of Clinical Trials | 2026-09-14 |
|
|
167
|
+
| EU Clinical Trials Register (EU-CTR) | 2026-09-21 |
|
|
168
|
+
| German Clinical Trials Register | 2026-09-14 |
|
|
169
|
+
| International Traditional Medicine Clinical Trial Registry (ITMCTR) | 2026-08-03 |
|
|
170
|
+
| Iranian Registry of Clinical Trials | 2026-06-01 |
|
|
171
|
+
| ISRCTN | 2026-09-21 |
|
|
172
|
+
| Japan Registry of Clinical Trials (jRCT) | 2026-09-14 |
|
|
173
|
+
| Lebanese Clinical Trials Registry (LBCTR) | 2026-02-02 |
|
|
174
|
+
| The Netherlands National Trial Register | 2026-09-21 |
|
|
175
|
+
| Pan African Clinical Trial Registry | 2026-09-14 |
|
|
176
|
+
| Peruvian Clinical Trials Registry (REPEC) | 2026-03-30 |
|
|
177
|
+
| Sri Lanka Clinical Trials Registry | 2026-09-14 |
|
|
178
|
+
| Thai Clinical Trials Registry (TCTR) | 2026-08-03 |
|
|
179
|
+
|
|
180
|
+
The import dates span six months. A trial registered last week in a registry
|
|
181
|
+
last imported in June is genuinely absent from ICTRP — not missing from this
|
|
182
|
+
package, and not a bug.
|
|
183
|
+
|
|
184
|
+
## Attribution and terms of use
|
|
185
|
+
|
|
186
|
+
WHO ICTRP data are publicly available for download at no charge, subject to
|
|
187
|
+
WHO's terms:
|
|
188
|
+
|
|
189
|
+
- Attribute the source as **WHO ICTRP**.
|
|
190
|
+
- Clearly display the date the data were processed by WHO ICTRP. ICTRP is
|
|
191
|
+
updated **weekly**.
|
|
192
|
+
- Do not assert proprietary rights over any portion of the ICTRP database.
|
|
193
|
+
- Do not use the WHO name or emblem in association with use of the data.
|
|
194
|
+
- No marketing, promotional or commercial use.
|
|
195
|
+
|
|
196
|
+
Chinese trials appear with `source_register = "ChiCTR"`. Attribute these as
|
|
197
|
+
**"ChiCTR via WHO ICTRP"** — this is data as synchronized by WHO, not live
|
|
198
|
+
ChiCTR data.
|
|
199
|
+
|
|
200
|
+
`ictrp_export` prepends this attribution header by default. Keep it on unless
|
|
201
|
+
your consumer adds its own.
|
|
202
|
+
|
|
203
|
+
**This project is not affiliated with or endorsed by WHO.**
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT for the code. The **data** is subject to WHO ICTRP terms, which are
|
|
208
|
+
separate and take precedence for any use of the data.
|
package/README_ZH.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# ictrp-mcp-server
|
|
2
|
+
|
|
3
|
+
[简体中文](./README_ZH.md) | [English](./README.md)
|
|
4
|
+
|
|
5
|
+
一个查询 **WHO 国际临床试验注册平台(ICTRP)** 的 MCP 服务器,以 npm 包形式分发。
|
|
6
|
+
上表面是 Node MCP,底下是 Python sidecar,全程纯 HTTP——不需要浏览器,
|
|
7
|
+
不需要无头 Chromium,不需要任何自动化运行时。
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx ictrp-mcp-server
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
本包的 Python 部分与
|
|
14
|
+
[`ictrp-mcp-service`](https://github.com/opencare-skillhub/ictrp-mcp-service) Python
|
|
15
|
+
发行版是**同一份代码**。npm 包把它包起来,让桌面宿主只需声明一条 stdio 命令,
|
|
16
|
+
就能得到一个可用的试验注册库数据源。
|
|
17
|
+
|
|
18
|
+
## 为什么要有 sidecar
|
|
19
|
+
|
|
20
|
+
ICTRP 的访问逻辑已经在 Python 里实现并测试过了,其中那些**来之不易的细节很要紧**:
|
|
21
|
+
postback 链里要携带哪些 HTML 控件、哪一种重定向意味着「被拦截」而不是「接口变了」、
|
|
22
|
+
一个合法的空 CSV 与一个失败的 CSV 如何区分。用 TypeScript 重写一遍,只会得到
|
|
23
|
+
第二份会与第一份逐渐漂移的实现。
|
|
24
|
+
|
|
25
|
+
所以分工是:**Node 负责 MCP 协议,Python 负责领域逻辑。** 两者通过一个小型本地 JSON API 通信。
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
MCP 客户端 ──stdio──▶ Node(本包) ──HTTP──▶ Python sidecar ──HTTPS──▶ ICTRP
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
sidecar 只用标准库,外加 `httpx`。这里没有任何东西会去下载浏览器。
|
|
32
|
+
|
|
33
|
+
## 安装
|
|
34
|
+
|
|
35
|
+
首次运行会创建一个私有虚拟环境,并把 Python sidecar 装进去。这一步**刻意不放在安装时**做:
|
|
36
|
+
打包后的桌面应用不会执行 npm 的 `postinstall`,所以任何必须在首次使用前完成的事,
|
|
37
|
+
都得放到首次使用时去做。
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx ictrp-setup doctor # 哪些就绪、哪些没有(退出码 0/1/2)
|
|
41
|
+
npx ictrp-setup setup # 创建 venv 并安装 sidecar
|
|
42
|
+
npx ictrp-setup sidecar # 前台运行 sidecar
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`doctor` 以退出码作为契约,安装器可据此驱动 UI:
|
|
46
|
+
|
|
47
|
+
| 退出码 | 含义 | UI 应如何应对 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| 0 | 就绪 | 启用该数据源 |
|
|
50
|
+
| 1 | 未就绪,需要用户动作 | 展示建议的操作 |
|
|
51
|
+
| 2 | 自举失败 | 展示错误;提供重试 |
|
|
52
|
+
|
|
53
|
+
需要 Node 20+ 与 Python 3.10+。Python 源码随本包一起发布在 `sidecar/vendor/` 下,
|
|
54
|
+
因此 `setup` 是在**本地构建**它——工具本身不需要 PyPI 上有 `ictrp-mcp-service` 的发行版,
|
|
55
|
+
只有它的依赖需要。
|
|
56
|
+
|
|
57
|
+
在网络缓慢或不可达时,`setup` 会按顺序尝试 PyPI 镜像——`aliyun`、`tencent`、`tsinghua`,
|
|
58
|
+
最后是官方源——失败则回退。有些镜像在无法提供包时**报告包不存在而不是报错**,
|
|
59
|
+
所以回退是唯一可靠的策略。可用 `--mirror=<name|url>` 或 `ICTRP_PIP_MIRROR` 指向你自己的镜像。
|
|
60
|
+
|
|
61
|
+
## 配置你的 MCP 客户端
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"ictrp": {
|
|
67
|
+
"command": "npx",
|
|
68
|
+
"args": ["-y", "ictrp-mcp-server"]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 工具
|
|
75
|
+
|
|
76
|
+
联网:
|
|
77
|
+
|
|
78
|
+
- **`ictrp_search`** — 跑完整条链,并把整个结果集**本地物化**。返回一页数据加一个 `set_id`。
|
|
79
|
+
唯一昂贵的调用。
|
|
80
|
+
|
|
81
|
+
本地,不发起上游请求:
|
|
82
|
+
|
|
83
|
+
- **`ictrp_filter`** — 对已物化的结果集做筛选、排序、分页。
|
|
84
|
+
- **`ictrp_field_query`** — 某字段的去重取值及其填充覆盖率。
|
|
85
|
+
- **`ictrp_registry_summary`** — 按注册库/分期/状态/年份/国家的构成。
|
|
86
|
+
- **`ictrp_find_duplicates`** — 通过标识符交叉比对找出重复。
|
|
87
|
+
- **`ictrp_export`** — 导出 CSV / JSON / JSONL / Markdown,带 provenance 头。
|
|
88
|
+
- **`ictrp_cache_status`** — 列出或清除已缓存的结果集。
|
|
89
|
+
- **`ictrp_snapshot`** — 把结果集写成离线快照文件。
|
|
90
|
+
- **`ictrp_bundle_status`** — 报告哪个快照可以回答某个关键词。
|
|
91
|
+
- **`ictrp_check_environment`** — 就绪状态报告与后续操作。
|
|
92
|
+
|
|
93
|
+
**物化一次,之后随便筛。**
|
|
94
|
+
|
|
95
|
+
完整示例见[项目 README](https://github.com/opencare-skillhub/ictrp-mcp-service#用法):
|
|
96
|
+
一次 `ibi343` 检索返回 12 条试验,**散落在三个注册库**(ClinicalTrials.gov 10、JPRN 1、
|
|
97
|
+
ChiCTR 1)——正是只查单一注册库会漏掉的情形。
|
|
98
|
+
|
|
99
|
+
## 决定每个响应的那个发现
|
|
100
|
+
|
|
101
|
+
**ICTRP 的 CSV 导出并不完整,而且它是静默失灵的。** 对照门户自己报告的命中数实测:
|
|
102
|
+
|
|
103
|
+
| 关键词 | 门户报告 | CSV 实际给出 | 缺口 |
|
|
104
|
+
|---|---|---|---|
|
|
105
|
+
| `ChiCTR` | 14,197 | 14,147 | 0.4% |
|
|
106
|
+
| `pancreatic` | 10,354 | 9,273 | 10.4% |
|
|
107
|
+
| `pancreatic cancer` | 6,952 | 6,262 | 9.9% |
|
|
108
|
+
| `KRAS` | 1,243 | 882 | **29.0%** |
|
|
109
|
+
|
|
110
|
+
这不是统计口径的差异:对 `KRAS` 的结果页做 HTML 分页得到 108 个互不相同的试验 ID,
|
|
111
|
+
其中 **8 个在 CSV 里不存在**——它们在结果页上可见,却没有出现在导出中。原因未知;
|
|
112
|
+
重复记录、固定行数上限、分页限制均已排除。
|
|
113
|
+
|
|
114
|
+
所以本服务**从不把取回的条数当作命中数**来呈现:
|
|
115
|
+
|
|
116
|
+
- 失败会抛错,绝不返回零行。一个空列表与「不存在这样的试验」无法区分,
|
|
117
|
+
而后者恰恰是本包存在的意义所在——防止这个误判。
|
|
118
|
+
- 不存在 `total_matched`。只有 `rows_returned`,以及与之**相互独立**的
|
|
119
|
+
`upstream_reported_total`,两者永不合并。
|
|
120
|
+
- 每个响应都带 `records_incomplete` 与 `incompleteness_notice`。
|
|
121
|
+
|
|
122
|
+
## 环境变量
|
|
123
|
+
|
|
124
|
+
| 变量 | 默认值 | 作用 |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `ICTRP_HOME` | 包根目录 | 可写状态的去向——venv、快照、缓存。当包本身只读时(asar 归档、系统路径下的全局安装),把它设为一个私有可写目录。**它不影响 sidecar 脚本从哪里读取。** |
|
|
127
|
+
| `ICTRP_VENV` | `<root>/.venv` | 私有虚拟环境的位置 |
|
|
128
|
+
| `ICTRP_SIDECAR_URL` | `http://127.0.0.1:8849` | Node 层到 sidecar 的地址 |
|
|
129
|
+
| `ICTRP_SIDECAR_PORT` | `8849` | sidecar 监听端口 |
|
|
130
|
+
| `ICTRP_SIDECAR_TIMEOUT_MS` | `30000` | 本地工具的每次调用超时 |
|
|
131
|
+
| `ICTRP_USE_SIDECAR` | `1` | 设为 `0` 则不使用 sidecar,并**响亮地失败** |
|
|
132
|
+
| `ICTRP_AUTOSTART_SIDECAR` | `1` | 设为 `0` 则要求外部自行管理 sidecar |
|
|
133
|
+
| `ICTRP_PIP_MIRROR` | `aliyun` | setup 时优先尝试的镜像;接受名称或 URL |
|
|
134
|
+
| `ICTRP_CACHE_DIR` | `~/.cache/ictrp-mcp-service` | 原始 CSV 导出的缓存位置 |
|
|
135
|
+
| `ICTRP_BUNDLE_PATH` | — | 指定离线快照文件 |
|
|
136
|
+
| `ICTRP_BUNDLE_DIR` | — | 存放 `<keyword-slug>.json` 快照的目录 |
|
|
137
|
+
| `ICTRP_BUNDLE_MAX_AGE_DAYS` | `28` | 超过此天数即把快照标记为过期 |
|
|
138
|
+
|
|
139
|
+
## 数据提供方
|
|
140
|
+
|
|
141
|
+
ICTRP 自己并不登记试验;它重新发布下列注册库的内容,每个库有各自的导入节奏。
|
|
142
|
+
一条记录来自哪个注册库,始终可通过 `source_register` 获知。
|
|
143
|
+
|
|
144
|
+
| 注册库 | 最后导入数据文件 |
|
|
145
|
+
|---|---|
|
|
146
|
+
| Australian New Zealand Clinical Trials Registry | 2026-09-21 |
|
|
147
|
+
| Brazilian Clinical Trials Registry (ReBec) | 2026-09-14 |
|
|
148
|
+
| Chinese Clinical Trial Registry | 2026-09-21 |
|
|
149
|
+
| Clinical Research Information Service — Republic of Korea | 2026-09-21 |
|
|
150
|
+
| Clinical Trials Information System (CTIS) | 2026-09-21 |
|
|
151
|
+
| Clinical Trials Registry — India | 2026-09-14 |
|
|
152
|
+
| ClinicalTrials.gov | 2026-09-21 |
|
|
153
|
+
| Cuban Public Registry of Clinical Trials | 2026-09-14 |
|
|
154
|
+
| EU Clinical Trials Register (EU-CTR) | 2026-09-21 |
|
|
155
|
+
| German Clinical Trials Register | 2026-09-14 |
|
|
156
|
+
| International Traditional Medicine Clinical Trial Registry (ITMCTR) | 2026-08-03 |
|
|
157
|
+
| Iranian Registry of Clinical Trials | 2026-06-01 |
|
|
158
|
+
| ISRCTN | 2026-09-21 |
|
|
159
|
+
| Japan Registry of Clinical Trials (jRCT) | 2026-09-14 |
|
|
160
|
+
| Lebanese Clinical Trials Registry (LBCTR) | 2026-02-02 |
|
|
161
|
+
| The Netherlands National Trial Register | 2026-09-21 |
|
|
162
|
+
| Pan African Clinical Trial Registry | 2026-09-14 |
|
|
163
|
+
| Peruvian Clinical Trials Registry (REPEC) | 2026-03-30 |
|
|
164
|
+
| Sri Lanka Clinical Trials Registry | 2026-09-14 |
|
|
165
|
+
| Thai Clinical Trials Registry (TCTR) | 2026-08-03 |
|
|
166
|
+
|
|
167
|
+
导入日期跨度达六个月。某个上周才在某库登记的试验,若该库最后一次导入是在六月,
|
|
168
|
+
那么它就是**真的不在 ICTRP 里**——不是本包漏了,也不是 bug。
|
|
169
|
+
|
|
170
|
+
## 归属与使用条款
|
|
171
|
+
|
|
172
|
+
WHO ICTRP 数据可免费公开下载,但须遵守 WHO 的条款:
|
|
173
|
+
|
|
174
|
+
- 注明数据来源为 **WHO ICTRP**。
|
|
175
|
+
- **清晰展示 WHO ICTRP 处理该数据的日期。** ICTRP **每周**更新。
|
|
176
|
+
- 不得对 ICTRP 数据库的任何部分主张专有权利。
|
|
177
|
+
- 不得将 WHO 名称或徽标与数据的使用相关联。
|
|
178
|
+
- 不得用于营销、推广或商业用途。
|
|
179
|
+
|
|
180
|
+
中国试验以 `source_register = "ChiCTR"` 出现。请把这些标注为
|
|
181
|
+
**「ChiCTR via WHO ICTRP」**——这是**经 WHO 同步后**的数据,不是 ChiCTR 的实时数据。
|
|
182
|
+
|
|
183
|
+
`ictrp_export` 默认会前置这段归属头。除非你的下游会自行添加,否则请保留它。
|
|
184
|
+
|
|
185
|
+
**本项目与 WHO 无隶属关系,亦未获其背书。**
|
|
186
|
+
|
|
187
|
+
## 许可证
|
|
188
|
+
|
|
189
|
+
代码为 MIT。**数据**受 WHO ICTRP 条款约束,该条款独立存在,且对任何数据使用具有优先效力。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Setup CLI.
|
|
4
|
+
*
|
|
5
|
+
* Exit codes are contractual -- installers and desktop hosts branch on them,
|
|
6
|
+
* so they must not drift:
|
|
7
|
+
*
|
|
8
|
+
* 0 success, or the environment is ready
|
|
9
|
+
* 1 not ready, and the user needs to do something
|
|
10
|
+
* 2 self-setup failed with a definite error
|
|
11
|
+
*
|
|
12
|
+
* Every subcommand is read-only except `setup`, `sidecar` and `clean`.
|
|
13
|
+
*/
|
|
14
|
+
export {};
|